001/* An actor that displays matrix inputs.
002
003 Copyright (c) 1997-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
027
028 */
029package ptolemy.actor.lib.gui;
030
031import java.awt.BorderLayout;
032import java.awt.Container;
033import java.awt.Dimension;
034import java.util.LinkedList;
035import java.util.List;
036
037import javax.swing.JFrame;
038import javax.swing.SwingUtilities;
039
040import ptolemy.actor.TypedIOPort;
041import ptolemy.actor.gui.AbstractPlaceableActor;
042import ptolemy.actor.gui.Configuration;
043import ptolemy.actor.gui.Effigy;
044import ptolemy.actor.gui.MatrixPane;
045import ptolemy.actor.gui.MatrixTokenTableau;
046import ptolemy.actor.gui.TableauFrame;
047import ptolemy.actor.gui.TokenEffigy;
048import ptolemy.actor.gui.TokenTableau;
049import ptolemy.data.IntToken;
050import ptolemy.data.MatrixToken;
051import ptolemy.data.Token;
052import ptolemy.data.expr.Parameter;
053import ptolemy.data.type.BaseType;
054import ptolemy.kernel.CompositeEntity;
055import ptolemy.kernel.util.Attribute;
056import ptolemy.kernel.util.IllegalActionException;
057import ptolemy.kernel.util.InternalErrorException;
058import ptolemy.kernel.util.KernelException;
059import ptolemy.kernel.util.NameDuplicationException;
060import ptolemy.kernel.util.NamedObj;
061import ptolemy.kernel.util.Workspace;
062
063///////////////////////////////////////////////////////////////////
064//// MatrixViewer
065
066/**
067 An actor that displays the contents of a matrix input. This
068 actor has a single input port, which only accepts MatrixTokens. One
069 token is consumed per firing (in postfire()).  The data in the MatrixToken is
070 displayed in a table format with scrollbars, using the swing JTable
071 class.
072
073 @author Bart Kienhuis and Edward A. Lee
074 @version $Id$
075 @since Ptolemy II 1.0
076 @Pt.ProposedRating Red (kienhuis)
077 @Pt.AcceptedRating Red (kienhuis)
078 */
079public class MatrixViewer extends AbstractPlaceableActor {
080    /** Construct an actor with the given container and name.
081     *  @param container The container.
082     *  @param name The name of this actor.
083     *  @exception IllegalActionException If the actor cannot be contained
084     *   by the proposed container.
085     *  @exception NameDuplicationException If the container already has an
086     *   actor with this name.
087     */
088    public MatrixViewer(CompositeEntity container, String name)
089            throws NameDuplicationException, IllegalActionException {
090        super(container, name);
091        input = new TypedIOPort(this, "input", true, false);
092        input.setTypeEquals(BaseType.MATRIX);
093
094        width = new Parameter(this, "width", new IntToken(500));
095        width.setTypeEquals(BaseType.INT);
096        height = new Parameter(this, "height", new IntToken(300));
097        height.setTypeEquals(BaseType.INT);
098    }
099
100    ///////////////////////////////////////////////////////////////////
101    ////                     ports and parameters                  ////
102
103    /** The input port.
104     */
105    public TypedIOPort input;
106
107    /** The width of the table in pixels. This must contain an
108     *  integer.  If the table is larger than this specified width,
109     *  then scrollbars will appear, or the column width is adjusted.
110     *  The default value is 500.
111     */
112    public Parameter width;
113
114    /** The height of the table in pixels. This must contain an
115     *  integer.  If the table is larger than this specified width,
116     *  then scrollbars will appear.  The default value is 300.
117     */
118    public Parameter height;
119
120    ///////////////////////////////////////////////////////////////////
121    ////                         public methods                    ////
122
123    /** Notification that an attribute has changed.  If the attribute is
124     *  width or height then read the value of the attribute.
125     *  @param attribute The attribute that changed.
126     *  @exception IllegalActionException If the expression of the
127     *   attribute cannot be parsed or cannot be evaluated.
128     */
129    @Override
130    public void attributeChanged(Attribute attribute)
131            throws IllegalActionException {
132        if (attribute == width) {
133            _width = ((IntToken) width.getToken()).intValue();
134        } else if (attribute == height) {
135            _height = ((IntToken) height.getToken()).intValue();
136        } else {
137            super.attributeChanged(attribute);
138        }
139    }
140
141    /** Clone the actor into the specified workspace. This calls the
142     *  base class and then removes association with graphical objects
143     *  belonging to the original class.
144     *  @param workspace The workspace for the new object.
145     *  @return A new actor.
146     *  @exception CloneNotSupportedException If a derived class contains
147     *   an attribute that cannot be cloned.
148     */
149    @Override
150    public Object clone(Workspace workspace) throws CloneNotSupportedException {
151        MatrixViewer newObject = (MatrixViewer) super.clone(workspace);
152
153        newObject._container = null;
154        newObject._effigy = null;
155        newObject._frame = null;
156        newObject._pane = null;
157        newObject._tableau = null;
158
159        return newObject;
160    }
161
162    /** Initialize this matrix viewer. If place() has not been called
163     *  with a container into which to place the display, then create a
164     *  new frame into which to put it.
165     *  @exception IllegalActionException If the parent class
166     *   throws it.
167     */
168    @Override
169    public void initialize() throws IllegalActionException {
170        super.initialize();
171
172        if (_container == null) {
173            // No current container for the pane.
174            // Need an effigy and a tableau so that menu ops work properly.
175            if (_tableau == null) {
176                Effigy containerEffigy = Configuration.findEffigy(toplevel());
177
178                if (containerEffigy == null) {
179                    throw new IllegalActionException(this,
180                            "Cannot find effigy for top level: "
181                                    + toplevel().getFullName());
182                }
183
184                try {
185                    _effigy = new TokenEffigy(containerEffigy,
186                            containerEffigy.uniqueName("tokenEffigy"));
187
188                    // The default identifier is "Unnamed", which is
189                    // no good for two reasons: Wrong title bar label,
190                    // and it causes a save-as to destroy the original window.
191                    _effigy.identifier.setExpression(getFullName());
192
193                    // The second argument prevents a status bar.
194                    _frame = new TableauFrame(null, null, this);
195                    _tableau = new MatrixTokenTableau(_effigy, "tokenTableau",
196                            (TableauFrame) _frame);
197                    ((TableauFrame) _frame).setTableau(_tableau);
198                    setFrame(_frame);
199                    _tableau.show();
200                } catch (Exception ex) {
201                    throw new IllegalActionException(this, null, ex,
202                            "Error creating effigy and tableau");
203                }
204            } else {
205                // Erase previous text.
206                _effigy.clear();
207
208                if (_frame != null) {
209                    // Do not use show() as it overrides manual placement.
210                    // FIXME: So does setVisible()... But with neither one used,
211                    // then if the user dismisses the window, it does not reappear
212                    // on re-running!
213                    // _frame.setVisible(true);
214                    _frame.toFront();
215                }
216            }
217        }
218    }
219
220    /** Specify the container in which the data should be displayed.
221     *  An instance of JTable will be added to that container. The
222     *  table is configured such that a user cannot reorder the
223     *  columns of the table. Also, the table maintains a fixed
224     *  preferred size, and will employ scrollbars if the table is
225     *  larger than the preferred size. If this method is not called,
226     *  the JTable will be placed in its own frame. The table is also
227     *  placed in its own frame if this method is called with a null
228     *  argument. The background of the table is set equal to that of
229     *  the container (unless it is null).
230     *
231     *  @param container The container into which to place the table.
232     */
233    @Override
234    public void place(Container container) {
235        // If there was a previous container that doesn't match this one,
236        // remove the pane from it.
237        if (_container != null && _pane != null) {
238            _container.remove(_pane);
239            _container = null;
240        }
241
242        if (_frame != null) {
243            _frame.dispose();
244            _frame = null;
245        }
246
247        _container = container;
248
249        if (container == null) {
250            // Reset everything.
251            if (_tableau != null) {
252                // This will have the side effect of removing the effigy
253                // from the directory if there are no more tableaux in it.
254                try {
255                    _tableau.setContainer(null);
256                } catch (KernelException ex) {
257                    throw new InternalErrorException(ex);
258                }
259            }
260
261            _tableau = null;
262            _effigy = null;
263            _pane = null;
264
265            return;
266        }
267
268        if (_pane == null) {
269            // Create the pane.
270            _pane = new MatrixPane();
271
272            // FIXME: The following, as usual with Swing, doesn't work.
273            Dimension size = new Dimension(_width, _height);
274            _pane.setPreferredSize(size);
275            _pane.setSize(size);
276        }
277
278        // Place the pane in supplied container.
279        _container.add(_pane, BorderLayout.CENTER);
280    }
281
282    /** Consume a matrix token from the <i>input</i> port
283     *  and display the token in a table.  If a token is not available,
284     *  do nothing.
285     *  @exception IllegalActionException If there is no director, or
286     *   if the base class throws it.
287     */
288    @Override
289    public boolean postfire() throws IllegalActionException {
290        if (input.hasToken(0)) {
291            Token in = input.get(0);
292
293            if (_frame != null) {
294                List<Token> tokens = new LinkedList<Token>();
295                tokens.add(in);
296                _effigy.setTokens(tokens);
297            } else if (_pane != null) {
298                _pane.display((MatrixToken) in);
299            }
300        }
301
302        return super.postfire();
303    }
304
305    /** Override the base class to remove the display from its graphical
306     *  container if the argument is null.
307     *  @param container The proposed container.
308     *  @exception IllegalActionException If the base class throws it.
309     *  @exception NameDuplicationException If the base class throws it.
310     */
311    @Override
312    public void setContainer(CompositeEntity container)
313            throws IllegalActionException, NameDuplicationException {
314        super.setContainer(container);
315
316        if (container == null) {
317            _remove();
318        }
319    }
320
321    /** Set a name to present to the user.
322     *  <p>If the MatrixViewer window has been rendered, then the title of the
323     *  MatrixViewer window will be updated to the value of the name parameter.</p>
324     *  @param name A name to present to the user.
325     *  @see #getDisplayName()
326     */
327    @Override
328    public void setDisplayName(String name) {
329        super.setDisplayName(name);
330        // See http://bugzilla.ecoinformatics.org/show_bug.cgi?id=4302
331        if (_tableau != null) {
332            _tableau.setTitle(name);
333        }
334    }
335
336    /** Specify the associated frame and set its properties (size, etc.)
337     *  to match those stored in the _windowProperties attribute. In this
338     *  class, if the frame is null, close the tableau and set it to null.
339     *  Once closed, the matrix window will not reopen unless _tableau is
340     *  null.
341     *  @param frame The associated frame.
342     */
343    @Override
344    public void setFrame(JFrame frame) {
345
346        super.setFrame(frame);
347
348        if (frame == null && _tableau != null) {
349            try {
350                _tableau.setContainer(null);
351            } catch (KernelException ex) {
352                throw new InternalErrorException(ex);
353            }
354            _tableau = null;
355            _effigy = null;
356        }
357    }
358
359    /** Set or change the name.  If a null argument is given the
360     *  name is set to an empty string.
361     *  Increment the version of the workspace.
362     *  This method is write-synchronized on the workspace.
363     *  <p>If the MatrixViewer window has been rendered, then the title of the
364     *  MatrixViewer window will be updated to the value of the name parameter.</p>
365     *  @param name The new name.
366     *  @exception IllegalActionException If the name contains a period
367     *   or if the object is a derived object and the name argument does
368     *   not match the current name.
369     *  @exception NameDuplicationException Not thrown in this base class.
370     *   May be thrown by derived classes if the container already contains
371     *   an object with this name.
372     *  @see #getName()
373     *  @see #getName(NamedObj)
374     */
375    @Override
376    public void setName(String name)
377            throws IllegalActionException, NameDuplicationException {
378        super.setName(name);
379        // See http://bugzilla.ecoinformatics.org/show_bug.cgi?id=4302
380        if (_tableau != null) {
381            _tableau.setTitle(name);
382        }
383    }
384
385    ///////////////////////////////////////////////////////////////////
386    ////                         private methods                   ////
387
388    /** Remove the display from the current container, if there is one.
389     */
390    private void _remove() {
391        SwingUtilities.invokeLater(new Runnable() {
392            @Override
393            public void run() {
394                if (_container != null && _pane != null) {
395                    _container.remove(_pane);
396                    _container = null;
397                }
398
399                if (_frame != null) {
400                    _frame.dispose();
401                    _frame = null;
402                }
403            }
404        });
405    }
406
407    ///////////////////////////////////////////////////////////////////
408    ////                         private variables                 ////
409
410    /** Container with the display, if any. */
411    private Container _container = null;
412
413    /** The effigy for the token data. */
414    private TokenEffigy _effigy;
415
416    /** Height of the matrix viewer in pixels. */
417    private int _height;
418
419    /** Pane with the matrix display. */
420    private MatrixPane _pane = null;
421
422    /** The tableau with the display, if any. */
423    private TokenTableau _tableau;
424
425    /** Width of the matrix viewer in pixels. */
426    private int _width;
427}