001/* Top-level window with a menubar and status bar. 002 003 Copyright (c) 1998-2016 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.gui; 028 029import java.awt.BorderLayout; 030import java.awt.Color; 031import java.awt.Component; 032import java.awt.Dimension; 033import java.awt.EventQueue; 034import java.awt.FileDialog; 035import java.awt.Frame; 036import java.awt.KeyboardFocusManager; 037import java.awt.Toolkit; 038import java.awt.Window; 039import java.awt.event.ActionEvent; 040import java.awt.event.ActionListener; 041import java.awt.event.KeyEvent; 042import java.awt.event.WindowAdapter; 043import java.awt.event.WindowEvent; 044import java.awt.event.WindowFocusListener; 045import java.awt.print.PageFormat; 046import java.awt.print.Pageable; 047import java.awt.print.Printable; 048import java.awt.print.PrinterException; 049import java.awt.print.PrinterJob; 050import java.io.BufferedReader; 051import java.io.File; 052import java.io.FileReader; 053import java.io.FileWriter; 054import java.io.FilenameFilter; 055import java.io.IOException; 056import java.lang.reflect.Field; 057import java.net.URL; 058import java.util.ArrayList; 059import java.util.Collections; 060import java.util.Iterator; 061import java.util.LinkedList; 062import java.util.List; 063import java.util.Map; 064import java.util.Timer; 065import java.util.TimerTask; 066import java.util.WeakHashMap; 067 068import javax.print.PrintService; 069import javax.print.attribute.Attribute; 070import javax.print.attribute.HashPrintRequestAttributeSet; 071import javax.print.attribute.PrintRequestAttributeSet; 072import javax.print.attribute.standard.Destination; 073import javax.swing.AbstractAction; 074import javax.swing.JFileChooser; 075import javax.swing.JFrame; 076import javax.swing.JMenu; 077import javax.swing.JMenuBar; 078import javax.swing.JMenuItem; 079import javax.swing.JOptionPane; 080import javax.swing.KeyStroke; 081import javax.swing.SwingUtilities; 082import javax.swing.filechooser.FileFilter; 083 084import ptolemy.util.MessageHandler; 085import ptolemy.util.StatusHandler; 086import ptolemy.util.StringUtilities; 087 088/////////////////////////////////////////////////////////////////// 089//// Top 090 091/** 092 This is a top-level window with a menubar and an optional status bar. 093 Derived classes should add components to the content pane using a 094 line like: 095 <pre> 096 getContentPane().add(component, BorderLayout.CENTER); 097 </pre> 098 Derived classes may wish to modify the menus. The File 099 and Help menus are exposed as protected members. 100 The File menu items in the _fileMenuItems protected array are, 101 in order, Open File, Open URL, New, Save, Save As, Print, Close, and Exit. 102 The Help menu items in the _helpMenuItems protected array are, 103 in order, About and Help. 104 <p> 105 A derived class can use the insert() methods of JMenu 106 to insert a menu item defined by an Action or a JMenuItem 107 into a specified position in the menu. 108 Derived classes can also insert separators using the 109 insertSeparator() method of JMenu. 110 In principle, derived classes can also remove menu items 111 using the remove() methods of JMenu; however, we discourage this. 112 A basic principle of user interface design is habituation, where 113 there is considerable value in having menus that have consistent 114 contents and layout throughout the application (Microsoft, for 115 example, violates this principle with adaptive menus). 116 <p> 117 Instead of removing items from the menu, they can be disabled. 118 For example, to disable the "Save" item in the File menu, do 119 <pre> 120 _fileMenuItems[3].setEnabled(false); 121 </pre> 122 <p> 123 Some menu items are provided, but are disabled by default. 124 The "New" item, for example, can be enabled with 125 <pre> 126 _fileMenuItems[2].setEnabled(true); 127 </pre> 128 A derived class that enables this menu item can populate the menu with 129 submenu items. This particular entry in the _fileMenuItems[2] 130 is a JMenu, not just a JMenuItem, so it can have menu items 131 added to it. 132 <p> 133 A derived class can add an entirely new menu (many do that). 134 However, at this time, the JMenuBar interface does not support 135 putting a new menu into an arbitrary position. For this reason, 136 derived classes should insert new menus into the menu bar only 137 in the _addMenus() protected method. This ensures that the File 138 menu is always the rightmost menu, and the Help menu is always 139 the leftmost menu. The _addMenus() method is called when the window 140 is first packed. 141 142 @author Edward A. Lee and Steve Neuendorffer 143 @version $Id$ 144 @since Ptolemy II 1.0 145 @Pt.ProposedRating Yellow (eal) 146 @Pt.AcceptedRating Yellow (janneck) 147 */ 148@SuppressWarnings("serial") 149public abstract class Top extends JFrame 150 implements WindowFocusListener, StatusHandler { 151 152 /** Construct an empty top-level frame with the default status 153 * bar. After constructing this, it is necessary to call 154 * pack() to have the menus added, and then setVisible(true) 155 * to make the frame appear. It may also be desirable to 156 * call centerOnScreen(). This can be done after 157 * pack() and before setVisible(). 158 */ 159 public Top() { 160 this(new StatusBar()); 161 } 162 163 /** Construct an empty top-level frame with the specified status 164 * bar. After constructing this, 165 * it is necessary to call pack() to have the menus added, and 166 * then setVisible(true) to make the frame appear. It may also 167 * be desirable to call centerOnScreen(). This can be done after 168 * pack() and before setVisible(). 169 * @param statusBar A status bar, or null to not insert one. 170 */ 171 public Top(StatusBar statusBar) { 172 super(); 173 174 _statusBar = statusBar; 175 176 // Ensure that user is prompted before closing if the data 177 // has been modified. 178 setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); 179 addWindowListener(new CloseWindowAdapter()); 180 181 getContentPane().setLayout(new BorderLayout()); 182 183 _fileMenuListener = new FileMenuListener(); 184 _helpMenuListener = new HelpMenuListener(); 185 _historyMenuListener = new HistoryMenuListener(); 186 187 // Make this the default context for modal messages. 188 UndeferredGraphicalMessageHandler.setContext(this); 189 190 // If we are on a Mac, initialize the quit menu 191 // so that Command-Q saves data. 192 if (PtGUIUtilities.macOSLookAndFeel()) { 193 _macInitializer(); 194 } 195 196 addWindowFocusListener(this); 197 } 198 199 /////////////////////////////////////////////////////////////////// 200 //// public methods //// 201 202 /** Open a dialog with basic information about this window. 203 * Derived classes may override {@link #_about()}. 204 */ 205 public final void about() { 206 // Under Mac OS X, the "About" menu choice invokes this method. 207 _about(); 208 } 209 210 /** Center the window on the screen. This must be called after the 211 * window is populated with its contents, since it depends on the size 212 * being known. If this method is called from a thread that is not 213 * the AWT event dispatch thread, then its execution is deferred 214 * and performed in that thread. 215 */ 216 public void centerOnScreen() { 217 Runnable doCenter = new CenterOnScreenRunnable(); 218 219 deferIfNecessary(doCenter); 220 } 221 222 /** Close the window, prompting the user to save changes if there 223 * have been any. Derived classes should override the protected 224 * method _close(), not this one. This method returns immediately 225 * if it is called outside the swing UI thread, deferring the action 226 * so that it is executed in the swing thread. 227 */ 228 public final void close() { 229 if (_debugClosing) { 230 System.out.println("Top.close() : " + this.getName()); 231 } 232 233 Runnable doClose = new CloseWindowRunnable(); 234 235 deferIfNecessary(doClose); 236 } 237 238 /** If this method is called in the AWT event dispatch thread, 239 * then simply execute the specified action. Otherwise, 240 * if there are already deferred actions, then add the specified 241 * one to the list. Otherwise, create a list of deferred actions, 242 * if necessary, and request that the list be processed in the 243 * event dispatch thread. 244 * <p> 245 * Note that it does not work nearly as well to simply schedule 246 * the action yourself on the event thread because if there are a 247 * large number of actions, then the event thread will not be able 248 * to keep up. By grouping these actions, we avoid this problem. 249 * @param action The Runnable object to execute. 250 */ 251 public static void deferIfNecessary(Runnable action) { 252 // NOTE: This is a static version of a method in PlotBox, but 253 // we do not want to create cross dependencies between these 254 // packages. 255 // In swing, updates to showing graphics must be done in the 256 // event thread. If we are in the event thread, then proceed. 257 // Otherwise, queue a request or add to a pending request. 258 if (EventQueue.isDispatchThread()) { 259 action.run(); 260 } else { 261 synchronized (_deferredActions) { 262 // Add the specified action to the list of actions to perform. 263 _deferredActions.add(action); 264 265 // If it hasn't already been requested, request that actions 266 // be performed in the event dispatch thread. 267 if (!_actionsDeferred) { 268 Runnable doActions = new DeferredActionsRunnable(); 269 270 // NOTE: Using invokeAndWait() here risks causing 271 // deadlock. Don't do it! 272 SwingUtilities.invokeLater(doActions); 273 _actionsDeferred = true; 274 } 275 } 276 } 277 } 278 279 /** Dispose of this frame. 280 * Override this dispose() method to unattach any listeners that may keep 281 * this model from getting garbage collected. This method invokes the 282 * dispose() method of the superclass, 283 * {@link javax.swing.JFrame}. 284 */ 285 @Override 286 public void dispose() { 287 if (_debugClosing) { 288 System.out.println("Top.dispose() : " + this.getName()); 289 } 290 291 removeWindowFocusListener(this); 292 293 // Deal with help menu action listeners 294 /*int c =*/MemoryCleaner.removeActionListeners(_historyMenu); 295 //System.out.println("_historyMenu: "+c); 296 if (_historyMenu != null) { 297 _historyMenusAndListeners.remove(_historyMenu); 298 } 299 _historyMenuListener = null; 300 301 // Don't call removeWindowListeners here because Tableau.setFrame() 302 // adds a WindowListener to windowsClosed events and that listener 303 // handles closing the plots associated with a model. 304 //MemoryCleaner.removeWindowListeners(this); 305 306 // Deal with file menu action listeners 307 /*c =*/MemoryCleaner.removeActionListeners(_fileMenu); 308 //System.out.println("_fileMenu: "+c); 309 for (JMenuItem menuItem : _fileMenuItems) { 310 /*c =*/MemoryCleaner.removeActionListeners(menuItem); 311 //System.out.println("_fileMenuItems["+i+"]: "+c); 312 } 313 _fileMenuListener = null; 314 315 // Deal with help menu action listeners 316 /*c =*/MemoryCleaner.removeActionListeners(_helpMenu); 317 //System.out.println("_helpMenu: "+c); 318 for (JMenuItem menuItem : _helpMenuItems) { 319 /*c =*/MemoryCleaner.removeActionListeners(menuItem); 320 //System.out.println("_helpMenuItems["+i+"]: "+c); 321 } 322 _helpMenuListener = null; 323 324 /*c =*/MemoryCleaner.removeActionListeners(_menubar); 325 //System.out.println("_menubar: "+c); 326 _menubar.removeAll(); 327 328 // ensure reference to this is removed 329 UndeferredGraphicalMessageHandler.setContext(null); 330 331 // I'm not sure exactly why this works but it does! 332 // I think it has to do with the KeyboardFocusManager 333 // holding onto the last focused component, so clearing and 334 // cycling seems to free up the reference to this window. 335 KeyboardFocusManager focusManager = KeyboardFocusManager 336 .getCurrentKeyboardFocusManager(); 337 focusManager.clearGlobalFocusOwner(); 338 focusManager.downFocusCycle(); 339 340 // Avoid a leak under RHEL and Java 1.8.0_55 341 // See https://wiki.eecs.berkeley.edu/ptexternal/Main/Main/MemoryLeaks#LinuxBltSubRegion 342 // http://oracle.developer-works.com/article/5359339/How+to+realy+unregister+listeners+%28or+how+%22great%22+swing+leaks+memory%29 343 focusManager.setGlobalCurrentFocusCycleRoot(null); 344 345 // Set any AbstractActions to null. This is not strictly necessary, 346 // but it helps free up memory more quickly. By doing this here, 347 // we no longer require people to update dispose() by hand. 348 Class myClass = getClass(); 349 // Loop through classes up to the parent class of Top. 350 while (myClass != JFrame.class) { 351 Field[] fields = myClass.getDeclaredFields(); 352 for (Field field : fields) { 353 try { 354 // Loop through the class hierarchy of each field. 355 // If the class is assignable from AbstractAction, then 356 // set it to null. 357 Class superclass = field.getType(); 358 while (superclass != null && superclass != Object.class) { 359 if (superclass.isAssignableFrom(AbstractAction.class)) { 360 try { 361 field.setAccessible(true); 362 field.set(this, null); 363 } catch (SecurityException ex) { 364 if (!_printedSecurityExceptionMessage) { 365 _printedSecurityExceptionMessage = true; 366 System.out.println("Warning: Failed set " 367 + field 368 + " accessible while disposing. " 369 + "(applets and -sandbox always causes this)"); 370 } 371 } 372 break; 373 } 374 superclass = superclass.getSuperclass(); 375 } 376 } catch (IllegalAccessException ex) { 377 throw new RuntimeException( 378 "Failed to get or set field " + field, ex); 379 } 380 } 381 myClass = myClass.getSuperclass(); 382 } 383 384 getContentPane().removeAll(); 385 _disposed = true; 386 387 java.awt.image.BufferStrategy bufferStrategy = getBufferStrategy(); 388 if (bufferStrategy != null) { 389 bufferStrategy.dispose(); 390 } 391 392 // Sigh. Under Mac OS X, we need to deal with the peers by hand. 393 // This code is left commented out because it is Mac-specific and 394 // accessing ComponentPeer produces a warning. 395 396 // See https://wiki.eecs.berkeley.edu/ptexternal/Main/Main/MemoryLeaks#CPlatformWindow 397 // See http://stackoverflow.com/questions/19781877/mac-os-java-7-jdialog-dispose-memory-leak 398 399 // java.awt.peer.ComponentPeer peer = getPeer(); 400 401 super.dispose(); 402 403 // if (peer != null) { 404 // try { 405 // Class<?> componentPeerClass = Class.forName("sun.lwawt.LWComponentPeer"); 406 // Field target = componentPeerClass.getDeclaredField("target"); 407 // target.setAccessible(true); 408 // target.set(peer, null); 409 410 // Field window = peer.getClass().getDeclaredField("platformWindow"); 411 // window.setAccessible(true); 412 413 // Object platformWindow = window.get(peer); 414 // target = platformWindow.getClass().getDeclaredField("target"); 415 // target.setAccessible(true); 416 // target.set(platformWindow, null); 417 418 // } catch (Throwable throwable) { 419 // if (_debugClosing) { 420 // throwable.printStackTrace(); 421 // } 422 // } 423 // } 424 } 425 426 /** Exit the application after querying the user to save data. 427 * Derived classes should override {@link #_exit()}. 428 * @return False if the use cancelled a saving a modified buffer, 429 * true otherwise. 430 */ 431 public final boolean exit() { 432 // Under Mac OS X, Command-q invokes this method. 433 if (_debugClosing) { 434 System.out.println("Top.exit() : " + this.getName()); 435 } 436 _exit(); 437 if (_exitResult == _CANCELED) { 438 return false; 439 } 440 return true; 441 } 442 443 /** Return true if the window is set to be centered when pack() is called. 444 * @return True if the window will be centered when pack is called. 445 * @see #setCentering(boolean) 446 */ 447 public boolean getCentering() { 448 return _centering; 449 } 450 451 /** Return the size of the contents of this window. 452 * @return The size of the contents. 453 */ 454 public Dimension getContentSize() { 455 return getContentPane().getSize(); 456 } 457 458 /** If called before the first time pack() is called, this 459 * method will prevent the appearance of a menu bar. This is 460 * rarely desirable, but some subclasses of Top have to 461 * contain panels that are not swing components. Such 462 * components do not work with menus (the menu seems to 463 * appear behind the component instead of in front of it). 464 * Call this to prevent a menu bar. 465 */ 466 public void hideMenuBar() { 467 _hideMenuBar = true; 468 } 469 470 /** Return true if this frame has been disposed. 471 * It is possible for the dispose() method to be called directly. 472 * This may conflict with another dispose call 473 * in a window listener or somewhere else. Before Top 474 * calls super.dispose() (and thus triggering listeners) this 475 * boolean is set to true so listeners can check and make sure 476 * they aren't calling dispose for a second (or third) time. 477 * @return true if this frame has been disposed. 478 */ 479 public boolean isDisposed() { 480 return _disposed; 481 } 482 483 /** Return true if the menu of this window has been populated. 484 * The menu is populated as a side effect of the first invocation to 485 * the pack() method. 486 * @return True if the menu bar has been populated. 487 */ 488 public synchronized boolean isMenuPopulated() { 489 return _menuPopulated; 490 } 491 492 /** Return true if the data associated with this window has been 493 * modified since it was first read or last saved. This returns 494 * the value set by calls to setModified(), or false if that method 495 * has not been called. 496 * @return True if the data has been modified. 497 */ 498 public boolean isModified() { 499 return _modified; 500 } 501 502 /** Size this window to its preferred size and make it 503 * displayable, and override the base class to populate the menu 504 * bar if the menus have not already been populated. If the 505 * window size has not been set (by some derived class), then 506 * this will center the window on the screen. This is 507 * done here rather than in the constructor so that derived 508 * classes are assured that their constructors have been fully 509 * executed when _addMenus() is called. If this method is called 510 * outside the AWT event thread, then its execution is deferred and 511 * performed in that thread. 512 */ 513 @Override 514 public void pack() { 515 Runnable doPack = new DoPackRunnable(); 516 517 deferIfNecessary(doPack); 518 } 519 520 /** Report a message to the user by displaying it in a status bar, 521 * if there is one. If this method is called outside the AWT event 522 * thread, then its execution is deferred and performed in that thread. 523 * @param message The message to report. 524 */ 525 public void report(final String message) { 526 Runnable doReport = new StatusBarMessageRunnable(message); 527 528 deferIfNecessary(doReport); 529 } 530 531 /** Report a Throwable, which is usually an Exception but can also 532 * be an Error. If this method is called outside the AWT event 533 * thread, then its execution is deferred and performed in that thread. 534 * This pops up a window with the option of examining the stack 535 * trace, and reports the specified message in the status bar, if 536 * there is one. 537 * @param message The message. 538 * @param throwable The Throwable to report. 539 */ 540 public void report(final String message, final Throwable throwable) { 541 Runnable doReport = new StatusBarMessageReportRunnable(message, 542 throwable); 543 544 deferIfNecessary(doReport); 545 } 546 547 /** Report a Throwable, which is usually an Exception but can also 548 * be an Error. This displays a message in a dialog by 549 * calling the two-argument version with an empty string as the 550 * first argument. If this method is called outside the AWT event 551 * thread, then its execution is deferred and performed in that thread. 552 * @param throwable The Throwable to report 553 * @see #report(String, Throwable) 554 */ 555 public void report(Throwable throwable) { 556 report("", throwable); 557 } 558 559 /** Set background color. This overrides the base class to set the 560 * background of the status bar. If this method is called outside 561 * the AWT event thread, then its execution is deferred and 562 * performed in that thread. 563 * @param background The background color. 564 */ 565 @Override 566 public void setBackground(final Color background) { 567 _statusBarBackground = background; 568 Runnable doSet = new SetBackgroundRunnable(); 569 570 deferIfNecessary(doSet); 571 } 572 573 /** Specify whether or not to center the window on the screen when 574 * packing it. The default is true. 575 * @param centering Set to false to disable centering. 576 * @see #getCentering() 577 */ 578 public void setCentering(boolean centering) { 579 _centering = centering; 580 } 581 582 /** 583 * Set the initial default directory. If this method is not 584 * called, then the initial default directory will be the value of 585 * the user.dir Java property, which is typically the current 586 * working directory. This method allows external configuration 587 * to determine the initial/default opening/saving directory to 588 * use for file dialogs. (Used in Kepler) 589 * @param dir the initial directory to use for file dialogs 590 */ 591 public static void setDirectory(File dir) { 592 _directory = dir; 593 } 594 595 /** Record whether the data associated with this window has been 596 * modified since it was first read or last saved. If you call 597 * this with a true argument, then subsequent attempts to close 598 * the window will trigger a dialog box to confirm the closing. 599 * @param modified Indicator of whether the data has been modified. 600 */ 601 public void setModified(boolean modified) { 602 _modified = modified; 603 } 604 605 /** Override the base class to deiconify 606 * the window, if necessary. If this method is called 607 * outside the AWT event thread, then its execution is deferred and 608 * performed in that thread. 609 */ 610 @Override 611 public void show() { 612 Runnable doShow = new ShowWindowRunnable(); 613 614 deferIfNecessary(doShow); 615 } 616 617 /** Display the specified message in the status bar. 618 * This message will be displayed for a maximum of MAXIMUM_STATUS_MESSAGE_TIME 619 * (default 30 seconds). 620 * If there is no status bar, print to standard out. 621 * @param message The message. 622 */ 623 @Override 624 public void status(final String message) { 625 if (_statusBar != null) { 626 if (_statusMessageTimer == null) { 627 // Second argument makes this a daemon thread, so it won't block exiting Vergil. 628 _statusMessageTimer = new Timer("Status message timer", true); 629 } 630 // The status bar update has to be performed in the Swing event thread. 631 // NOTE: If this is called from outside the Swing event thread, then 632 // the order in which messages are reported gets messed up. 633 // The only simple solutions seems to be to call this from the Swing event thread. 634 deferIfNecessary(new Runnable() { 635 @Override 636 public void run() { 637 if (_lastStatusMessageClearingTask != null) { 638 _lastStatusMessageClearingTask.cancel(); 639 } 640 _statusBar.setMessage(message); 641 642 // If the message is non-empty, schedule a clearing message. 643 if (!message.trim().equals("")) { 644 _lastStatusMessageClearingTask = new TimerTask() { 645 @Override 646 public void run() { 647 status(""); 648 _lastStatusMessageClearingTask = null; 649 } 650 }; 651 _statusMessageTimer.schedule( 652 _lastStatusMessageClearingTask, 653 MAXIMUM_STATUS_MESSAGE_TIME); 654 } 655 } 656 }); 657 } else { 658 System.out.println(message); 659 } 660 } 661 662 /** Register with the global message handler to receive status messages. 663 * @see MessageHandler#status(String) 664 * @param event The window event. 665 */ 666 @Override 667 public void windowGainedFocus(WindowEvent event) { 668 MessageHandler.setStatusHandler(this); 669 } 670 671 /** Unregister with the global message handler to receive status messages. 672 * @see MessageHandler#status(String) 673 * @param event The window event. 674 */ 675 @Override 676 public void windowLostFocus(WindowEvent event) { 677 MessageHandler.setStatusHandler(null); 678 } 679 680 /////////////////////////////////////////////////////////////////// 681 //// public fields //// 682 683 /** Maximum amount of time that a status message is displayed in milliseconds. */ 684 public static final long MAXIMUM_STATUS_MESSAGE_TIME = 30000; 685 686 /////////////////////////////////////////////////////////////////// 687 //// protected methods //// 688 689 /** Open a dialog with basic information about this window. 690 */ 691 protected void _about() { 692 JOptionPane.showMessageDialog(this, 693 "Ptolemy II " + getClass().getName() + "\n" 694 + "By: Claudius Ptolemaeus, ptolemy@eecs.berkeley.edu\n" 695 + "For more information, see\n" 696 + "http://ptolemy.eecs.berkeley.edu/ptolemyII\n\n" 697 + "Copyright (c) 1997-2018, " 698 + "The Regents of the University of California.", 699 "About Ptolemy II", JOptionPane.INFORMATION_MESSAGE); 700 } 701 702 /** Add menus to the menu bar. In this base class, this does nothing. 703 * In derived classes, however, it will add items with commands like 704 * <pre> 705 * JMenu newMenu = new JMenu("My Menu"); 706 * _menubar.add(newMenu); 707 * </pre> 708 * The reason for doing this in a protected method rather than 709 * doing it directly in the constructor of the base class is subtle. 710 * Unfortunately, at this time, Java provides no mechanism for 711 * derived classes to insert menus at arbitrary points in the 712 * menu bar. Also, the menubar ignores the alignment property 713 * of the JMenu. By convention, however, we want the help menu to 714 * be the rightmost menu. Thus, we use a strategy pattern here, 715 * and call a protected method that derived classes can use to 716 * add menus. Thus, this method is called before the Help menu 717 * is added, and hence menus added in this method will appear to 718 * the left of the Help menu. 719 */ 720 protected void _addMenus() { 721 } 722 723 /** Clear the current contents. This base class checks to see whether 724 * the contents have been modified, and if so, then prompts the user 725 * to save them. Derived classes should override this method to 726 * first call this parent class method, then clear the data, 727 * unless the return value is false. A return value of false 728 * indicates that the user has canceled the action. 729 * @return True if the current contents are either saved or discarded 730 * with permission from the user. 731 */ 732 protected boolean _clear() { 733 int result = _queryForSave(); 734 return result == _SAVED || result == _DISCARDED; 735 } 736 737 /** Close the window. Derived classes should override this to 738 * release any resources or remove any listeners. In this class, 739 * if the data associated with this window has been modified, as 740 * indicated by isModified(), then ask the user whether to save 741 * the data before closing. 742 * @return False if the user cancels on a save query. 743 */ 744 protected boolean _close() { 745 if (_debugClosing) { 746 System.out.println("Top._close() : " + this.getName()); 747 } 748 749 // NOTE: We use dispose() here rather than just hiding the 750 // window. This ensures that derived classes can react to 751 // windowClosed events rather than overriding the 752 // windowClosing behavior given here. 753 if (isModified()) { 754 int result = _queryForSave(); 755 756 if (result == _SAVED || result == _DISCARDED) { 757 dispose(); 758 return true; 759 } 760 761 return false; 762 } else { 763 // Window is not modified, so just dispose. 764 dispose(); 765 return true; 766 } 767 } 768 769 /** Create the items in the File menu. A null element in the array 770 * represents a separator in the menu. 771 * 772 * @return The items in the File menu. 773 */ 774 @SuppressWarnings("deprecation") // Java 10, inanely, deprecated getMenuShortcutKeyMask and replaced it with getMenuShortcutKeyMaskEx, which has the same signature. This makes no sense and violates principles of OO design. They should have just fixed getMenuShortcutKeyMask. 775 protected JMenuItem[] _createFileMenuItems() { 776 JMenuItem[] fileMenuItems = new JMenuItem[13]; 777 778 fileMenuItems[0] = new JMenuItem("Open File", KeyEvent.VK_O); 779 fileMenuItems[1] = new JMenuItem("Open URL", KeyEvent.VK_U); 780 // The following assumes _NEW_MENU_INDEX = 2. 781 fileMenuItems[_NEW_MENU_INDEX] = new JMenu("New"); 782 fileMenuItems[3] = new JMenuItem("Save", KeyEvent.VK_S); 783 fileMenuItems[4] = new JMenuItem("Save As", KeyEvent.VK_A); 784 // The following assumes _IMPORT_MENU_INDEX = 5. 785 fileMenuItems[_IMPORT_MENU_INDEX] = new JMenu("Import"); 786 // The following assumes _EXPORT_MENU_INDEX = 6. 787 fileMenuItems[_EXPORT_MENU_INDEX] = new JMenu("Export"); 788 fileMenuItems[7] = new JMenuItem("Print", KeyEvent.VK_P); 789 fileMenuItems[8] = new JMenuItem("Close", KeyEvent.VK_C); 790 791 // Separators 792 fileMenuItems[9] = null; 793 fileMenuItems[11] = null; 794 795 // History submenu 796 JMenu history = new JMenu("Recent Files"); 797 fileMenuItems[10] = history; 798 799 // Exit 800 fileMenuItems[12] = new JMenuItem("Exit", KeyEvent.VK_Q); 801 802 if (StringUtilities.inApplet()) { 803 JMenuItem[] appletFileMenuItems = new JMenuItem[8]; 804 System.arraycopy(fileMenuItems, 0, appletFileMenuItems, 0, 805 appletFileMenuItems.length); 806 appletFileMenuItems[7] = fileMenuItems[10]; 807 fileMenuItems = appletFileMenuItems; 808 // If we are in an applet, disable certain menu items. 809 fileMenuItems[0].setEnabled(false); 810 fileMenuItems[2].setEnabled(false); 811 fileMenuItems[3].setEnabled(false); 812 fileMenuItems[4].setEnabled(false); 813 fileMenuItems[_IMPORT_MENU_INDEX].setEnabled(false); 814 fileMenuItems[_EXPORT_MENU_INDEX].setEnabled(false); 815 return fileMenuItems; 816 } 817 818 // Open button = ctrl-o. 819 fileMenuItems[0].setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, 820 Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); 821 822 // The mnemonic isn't set in the static 823 // initializer because JMenu doesn't have an 824 // appropriate constructor. 825 fileMenuItems[2].setMnemonic(KeyEvent.VK_N); 826 fileMenuItems[_EXPORT_MENU_INDEX].setMnemonic(KeyEvent.VK_E); 827 828 // New Import and Export buttons disabled by default. 829 fileMenuItems[2].setEnabled(false); 830 fileMenuItems[_IMPORT_MENU_INDEX].setEnabled(false); 831 fileMenuItems[_EXPORT_MENU_INDEX].setEnabled(false); 832 833 // Save button = ctrl-s. 834 fileMenuItems[3].setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, 835 Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); 836 837 // Print button = ctrl-p. 838 fileMenuItems[7].setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, 839 Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); 840 841 // Print button disabled by default, unless this class implements 842 // one of the JDK1.2 printing interfaces. 843 if (Top.this instanceof Printable || Top.this instanceof Pageable) { 844 fileMenuItems[7].setEnabled(true); 845 } else { 846 fileMenuItems[7].setEnabled(false); 847 } 848 849 // Close button = ctrl-w. 850 fileMenuItems[8].setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_W, 851 Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); 852 853 // Close button = ctrl-q. 854 fileMenuItems[12].setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, 855 Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); 856 // Sadly, the above doesn't actually work. Command-Q is not caught. 857 858 return fileMenuItems; 859 } 860 861 /** Exit the application after querying the user to save data. 862 * Derived classes should override this to do something more 863 * reasonable, so that user data is not discarded. 864 */ 865 protected void _exit() { 866 if (isModified()) { 867 int result = _queryForSave(); 868 _exitResult = result; 869 if (result == _SAVED || result == _DISCARDED) { 870 StringUtilities.exit(0); 871 } 872 } else { 873 // Window is not modified, so just exit. 874 StringUtilities.exit(0); 875 } 876 } 877 878 /** Return the current directory. 879 * If {@link #setDirectory(File)} or 880 * {@link #_open()}, then the value of the "user.dir" 881 * property is returned. 882 * @return The current directory. 883 */ 884 protected File _getCurrentDirectory() { 885 if (_directory != null) { 886 return _directory; 887 } else { 888 // The default on Windows is to open at user.home, which is 889 // typically not what we want. 890 // So we use the current directory instead. 891 // This will fail with a security exception in applets. 892 String currentWorkingDirectory = StringUtilities 893 .getProperty("user.dir"); 894 if (currentWorkingDirectory == null) { 895 return null; 896 } else { 897 return new File(currentWorkingDirectory); 898 } 899 } 900 } 901 902 /** Get the name of this object, which in this base class is 903 * either the name of the file that has been associated with this 904 * object, or the string "Unnamed" if none. 905 * @return The name. 906 */ 907 protected String _getName() { 908 if (_file == null) { 909 return "Unnamed"; 910 } 911 912 return _file.getName(); 913 } 914 915 /** Display the same information given by _about(). 916 * Derived classes should override this to give information 917 * about the particular window and its role. 918 */ 919 protected void _help() { 920 _about(); 921 } 922 923 /** Open a file dialog to identify a file to be opened, and then call 924 * _read() to open the file. If {@link ptolemy.gui.PtGUIUtilities#useFileDialog()} 925 * return true, then java.awt.FileDialog is used. 926 * Otherwise, javax.swing.JFileChooser is used. 927 */ 928 protected void _open() { 929 if (PtGUIUtilities.useFileDialog()) { 930 _openFileDialog(); 931 } else { 932 _openJFileChooser(); 933 } 934 } 935 936 /** Open a dialog to enter a URL, and then invoke 937 * _read() to open the URL. 938 */ 939 protected void _openURL() { 940 Query query = new Query(); 941 query.setTextWidth(60); 942 query.addLine("url", "URL", _lastURL); 943 944 ComponentDialog dialog = new ComponentDialog(this, "Open URL", query); 945 946 if (dialog.buttonPressed().equals("OK")) { 947 _lastURL = query.getStringValue("url"); 948 949 try { 950 URL url = new URL(_lastURL); 951 _read(url); 952 } catch (Exception ex) { 953 report("Error reading URL:\n" + _lastURL, ex); 954 } 955 } 956 } 957 958 /** Print the contents. If this frame implements either the 959 * Printable or Pageable then those interfaces are used to print 960 * it. 961 */ 962 protected void _print() { 963 // If you are using $PTII/bin/vergil, under bash, set this property: 964 // export JAVAFLAGS=-Dptolemy.ptII.print.platform=CrossPlatform 965 // and then run $PTII/bin/vergil 966 if (StringUtilities.getProperty("ptolemy.ptII.print.platform") 967 .equals("CrossPlatform")) { 968 _printCrossPlatform(); 969 } else { 970 _printNative(); 971 } 972 } 973 974 /** Print using the cross platform dialog. 975 * Note that in java 1.6.0_05, the properties button is disabled, 976 * so using _printNative() is preferred. 977 */ 978 protected void _printCrossPlatform() { 979 // FIXME: Code duplication with PlotBox and PlotFrame. 980 // See PlotFrame for notes. 981 982 // Build a set of attributes 983 PrintRequestAttributeSet aset = new HashPrintRequestAttributeSet(); 984 PrinterJob job = PrinterJob.getPrinterJob(); 985 986 _macCheck(); 987 988 if (this instanceof Pageable) { 989 job.setPageable((Pageable) this); 990 } else if (this instanceof Printable) { 991 //PageFormat format = job.pageDialog(job.defaultPage()); 992 //job.setPrintable((Printable) this, format); 993 job.setPrintable((Printable) this); 994 } else { 995 // Can't print it. 996 return; 997 } 998 999 if (job.printDialog(aset)) { 1000 try { 1001 job.print(aset); 1002 } catch (Exception ex) { 1003 MessageHandler.error("Cross Platform Printing Failed", ex); 1004 } 1005 } 1006 } 1007 1008 /** If a PDF printer is available print to it. 1009 * @exception PrinterException If a printer with the string "PDF" 1010 * cannot be found or if the job cannot be set to the PDF print 1011 * service or if there is another problem printing. 1012 */ 1013 protected void _printPDF() throws PrinterException { 1014 // Find something that will print to PDF 1015 boolean foundPDFPrinter = false; 1016 1017 PrintService pdfPrintService = null; 1018 PrintService printServices[] = PrinterJob.lookupPrintServices(); 1019 for (PrintService printService : printServices) { 1020 if (printService.getName().indexOf("PDF") != -1) { 1021 foundPDFPrinter = true; 1022 pdfPrintService = printService; 1023 } 1024 } 1025 1026 if (pdfPrintService == null || foundPDFPrinter == false) { 1027 throw new PrinterException("Could not find a printer with the " 1028 + "string \"PDF\" in its name. Currently, the -printPDF " 1029 + "facility requires a PDF printer such as the non-free " 1030 + "full version of Adobe Acrobat."); 1031 } 1032 1033 PrinterJob job = PrinterJob.getPrinterJob(); 1034 PageFormat pageFormat = job.defaultPage(); 1035 job.setPrintService(pdfPrintService); 1036 1037 PrintRequestAttributeSet aset = new HashPrintRequestAttributeSet(); 1038 1039 _macCheck(); 1040 1041 if (this instanceof Pageable) { 1042 // FIXME: what about the page format? 1043 job.setPageable((Pageable) this); 1044 job.validatePage(pageFormat); 1045 } else if (this instanceof Printable) { 1046 job.setPrintable((Printable) this, pageFormat); 1047 } else { 1048 System.out.println("Can't print a " + this 1049 + ", it must be either Pageable or Printable"); 1050 // Can't print it. 1051 return; 1052 } 1053 if (foundPDFPrinter) { 1054 // This gets ignored, but let's try it anyway 1055 Destination destination = new Destination( 1056 new File("ptolemy.pdf").toURI()); 1057 aset.add(destination); 1058 1059 // On the Mac, calling job.setJobName() will set the file name, 1060 // but not the directory. 1061 System.out.println( 1062 "Top._printPDF(): Print Job information, much of which is ignored?\n" 1063 + "JobName: " + job.getJobName() + "\nUserName: " 1064 + job.getUserName()); 1065 javax.print.attribute.Attribute[] attributes = aset.toArray(); 1066 for (Attribute attribute : attributes) { 1067 System.out.println(attribute.getName() + " " 1068 + attribute.getCategory() + " " + attribute); 1069 } 1070 1071 job.print(aset); 1072 System.out.println("Window printed from command line. " 1073 + "Under MacOSX, look for " 1074 + "~/Desktop/Java Printing.pdf"); 1075 } 1076 } 1077 1078 /** Print using the native dialog. 1079 */ 1080 protected void _printNative() { 1081 // FIXME: Code duplication with PlotBox and PlotFrame. 1082 // See PlotFrame for notes. 1083 1084 PrinterJob job = PrinterJob.getPrinterJob(); 1085 PageFormat defaultFormat = job.defaultPage(); 1086 PageFormat pageFormat = job.pageDialog(defaultFormat); 1087 1088 // If the print dialog is cancelled, we do not handle the job any more. 1089 // -- tfeng (12/12/2008) 1090 if (defaultFormat == pageFormat) { 1091 return; 1092 } 1093 1094 _macCheck(); 1095 1096 if (this instanceof Pageable) { 1097 // FIXME: what about the page format? 1098 job.setPageable((Pageable) this); 1099 job.validatePage(pageFormat); 1100 } else if (this instanceof Printable) { 1101 job.setPrintable((Printable) this, pageFormat); 1102 } else { 1103 // Can't print it. 1104 return; 1105 } 1106 1107 if (job.printDialog()) { 1108 try { 1109 job.print(); 1110 } catch (Exception ex) { 1111 MessageHandler.error("Native Printing Failed", ex); 1112 } 1113 } 1114 } 1115 1116 /** Open a dialog to prompt the user to save the data. 1117 * Return false if the user clicks "cancel", and otherwise return true. 1118 * If the user clicks "Save", this also saves the data. 1119 * @return _SAVED if the file is saved, _DISCARDED if the modifications are 1120 * discarded, _CANCELED if the operation is canceled by the user, and 1121 * _FAILED if the user selects save and the save fails. 1122 */ 1123 protected int _queryForSave() { 1124 Object[] options = { "Save", "Discard changes", "Cancel" }; 1125 1126 String query = "Save changes to " + StringUtilities.split(_getName()) 1127 + "?"; 1128 1129 // Show the MODAL dialog 1130 int selected = JOptionPane.showOptionDialog(this, query, 1131 "Save Changes?", JOptionPane.YES_NO_CANCEL_OPTION, 1132 JOptionPane.QUESTION_MESSAGE, null, options, options[0]); 1133 1134 if (selected == 0) { 1135 if (_save()) { 1136 return _SAVED; 1137 } else { 1138 return _FAILED; 1139 } 1140 } 1141 1142 if (selected == 1) { 1143 // To prevent the user from being asked again: 1144 setModified(false); 1145 return _DISCARDED; 1146 } 1147 1148 return _CANCELED; 1149 } 1150 1151 /** Read the specified URL. 1152 * @param url The URL to read. 1153 * @exception Exception If the URL cannot be read. 1154 */ 1155 protected abstract void _read(URL url) throws Exception; 1156 1157 /** Save the model to the current file, if there is one, and otherwise 1158 * invoke _saveAs(). This calls _writeFile(). 1159 * @return True if the save succeeds. 1160 */ 1161 protected boolean _save() { 1162 if (_file != null) { 1163 try { 1164 _writeFile(_file); 1165 setModified(false); 1166 return true; 1167 } catch (IOException ex) { 1168 report("Error writing file", ex); 1169 return false; 1170 } 1171 } else { 1172 return _saveAs(); 1173 } 1174 } 1175 1176 /** Query the user for a filename and save the model to that file. 1177 * If {@link ptolemy.gui.PtGUIUtilities#useFileDialog()} 1178 * return true, then java.awt.FileDialog is used. 1179 * Otherwise, javax.swing.JFileChooser is used. 1180 * @return True if the save succeeds. 1181 */ 1182 protected boolean _saveAs() { 1183 if (PtGUIUtilities.useFileDialog()) { 1184 return _saveAsFileDialogImplementation(); 1185 } else { 1186 return _saveAsJFileChooserImplementation(); 1187 } 1188 } 1189 1190 /** Create and return a file dialog for the "Save As" command. 1191 * @return A file dialog for save as. 1192 * @deprecated Use {@link #_saveAsJFileChooserComponent()} 1193 */ 1194 @Deprecated 1195 protected JFileChooser _saveAsFileDialog() { 1196 // Too bad this method was incorrectly named. This method 1197 // returns a JFileChooser, why on earth would its name 1198 // have "FileDialog" in it? 1199 return _saveAsJFileChooserComponent(); 1200 } 1201 1202 /** Create and return a FileDialog for the "Save As" command. 1203 * If {@link ptolemy.gui.PtGUIUtilities#useFileDialog()} returns true, 1204 * then {@link #_saveAs()} uses this method. Otherwise, 1205 * {@link #_saveAsJFileChooserComponent()} is used. 1206 * @return A file dialog for save as. 1207 */ 1208 protected FileDialog _saveAsFileDialogComponent() { 1209 // The name of this method ends in "Component" because 1210 // there already was a _saveAsFileDialog() method. 1211 FileDialog fileDialog = new FileDialog(this, "Save as...", 1212 FileDialog.SAVE); 1213 1214 if (_filenameFilter != null) { 1215 fileDialog.setFilenameFilter(_filenameFilter); 1216 } 1217 1218 fileDialog.setDirectory(_getCurrentDirectory().toString()); 1219 return fileDialog; 1220 } 1221 1222 /** Create and return a JFileChooser for the "Save As" command. 1223 * If {@link ptolemy.gui.PtGUIUtilities#useFileDialog()} returns false, 1224 * then {@link #_saveAs()} uses this method. Otherwise, 1225 * {@link #_saveAsFileDialogComponent()} is used. 1226 * @return A JFileChooser for save as. 1227 */ 1228 protected JFileChooser _saveAsJFileChooserComponent() { 1229 JFileChooser fileDialog = new JFileChooser(); 1230 1231 if (_fileFilter != null) { 1232 fileDialog.addChoosableFileFilter(_fileFilter); 1233 } 1234 1235 fileDialog.setDialogTitle("Save as..."); 1236 fileDialog.setCurrentDirectory(_getCurrentDirectory()); 1237 return fileDialog; 1238 } 1239 1240 /** Update the submenu with a history list 1241 * and add a listener to each line. 1242 * @param historyList The list of history items, 1243 * where each element is a String is the name of the 1244 * menu item. 1245 */ 1246 protected void _populateHistory(List historyList) { 1247 //System.out.println("_populateHistory("+historyList.size()+")"); 1248 1249 // Make sure we have a reference to the Recent Files menu 1250 if (_historyMenu == null) { 1251 Component[] components = _fileMenu.getMenuComponents(); 1252 for (Component component : components) { 1253 if (component instanceof JMenu) { 1254 JMenu menu = (JMenu) component; 1255 String menuText = menu.getText(); 1256 if (menuText.equals("Recent Files")) { 1257 _historyMenu = (JMenu) component; 1258 } 1259 } 1260 } 1261 // If we found it, add to set of history menus 1262 if (_historyMenu != null) { 1263 _historyMenusAndListeners.put(_historyMenu, 1264 _historyMenuListener); 1265 } 1266 } 1267 1268 //System.out.println("number of history menus: " + _historyMenus.size()); 1269 1270 // Update the history menu in each Top 1271 for (Map.Entry<JMenu, HistoryMenuListener> entry : _historyMenusAndListeners 1272 .entrySet()) { 1273 //System.out.println("updating menu " + historyMenu); 1274 1275 JMenu historyMenu = entry.getKey(); 1276 1277 /*int c =*/MemoryCleaner.removeActionListeners(historyMenu); 1278 //System.out.println("_historyMenu: "+c); 1279 1280 historyMenu.removeAll(); 1281 1282 for (int i = 0; i < historyList.size(); i++) { 1283 String recentFileString = (String) historyList.get(i); 1284 JMenuItem item = new JMenuItem(recentFileString); 1285 item.addActionListener(entry.getValue()); 1286 historyMenu.add(item); 1287 } 1288 } 1289 } 1290 1291 /** Add the name of the last file open or set the name to the 1292 * first position if already in the list. 1293 * @param file name of the file to add 1294 * @param delete If true, remove from the history list, otherwise 1295 * the file is added to the beginning. 1296 * @exception IOException If the history file cannot be created, written to, 1297 * or saved. 1298 */ 1299 protected void _updateHistory(String file, boolean delete) 1300 throws IOException { 1301 List<String> historyList = _readHistory(); 1302 1303 // Remove if already present (then added to first position) 1304 for (int i = 0; i < historyList.size(); i++) { 1305 if (historyList.get(i).equals(file)) { 1306 historyList.remove(i); 1307 } 1308 } 1309 1310 // Remove if depth > limit 1311 if (historyList.size() >= _historyDepth) { 1312 historyList.remove(historyList.size() - 1); 1313 } 1314 1315 // Add to fist position 1316 if (!delete) { 1317 historyList.add(0, file); 1318 } 1319 1320 // Serialize history 1321 _writeHistory(historyList); 1322 1323 // Update submenu 1324 _populateHistory(historyList); 1325 } 1326 1327 /////////////////////////////////////////////////////////////////// 1328 //// protected variables //// 1329 1330 /** Write the model to the specified file. 1331 * @param file The file to write to. 1332 * @exception IOException If the write fails. 1333 */ 1334 protected abstract void _writeFile(File file) throws IOException; 1335 1336 /** Indicator that a close operation is canceled. */ 1337 protected static final int _CANCELED = 2; 1338 1339 /** Indicator that a file is discarded. */ 1340 protected static final int _DISCARDED = 1; 1341 1342 /** Indicator that a file save failed. */ 1343 protected static final int _FAILED = 3; 1344 1345 /** Indicator that a file is saved. */ 1346 protected static final int _SAVED = 0; 1347 1348 /** The most recent directory used in a file dialog. */ 1349 protected static File _directory = null; 1350 1351 /** The return value of the _exit() menu. 1352 * We use a separate variable here for backward compatibility. 1353 * The values of this variable are the values returned by _queryForSave. 1354 */ 1355 protected int _exitResult = _CANCELED; 1356 1357 /** The FileFilter that determines what files are displayed by 1358 * the Open dialog and the Save As dialog 1359 * The initial default is null, which causes no FileFilter to be 1360 * applied, which results in all files being displayed. 1361 */ 1362 protected FileFilter _fileFilter = null; 1363 1364 /** The FileFilter used by the java.awt.FileDialog that determines 1365 * what files are displayed by the Open dialog and the Save As 1366 * dialog The initial default is null, which causes no FileFilter 1367 * to be applied, which results in all files being displayed. 1368 * 1369 * <p>Note that this class can use either java.awt.FileDialog 1370 * or javax.swing.JFileChooser, so classes should set 1371 * both _fileFilter and _filenameFilter. Note that it 1372 * possible to define an inner class that extends 1373 * javax.swing.filechooser.FileFilter and implements 1374 * java.io.FilenameFilter and that has two separate accept() 1375 * methods. This inner class can then be set as the 1376 * value for both _fileFilter and _filenameFilter: 1377 * <pre> 1378 * ... 1379 * MyFileFilter myFileFilter = new MyFileFilter(); 1380 * _fileFilter = myFileFilter; 1381 * _filenameFilter = myFileFilter; 1382 * ... 1383 * static MyFileFilter extends FileFilter implements FilenameFilter { 1384 * public boolean accept(File file) { 1385 * // For FileFilter 1386 * return true; 1387 * } 1388 * 1389 * public boolean accept(File director, String name) { 1390 * // For FilenameFilter 1391 * return true; 1392 * } 1393 * 1394 * public String getDescription() { 1395 * // For FileFilter 1396 * return "My filter description."; 1397 * } 1398 * } 1399 * </pre> 1400 */ 1401 protected FilenameFilter _filenameFilter = null; 1402 1403 /** File menu for this frame. */ 1404 protected JMenu _fileMenu = new JMenu("File"); 1405 1406 /** Items in the file menu. A null element represents a separator. */ 1407 protected JMenuItem[] _fileMenuItems = _createFileMenuItems(); 1408 1409 /** Index in the _fileMenuItems array of the New item. */ 1410 protected static int _NEW_MENU_INDEX = 2; 1411 1412 /** Index in the _fileMenuItems array of the Import item. */ 1413 protected static int _IMPORT_MENU_INDEX = 5; 1414 1415 /** Index in the _fileMenuItems array of the Export item. */ 1416 protected static int _EXPORT_MENU_INDEX = 6; 1417 1418 /** Help menu for this frame. */ 1419 protected JMenu _helpMenu = new JMenu("Help"); 1420 1421 /** Help menu items. */ 1422 protected JMenuItem[] _helpMenuItems = { 1423 new JMenuItem("About", KeyEvent.VK_A), 1424 new JMenuItem("Help", KeyEvent.VK_H), }; 1425 1426 /** Menubar for this frame. */ 1427 protected JMenuBar _menubar = new JMenuBar(); 1428 1429 /** The status bar. */ 1430 protected StatusBar _statusBar = null; 1431 1432 /** Listener for file menu commands. */ 1433 class FileMenuListener implements ActionListener { 1434 @Override 1435 public void actionPerformed(ActionEvent e) { 1436 // Make this the default context for modal messages. 1437 UndeferredGraphicalMessageHandler.setContext(Top.this); 1438 1439 JMenuItem target = (JMenuItem) e.getSource(); 1440 String actionCommand = target.getActionCommand(); 1441 1442 try { 1443 if (actionCommand.equals("Open File")) { 1444 _open(); 1445 } else if (actionCommand.equals("Open URL")) { 1446 _openURL(); 1447 } else if (actionCommand.equals("Save")) { 1448 _save(); 1449 } else if (actionCommand.equals("Save As")) { 1450 _saveAs(); 1451 } else if (actionCommand.equals("Print")) { 1452 _print(); 1453 } else if (actionCommand.equals("Close")) { 1454 if (!isDisposed()) { 1455 Window thisWindow = Top.this; 1456 WindowEvent wev = new WindowEvent(thisWindow, 1457 WindowEvent.WINDOW_CLOSING); 1458 Toolkit toolkit = Toolkit.getDefaultToolkit(); 1459 EventQueue queue = toolkit.getSystemEventQueue(); 1460 queue.postEvent(wev); 1461 } 1462 } else if (actionCommand.equals("Exit")) { 1463 _exit(); 1464 } 1465 } catch (Throwable throwable) { 1466 // If we do not catch exceptions here, then they 1467 // disappear to stdout, which is bad if we launched 1468 // where there is no stdout visible. 1469 MessageHandler.error("File Menu Exception:", throwable); 1470 } 1471 1472 // NOTE: The following should not be needed, but jdk1.3beta 1473 // appears to have a bug in swing where repainting doesn't 1474 // properly occur. 1475 repaint(); 1476 } 1477 } 1478 1479 /** Listener for help menu commands. */ 1480 class HelpMenuListener implements ActionListener { 1481 @Override 1482 public void actionPerformed(ActionEvent e) { 1483 // Make this the default context for modal messages. 1484 UndeferredGraphicalMessageHandler.setContext(Top.this); 1485 1486 JMenuItem target = (JMenuItem) e.getSource(); 1487 String actionCommand = target.getActionCommand(); 1488 1489 try { 1490 if (actionCommand.equals("About")) { 1491 _about(); 1492 } else if (actionCommand.equals("Help")) { 1493 _help(); 1494 } 1495 } catch (Throwable throwable) { 1496 // If we do not catch exceptions here, then they 1497 // disappear to stdout, which is bad if we launched 1498 // where there is no stdout visible. 1499 MessageHandler.error("Help Menu Exception:", throwable); 1500 } 1501 1502 // NOTE: The following should not be needed, but there jdk1.3beta 1503 // appears to have a bug in swing where repainting doesn't 1504 // properly occur. 1505 repaint(); 1506 } 1507 } 1508 1509 /////////////////////////////////////////////////////////////////// 1510 //// private methods //// 1511 1512 // Execute all actions pending on the deferred action list. 1513 // The list is cleared and the _actionsDeferred variable is set 1514 // to false, even if one of the deferred actions fails. 1515 // This method should only be invoked in the event dispatch thread. 1516 // It is synchronized on the _deferredActions list, so the integrity 1517 // of that list is ensured, since modifications to that list occur 1518 // only in other places that are also synchronized on the list. 1519 private static void _executeDeferredActions() { 1520 // Copy the list so that we do not hold the synchronization lock 1521 // while executing the actions. 1522 LinkedList actionsCopy = null; 1523 synchronized (_deferredActions) { 1524 actionsCopy = new LinkedList(_deferredActions); 1525 _actionsDeferred = false; 1526 _deferredActions.clear(); 1527 } 1528 Iterator actions = actionsCopy.iterator(); 1529 1530 // Collect and report exceptions. 1531 LinkedList<Throwable> exceptions = null; 1532 while (actions.hasNext()) { 1533 Runnable action = (Runnable) actions.next(); 1534 try { 1535 action.run(); 1536 } catch (Throwable ex) { 1537 if (exceptions == null) { 1538 exceptions = new LinkedList<Throwable>(); 1539 } 1540 exceptions.add(ex); 1541 } 1542 } 1543 if (exceptions != null) { 1544 StringBuffer message = new StringBuffer( 1545 "Exceptions occurred in deferred actions:\n"); 1546 for (Throwable exception : exceptions) { 1547 message.append(exception); 1548 message.append("\n"); 1549 } 1550 MessageHandler.error(message.toString(), exceptions.get(0)); 1551 } 1552 } 1553 1554 /** Return the value of the history file name. 1555 * @return The value of the history file name, which is usually in 1556 * the Ptolemy II preferences directory. The value returned is usually. 1557 * "~/.ptolemyII/history.txt". 1558 * @exception IOException If thrown while reading the preferences directory. 1559 */ 1560 private String _getHistoryFileName() throws IOException { 1561 return StringUtilities.preferencesDirectory() + "history.txt"; 1562 } 1563 1564 /** Open a file dialog using the java.awt.Dialog class to identify 1565 * a file to be opened, and then call _read() to open the file. 1566 * Under Mac OS X, this method is preferred over _openJFileChooser() 1567 * because this method uses java.awt.Dialog, whereas _openJFileChooser() 1568 * uses javax.swing.JFileChooser. 1569 */ 1570 private void _openFileDialog() { 1571 FileDialog fileDialog = new FileDialog(this, "Select a model file", 1572 FileDialog.LOAD); 1573 1574 if (_filenameFilter != null) { 1575 fileDialog.setFilenameFilter(_filenameFilter); 1576 } 1577 boolean fail = false; 1578 if (_directory != null) { 1579 try { 1580 fileDialog.setDirectory(_directory.getCanonicalPath()); 1581 } catch (IOException ex) { 1582 fail = true; 1583 } 1584 } 1585 if (fail || _directory == null) { 1586 // The default on Windows is to open at user.home, which is 1587 // typically an absurd directory inside the O/S installation. 1588 // So we use the current directory instead. 1589 // This will throw a security exception in an applet. 1590 // FIXME: we should support users under applets opening files 1591 // on the server. 1592 String currentWorkingDirectory = StringUtilities 1593 .getProperty("user.dir"); 1594 1595 if (currentWorkingDirectory != null) { 1596 fileDialog.setDirectory(currentWorkingDirectory); 1597 } 1598 } 1599 1600 fileDialog.show(); 1601 1602 if (fileDialog.getDirectory() == null) { 1603 _directory = new File(""); 1604 } else { 1605 _directory = new File(fileDialog.getDirectory()); 1606 } 1607 1608 try { 1609 // NOTE: It would be nice if it were possible to enter 1610 // a URL in the file chooser, but Java's file chooser does 1611 // not permit this, regrettably. So we have a separate 1612 // menu item for this. 1613 File file = new File(_directory, fileDialog.getFile()); 1614 // Report on the time it takes to open the model. 1615 long startTime = System.currentTimeMillis(); 1616 _read(file.toURI().toURL()); 1617 long endTime = System.currentTimeMillis(); 1618 if (endTime > startTime + 10000) { 1619 // Only print the time if it is more than 10 1620 // seconds See also PtolemyEffigy. Perhaps 1621 // this code should be in PtolemyEffigy, but 1622 // if it is here, we get the time it takes to 1623 // read any file, not just a Ptolemy model. 1624 System.out.println("Opened " + file + " in " 1625 + (System.currentTimeMillis() - startTime) + " ms."); 1626 } 1627 // Only add file if no exception 1628 _updateHistory(file.getAbsolutePath(), false); 1629 1630 } catch (Error error) { 1631 // Be sure to catch Error here so that if we throw an 1632 // Error, then we will report it to with a window. 1633 try { 1634 throw new RuntimeException(error); 1635 } catch (Exception ex2) { 1636 report("Error while reading input:", ex2); 1637 } 1638 } catch (Exception ex) { 1639 // NOTE: The XML parser can only throw an 1640 // XmlException. It signals that it is a user 1641 // cancellation with the special string pattern 1642 // "*** Canceled." in the message. 1643 1644 if (ex.getMessage() != null 1645 && !ex.getMessage().startsWith("*** Canceled.")) { 1646 // No need to report a CancelException, since 1647 // it results from the user clicking a 1648 // "Cancel" button. 1649 report("Error reading input", ex); 1650 } 1651 } 1652 } 1653 1654 /** Open a file dialog to identify a file to be opened, and then call 1655 * _read() to open the file. 1656 */ 1657 private void _openJFileChooser() { 1658 // Swap backgrounds and avoid white boxes in "common places" dialog 1659 JFileChooserBugFix jFileChooserBugFix = new JFileChooserBugFix(); 1660 Color background = null; 1661 try { 1662 background = jFileChooserBugFix.saveBackground(); 1663 1664 JFileChooser fileDialog = new JFileChooser(); 1665 1666 // To disable the Windows places bar on the left, uncomment the 1667 // line below. 1668 // fileDialog.putClientProperty("FileChooser.useShellFolder", Boolean.FALSE); 1669 if (_fileFilter != null) { 1670 fileDialog.addChoosableFileFilter(_fileFilter); 1671 } 1672 1673 fileDialog.setDialogTitle("Select a model file."); 1674 1675 if (_directory != null) { 1676 fileDialog.setCurrentDirectory(_directory); 1677 } else { 1678 // The default on Windows is to open at user.home, which is 1679 // typically an absurd directory inside the O/S installation. 1680 // So we use the current directory instead. 1681 // This will throw a security exception in an applet. 1682 // FIXME: we should support users under applets opening files 1683 // on the server. 1684 String currentWorkingDirectory = StringUtilities 1685 .getProperty("user.dir"); 1686 1687 if (currentWorkingDirectory != null) { 1688 fileDialog.setCurrentDirectory( 1689 new File(currentWorkingDirectory)); 1690 } 1691 } 1692 1693 if (fileDialog 1694 .showOpenDialog(this) == JFileChooser.APPROVE_OPTION) { 1695 _directory = fileDialog.getCurrentDirectory(); 1696 1697 try { 1698 // NOTE: It would be nice if it were possible to enter 1699 // a URL in the file chooser, but Java's file chooser does 1700 // not permit this, regrettably. So we have a separate 1701 // menu item for this. 1702 File file = fileDialog.getSelectedFile().getCanonicalFile(); 1703 // Report on the time it takes to open the model. 1704 long startTime = System.currentTimeMillis(); 1705 _read(file.toURI().toURL()); 1706 long endTime = System.currentTimeMillis(); 1707 if (endTime > startTime + 10000) { 1708 // Only print the time if it is more than 10 1709 // seconds See also PtolemyEffigy. Perhaps 1710 // this code should be in PtolemyEffigy, but 1711 // if it is here, we get the time it takes to 1712 // read any file, not just a Ptolemy model. 1713 System.out.println("Opened " + file + " in " 1714 + (System.currentTimeMillis() - startTime) 1715 + " ms."); 1716 } 1717 // Only add file if no exception 1718 _updateHistory(file.getAbsolutePath(), false); 1719 1720 } catch (Error error) { 1721 // Be sure to catch Error here so that if we throw an 1722 // Error, then we will report it to with a window. 1723 try { 1724 throw new RuntimeException(error); 1725 } catch (Exception ex2) { 1726 report("Error while reading input:", ex2); 1727 } 1728 } catch (Exception ex) { 1729 // NOTE: The XML parser can only throw an 1730 // XmlException. It signals that it is a user 1731 // cancellation with the special string pattern 1732 // "*** Canceled." in the message. 1733 1734 if (ex.getMessage() != null 1735 && !ex.getMessage().startsWith("*** Canceled.")) { 1736 // No need to report a CancelException, since 1737 // it results from the user clicking a 1738 // "Cancel" button. 1739 report("Error reading input", ex); 1740 } 1741 } 1742 } 1743 } finally { 1744 jFileChooserBugFix.restoreBackground(background); 1745 } 1746 } 1747 1748 /** If we are running under Mac OS X and Java 1.5, the print a 1749 * message about printing problems. 1750 */ 1751 private static void _macCheck() { 1752 if (PtGUIUtilities.macOSLookAndFeel() 1753 && System.getProperty("java.version").startsWith("1.5")) { 1754 System.out.println( 1755 "Warning, under Mac OS X with Java 1.5, printing might " 1756 + "not work. Try recompiling with Java 1.6 or setting a property:\n" 1757 + "export JAVAFLAGS=-Dptolemy.ptII.print.platform=CrossPlatform\n" 1758 + "and restarting vergil: $PTII/bin/vergil"); 1759 } 1760 } 1761 1762 /** Initialize the menus and key bindings for Mac OS X. */ 1763 private void _macInitializer() { 1764 // FIXME: This code causes a memory leak because 1765 // MacOSXAdapter and JPopupMenu return retain references to 1766 // ActorGraphFrame. Also, MacOSXAdapter uses deprecated 1767 // methods via reflection. 1768 try { 1769 // To set the about name, set the 1770 // com.apple.mrj.application.apple.menu.about.name 1771 // property in the main thread, not the event thread. See 1772 // VergilApplication.java 1773 MacOSXAdapter.setAboutMethod(this, 1774 getClass().getMethod("about", (Class[]) null)); 1775 MacOSXAdapter.setQuitMethod(this, 1776 getClass().getMethod("exit", (Class[]) null)); 1777 } catch (NoSuchMethodException ex) { 1778 report("Mac OS X specific initializations failed.", ex); 1779 } 1780 } 1781 1782 /** Get the history from the file that contains names 1783 * Always return a list, that can be empty 1784 * @return list of file history 1785 */ 1786 private List<String> _readHistory() throws IOException { 1787 ArrayList<String> historyList = new ArrayList<String>(); 1788 String historyFileName = _getHistoryFileName(); 1789 if (!new File(historyFileName).exists()) { 1790 // No history file, so just return 1791 return historyList; 1792 } 1793 FileReader fileReader = null; 1794 BufferedReader bufferedReader = null; 1795 try { 1796 fileReader = new FileReader(historyFileName); 1797 bufferedReader = new BufferedReader(fileReader); 1798 String line; 1799 while ((line = bufferedReader.readLine()) != null) { 1800 historyList.add(line); 1801 } 1802 } finally { 1803 if (bufferedReader != null) { 1804 bufferedReader.close(); 1805 } 1806 } 1807 1808 return historyList; 1809 } 1810 1811 /** Query the user for a filename and save the model to that file. 1812 * @return True if the save succeeds. 1813 */ 1814 private boolean _saveAsFileDialogImplementation() { 1815 // This method name ends with "Implementation" because there 1816 // already was a _saveAsFileDialog() method 1817 1818 // Use the strategy pattern here to create the actual 1819 // dialog so that subclasses can customize this dialog. 1820 FileDialog fileDialog = _saveAsFileDialogComponent(); 1821 1822 if (fileDialog == null) { 1823 // Action was canceled. 1824 return false; 1825 } 1826 1827 fileDialog.show(); 1828 1829 _directory = new File(fileDialog.getDirectory()); 1830 1831 _file = new File(_directory, fileDialog.getFile()); 1832 1833 // If the file exists, then FileDialog will prompt the user 1834 // so we don't want to prompt them again. 1835 // See "saving xml to existing file asks twice on mac" 1836 // http://bugzilla.ecoinformatics.org/show_bug.cgi?id=5760 1837 // if (_file.exists()) { 1838 // // Ask for confirmation before overwriting a file. 1839 // String query = "Overwrite " + _file.getName() + "?"; 1840 // 1841 // // Show a MODAL dialog 1842 // int selected = JOptionPane.showOptionDialog(this, query, 1843 // "Save Changes?", JOptionPane.YES_NO_OPTION, 1844 // JOptionPane.QUESTION_MESSAGE, null, null, null); 1845 // 1846 // if (selected == 1) { 1847 // return false; 1848 // } 1849 // } 1850 1851 // Truncate the name so that dialogs under Web Start on the Mac 1852 // work better. 1853 setTitle(StringUtilities.abbreviate(_getName())); 1854 return _save(); 1855 } 1856 1857 /** Query the user for a filename and save the model to that file. 1858 * @return True if the save succeeds. 1859 */ 1860 private boolean _saveAsJFileChooserImplementation() { 1861 // Swap backgrounds and avoid white boxes in "common places" dialog 1862 JFileChooserBugFix jFileChooserBugFix = new JFileChooserBugFix(); 1863 Color background = null; 1864 try { 1865 background = jFileChooserBugFix.saveBackground(); 1866 1867 // Use the strategy pattern here to create the actual 1868 // dialog so that subclasses can customize this dialog. 1869 JFileChooser fileDialog = _saveAsFileDialog(); 1870 1871 // Under Java 1.6 and Mac OS X, showSaveDialog() ignores the filter. 1872 if (fileDialog 1873 .showSaveDialog(this) == JFileChooser.APPROVE_OPTION) { 1874 _file = fileDialog.getSelectedFile(); 1875 1876 if (_file.exists()) { 1877 // Ask for confirmation before overwriting a file. 1878 String query = "Overwrite " + _file.getName() + "?"; 1879 1880 // Show a MODAL dialog 1881 int selected = JOptionPane.showOptionDialog(this, query, 1882 "Save Changes?", JOptionPane.YES_NO_OPTION, 1883 JOptionPane.QUESTION_MESSAGE, null, null, null); 1884 1885 if (selected == 1) { 1886 return false; 1887 } 1888 } 1889 1890 // Truncate the name so that dialogs under Web Start on the Mac 1891 // work better. 1892 setTitle(StringUtilities.abbreviate(_getName())); 1893 _directory = fileDialog.getCurrentDirectory(); 1894 return _save(); 1895 } 1896 1897 // Action was canceled. 1898 return false; 1899 } finally { 1900 jFileChooserBugFix.restoreBackground(background); 1901 } 1902 } 1903 1904 /** Write history to the file defined by _getHistoryFileName(). */ 1905 private void _writeHistory(List<String> historyList) throws IOException { 1906 FileWriter fileWriter = null; 1907 try { 1908 fileWriter = new FileWriter(_getHistoryFileName()); 1909 for (String line : historyList) { 1910 fileWriter.write(line + "\n"); 1911 } 1912 } finally { 1913 if (fileWriter != null) { 1914 fileWriter.close(); 1915 } 1916 } 1917 } 1918 1919 /////////////////////////////////////////////////////////////////// 1920 //// protected variables //// 1921 1922 /** Set to true to print closing sequence information to standard 1923 * out. 1924 */ 1925 protected boolean _debugClosing = false; 1926 1927 /////////////////////////////////////////////////////////////////// 1928 //// inner classes //// 1929 1930 /** A runnable for showing the window. */ 1931 class ShowWindowRunnable implements Runnable { 1932 @Override 1933 public void run() { 1934 // NOTE: We used to call pack() here, but this would 1935 // override any manual changes in sizing that had been 1936 // made. 1937 setState(Frame.NORMAL); 1938 // FIXME: show() is deprecated, but calling setVisible() 1939 // here results in a loop. 1940 //Top.super.setVisible(true); 1941 Top.super.show(); 1942 1943 } 1944 } 1945 1946 /** A runnable for setting the status bar message for a report. */ 1947 class StatusBarMessageReportRunnable implements Runnable { 1948 public StatusBarMessageReportRunnable(String message, 1949 Throwable throwable) { 1950 _message = message; 1951 _throwable = throwable; 1952 } 1953 1954 @Override 1955 public void run() { 1956 if (_statusBar != null) { 1957 _statusBar 1958 .setMessage(MessageHandler.shortDescription(_throwable) 1959 + ". " + _message); 1960 } 1961 1962 MessageHandler.error(_message, _throwable); 1963 } 1964 1965 private String _message; 1966 private Throwable _throwable; 1967 } 1968 1969 /** A runnable for setting the status bar message. */ 1970 class StatusBarMessageRunnable implements Runnable { 1971 public StatusBarMessageRunnable(String message) { 1972 _message = message; 1973 } 1974 1975 @Override 1976 public void run() { 1977 if (_statusBar != null) { 1978 _statusBar.setMessage(_message); 1979 } 1980 } 1981 1982 private String _message; 1983 } 1984 1985 /** A runnable for packing the Window. */ 1986 class DoPackRunnable implements Runnable { 1987 @Override 1988 public void run() { 1989 // NOTE: This always runs in the swing thread, 1990 // so there is no need to synchronize. 1991 if (!_menuPopulated) { 1992 // Set up the menus. 1993 _fileMenu.setMnemonic(KeyEvent.VK_F); 1994 _helpMenu.setMnemonic(KeyEvent.VK_H); 1995 1996 // Construct the File menu by adding action commands 1997 // and action listeners. 1998 1999 // Set the action command and listener for each menu item. 2000 for (JMenuItem _fileMenuItem : _fileMenuItems) { 2001 if (_fileMenuItem == null) { 2002 _fileMenu.addSeparator(); 2003 } else { 2004 _fileMenuItem.setActionCommand(_fileMenuItem.getText()); 2005 _fileMenuItem.addActionListener(_fileMenuListener); 2006 _fileMenu.add(_fileMenuItem); 2007 } 2008 } 2009 2010 _menubar.add(_fileMenu); 2011 2012 // History fill 2013 try { 2014 _populateHistory(_readHistory()); 2015 } catch (IOException ex) { 2016 // Ignore 2017 } catch (SecurityException ex) { 2018 // Ignore 2019 } 2020 2021 // Construct the Help menu by adding action commands 2022 // and action listeners. 2023 2024 // Set the action command and listener for each menu item. 2025 for (JMenuItem _helpMenuItem : _helpMenuItems) { 2026 _helpMenuItem.setActionCommand(_helpMenuItem.getText()); 2027 _helpMenuItem.addActionListener(_helpMenuListener); 2028 _helpMenu.add(_helpMenuItem); 2029 } 2030 2031 // Unfortunately, at this time, Java provides no 2032 // mechanism for derived classes to insert menus 2033 // at arbitrary points in the menu bar. Also, the 2034 // menubar ignores the alignment property of the 2035 // JMenu. By convention, however, we want the 2036 // help menu to be the rightmost menu. Thus, we 2037 // use a strategy pattern here, and call a 2038 // protected method that derived classes can use 2039 // to add menus. 2040 _addMenus(); 2041 2042 _menubar.add(_helpMenu); 2043 2044 if (!_hideMenuBar) { 2045 setJMenuBar(_menubar); 2046 } 2047 2048 // Add the status bar, if there is one. 2049 if (_statusBar != null) { 2050 getContentPane().add(_statusBar, BorderLayout.SOUTH); 2051 } 2052 } 2053 2054 Top.super.pack(); 2055 2056 if (_centering) { 2057 centerOnScreen(); 2058 } 2059 _menuPopulated = true; 2060 } 2061 } 2062 2063 /** A runnable for executing deferred actions. */ 2064 static class DeferredActionsRunnable implements Runnable { 2065 @Override 2066 public void run() { 2067 _executeDeferredActions(); 2068 } 2069 } 2070 2071 /** A runnable for closing the window. */ 2072 class CloseWindowRunnable implements Runnable { 2073 @Override 2074 public void run() { 2075 _close(); 2076 } 2077 } 2078 2079 /** A runnable for centering the window on the screen. */ 2080 class CenterOnScreenRunnable implements Runnable { 2081 @Override 2082 public void run() { 2083 Toolkit tk = Toolkit.getDefaultToolkit(); 2084 setLocation((tk.getScreenSize().width - getSize().width) / 2, 2085 (tk.getScreenSize().height - getSize().height) / 2); 2086 2087 // Make this the default context for modal messages. 2088 UndeferredGraphicalMessageHandler.setContext(Top.this); 2089 } 2090 } 2091 2092 /** A runnable for setting the background color of the status bar. */ 2093 class SetBackgroundRunnable implements Runnable { 2094 @Override 2095 public void run() { 2096 Top.super.setBackground(_statusBarBackground); 2097 2098 // This seems to be called in a base class 2099 // constructor, before this variable has been 2100 // set. Hence the test against null. 2101 if (_statusBar != null) { 2102 _statusBar.setBackground(_statusBarBackground); 2103 } 2104 } 2105 } 2106 2107 /** Listener for windowClosing action. */ 2108 class CloseWindowAdapter extends WindowAdapter { 2109 @Override 2110 public void windowClosing(WindowEvent e) { 2111 if (_debugClosing) { 2112 System.out.println("Top$CloseWindowAdapter.windowClosing() : " 2113 + Top.this.getName()); 2114 } 2115 2116 Window window = e.getWindow(); 2117 if (window instanceof Top) { 2118 Top top = (Top) window; 2119 if (!top.isDisposed()) { 2120 _close(); 2121 } 2122 } 2123 } 2124 } 2125 2126 /** Listener for history menu commands. */ 2127 class HistoryMenuListener implements ActionListener { 2128 @Override 2129 public void actionPerformed(ActionEvent event) { 2130 // Make this the default context for modal messages. 2131 UndeferredGraphicalMessageHandler.setContext(Top.this); 2132 2133 JMenuItem target = (JMenuItem) event.getSource(); 2134 String path = target.getActionCommand(); 2135 2136 File file = new File(path); 2137 2138 // See if the file is still there. 2139 if (!file.exists()) { 2140 MessageHandler.error("File no longer exists."); 2141 try { 2142 _updateHistory(path, true); 2143 } catch (IOException ex2) { 2144 // Ignore 2145 } 2146 return; 2147 } 2148 2149 // Try to read the file. 2150 try { 2151 _read(file.toURI().toURL()); 2152 } catch (Exception e) { 2153 MessageHandler.error("Error reading " + file.getAbsolutePath(), 2154 e); 2155 try { 2156 _updateHistory(path, true); 2157 } catch (IOException ex2) { 2158 // Ignore 2159 } 2160 return; 2161 } 2162 2163 // Try to move the file to the top of the history menu. 2164 try { 2165 _updateHistory(path, false); 2166 } catch (IOException e) { 2167 MessageHandler.error("Error updating history file.", e); 2168 return; 2169 } 2170 2171 setDirectory(file.getParentFile()); 2172 } 2173 } 2174 2175 /////////////////////////////////////////////////////////////////// 2176 //// private variables //// 2177 2178 /** An ActionListener for the menu items in the file menu. */ 2179 private FileMenuListener _fileMenuListener; 2180 2181 /** An ActionListener for the menu items in the help menu. */ 2182 private HelpMenuListener _helpMenuListener; 2183 2184 /** An ActionListener for the menu items in the history menu. */ 2185 private HistoryMenuListener _historyMenuListener; 2186 2187 /** A reference to the history menu. */ 2188 private JMenu _historyMenu; 2189 2190 /** A mapping of history menu to history menu listener for all open Top 2191 * windows. 2192 */ 2193 private static Map<JMenu, HistoryMenuListener> _historyMenusAndListeners = Collections 2194 .synchronizedMap(new WeakHashMap<JMenu, HistoryMenuListener>()); 2195 2196 /** The background color of the status bar */ 2197 private Color _statusBarBackground; 2198 2199 /** Indicator of whether actions are deferred. */ 2200 private static boolean _actionsDeferred = false; 2201 2202 // A flag indicating whether or not to center the window. 2203 private boolean _centering = true; 2204 2205 /** List of deferred actions. */ 2206 private static List _deferredActions = new LinkedList(); 2207 2208 /** True if this frame has been disposed. */ 2209 private boolean _disposed = false; 2210 2211 /** The input file. */ 2212 private File _file = null; 2213 2214 /** Flag to hide the menu bar. */ 2215 private boolean _hideMenuBar = false; 2216 2217 /** History depth. */ 2218 private int _historyDepth = 10; 2219 2220 /** The most recently entered URL in Open URL. */ 2221 private String _lastURL = "http://ptolemy.eecs.berkeley.edu/xml/models/"; 2222 2223 /** Last status message clearing task. */ 2224 private TimerTask _lastStatusMessageClearingTask = null; 2225 2226 /** Indicator that the menu has been populated. */ 2227 private boolean _menuPopulated = false; 2228 2229 /** Indicator that the data represented in the window has been modified. */ 2230 private boolean _modified = false; 2231 2232 /** True if we have printed the securityException message. */ 2233 private static boolean _printedSecurityExceptionMessage = false; 2234 2235 /** Timer used for status messages. */ 2236 private Timer _statusMessageTimer; 2237}