001/* An AWT and Swing implementation of the the ImageDisplayInterface
002 that displays a java.awt.Image.
003
004 @Copyright (c) 1998-2016 The Regents of the University of California.
005 All rights reserved.
006
007 Permission is hereby granted, without written agreement and without
008 license or royalty fees, to use, copy, modify, and distribute this
009 software and its documentation for any purpose, provided that the
010 above copyright notice and the following two paragraphs appear in all
011 copies of this software.
012
013 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
014 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
015 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
016 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
017 SUCH DAMAGE.
018
019 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
020 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
021 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
022 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
023 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
024 ENHANCEMENTS, OR MODIFICATIONS.
025
026 PT_COPYRIGHT_VERSION 2
027 COPYRIGHTENDKEY
028 */
029package ptolemy.actor.lib.image;
030
031import java.awt.BorderLayout;
032import java.awt.Color;
033import java.awt.Component;
034import java.awt.Container;
035import java.awt.Image;
036import java.awt.event.WindowEvent;
037
038import javax.swing.JFrame;
039import javax.swing.SwingUtilities;
040
041import ptolemy.actor.gui.AbstractPlaceableJavaSE;
042import ptolemy.actor.gui.Configuration;
043import ptolemy.actor.gui.Effigy;
044import ptolemy.actor.gui.ImageTokenEffigy;
045import ptolemy.actor.gui.SizeAttribute;
046import ptolemy.actor.gui.TableauFrame;
047import ptolemy.actor.gui.WindowPropertiesAttribute;
048import ptolemy.data.ImageToken;
049import ptolemy.data.Token;
050import ptolemy.kernel.util.IllegalActionException;
051import ptolemy.kernel.util.InternalErrorException;
052import ptolemy.kernel.util.KernelException;
053import ptolemy.kernel.util.NameDuplicationException;
054import ptolemy.media.Picture;
055
056///////////////////////////////////////////////////////////////////
057////ImageDisplayJavaSE
058
059/**
060<p>
061ImageDisplayJavaSE is the implementation of the ImageDisplayInterface that uses AWT and Swing
062classes.</p>
063
064Note that this class does not work well when executed within Eclipse. In Eclipse, the
065Swing event thread blocks "waiting for: OGLRenderQueue$QueueFluher", and spends most of its
066time blocked rather than rendering. Hence, we do not get smooth updates of images.
067
068
069@author Jianwu Wang, Based on code by James Yeh, Edward A. Lee
070@version $Id$
071@since Ptolemy II 10.0
072@Pt.ProposedRating
073@Pt.AcceptedRating
074 */
075
076public class ImageDisplayJavaSE extends AbstractPlaceableJavaSE
077        implements ImageDisplayInterface {
078
079    ///////////////////////////////////////////////////////////////////
080    ////                         public methods                    ////
081    /**
082     * Free up memory when closing.
083     */
084    @Override
085    public void cleanUp() {
086        _tableau = null;
087    }
088
089    /** Display the specified token. This must be called in the Swing
090     *  event thread.
091     *  @param in The token to display
092     *  @exception IllegalActionException If the input is not an ImageToken
093     */
094    @Override
095    public void display(final Token in) throws IllegalActionException {
096        if (!(in instanceof ImageToken)) {
097            throw new IllegalActionException(_display,
098                    "Input is not an ImageToken. It is: " + in);
099        }
100
101        // Display probably to be done in the Swing event thread.
102        Runnable doDisplay = new Runnable() {
103            @Override
104            public void run() {
105                _display(in);
106            }
107        };
108
109        SwingUtilities.invokeLater(doDisplay);
110    }
111
112    /** Get the background.
113     *  @return The background color.
114     *  @see #setBackground(Color)
115     */
116    @Override
117    public Color getBackground() {
118        return _container.getBackground();
119    }
120
121    /**
122     * Get the image's frame.
123     * @return the image's frame.
124     * @see #setFrame(Object)
125     */
126    @Override
127    public Object getFrame() {
128        return _imageWindowFrame;
129    }
130
131    /**
132     * Get the platform dependent picture that contains the image.
133     * @return the platform dependent container.
134     * @see #setPicture(Object)
135     */
136    @Override
137    public Object getPicture() {
138        return _picture;
139    }
140
141    /**
142     * Get the platform dependent container that contains the image.
143     * @return the platform dependent container.
144     * @see #setPlatformContainer(Object)
145     */
146    @Override
147    public Object getPlatformContainer() {
148        return _container;
149    }
150
151    /**
152     * Get the image tableau.
153     * @return the image tableau.
154     */
155    @Override
156    public Object getTableau() {
157        return _tableau;
158    }
159
160    /** Initialize an object.
161     * @param imageDisplayActor The object to be initialized.
162     * @exception IllegalActionException If the entity cannot be contained
163     * by the proposed container.
164     * @exception NameDuplicationException If the container already has an
165     * actor with this name.
166     */
167    @Override
168    public void init(ImageDisplay imageDisplayActor)
169            throws IllegalActionException, NameDuplicationException {
170        _display = imageDisplayActor;
171        super.init(imageDisplayActor);
172    }
173
174    /**
175     * Initialize the effigy of the image.
176     * @exception IllegalActionException If there is a problem initializing the effigy
177     */
178    @Override
179    public void initializeEffigy() throws IllegalActionException {
180        // This has to be done in the Swing event thread.
181        Runnable doDisplay = new Runnable() {
182            @Override
183            public void run() {
184                _createOrShowWindow();
185            }
186        };
187
188        SwingUtilities.invokeLater(doDisplay);
189    }
190
191    /**
192     * Initialize the effigy of the plotter.
193     * @exception IllegalActionException If there is a problem initializing the effigy
194     */
195    @Override
196    public void initWindowAndSizeProperties()
197            throws IllegalActionException, NameDuplicationException {
198        _windowProperties = (WindowPropertiesAttribute) _display.getAttribute(
199                "_windowProperties", WindowPropertiesAttribute.class);
200        if (_windowProperties == null) {
201            _windowProperties = new WindowPropertiesAttribute(_display,
202                    "_windowProperties");
203            // Note that we have to force this to be persistent because
204            // there is no real mechanism for the value of the properties
205            // to be updated when the window is moved or resized. By
206            // making it persistent, when the model is saved, the
207            // attribute will determine the current size and position
208            // of the window and save it.
209            _windowProperties.setPersistent(true);
210        }
211        _pictureSize = (SizeAttribute) _display.getAttribute("_pictureSize",
212                SizeAttribute.class);
213        if (_pictureSize == null) {
214            _pictureSize = new SizeAttribute(_display, "_pictureSize");
215            _pictureSize.setPersistent(true);
216        }
217    }
218
219    /**
220     * Remove the plot from the frame if the container is null.
221     */
222    @Override
223    public void placeContainer(Container container) {
224        // If there was a previous container that doesn't match this one,
225        // remove the pane from it.
226        if (_container != null && _picture != null) {
227            _container.remove(_picture);
228            _container = null;
229        }
230
231        if (_imageWindowFrame != null) {
232            _imageWindowFrame.dispose();
233            _imageWindowFrame = null;
234        }
235
236        _container = container;
237
238        if (container == null) {
239            // Reset everything.
240            if (_tableau != null) {
241                // This will have the side effect of removing the effigy
242                // from the directory if there are no more tableaux in it.
243                try {
244                    _tableau.setContainer(null);
245                } catch (KernelException ex) {
246                    throw new InternalErrorException(ex);
247                }
248            }
249
250            _tableau = null;
251            _effigy = null;
252            _picture = null;
253            _oldXSize = 0;
254            _oldYSize = 0;
255
256            return;
257        }
258
259        if (_picture == null) {
260            // Create the pane.
261            _picture = new Picture(_oldXSize, _oldYSize);
262        }
263
264        // Place the pane in supplied container.
265        _container.add(_picture, BorderLayout.CENTER);
266    }
267
268    /** Set the background.
269     *  @param background The background color.
270     *  @see #getBackground()
271     */
272    @Override
273    public void setBackground(Color background) {
274        _container.setBackground(background);
275    }
276
277    /**
278     * Set the frame of the image.
279     * @param frame The frame to set.
280     * @see #getFrame()
281     */
282    @Override
283    public void setFrame(Object frame) {
284        if (_imageWindowFrame != null) {
285            _imageWindowFrame.removeWindowListener(_windowClosingAdapter);
286        }
287
288        if (frame == null) {
289            _imageWindowFrame = null;
290            return;
291        }
292
293        _imageWindowFrame = (ImageWindow) frame;
294
295        _windowClosingAdapter = new WindowClosingAdapter();
296        _imageWindowFrame.addWindowListener(_windowClosingAdapter);
297
298        _windowProperties.setProperties(_imageWindowFrame);
299    }
300
301    /**
302     * Set the platform dependent picture of the image.
303     * The container can be AWT container or Android view.
304     * @param picture The picture
305     * @see #getPicture()
306     */
307    @Override
308    public void setPicture(Object picture) {
309        _picture = (Picture) picture;
310    }
311
312    /**
313     * Set the platform dependent container of the image.
314     * The container can be AWT container or Android view.
315     * @param container the platform dependent container.
316     * @see #getPlatformContainer()
317     */
318    @Override
319    public void setPlatformContainer(Object container) {
320        _container = (Container) container;
321    }
322
323    ///////////////////////////////////////////////////////////////////
324    ////                         protected variables               ////
325    /** The container for the image display, set by calling place(). */
326    protected Container _container;
327
328    /** The effigy for the image data. */
329    protected ImageTokenEffigy _effigy;
330
331    /** The frame, if one is used. */
332    protected ImageWindow _imageWindowFrame;
333
334    /** The picture panel. */
335    protected Picture _picture;
336
337    /** The horizontal size of the previous image. */
338    protected int _oldXSize = 0;
339
340    /** The vertical size of the previous image. */
341    protected int _oldYSize = 0;
342
343    ///////////////////////////////////////////////////////////////////
344    ////                         private methods                   ////
345
346    /** Create or show the top-level window, unless there already is a
347     *  container. This must be called in the Swing event thread.
348     */
349    private void _createOrShowWindow() {
350        if (_container == null) {
351            // No current container for the pane.
352            // Need an effigy and a tableau so that menu ops work properly.
353            if (_tableau == null) {
354                Effigy containerEffigy = Configuration
355                        .findEffigy(_display.toplevel());
356
357                if (containerEffigy == null) {
358                    throw new InternalErrorException(
359                            "Cannot find effigy for top level \""
360                                    + _display.toplevel().getFullName()
361                                    + "\".  This can happen when a is invoked"
362                                    + " with a non-graphical execution engine"
363                                    + " such as ptolemy.moml.MoMLSimpleApplication"
364                                    + " but the "
365                                    + " ptolemy.moml.filter.RemoveGraphicalClasses"
366                                    + " MoML filter is not replacing the"
367                                    + " class that extends ImageDisplay.");
368                }
369
370                try {
371                    _effigy = new ImageTokenEffigy(containerEffigy,
372                            containerEffigy.uniqueName("imageEffigy"));
373
374                    // The default identifier is "Unnamed", which is
375                    // no good for two reasons: Wrong title bar label,
376                    // and it causes a save-as to destroy the original window.
377                    _effigy.identifier.setExpression(_display.getFullName());
378
379                    _imageWindowFrame = new ImageWindow();
380
381                    _tableau = new ImageTableau(_effigy, "tokenTableau",
382                            _imageWindowFrame, _oldXSize, _oldYSize);
383                    _tableau.setTitle(_display.getName());
384                    _imageWindowFrame.setTableau(_tableau);
385                    _windowProperties.setProperties(_imageWindowFrame);
386
387                    // Regrettably, since setSize() in swing doesn't actually
388                    // set the size of the frame, we have to also set the
389                    // size of the internal component.
390                    Component[] components = _imageWindowFrame.getContentPane()
391                            .getComponents();
392
393                    if (components.length > 0) {
394                        _pictureSize.setSize(components[0]);
395                    }
396
397                    _tableau.show();
398                } catch (Exception ex) {
399                    throw new InternalErrorException(ex);
400                }
401            } else {
402                // Erase previous image.
403                _effigy.clear();
404
405                if (_imageWindowFrame != null) {
406                    // Do not use show() as it overrides manual placement.
407                    _imageWindowFrame.toFront();
408                }
409            }
410        }
411
412        if (_imageWindowFrame != null) {
413            _imageWindowFrame.setVisible(true);
414            _imageWindowFrame.toFront();
415        }
416    }
417
418    /** Display the specified token. This must be called in the Swing
419     *  event thread.
420     *  @param in The token to display
421     */
422    private void _display(Token in) {
423        if (!(in instanceof ImageToken)) {
424            throw new InternalErrorException(
425                    "Input is not an ImageToken. It is: " + in);
426        }
427
428        // See also ptolemy/actor/lib/image/ImageTableau.java
429        if (_imageWindowFrame != null) {
430            try {
431                _effigy.setImage((ImageToken) in);
432            } catch (IllegalActionException e) {
433                throw new InternalErrorException(e);
434            }
435        } else if (_picture != null) {
436            Image image = ((ImageToken) in).asAWTImage();
437            int xSize = image.getWidth(null);
438            int ySize = image.getHeight(null);
439
440            // If the size has changed, have to recreate the Picture object.
441            if (_oldXSize != xSize || _oldYSize != ySize) {
442                _oldXSize = xSize;
443                _oldYSize = ySize;
444
445                Container container = _picture.getParent();
446
447                container.remove(_picture);
448
449                _picture = new Picture(xSize, ySize);
450                _picture.setImage(image);
451                _picture.setBackground(null);
452                container.add("Center", _picture);
453                container.validate();
454                container.invalidate();
455                container.repaint();
456                container.doLayout();
457
458                Container c = container.getParent();
459
460                while (c.getParent() != null) {
461                    c.invalidate();
462                    c.validate();
463                    c = c.getParent();
464
465                    if (c instanceof JFrame) {
466                        ((JFrame) c).pack();
467                    }
468                }
469            } else {
470                _picture.setImage(((ImageToken) in).asAWTImage());
471                _picture.displayImage();
472                _picture.repaint();
473            }
474        }
475    }
476
477    ///////////////////////////////////////////////////////////////////
478    ////                         private variables                 ////
479    /** Reference to the ImageDisplay actor */
480    private ImageDisplay _display;
481
482    /** The tableau with the display, if any. */
483    private ImageTableau _tableau;
484
485    /** A specification of the size of the picture if it's in its own window.
486     */
487    private SizeAttribute _pictureSize;
488
489    ///////////////////////////////////////////////////////////////////
490    ////                         inner classes                     ////
491    /** Version of TableauFrame that removes its association with the
492     *  ImageDisplay upon closing, and also records the size of the display.
493     */
494    @SuppressWarnings("serial")
495    protected class ImageWindow extends TableauFrame {
496        /** Construct an empty window.
497         *  After constructing this, it is necessary
498         *  to call setVisible(true) to make the frame appear
499         *  and setTableau() to associate it with a tableau.
500         */
501        public ImageWindow() {
502            // The null second argument prevents a status bar.
503            super(null, null);
504        }
505
506        /** Close the window.  This overrides the base class to remove
507         *  the association with the ImageDisplay actor and to record window
508         *  properties.
509         *  @return True.
510         */
511        @Override
512        protected boolean _close() {
513            // Record the window properties before closing.
514            _windowProperties.recordProperties(this);
515
516            // Regrettably, have to also record the size of the contents
517            // because in Swing, setSize() methods do not set the size.
518            // Only the first component size is recorded.
519            Component[] components = getContentPane().getComponents();
520
521            if (components.length > 0) {
522                _pictureSize.recordSize(components[0]);
523            }
524
525            super._close();
526            _display.place(null);
527            return true;
528        }
529    }
530
531    /** Listener for windowClosing action. */
532    class WindowClosingAdapter
533            extends AbstractPlaceableJavaSE.WindowClosingAdapter {
534        @Override
535        public void windowClosing(WindowEvent e) {
536            _display.cleanUp();
537        }
538    }
539}