001/* A top-level dialog window containing an arbitrary component.
002
003 Copyright (c) 1998-2016 The Regents of the University of California.
004 All rights reserved.
005 Permission is hereby granted, without written agreement and without
006 license or royalty fees, to use, copy, modify, and distribute this
007 software and its documentation for any purpose, provided that the above
008 copyright notice and the following two paragraphs appear in all copies
009 of this software.
010
011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
015 SUCH DAMAGE.
016
017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
022 ENHANCEMENTS, OR MODIFICATIONS.
023
024 PT_COPYRIGHT_VERSION_2
025 COPYRIGHTENDKEY
026 */
027
028// In addition portions of the WindowClosingAdapter are derived from
029// https://docs.oracle.com/javase/tutorial/uiswing/examples/components/DialogDemoProject/src/components/CustomDialog.java
030// which has the following license:
031
032/*
033 * Copyright (c) 1995, 2008, Oracle and/or its affiliates. All rights reserved.
034 *
035 * Redistribution and use in source and binary forms, with or without
036 * modification, are permitted provided that the following conditions
037 * are met:
038 *
039 *   - Redistributions of source code must retain the above copyright
040 *     notice, this list of conditions and the following disclaimer.
041 *
042 *   - Redistributions in binary form must reproduce the above copyright
043 *     notice, this list of conditions and the following disclaimer in the
044 *     documentation and/or other materials provided with the distribution.
045 *
046 *   - Neither the name of Oracle or the names of its
047 *     contributors may be used to endorse or promote products derived
048 *     from this software without specific prior written permission.
049 *
050 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
051 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
052 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
053 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
054 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
055 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
056 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
057 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
058 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
059 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
060 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
061 */
062
063package ptolemy.gui;
064
065import java.awt.Component;
066import java.awt.Dimension;
067import java.awt.Font;
068import java.awt.Frame;
069import java.awt.Toolkit;
070import java.awt.event.WindowAdapter;
071import java.awt.event.WindowEvent;
072import java.beans.PropertyChangeEvent;
073import java.beans.PropertyChangeListener;
074
075import javax.swing.Box;
076import javax.swing.BoxLayout;
077import javax.swing.JDialog;
078import javax.swing.JOptionPane;
079import javax.swing.JPanel;
080import javax.swing.JTextArea;
081
082///////////////////////////////////////////////////////////////////
083//// ComponentDialog
084
085/**
086
087 This class is a modal dialog box that contains an arbitrary component.
088 It can be used, for example, to put an instance of Query in a
089 top-level dialog box.  The general way to use this class is to create
090 the component that you wish to have contained in the dialog.
091 Then pass that component to the constructor of this class.  The dialog
092 is modal, so the statement that creates the dialog will not return
093 until the user dismisses the dialog.  The method buttonPressed()
094 can then be called to find out whether the user clicked the OK button
095 or the Cancel button (or any other button specified in the constructor).
096 Then you can access the component to determine what values were set
097 by the user.
098 <p>
099 If the component that is added implements the CloseListener interface,
100 then that component is notified when this dialog closes.
101
102 @see CloseListener
103 @author Edward A. Lee
104 @version $Id$
105 @since Ptolemy II 0.4
106 @Pt.ProposedRating Yellow (eal)
107 @Pt.AcceptedRating Yellow (janneck)
108 */
109@SuppressWarnings("serial")
110public class ComponentDialog extends JDialog {
111    /** Construct a dialog with the specified owner, title, and component.
112     *  An "OK" and a "Cancel" button are added to the dialog.
113     *  The dialog is placed relative to the owner.
114     *  @param owner The object that, per the user, appears to be
115     *   generating the dialog.
116     *  @param title The title of the dialog.
117     *  @param component The component to insert in the dialog.
118     */
119    public ComponentDialog(Frame owner, String title, Component component) {
120        this(owner, title, component, null, null);
121    }
122
123    /** Construct a dialog with the specified owner, title, component,
124     *  and buttons.  The first button is the "default" in that
125     *  it is the one activated by "Enter" or "Return" keys.
126     *  If the last argument is null, then an "OK"
127     *  and a "Cancel" button will be created.
128     *  The dialog is placed relative to the owner.
129     *  @param owner The object that, per the user, appears to be
130     *   generating the dialog.
131     *  @param title The title of the dialog.
132     *  @param component The component to insert in the dialog.
133     *  @param buttons An array of labels for buttons at the bottom
134     *   of the dialog.
135     */
136    public ComponentDialog(Frame owner, String title, Component component,
137            String[] buttons) {
138        this(owner, title, component, buttons, null);
139    }
140
141    /** Construct a dialog with the specified owner, title, component,
142     *  buttons, and message.  The message is placed above the component.
143     *  The first button is the "default" in that
144     *  it is the one activated by "Enter" or "Return" keys.
145     *  If the <i>buttons</i> argument is null, then an "OK"
146     *  and a "Cancel" button will be created.
147     *  The dialog is placed relative to the owner.
148     *  @param owner The object that, per the user, appears to be
149     *   generating the dialog.
150     *  @param title The title of the dialog.
151     *  @param component The component to insert in the dialog.
152     *  @param buttons An array of labels for buttons at the bottom
153     *   of the dialog.
154     *  @param message A message to place above the component, or null
155     *   if no message is needed.
156     */
157    public ComponentDialog(Frame owner, String title, Component component,
158            String[] buttons, String message) {
159        this(owner, title, component, buttons, message, false);
160    }
161
162    /** Construct a dialog with the specified owner, title, component,
163     *  buttons, and message.  The message is placed above the component.
164     *  The first button is the "default" in that
165     *  it is the one activated by "Enter" or "Return" keys.
166     *  If the <i>buttons</i> argument is null, then an "OK"
167     *  and a "Cancel" button will be created.
168     *  The dialog is placed relative to the owner.
169     *  @param owner The object that, per the user, appears to be
170     *   generating the dialog.
171     *  @param title The title of the dialog.
172     *  @param component The component to insert in the dialog.
173     *  @param buttons An array of labels for buttons at the bottom
174     *   of the dialog.
175     *  @param message A message to place above the component, or null
176     *   if no message is needed.
177     *  @param resizable True to allow the dialog to be resized.
178     */
179    public ComponentDialog(Frame owner, String title, Component component,
180            String[] buttons, String message, boolean resizable) {
181        super(owner, title, true);
182
183        // Create a panel that contains the optional message
184        // and the specified component, separated by a spacing rigid area.
185        JPanel panel = new JPanel();
186
187        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
188
189        if (message != null) {
190            _messageArea = new JTextArea(message);
191            _messageArea.setFont(new Font("SansSerif", Font.PLAIN, 12));
192            _messageArea.setEditable(false);
193            _messageArea.setLineWrap(true);
194            _messageArea.setWrapStyleWord(true);
195            _messageArea.setBackground(getContentPane().getBackground());
196
197            // Left Justify.
198            _messageArea.setAlignmentX(0.0f);
199            panel.add(_messageArea);
200            panel.add(Box.createRigidArea(new Dimension(0, 10)));
201        }
202
203        panel.add(component);
204        contents = component;
205
206        if (buttons != null) {
207            _buttons = buttons;
208        } else {
209            _buttons = _defaultButtons;
210        }
211
212        _optionPane = new JOptionPane(panel, JOptionPane.QUESTION_MESSAGE,
213                JOptionPane.YES_NO_OPTION, null, _buttons, _buttons[0]);
214
215        // The following code is based on Sun's CustomDialog example...
216        _propChangeListener = new PropChangeListener();
217        _optionPane.addPropertyChangeListener(_propChangeListener);
218
219        getContentPane().add(_optionPane);
220        pack();
221        setResizable(true);
222
223        if (owner != null) {
224            setLocationRelativeTo(owner);
225        } else {
226            // Center on screen.  According to the Java docs,
227            // passing null to setLocationRelationTo() _may_ result
228            // in centering on the screen, but it is not required to.
229            Toolkit tk = Toolkit.getDefaultToolkit();
230            setLocation((tk.getScreenSize().width - getSize().width) / 2,
231                    (tk.getScreenSize().height - getSize().height) / 2);
232        }
233
234        // NOTE: Java's AWT may yield random results if we do the following.
235        // And anyway, it doesn't work.  Components still don't
236        // have their ComponentListener methods called to indicate
237        // that they have become invisible.
238        setDefaultCloseOperation(DISPOSE_ON_CLOSE);
239
240        // Catch closing events so that components are notified if
241        // the window manager is used to close the window.
242        _windowClosingAdapter = new WindowClosingAdapter();
243        addWindowListener(_windowClosingAdapter);
244
245        // Make the window visible.
246        setVisible(true);
247    }
248
249    ///////////////////////////////////////////////////////////////////
250    ////                         public methods                    ////
251
252    /** Return the label of the button that triggered closing the
253     *  dialog, or an empty string if none.
254     *  @return The label of the button pressed.
255     */
256    public String buttonPressed() {
257        return _buttonPressed;
258    }
259
260    @Override
261    public void dispose() {
262        _optionPane.removePropertyChangeListener(_propChangeListener);
263        _propChangeListener = null;
264        this.removeWindowListener(_windowClosingAdapter);
265        _windowClosingAdapter = null;
266        getContentPane().removeAll();
267        super.dispose();
268    }
269
270    ///////////////////////////////////////////////////////////////////
271    ////                         protected methods                 ////
272
273    /** Change the message that was specified in the constructor to
274     *  read as specified.  If no message was specified in the constructor,
275     *  then do nothing.
276     *  @param message The new message.
277     */
278    public void setMessage(String message) {
279        if (_messageArea != null) {
280            _messageArea.setText(message);
281        }
282    }
283
284    ///////////////////////////////////////////////////////////////////
285    ////                         public variables                  ////
286
287    /** The component contained by this dialog.
288     */
289    public Component contents;
290
291    ///////////////////////////////////////////////////////////////////
292    ////                         protected methods                 ////
293
294    /** If the contents of this dialog implements the CloseListener
295     *  interface, then notify it that the window has closed, unless
296     *  notification has already been done (it is guaranteed to be done
297     *  only once).
298     */
299    protected void _handleClosing() {
300        // Close the window.
301        setVisible(false);
302        dispose();
303
304        if (contents instanceof CloseListener && !_doneHandleClosing) {
305            _doneHandleClosing = true;
306            ((CloseListener) contents).windowClosed(this, _buttonPressed);
307        }
308    }
309
310    ///////////////////////////////////////////////////////////////////
311    ////                         protected variables               ////
312
313    /** The label of the button pushed to dismiss the dialog. */
314    protected String _buttonPressed = "";
315
316    /** A reference to the WindowClosingAdapter.*/
317    protected WindowClosingAdapter _windowClosingAdapter;
318
319    /** A reference to the PropertyChangeListener.*/
320    protected PropChangeListener _propChangeListener;
321
322    ///////////////////////////////////////////////////////////////////
323    ////                         private variables                 ////
324
325    /** Button labels. */
326    private static String[] _buttons;
327
328    /** Default button labels. */
329    private static String[] _defaultButtons = { "OK", "Cancel" };
330
331    /** Indicator that we have notified of window closing. */
332    private boolean _doneHandleClosing = false;
333
334    /** The pane with the buttons. */
335    private JOptionPane _optionPane;
336
337    /** The container for messages. */
338    private JTextArea _messageArea;
339
340    ///////////////////////////////////////////////////////////////////
341    ////                         inner classes                     ////
342
343    /** Listener for windowClosing action. */
344    class WindowClosingAdapter extends WindowAdapter {
345        @Override
346        public void windowClosing(WindowEvent e) {
347            _handleClosing();
348        }
349    }
350
351    /** Listen for property changes.
352     *  This inner class is derived from
353     *  https://docs.oracle.com/javase/tutorial/uiswing/examples/components/DialogDemoProject/src/components/CustomDialog.java
354     *  See the top of this file for the CustomDialog.java license.
355     */
356    class PropChangeListener implements PropertyChangeListener {
357        @Override
358        public void propertyChange(PropertyChangeEvent e) {
359            String prop = e.getPropertyName();
360
361            // PropertyChange is an extremely non-selective listener,
362            // so we have to filter...
363            if (isVisible() && e.getSource() == _optionPane
364                    && (prop.equals(JOptionPane.VALUE_PROPERTY)
365                            || prop.equals(JOptionPane.INPUT_VALUE_PROPERTY))) {
366                Object value = _optionPane.getValue();
367
368                // Reset should be ignored.
369                if (value == JOptionPane.UNINITIALIZED_VALUE) {
370                    return;
371                }
372
373                // The value of JOptionPane is reset.
374                // Resetting is required, otherwise when
375                // the button is pressed again, a property
376                // change event will not be fired.
377                // Note that this seems to trigger the listener
378                // again, so the previous line is essential.
379                _optionPane.setValue(JOptionPane.UNINITIALIZED_VALUE);
380
381                if (value instanceof String) {
382                    // A button was pressed...
383                    _buttonPressed = (String) value;
384                } else {
385                    _buttonPressed = "";
386                }
387
388                // Take any action that might be associated with
389                // window closing.
390                _handleClosing();
391            }
392        }
393    }
394}