001/* An AWT and Swing implementation of the the DisplayInterface 002 that displays input data in a text area on the screen. 003 004 @Copyright (c) 1998-2014 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 */ 029 030package ptolemy.actor.lib.gui; 031 032import java.awt.Color; 033import java.awt.Container; 034import java.lang.ref.WeakReference; 035 036import javax.swing.BorderFactory; 037import javax.swing.JScrollPane; 038import javax.swing.JTextArea; 039import javax.swing.ScrollPaneConstants; 040import javax.swing.border.EmptyBorder; 041import javax.swing.border.LineBorder; 042import javax.swing.text.BadLocationException; 043 044import ptolemy.actor.gui.AbstractPlaceableJavaSE; 045import ptolemy.actor.gui.Configuration; 046import ptolemy.actor.gui.Effigy; 047import ptolemy.actor.gui.Tableau; 048import ptolemy.actor.gui.TextEditor; 049import ptolemy.actor.gui.TextEffigy; 050import ptolemy.actor.injection.PortableContainer; 051import ptolemy.data.IntToken; 052import ptolemy.data.Token; 053import ptolemy.gui.Top; 054import ptolemy.kernel.util.IllegalActionException; 055import ptolemy.kernel.util.NameDuplicationException; 056import ptolemy.util.MessageHandler; 057 058/////////////////////////////////////////////////////////////////// 059//// DisplayJavaSE 060 061/** 062<p> 063DisplayJavaSE is the implementation of the DisplayInterface that uses AWT and Swing 064classes. Values of the tokens arriving on the input channels in a 065text area on the screen. Each input token is written on a 066separate line. The input type can be of any type. 067Thus, string-valued tokens can be used to 068generate arbitrary textual output, at one token per line. 069</p><p> 070Note that because of complexities in Swing, if you resize the display 071window, then, unlike the plotters, the new size will not be persistent. 072That is, if you save the model and then re-open it, the new size is 073forgotten. To control the size, you should set the <i>rowsDisplayed</i> 074and <i>columnsDisplayed</i> parameters. 075</p><p> 076Note that this actor internally uses JTextArea, a Java Swing object 077that is known to consume large amounts of memory. It is not advisable 078to use this actor to log large output streams.</p> 079 080@author Yuhong Xiong, Edward A. Lee Contributors: Ishwinder Singh 081@version $Id$ 082@since Ptolemy II 10.0 083 */ 084 085public class DisplayJavaSE extends AbstractPlaceableJavaSE 086 implements DisplayInterface { 087 088 /////////////////////////////////////////////////////////////////// 089 //// public methods //// 090 091 /** Free up memory when closing. 092 * This is executed in the Swing event thread. 093 */ 094 @Override 095 public void cleanUp() { 096 Runnable doIt = new Runnable() { 097 @Override 098 public void run() { 099 _tableau = null; 100 101 if (_scrollPane != null) { 102 _scrollPane.removeAll(); 103 _scrollPane = null; 104 } 105 if (textArea != null) { 106 textArea.removeAll(); 107 textArea = null; 108 } 109 _frame = null; 110 DisplayJavaSE.super.cleanUp(); 111 } 112 }; 113 Top.deferIfNecessary(doIt); 114 } 115 116 /** Append the string value of the token to the text area 117 * on the screen. Each value is terminated with a newline 118 * character. This is executed in the Swing event thread. 119 * @param value The string to be displayed 120 */ 121 @Override 122 public void display(final String value) { 123 Runnable doIt = new Runnable() { 124 @Override 125 public void run() { 126 if (textArea == null) { 127 return; 128 } 129 130 textArea.append(value); 131 132 // Append a newline character. 133 if (value.length() > 0 || !_display._isSuppressBlankLines) { 134 textArea.append("\n"); 135 } 136 137 // Regrettably, the default in swing is that the top 138 // of the textArea is visible, not the most recent text. 139 // So we have to manually move the scrollbar. 140 // The (undocumented) way to do this is to set the 141 // caret position (despite the fact that the caret 142 // is already where want it). 143 try { 144 int lineOffset = textArea 145 .getLineStartOffset(textArea.getLineCount() - 1); 146 textArea.setCaretPosition(lineOffset); 147 } catch (BadLocationException ex) { 148 // Ignore ... worst case is that the scrollbar 149 // doesn't move. 150 } 151 } 152 }; 153 Top.deferIfNecessary(doIt); 154 } 155 156 /** Return the object of the containing text area. 157 * @return the text area. 158 */ 159 @Override 160 public Object getTextArea() { 161 return textArea; 162 } 163 164 /** Set the number of rows for the text area. 165 * @param displayActor The display actor to be initialized. 166 * @exception IllegalActionException If the entity cannot be contained 167 * by the proposed container. 168 * @exception NameDuplicationException If the container already has an 169 * actor with this name. 170 */ 171 @Override 172 public void init(Display displayActor) 173 throws IllegalActionException, NameDuplicationException { 174 _display = displayActor; 175 super.init(displayActor); 176 } 177 178 /** Open the display window if it has not been opened. 179 * @exception IllegalActionException If there is a problem creating 180 * the effigy and tableau. 181 * This is executed in the Swing event thread. 182 */ 183 @Override 184 public void openWindow() throws IllegalActionException { 185 Runnable doIt = new Runnable() { 186 @Override 187 public void run() { 188 if (textArea == null) { 189 // No container has been specified for display. 190 // Place the text area in its own frame. 191 // Need an effigy and a tableau so that menu ops work properly. 192 193 Effigy containerEffigy = Configuration 194 .findEffigy(_display.toplevel()); 195 196 try { 197 if (containerEffigy == null) { 198 throw new IllegalActionException( 199 "Cannot find effigy for top level \"" 200 + _display.toplevel().getFullName() 201 + "\". This can happen when a is invoked" 202 + " with a non-graphical execution engine" 203 + " such as ptolemy.moml.MoMLSimpleApplication" 204 + " but the " 205 + " ptolemy.moml.filter.RemoveGraphicalClasses" 206 + " MoML filter is not replacing the" 207 + " class that extends Display."); 208 } 209 TextEffigy textEffigy = TextEffigy 210 .newTextEffigy(containerEffigy, ""); 211 212 // The default identifier is "Unnamed", which is no good for 213 // two reasons: Wrong title bar label, and it causes a save-as 214 // to destroy the original window. 215 216 textEffigy.identifier 217 .setExpression(_display.getFullName()); 218 219 _tableau = new DisplayWindowTableau(_display, 220 textEffigy, "tableau"); 221 _frame = _tableau.frame.get(); 222 223 // Require a vertical scrollbar always so that we don't get a horizontal 224 // scrollbar when it appears. 225 JScrollPane pane = ((TextEditor) _frame) 226 .getScrollPane(); 227 if (pane != null) { 228 pane.setVerticalScrollBarPolicy( 229 ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); 230 } 231 232 textArea = ((TextEditor) _frame).text; 233 234 int numRows = ((IntToken) _display.rowsDisplayed 235 .getToken()).intValue(); 236 textArea.setRows(numRows); 237 238 int numColumns = ((IntToken) _display.columnsDisplayed 239 .getToken()).intValue(); 240 241 textArea.setColumns(numColumns); 242 setFrame(_frame); 243 _frame.pack(); 244 } catch (Exception ex) { 245 MessageHandler.error( 246 "Error opening window for Display actor.", ex); 247 } 248 } else { 249 // Erase previous text. 250 textArea.setText(null); 251 } 252 253 if (_frame != null) { 254 // show() used to override manual placement by calling pack. 255 // No more. 256 _frame.setVisible(true); 257 _frame.toFront(); 258 } 259 } 260 }; 261 Top.deferIfNecessary(doIt); 262 } 263 264 /** Specify the container in which the data should be displayed. 265 * An instance of JTextArea will be added to that container. 266 * This method needs to be called before the first call to initialize(). 267 * Otherwise, an instance of JTextArea will be placed in its own frame. 268 * The text area is also placed in its own frame if this method 269 * is called with a null argument. 270 * The background of the text area is set equal to that of the container 271 * (unless it is null). 272 * This is executed in the Swing event thread. 273 * @param portableContainer The container into which to place the 274 * text area, or null to specify that there is no current 275 * container. 276 */ 277 @Override 278 public void place(final PortableContainer portableContainer) { 279 Runnable doIt = new Runnable() { 280 @Override 281 public void run() { 282 Container container = (Container) (portableContainer != null 283 ? portableContainer.getPlatformContainer() 284 : null); 285 if (container == null) { 286 // Reset everything. 287 // NOTE: _remove() doesn't work here. Why? 288 if (_frame != null) { 289 if (_frame instanceof Top) { 290 Top top = (Top) _frame; 291 if (!top.isDisposed()) { 292 top.dispose(); 293 } 294 } else { 295 _frame.dispose(); 296 } 297 } 298 299 _frame = null; 300 _scrollPane = null; 301 textArea = null; 302 return; 303 } 304 305 textArea = new JTextArea(); 306 _scrollPane = new JScrollPane(textArea); 307 308 // java.awt.Component.setBackground(color) says that 309 // if the color "parameter is null then this component 310 // will inherit the background color of its parent." 311 _scrollPane.setBackground(null); 312 _scrollPane.setBorder(new EmptyBorder(10, 10, 10, 10)); 313 _scrollPane.setViewportBorder(new LineBorder(Color.black)); 314 315 // Always have a vertical scrollbar so that we don't get a horizontal scrollbar when it appers. 316 _scrollPane.setVerticalScrollBarPolicy( 317 ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); 318 319 container.add(_scrollPane); 320 textArea.setBackground(Color.white); 321 322 String titleSpec; 323 try { 324 titleSpec = _display.title.stringValue(); 325 } catch (IllegalActionException e) { 326 titleSpec = "Error in title: " + e.getMessage(); 327 } 328 329 if (!titleSpec.trim().equals("")) { 330 _scrollPane.setBorder( 331 BorderFactory.createTitledBorder(titleSpec)); 332 } 333 334 try { 335 int numRows = ((IntToken) _display.rowsDisplayed.getToken()) 336 .intValue(); 337 textArea.setRows(numRows); 338 339 int numColumns = ((IntToken) _display.columnsDisplayed 340 .getToken()).intValue(); 341 textArea.setColumns(numColumns); 342 343 } catch (IllegalActionException ex) { 344 // Ignore, and use default number of rows. 345 } 346 347 // Make sure the text is not editable. 348 textArea.setEditable(false); 349 _awtContainer = container; 350 } 351 }; 352 Top.deferIfNecessary(doIt); 353 } 354 355 /** Remove the display from the current container, if there is one. 356 * This is executed in the Swing thread later. 357 */ 358 @Override 359 public void remove() { 360 Runnable doIt = new Runnable() { 361 @Override 362 public void run() { 363 if (textArea != null) { 364 if (_awtContainer != null && _scrollPane != null) { 365 _awtContainer.remove(_scrollPane); 366 _awtContainer.invalidate(); 367 _awtContainer.repaint(); 368 } else if (_frame != null) { 369 _frame.dispose(); 370 } 371 } 372 } 373 }; 374 Top.deferIfNecessary(doIt); 375 } 376 377 /** Set the desired number of columns of the textArea, if there is one. 378 * This is executed in the Swing event thread. 379 * @param numberOfColumns The new value of the attribute. 380 * @exception IllegalActionException If the specified attribute 381 * is <i>rowsDisplayed</i> and its value is not positive. 382 */ 383 @Override 384 public void setColumns(final int numberOfColumns) 385 throws IllegalActionException { 386 Runnable doIt = new Runnable() { 387 @Override 388 public void run() { 389 if (textArea != null) { 390 // Unset any previously set size. 391 try { 392 _paneSize.setToken((Token) null); 393 } catch (IllegalActionException e) { 394 MessageHandler.error( 395 "Unexpected error: Unable to unset previous pane size.", 396 e); 397 } 398 setFrame(_frame); 399 400 textArea.setColumns(numberOfColumns); 401 402 if (_frame != null) { 403 _frame.pack(); 404 _frame.setVisible(true); 405 } 406 } 407 } 408 }; 409 Top.deferIfNecessary(doIt); 410 } 411 412 /** Set the desired number of rows of the textArea, if there is one. 413 * This is executed in the Swing event thread. 414 * @param numberOfRows The new value of the attribute. 415 * @exception IllegalActionException If the specified attribute 416 * is <i>rowsDisplayed</i> and its value is not positive. 417 */ 418 @Override 419 public void setRows(final int numberOfRows) throws IllegalActionException { 420 Runnable doIt = new Runnable() { 421 @Override 422 public void run() { 423 if (textArea != null) { 424 // Unset any previously set size. 425 try { 426 _paneSize.setToken((Token) null); 427 } catch (IllegalActionException e) { 428 MessageHandler.error( 429 "Unexpected error: Unable to unset previous pane size.", 430 e); 431 } 432 setFrame(_frame); 433 434 textArea.setRows(numberOfRows); 435 436 if (_frame != null) { 437 _frame.pack(); 438 _frame.setVisible(true); 439 } 440 } 441 } 442 }; 443 Top.deferIfNecessary(doIt); 444 } 445 446 /** Set the title of the window. 447 * <p>If the <i>title</i> parameter is set to the empty string, 448 * and the Display window has been rendered, then the title of 449 * the Display window will be updated to the value of the name 450 * parameter.</p> 451 * This is executed in the Swing event thread. 452 * @param stringValue The title to be set. 453 * @exception IllegalActionException If the title cannot be set. 454 */ 455 @Override 456 public void setTitle(final String stringValue) 457 throws IllegalActionException { 458 Runnable doIt = new Runnable() { 459 @Override 460 public void run() { 461 if (_tableau != null) { 462 try { 463 if (_display.title.stringValue().trim().equals("")) { 464 _tableau.setTitle(stringValue); 465 } 466 } catch (IllegalActionException e) { 467 _tableau.setTitle("Error getting title"); 468 } 469 } 470 } 471 }; 472 Top.deferIfNecessary(doIt); 473 } 474 475 /////////////////////////////////////////////////////////////////// 476 //// public members //// 477 478 /** The text area in which the data will be displayed. */ 479 public transient JTextArea textArea; 480 481 /////////////////////////////////////////////////////////////////// 482 //// private members //// 483 484 /** The AWT Container */ 485 private Container _awtContainer; 486 487 /** Reference to the Display actor */ 488 private Display _display; 489 490 /** The version of TextEditorTableau that creates a Display window. */ 491 private DisplayWindowTableau _tableau; 492 493 /** The scroll pane. */ 494 private JScrollPane _scrollPane; 495 496 /////////////////////////////////////////////////////////////////// 497 //// inner classes //// 498 499 /** Version of TextEditorTableau that creates DisplayWindow. 500 */ 501 private static class DisplayWindowTableau extends Tableau { 502 // FindBugs suggested refactoring this into a static class. 503 504 /** Construct a new tableau for the model represented by the 505 * given effigy. 506 * @param display The Display actor associated with this tableau. 507 * @param container The container. 508 * @param name The name. 509 * @exception IllegalActionException If the container does not accept 510 * this entity (this should not occur). 511 * @exception NameDuplicationException If the name coincides with an 512 * attribute already in the container. 513 */ 514 public DisplayWindowTableau(Display display, TextEffigy container, 515 String name) 516 throws IllegalActionException, NameDuplicationException { 517 super(container, name); 518 519 String title = display.title.stringValue(); 520 521 if (title.trim().equals("")) { 522 title = display.getFullName(); 523 } 524 525 TextEditor editor = new TextEditor(title, null, display); 526 frame = new WeakReference<TextEditor>(editor); 527 528 // Also need to set the title of this Tableau. 529 setTitle(title); 530 531 // Make sure that the effigy and the text area use the same 532 // Document (so that they contain the same data). 533 editor.text.setDocument(container.getDocument()); 534 setFrame(editor); 535 editor.setTableau(this); 536 } 537 538 public WeakReference<TextEditor> frame; 539 } 540}