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}