001/* An object that represents a graphical view of a ptolemy model.
002
003 Copyright (c) 1998-2014 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 */
027package ptolemy.actor.gui;
028
029import java.awt.Frame;
030import java.awt.Window;
031import java.awt.event.WindowAdapter;
032import java.awt.event.WindowEvent;
033
034import javax.swing.JFrame;
035
036import ptolemy.gui.MemoryCleaner;
037import ptolemy.gui.Top;
038import ptolemy.kernel.CompositeEntity;
039import ptolemy.kernel.util.Attribute;
040import ptolemy.kernel.util.IllegalActionException;
041import ptolemy.kernel.util.KernelException;
042import ptolemy.kernel.util.NameDuplicationException;
043import ptolemy.kernel.util.Workspace;
044import ptolemy.util.CancelException;
045import ptolemy.util.MessageHandler;
046import ptolemy.util.StringUtilities;
047
048///////////////////////////////////////////////////////////////////
049//// Tableau
050
051/**
052 A tableau is a visual representation of a Ptolemy II model in a top-level
053 window.  This class represents such a top level window.  The top-level
054 is always a frame, which is a window with a border and title bar. The
055 window itself is specified by the setFrame() method, and accessed by
056 the getFrame() method.  An instance of this class will be contained
057 by the instance of Effigy that represents the model that is depicted
058 in the top-level window.
059 <p>
060 By convention, the constructor for a tableau does not (necessarily)
061 make the associated frame visible.  To do that, call show().
062
063 @author Steve Neuendorffer and Edward A. Lee
064 @version $Id$
065 @since Ptolemy II 1.0
066 @Pt.ProposedRating Yellow (neuendor)
067 @Pt.AcceptedRating Red (neuendor)
068 @see Effigy
069 */
070public class Tableau extends CompositeEntity {
071    /** Construct a tableau in the specified workspace.
072     *  @param workspace The workspace.
073     *  @exception IllegalActionException If an error occurs creating
074     *   the size attribute (should not occur).
075     *  @exception NameDuplicationException If the base class has already
076     *   created an attribute with name "size" (should not occur).
077     */
078    public Tableau(Workspace workspace)
079            throws IllegalActionException, NameDuplicationException {
080        super(workspace);
081
082        size = new SizeAttribute(this, "size");
083    }
084
085    /** Construct a tableau with the given name and container.
086     *  @param container The container.
087     *  @param name The name of the tableau.
088     *  @exception IllegalActionException If the tableau cannot be contained
089     *   by the proposed container.
090     *  @exception NameDuplicationException If the name coincides with
091     *   an entity already in the container.
092     */
093    public Tableau(CompositeEntity container, String name)
094            throws IllegalActionException, NameDuplicationException {
095        super(container, name);
096
097        size = new SizeAttribute(this, "size");
098    }
099
100    ///////////////////////////////////////////////////////////////////
101    ////                         public parameters                 ////
102
103    /** A specification for the size of the frame.
104     */
105    public SizeAttribute size;
106
107    ///////////////////////////////////////////////////////////////////
108    ////                         public methods                    ////
109
110    /** If the argument is the <i>size</i> parameter, and a
111     *  frame has been specified with setFrame(), then set the size
112     *  of the frame.
113     *  @param attribute The attribute that changed.
114     *  @exception IllegalActionException If the size
115     *   specification is not correctly formatted, or if the base
116     *   class throws it.
117     */
118    @Override
119    public void attributeChanged(Attribute attribute)
120            throws IllegalActionException {
121        if (attribute == size && _frame != null) {
122            size.setSize(_frame);
123        } else {
124            super.attributeChanged(attribute);
125        }
126    }
127
128    /** Clone the object into the specified workspace. This calls the
129     *  base class and then sets the associated frame to null.
130     *  Thus, the resulting tableau has no frame associated with it.
131     *  @param workspace The workspace for the new object.
132     *  @return A new object.
133     *  @exception CloneNotSupportedException If a derived class contains
134     *   an attribute that cannot be cloned.
135     */
136    @Override
137    public Object clone(Workspace workspace) throws CloneNotSupportedException {
138        Tableau newObject = (Tableau) super.clone(workspace);
139        newObject._frame = null;
140        return newObject;
141    }
142
143    /** Close this tableau by calling dispose() on the associated
144     *  frame, or if the associated frame is an instance of TableauFrame,
145     *  by calling _close() on it.
146     *  @return False if the user cancels on a save query.
147     */
148    public boolean close() {
149        if (_debugClosing) {
150            System.out.println("Tableau.close() : " + getFrame().getName());
151        }
152
153        JFrame frame = getFrame();
154
155        if (frame instanceof TableauFrame) {
156            // NOTE: Calling a protected method, but this class is in the
157            // same package.
158            if (!((TableauFrame) frame)._close()) {
159                return false;
160            }
161        } else if (this instanceof DialogTableau
162                && frame instanceof PortConfigurerDialog) {
163            if (!((PortConfigurerDialog) frame).close()) {
164                return false;
165            }
166        } else if (frame != null) {
167            frame.dispose();
168        }
169
170        return true;
171    }
172
173    /** Return the top-level window that implements the display of
174     *  this tableau.
175     *  @return A top-level window.
176     *  @see #setFrame(JFrame)
177     */
178    public JFrame getFrame() {
179        return _frame;
180    }
181
182    /** Return the title of this tableau.  Subclasses can override this to
183     *  provide a better description of themselves for use in the title.
184     *  This base class returns the value set by a call to setTitle(),
185     *  if it has been called. If not, then it returns an identifier
186     *  of the effigy containing this tableau, or the string "Unnamed"
187     *  if there is no such identifier.
188     *  The title is used as the title of the top-level window in
189     *  the setFrame() method.
190     *  @return The title to put on the window.
191     *  @see #setTitle(String)
192     */
193    public String getTitle() {
194        if (_title == null) {
195            Effigy effigy = (Effigy) getContainer();
196
197            // Abbreviate the title to 80 chars for use on the Mac.
198            return StringUtilities
199                    .abbreviate(effigy.identifier.getExpression());
200        } else {
201            return _title;
202        }
203    }
204
205    /** Return true if the tableau is editable. This base class returns
206     *  whatever value has been set by setEditable(), or <i>true</i> if
207     *  none has been specified.
208     *  @see #setEditable(boolean)
209     *  @return True if the tableau is editable.
210     */
211    public boolean isEditable() {
212        return _editable;
213    }
214
215    /** Return true if this tableau is a master, which means that
216     *  if that if its window is closed, then all other windows associated
217     *  with the model are also closed. A tableau is a master if its
218     *  container effigy is a master (its masterEffigy() method returns
219     *  itself).
220     *  @return True if the tableau is a master.
221     */
222    public boolean isMaster() {
223        return _master;
224    }
225
226    /** Override the base class so that if the argument is null and the
227     *  window is a master, then all other windows associated with the
228     *  container are closed and the model is removed from the ModelDirectory.
229     *  If this window is not a master, but after removing it there are
230     *  no more windows associated with the model, then also remove it
231     *  from the ModelDirectory.
232     *  @param container The container to attach this attribute to.
233     *  @exception IllegalActionException If the proposed container is not
234     *   an instance of Effigy, or if this attribute is not of the
235     *   expected class for the container, or it has no name,
236     *   or the attribute and container are not in the same workspace, or
237     *   the proposed container would result in recursive containment.
238     *  @exception NameDuplicationException If the container already has
239     *   an attribute with the name of this attribute.
240     */
241    @Override
242    public void setContainer(CompositeEntity container)
243            throws IllegalActionException, NameDuplicationException {
244        if (container == null) {
245            Effigy oldContainer = (Effigy) getContainer();
246            super.setContainer(/* container*/null);
247
248            // Blow away the frame.
249            if (_frame != null) {
250                if (!isMaster()) {
251                    if (_frame instanceof Top) {
252                        Top top = (Top) _frame;
253                        if (!top.isDisposed()) {
254                            top.dispose();
255                        }
256                    }
257                }
258            }
259
260            if (isMaster() && oldContainer != null) {
261                // Window is a master.  Close the model which will close all
262                // other tableaux.
263                oldContainer.setContainer(null);
264            }
265        } else if (container instanceof Effigy) {
266            super.setContainer(container);
267        } else {
268            throw new IllegalActionException(this, container,
269                    "The container can only be set to an "
270                            + "instance of Effigy");
271        }
272    }
273
274    /** Make the tableau editable or uneditable.  Notice that this does
275     *  not change whether the effigy is modifiable, so other tableaux
276     *  on the same effigy may still modify the associated file.
277     *  Derived class will usually need to override this method to
278     *  set whether their associated interfaces are editable or not.
279     *  They should call this superclass method so that isEditable()
280     *  returns the value specified here.
281     *  @see #isEditable()
282     *  @param flag False to make the tableau uneditable.
283     */
284    public void setEditable(boolean flag) {
285        _editable = flag;
286    }
287
288    /** Set the top-level window associated with this tableau.
289     *  @param frame The top-level window associated with the tableau.
290     *  @exception IllegalActionException If the frame is not acceptable
291     *   (not thrown in this base class).
292     *  @see #getFrame()
293     */
294    public void setFrame(JFrame frame) throws IllegalActionException {
295        if (_frame == frame) {
296            return;
297        }
298        if (frame == null) {
299            _frame = null;
300            return;
301        }
302
303        _frame = frame;
304
305        size.setSize(frame);
306
307        // Truncate the name so that dialogs under Web Start on the Mac
308        // work better.
309        frame.setTitle(getTitle());
310
311        // Set up a listener for window closing events.
312        _windowClosedAdapter = new WindowClosedAdapter();
313        frame.addWindowListener(_windowClosedAdapter);
314    }
315
316    /** Specify whether the window associated with this tableau
317     *  is a master, which means that if that window is closed, then
318     *  all windows associated with the model are closed.
319     *  @param flag If true, makes the window a master.
320     */
321    public void setMaster(boolean flag) {
322        _master = flag;
323    }
324
325    /** Set the title of this tableau, changing the title of the
326     *  associated top-level window.  Call this with a null argument
327     *  to use the identifier of the containing effigy as a title.
328     *  @param title The title to put on the window.
329     *  @see #getTitle()
330     */
331    public void setTitle(String title) {
332        _title = StringUtilities.abbreviate(title);
333
334        if (_frame != null) {
335            _frame.setTitle(getTitle());
336        }
337    }
338
339    /** Make this tableau visible by calling setVisible(true), and
340     *  raising or deiconifying its window.
341     *  If no frame has been set, then do nothing.
342     */
343    public void show() {
344        JFrame frame = getFrame();
345
346        if (frame != null) {
347            if (!frame.isVisible()) {
348                size.setSize(frame);
349
350                // NOTE: This used to override the location that might
351                // have been set in the _windowProperties attribute.
352                /*
353                 if (frame instanceof Top) {
354                 ((Top)frame).centerOnScreen();
355                 }
356                 */
357                frame.pack();
358                frame.setVisible(true);
359
360                // NOTE: The above calls Component.show()...
361                // We used to override Top.show() (Top extends JFrame)
362                // to call pack(), but this had the unfortunate side
363                // effect of overriding manual placement of windows.
364                // However, due to some weirdness in Swing or the AWT,
365                // calling pack before setVisible() does not have the
366                // same effect as calling pack() within show().
367                // Calling it after, however, is not sufficient.
368
369                // We used to have to call pack() both before and
370                // after setVisible() because the the HTML welcome
371                // window that appears when vergil starts up was the
372                // wrong size.  However, if we call pack() twice, then
373                // under Java 1.5, the View XML window is too large,
374                // the horizontal scrollbar ends up off the screen.
375
376                //frame.pack();
377            }
378
379            // Deiconify the window.
380            frame.setState(Frame.NORMAL);
381            frame.toFront();
382        }
383    }
384
385    ///////////////////////////////////////////////////////////////////
386    ////                         inner classes                     ////
387
388    class WindowClosedAdapter extends WindowAdapter {
389        // This is invoked if the window
390        // is disposed by the _close() method of Top.
391        @Override
392        public void windowClosed(WindowEvent e) {
393            if (_debugClosing) {
394                System.out.println("Tableau$WindowClosedAdapter.windowClosed("
395                        + e.getWindow().getName() + ")");
396            }
397
398            Window frame = e.getWindow();
399            try {
400                Tableau.this.setContainer(null);
401            } catch (KernelException ex) {
402                try {
403                    MessageHandler.warning("Cannot remove tableau: " + ex);
404                } catch (CancelException exception) {
405                }
406            }
407            // System.out.println(frame.getWindowListeners().length);
408            /*int removed =*/MemoryCleaner.removeWindowListeners(frame);
409            //System.out.println("Window listeners removed: " + removed);
410            _windowClosedAdapter = null;
411            _frame = null;
412        }
413
414        // NOTE: We do not want to do the same in windowClosing()
415        // because this will override saving if modified as implemented
416        // in Top.
417    }
418
419    ///////////////////////////////////////////////////////////////////
420    ////                         protected variables               ////
421
422    /** Set to true to print closing sequence information to standard
423     * out.
424     */
425    protected boolean _debugClosing = false;
426
427    ///////////////////////////////////////////////////////////////////
428    ////                         private variables                 ////
429
430    /** Flag indicating whether the tableau is editable. */
431    private boolean _editable;
432
433    /** The frame that the tableau is shown in.
434     */
435    private JFrame _frame;
436
437    /** The adapter responsible for the windowClosed event.
438     */
439    private WindowClosedAdapter _windowClosedAdapter;
440
441    /** True if this tableau is a master tableau.  Default value is false.
442     */
443    private boolean _master = false;
444
445    /** The title set by setTitle(). */
446    private String _title;
447}