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}