001/* 002 * InputDialog -- Dialog with user editable items that can be configured using DialogItem elements. 003 * 004 * Copyright (C) 2009-2017, by Joseph A. Huwaldt. 005 * All rights reserved. 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public License 018 * along with this program; if not, write to the Free Software 019 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 020 * Or visit: http://www.gnu.org/licenses/lgpl.html 021 */ 022package geomss.ui; 023 024import jahuwaldt.swing.AppUtilities; 025import jahuwaldt.swing.EscapeJDialog; 026import jahuwaldt.swing.JButtonGroup; 027import jahuwaldt.swing.SpringUtilities; 028import java.awt.BorderLayout; 029import java.awt.Container; 030import java.awt.Dimension; 031import java.awt.Frame; 032import java.awt.event.ActionEvent; 033import java.awt.event.ActionListener; 034import java.awt.event.FocusAdapter; 035import java.awt.event.FocusEvent; 036import java.io.File; 037import java.text.MessageFormat; 038import java.text.ParseException; 039import java.util.ArrayList; 040import java.util.List; 041import static java.util.Objects.isNull; 042import static java.util.Objects.nonNull; 043import static java.util.Objects.requireNonNull; 044import java.util.ResourceBundle; 045import java.util.logging.Level; 046import java.util.logging.Logger; 047import javax.swing.*; 048 049/** 050 * A dialog that allows the user to edit values of various types from a list of 051 * {@link DialogItem} objects. 052 * 053 * <p> Modified by: Joseph A. Huwaldt </p> 054 * 055 * @author Joseph A. Huwaldt Date: May 5, 2009 056 * @version January 31, 2017 057 */ 058public class InputDialog extends EscapeJDialog { 059 060 private static final long serialVersionUID = 1L; 061 062 /** 063 * The resource bundle for this class. 064 */ 065 private static final ResourceBundle RB 066 = ResourceBundle.getBundle("geomss.ui.UIResources", java.util.Locale.getDefault()); 067 068 // Save a reference to the parent frame. 069 private final Frame _parent; 070 071 // A list of dialog items. 072 private final List<DialogItem> _items = new ArrayList(); 073 074 // The list for output. 075 private List<DialogItem> _outputItems = null; 076 077 /** 078 * Construct a dialog to allow the user to edit a list of items. 079 * 080 * @param parent A reference to the parent component that this dialog belongs to. 081 * @param title The title for the dialog window. 082 * @param message A message to display telling the user what to do. 083 * @param items A list of items to be edited in this dialog. 084 */ 085 @SuppressWarnings("OverridableMethodCallInConstructor") 086 public InputDialog(Frame parent, String title, String message, List<DialogItem> items) { 087 super(parent, title, true); 088 requireNonNull(items); 089 090 // Have the dialog automatically dispose on close. 091 setDefaultCloseOperation(DISPOSE_ON_CLOSE); 092 093 _parent = parent; 094 095 // Layout the dialog window. 096 Container cp = this.getContentPane(); 097 cp.setLayout(new BorderLayout()); 098 099 // Add instructions at the top. 100 if (nonNull(message) && !message.equals("")) { 101 Box topPanel = Box.createHorizontalBox(); 102 cp.add(topPanel, BorderLayout.NORTH); 103 104 topPanel.add(Box.createHorizontalStrut(10)); 105 JLabel label = new JLabel(message); 106 label.setBorder(BorderFactory.createEmptyBorder(6, 6, 6, 6)); 107 topPanel.add(label); 108 topPanel.add(Box.createGlue()); 109 } 110 111 // Create a panel to display in the center. 112 JPanel centerPanel = new JPanel(new SpringLayout()); 113 centerPanel.setBorder(BorderFactory.createEmptyBorder(4, 10, 4, 10)); 114 cp.add(centerPanel, BorderLayout.CENTER); 115 116 // Loop over all the dialog items. 117 for (DialogItem item : items) { 118 119 // Make a shallow copy of each item. 120 item = (DialogItem)item.clone(); 121 122 // Add the prefix to the panel as a label. 123 centerPanel.add(new JLabel(item.getPrefix(), SwingConstants.RIGHT)); 124 125 // Create different inputs fields depending on the type of input element. 126 Object element = item.getElement(); 127 if (element instanceof Number) 128 centerPanel.add(handleNumber(item)); 129 130 else if (element instanceof String) 131 centerPanel.add(handleString(item)); 132 133 else if (element instanceof Boolean) 134 centerPanel.add(handleBoolean(item)); 135 136 else if (element instanceof File) 137 centerPanel.add(handleFile(item)); 138 139 else if (element instanceof List) 140 centerPanel.add(handleList(item)); 141 142 else 143 throw new IllegalArgumentException(RB.getString("unknownObjectType") + " " 144 + element.getClass().getName()); 145 146 // Add the suffix to the panel as a label. 147 String suffix = item.getSuffix(); 148 if (nonNull(suffix)) 149 centerPanel.add(new JLabel(suffix)); 150 else 151 centerPanel.add(new JLabel(" ")); 152 153 // Save the cloned item in the output list. 154 _items.add(item); 155 156 } 157 158 // Layout the panel. 159 SpringUtilities.makeCompactGrid(centerPanel, items.size(), 3, 4, 4, 4, 4); 160 161 // Define OK and Cancel buttons. 162 Box box = Box.createHorizontalBox(); 163 box.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4)); 164 cp.add(box, BorderLayout.SOUTH); 165 box.add(Box.createGlue()); 166 167 JButton cancelBtn = new JButton(RB.getString("cancelBtnText")); 168 cancelBtn.addActionListener(new ActionListener() { 169 @Override 170 public void actionPerformed(ActionEvent e) { 171 performEscapeAction(null); 172 } 173 }); 174 box.add(cancelBtn); 175 176 box.add(Box.createHorizontalStrut(40)); 177 178 JButton okayBtn = new JButton(RB.getString("doneBtnText")); 179 okayBtn.addActionListener(new ActionListener() { 180 @Override 181 public void actionPerformed(ActionEvent e) { 182 handleOK(); 183 } 184 }); 185 this.getRootPane().setDefaultButton(okayBtn); 186 box.add(okayBtn); 187 188 box.add(Box.createHorizontalStrut(20)); 189 190 // Pack up the window. 191 this.pack(); 192 193 // Position the window automatically. 194 if (isNull(parent)) 195 setLocationByPlatform(true); 196 else 197 setLocationRelativeTo(parent); 198 199 // Display the dialog. 200 this.setVisible(true); 201 } 202 203 /** 204 * Handle the user inputting a Number field. 205 */ 206 private JComponent handleNumber(DialogItem item) { 207 // Got a number, use a formatted text field. 208 JFormattedTextField ftf = new JFormattedTextField(); 209 210 ftf.setValue(item.getElement()); 211 ftf.addFocusListener(new NumberFocusListener(item)); 212 ftf.setColumns(10); 213 214 return ftf; 215 } 216 217 /** 218 * Handles changes in a Number input. 219 */ 220 private class NumberFocusListener extends FocusAdapter { 221 222 private final DialogItem _item; 223 224 public NumberFocusListener(DialogItem item) { 225 _item = item; 226 } 227 228 @Override 229 public void focusLost(FocusEvent e) { 230 try { 231 JFormattedTextField textField = (JFormattedTextField)e.getComponent(); 232 textField.commitEdit(); 233 Number value = (Number)textField.getValue(); 234 Number oldValue = (Number)_item.getElement(); 235 if (!value.equals(oldValue)) { 236 _item.setElement(value); 237 } 238 } catch (ParseException err) { 239 Logger.getLogger(InputDialog.class.getName()).log(Level.WARNING, "", err); 240 } 241 } 242 } 243 244 /** 245 * Handle the user inputting a String field. 246 */ 247 private JComponent handleString(DialogItem item) { 248 JTextField tf = new JTextField(); 249 250 tf.setText((String)item.getElement()); 251 tf.addFocusListener(new StringFocusListener(item)); 252 tf.setColumns(10); 253 254 return tf; 255 } 256 257 /** 258 * Handles changes in a String input. 259 */ 260 private class StringFocusListener extends FocusAdapter { 261 262 private final DialogItem _item; 263 264 public StringFocusListener(DialogItem item) { 265 _item = item; 266 } 267 268 @Override 269 public void focusLost(FocusEvent e) { 270 JTextField textField = (JTextField)e.getComponent(); 271 String value = textField.getText(); 272 String oldValue = (String)_item.getElement(); 273 if (!value.equals(oldValue)) { 274 _item.setElement(value); 275 } 276 } 277 } 278 279 /** 280 * Handle the user inputting a Boolean field. 281 */ 282 private Box handleBoolean(DialogItem item) { 283 284 // Set up the radio buttons. 285 Box box = Box.createHorizontalBox(); 286 JRadioButton rb = new JRadioButton(RB.getString("trueBtnText")); 287 JButtonGroup btnGroup = new JButtonGroup(); 288 btnGroup.add(rb); 289 box.add(rb); 290 boolean initValue = ((Boolean)item.getElement()); 291 if (initValue) 292 rb.setSelected(true); 293 rb.addActionListener(new TrueFalseButtonListener(item, true)); 294 box.add(Box.createRigidArea(new Dimension(4, 4))); 295 296 rb = new JRadioButton(RB.getString("falseBtnText")); 297 btnGroup.add(rb); 298 box.add(rb); 299 if (!initValue) 300 rb.setSelected(true); 301 rb.addActionListener(new TrueFalseButtonListener(item, false)); 302 303 return box; 304 } 305 306 /** 307 * True/False button listener. 308 */ 309 private class TrueFalseButtonListener implements ActionListener { 310 311 private final boolean _trueBtn; 312 private final DialogItem _item; 313 314 TrueFalseButtonListener(DialogItem item, boolean trueBtn) { 315 _trueBtn = trueBtn; 316 _item = item; 317 } 318 319 @Override 320 public void actionPerformed(ActionEvent e) { 321 JRadioButton rb = (JRadioButton)e.getSource(); 322 if (_trueBtn) 323 _item.setElement(rb.isSelected()); 324 else 325 _item.setElement(!rb.isSelected()); 326 } 327 } 328 329 /** 330 * Handle the user inputting a File field. 331 */ 332 private JComponent handleFile(DialogItem item) { 333 Box box = Box.createHorizontalBox(); 334 335 JTextField tf = new JTextField(); 336 tf.setText(((File)item.getElement()).getName()); 337 tf.setColumns(10); 338 tf.addFocusListener(new FileFocusListener(item)); 339 box.add(tf); 340 341 box.add(Box.createHorizontalStrut(4)); 342 343 JButton btn = new JButton(RB.getString("selectBtnText")); 344 btn.addActionListener(new FileSelectListener(item, tf)); 345 box.add(btn); 346 347 return box; 348 } 349 350 /** 351 * Handles changes in a File input. 352 */ 353 private class FileSelectListener implements ActionListener { 354 355 private final DialogItem _item; 356 private final JTextField _tf; 357 358 public FileSelectListener(DialogItem item, JTextField tf) { 359 _item = item; 360 _tf = tf; 361 } 362 363 @Override 364 public void actionPerformed(ActionEvent e) { 365 File old = (File)_item.getElement(); 366 367 File newFile; 368 if (_item.isLoadFile()) { 369 // Use a LOAD dialog. 370 String msg = RB.getString("fileDialogLoad"); 371 newFile = AppUtilities.selectFile(_parent, java.awt.FileDialog.LOAD, msg, 372 old.getParent(), old.getName(), null); 373 374 } else { 375 // Use a SAVE dialog. 376 String extension = _item.getFileExtension(); 377 if (isNull(extension)) 378 extension = ""; 379 String msg = MessageFormat.format(RB.getString("fileSaveDialog"), extension.toUpperCase()); 380 newFile = AppUtilities.selectFile4Save(_parent, msg, 381 old.getParent(), old.getName(), null, extension, 382 RB.getString("fileExists"), 383 RB.getString("warningTitle")); 384 } 385 386 // Update the display and output element. 387 if (nonNull(newFile)) { 388 _item.setElement(newFile); 389 _tf.setText(newFile.getName()); 390 } 391 } 392 } 393 394 /** 395 * Handles changes in a File input. 396 */ 397 private class FileFocusListener extends FocusAdapter { 398 399 private final DialogItem _item; 400 401 public FileFocusListener(DialogItem item) { 402 _item = item; 403 } 404 405 @Override 406 public void focusLost(FocusEvent e) { 407 JTextField tf = (JTextField)e.getSource(); 408 409 File old = (File)_item.getElement(); 410 String oldName = old.getName(); 411 String newName = tf.getText(); 412 413 if (!newName.equals(oldName)) { 414 _item.setElement(new File(old.getParent(), newName)); 415 } 416 } 417 } 418 419 /** 420 * Handle the user inputting a List field. 421 */ 422 private JComponent handleList(DialogItem item) { 423 424 @SuppressWarnings("UseOfObsoleteCollectionType") 425 JComboBox cbox = new JComboBox(new java.util.Vector((List)item.getElement())); 426 cbox.addActionListener(new ListComboBoxListener(item)); 427 428 // Change the element in this item to the 1st list item. 429 List list = (List)item.getElement(); 430 item.setElement(list.get(0)); 431 432 return cbox; 433 } 434 435 /** 436 * Handles changes in a File input. 437 */ 438 private class ListComboBoxListener implements ActionListener { 439 440 private final DialogItem _item; 441 442 public ListComboBoxListener(DialogItem item) { 443 _item = item; 444 } 445 446 @Override 447 public void actionPerformed(ActionEvent e) { 448 JComboBox cb = (JComboBox)e.getSource(); 449 _item.setElement(cb.getSelectedItem()); 450 } 451 } 452 453 /** 454 * This method is called when the user clicks on the "OK Button". 455 */ 456 public void handleOK() { 457 458 // Copy the internal item list to the output list. 459 _outputItems = _items; 460 461 // Close the window. 462 this.setVisible(false); 463 } 464 465 /** 466 * Return the list of edited {@link DialogItem} objects or <code>null</code> if the 467 * user canceled. 468 * 469 * @return The list of edited DialogItem objects or null if the user canceled 470 */ 471 public List<DialogItem> getOutput() { 472 return _outputItems; 473 } 474 475}