001/** 002 * MainWindow -- The main document window for the GeomSS application. 003 * 004 * Copyright (C) 2009-2024, by Joseph A. Huwaldt. All rights reserved. 005 * 006 * This library is free software; you can redistribute it and/or modify it under the terms 007 * of the GNU Lesser General Public License as published by the Free Software Foundation; 008 * either version 2.1 of the License, or (at your option) any later version. 009 * 010 * This library is distributed in the hope that it will be useful, but WITHOUT ANY 011 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 012 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. 013 * 014 * You should have received a copy of the GNU Lesser General Public License along with 015 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - 016 * Suite 330, Boston, MA 02111-1307, USA. Or visit: http://www.gnu.org/licenses/lgpl.html 017 */ 018package geomss.app; 019 020import bsh.EvalError; 021import bsh.Interpreter; 022import bsh.util.JConsole; 023import geomss.GeomSSApp; 024import geomss.GeomSSScene; 025import geomss.GeomSSUtil; 026import geomss.geom.GeomElement; 027import geomss.geom.GeomList; 028import geomss.geom.GeometryList; 029import geomss.geom.reader.GeomReader; 030import geomss.geom.reader.GeomReaderFactory; 031import geomss.geom.reader.XGSSGeomReader; 032import static geomss.j3d.ProjectionPolicy.PARALLEL_PROJECTION; 033import static geomss.j3d.ProjectionPolicy.PERSPECTIVE_PROJECTION; 034import geomss.j3d.RenderType; 035import geomss.ui.DialogItem; 036import geomss.ui.InputDialog; 037import jahuwaldt.io.ExtFilenameFilter; 038import jahuwaldt.io.FileUtils; 039import jahuwaldt.j3d.TransformChangeEvent; 040import jahuwaldt.j3d.TransformChangeListener; 041import jahuwaldt.j3d.image.JPEGImageObserver; 042import jahuwaldt.j3d.image.PNGImageObserver; 043import jahuwaldt.swing.*; 044import java.awt.*; 045import java.awt.event.ActionEvent; 046import java.awt.event.ActionListener; 047import java.awt.event.WindowEvent; 048import java.awt.print.PageFormat; 049import java.awt.print.PrinterException; 050import java.awt.print.PrinterJob; 051import java.io.File; 052import java.io.IOException; 053import java.net.URL; 054import java.text.MessageFormat; 055import java.util.ArrayList; 056import java.util.List; 057import static java.util.Objects.isNull; 058import static java.util.Objects.nonNull; 059import static java.util.Objects.requireNonNull; 060import java.util.ResourceBundle; 061import javax.swing.*; 062import javax.swing.text.DefaultEditorKit; 063import javolution.text.TypeFormat; 064import javolution.util.FastMap; 065 066 067/** 068 * Main window for the GeomSS program. Most of the program code is based here. 069 * 070 * <p> Modified by: Joseph A. Huwaldt </p> 071 * 072 * @author Joseph A. Huwaldt, Date: May 2, 2009 073 * @version January 1, 2024 074 */ 075@SuppressWarnings("serial") 076public class MainWindow extends JFrame { 077 078 private static final short CANVAS_WIDTH = 640; // Initial 3D canvas width & height. 079 private static final short CANVAS_HEIGHT = 480; 080 081 // Menu items for the File menu. 082 private static final int EXPORT_AS_ITEM = 9; 083 084 // Menu items for the Edit menu. 085 private static final int CUT_ITEM = 3; 086 private static final int COPY_ITEM = 4; 087 private static final int PASTE_ITEM = 5; 088 089 // The page format used for printing. 090 private PageFormat _pf = null; 091 092 /** 093 * A count of the number of instances of the application window that have been opened. 094 */ 095 private static int _instanceCount = 0; 096 097 // A list of available point geometry writers. 098 private final List<GeomReader> _geomWriters = new ArrayList(); 099 100 // A reference to this application's 3D canvas. 101 private GeomSSCanvas3D _canvas; 102 103 // An image observer that writes out PNG files. 104 private final PNGImageObserver _PNGObserver = new PNGImageObserver(); 105 106 // An image observer that writes out PNG files. 107 private final JPEGImageObserver _JPEGObserver = new JPEGImageObserver(); 108 109 // The BeanShell console for this window. 110 private final JConsole _console = new JConsole(); 111 112 // The BeanShell interpreter for this window. 113 private final Interpreter _bsh = new Interpreter(_console); 114 115 // Reference to the last saved file. 116 private File _fileRef = null; 117 118 /** 119 * Constructor for our application window. 120 * 121 * @param name The name of this window (usually file name of data file or 122 * "Untitled"). May not be null. 123 * @param newData The data set this window contains. May not be null. 124 */ 125 @SuppressWarnings("OverridableMethodCallInConstructor") 126 private MainWindow(String name, GeomElement newData) throws NoSuchMethodException { 127 super(requireNonNull(name)); 128 requireNonNull(newData); 129 130 // Have the window do nothing when it closes. 131 this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); 132 133 // Position the window automatically. 134 this.setLocationByPlatform(true); 135 136 // Define the window icon in the taskbar. 137 URL imgURL = ClassLoader.getSystemResource(MDIApplication.getInstance().getResourceBundle().getString("applicationIconURL")); 138 Image image = Toolkit.getDefaultToolkit().getImage(imgURL); 139 if (nonNull(image)) 140 this.setIconImage(image); 141 142 // Layout the display of this window. 143 initDisplay(newData); 144 145 // Set up the menu bar. 146 JMenuBar menuBar = createMenuBar(); 147 this.setJMenuBar(menuBar); 148 149 // Pack the window. 150 this.pack(); 151 152 // Initialize the BeanShell interpreter. 153 initializeBeanShell(); 154 155 // Increment our instance counter. 156 ++_instanceCount; 157 } 158 159 /** 160 * Initializes the BeanShell environment for this program. 161 */ 162 private void initializeBeanShell() { 163 164 // Start the BeanShell Interpreter. 165 new Thread(_bsh).start(); 166 167 // Initialize BeanShell 168 SwingUtilities.invokeLater(new Runnable() { 169 @Override 170 public void run() { 171 try { 172 GeomSSUtil.setInterpreter(_bsh); 173 174 // Initialize BeanShell with our default imports, etc. 175 ResourceBundle resBundle = MDIApplication.getInstance().getResourceBundle(); 176 GeomSSBatch.initializeBeanShell(resBundle.getString("appName"), _bsh, new PublicInterface()); 177 178 // Set the working directory to the saved path. 179 String path = MDIApplication.getInstance().getPreferences().getLastPath(); 180 if (nonNull(path)) 181 _bsh.eval("cd(\"" + path + "\");"); 182 183 } catch (Exception e) { 184 e.printStackTrace(); 185 } 186 } 187 }); 188 189 } 190 191 //----------------------------------------------------------------------------------- 192 /** 193 * Sets the title for this frame to the specified string. Also notifies the main 194 * application class so that the "Windows" menu gets updated too. 195 * 196 * @param title The title to be displayed in the frame's border. 197 */ 198 @Override 199 public void setTitle(String title) { 200 super.setTitle(requireNonNull(title)); 201 MDIApplication.windowTitleChanged(this); 202 } 203 204 /** 205 * Layouts the contents of this main application window. 206 * 207 * @param theData The data in this window. 208 */ 209 private void initDisplay(GeomElement theData) throws NoSuchMethodException { 210 211 // Get the content pane of this window. 212 Container cp = this.getContentPane(); 213 cp.setLayout(new BorderLayout()); 214 215 // Create a 3D canvas to hold our model. 216 _canvas = new GeomSSCanvas3D(theData, CANVAS_WIDTH, CANVAS_HEIGHT); 217 _canvas.addCaptureObserver(_PNGObserver); 218 _canvas.addCaptureObserver(_JPEGObserver); 219 220 // Add all the content of this window (other than the toolbar) to a split pane in the center. 221 JSplitPane content = new JSplitPane(JSplitPane.VERTICAL_SPLIT, _console, _canvas); 222 cp.add(content, BorderLayout.CENTER); 223 content.setOneTouchExpandable(true); 224// content.setDividerLocation(CANVAS_HEIGHT); 225 226 // Provide minimum sizes for the components. 227 _canvas.setMinimumSize(new Dimension(200, 200)); 228 _canvas.setPreferredSize(new Dimension(CANVAS_WIDTH, CANVAS_HEIGHT)); 229 _console.setPreferredSize(new Dimension(CANVAS_WIDTH, 200)); 230 231 // Configure the BeanShell Console. 232 _console.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); 233// _console.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); 234 235 // Create a tool bar for the window and add it to an edge. 236 JToolBar toolbar = createToolbar(); 237 cp.add(toolbar, BorderLayout.NORTH); 238 239 } 240 241 public GeomSSScene getScene() { 242 return _canvas.getScene(); 243 } 244 245 /** 246 * Initializes the menus associated with this window. 247 */ 248 private JMenuBar createMenuBar() throws NoSuchMethodException { 249 MDIApplication guiApp = MDIApplication.getInstance(); 250 ResourceBundle resBundle = guiApp.getResourceBundle(); 251 252 JMenuBar menuBar = new JMenuBar(); 253 254 // Set up the file menu. 255 List<String[]> menuStrings = new ArrayList(); 256 String[] row = new String[3]; 257 row[0] = resBundle.getString("newItemText"); 258 row[1] = resBundle.getString("newItemKey"); 259 row[2] = "handleNew"; 260 menuStrings.add(row); 261 row = new String[3]; 262 row[0] = resBundle.getString("openItemText"); 263 row[1] = resBundle.getString("openItemKey"); 264 row[2] = "handleOpen"; 265 menuStrings.add(row); 266 row = new String[3]; 267 row[0] = resBundle.getString("importItemText"); 268 row[1] = resBundle.getString("importItemKey"); 269 row[2] = "handleImport"; 270 menuStrings.add(row); 271 row = new String[3]; 272 row[0] = resBundle.getString("srcScriptItemText"); 273 row[1] = null; 274 row[2] = "handleSrcScript"; 275 menuStrings.add(row); 276 menuStrings.add(new String[3]); // Blank line 277 row = new String[3]; 278 row[0] = resBundle.getString("closeItemText"); 279 row[1] = resBundle.getString("closeItemKey"); 280 row[2] = "handleClose"; 281 menuStrings.add(row); 282 row = new String[3]; 283 row[0] = resBundle.getString("changeCWDItemText"); 284 row[1] = null; 285 row[2] = "handleChangeCWD"; 286 menuStrings.add(row); 287 row = new String[3]; 288 row[0] = resBundle.getString("saveItemText"); 289 row[1] = resBundle.getString("saveItemKey"); 290 row[2] = "handleSave"; 291 menuStrings.add(row); 292 row = new String[3]; 293 row[0] = resBundle.getString("saveAsItemText"); 294 row[1] = null; 295 row[2] = "handleSaveAs"; 296 menuStrings.add(row); 297 row = new String[3]; 298 row[0] = resBundle.getString("exportAsItemText"); 299 row[1] = null; 300 row[2] = null; 301 menuStrings.add(row); 302 menuStrings.add(new String[3]); // Blank line 303 row = new String[3]; 304 row[0] = resBundle.getString("pageSetupItemText"); 305 row[1] = null; 306 row[2] = "handlePageSetup"; 307 menuStrings.add(row); 308 row = new String[3]; 309 row[0] = resBundle.getString("printItemText"); 310 row[1] = resBundle.getString("printItemKey"); 311 row[2] = "handlePrint"; 312 menuStrings.add(row); 313 314 JMenu menu = AppUtilities.buildMenu(this, resBundle.getString("fileMenuText"), menuStrings); 315 menuBar.add(menu); 316 317 // Add the Export As sub-menu. 318 JMenu exportMenu = buildExportMenu(menu.getItem(EXPORT_AS_ITEM).getText()); 319 if (nonNull(exportMenu)) { 320 menu.remove(EXPORT_AS_ITEM); 321 menu.add(exportMenu, EXPORT_AS_ITEM); 322 } 323 324 // Add a Quit menu item (if it isn't automatically present). 325 if (!QuitJMenuItem.isAutomaticallyPresent()) { 326 menu.addSeparator(); 327 menu.add(guiApp.getQuitJMenuItem()); 328 } 329 330 // Set up the edit menu. 331 menuStrings.clear(); 332 row = new String[3]; 333 row[0] = resBundle.getString("undoItemText"); 334 row[1] = resBundle.getString("undoItemKey"); 335 row[2] = null; 336 menuStrings.add(row); 337 row = new String[3]; 338 row[0] = resBundle.getString("redoItemText"); 339 row[1] = null; 340 row[2] = null; 341 menuStrings.add(row); 342 menuStrings.add(new String[3]); // Blank line. 343 row = new String[3]; 344 row[0] = resBundle.getString("cutItemText"); 345 row[1] = resBundle.getString("cutItemKey"); 346 row[2] = null; 347 menuStrings.add(row); 348 row = new String[3]; 349 row[0] = resBundle.getString("copyItemText"); 350 row[1] = resBundle.getString("copyItemKey"); 351 row[2] = null; 352 menuStrings.add(row); 353 row = new String[3]; 354 row[0] = resBundle.getString("pasteItemText"); 355 row[1] = resBundle.getString("pasteItemKey"); 356 row[2] = null; 357 menuStrings.add(row); 358 menu = AppUtilities.buildMenu(this, resBundle.getString("editMenuText"), menuStrings); 359 360 // Add support for cut, copy & paste in all components. 361 TransferActionListener transferActionListener = new TransferActionListener(); 362 setMenuItemAction(menu.getItem(CUT_ITEM), transferActionListener, new DefaultEditorKit.CutAction(), 363 (String)TransferHandler.getCutAction().getValue(Action.NAME)); 364 setMenuItemAction(menu.getItem(COPY_ITEM), transferActionListener, new DefaultEditorKit.CopyAction(), 365 (String)TransferHandler.getCopyAction().getValue(Action.NAME)); 366 setMenuItemAction(menu.getItem(PASTE_ITEM), transferActionListener, new DefaultEditorKit.PasteAction(), 367 (String)TransferHandler.getPasteAction().getValue(Action.NAME)); 368 369 menuBar.add(menu); 370 371 // Create a Window's menu. 372 menu = MDIApplication.newWindowsMenu(resBundle.getString("windowsMenuText")); 373 menuBar.add(menu); 374 375 // Create an about menu item (if not automatically present). 376 if (!AboutJMenuItem.isAutomaticallyPresent()) { 377 // Create Help menu. 378 menu = new JMenu(resBundle.getString("helpMenuText")); 379 menuBar.add(menu); 380 381 // Add the "About" item to the Help menu. 382 menu.add(guiApp.getAboutJMenuItem()); 383 } 384 385 return menuBar; 386 } 387 388 /** 389 * Method that will build an "Export" menu containing a list of all the ModelWriter 390 * objects that are available. 391 * 392 * @param title The title for the menu that is created. 393 * @returns A JMenu instance containing a list of all ModelWriter objects that can 394 * write to files in various formats. 395 */ 396 private JMenu buildExportMenu(String title) throws NoSuchMethodException { 397 ResourceBundle resBundle = MDIApplication.getInstance().getResourceBundle(); 398 399 // Create the menu 400 JMenu menu = new JMenu(title); 401 402 // Get the list of data readers that are available. 403 GeomReader[] allReaders = GeomReaderFactory.getAllReaders(); 404 405 // Loop over all the readers. 406 if (nonNull(allReaders)) { 407 for (GeomReader reader : allReaders) { 408 409 if (reader.canWriteData()) { 410 // Found a "writer". 411 412 _geomWriters.add(reader); 413 414 // Create a menu item for it. 415 JMenuItem menuItem = new JMenuItem(reader.toString() + "..."); 416 menuItem.addActionListener(new ExportAsMenuListener(reader)); 417 menuItem.setActionCommand(reader.toString()); 418 menuItem.setEnabled(true); 419 420 // Add the menu item to the menu. 421 menu.add(menuItem); 422 } 423 } 424 } 425 426 // Create an export to PNG item. 427 JMenuItem menuItem = new JMenuItem(resBundle.getString("exportAsPNGItemText")); 428 menuItem.addActionListener(AppUtilities.getActionListenerForMethod(this, "handleSaveAsPNG")); 429 menuItem.setEnabled(true); 430 menu.add(menuItem); 431 432 // Create an export to JPEG item. 433 menuItem = new JMenuItem(resBundle.getString("exportAsJPEGItemText")); 434 menuItem.addActionListener(AppUtilities.getActionListenerForMethod(this, "handleSaveAsJPEG")); 435 menuItem.setEnabled(true); 436 menu.add(menuItem); 437 438 return menu; 439 } 440 441 /** 442 * Defines an action listener for our Export As menu items. 443 */ 444 private class ExportAsMenuListener implements ActionListener { 445 446 // Keep a reference to the reader used by this menu. 447 private final GeomReader _reader; 448 449 public ExportAsMenuListener(GeomReader reader) { 450 _reader = reader; 451 } 452 453 @Override 454 public void actionPerformed(ActionEvent evt) { 455 handleExportGeom(_reader); 456 } 457 458 } 459 460 /** 461 * Method that sets the specified action to a menu item while preserving the existing, 462 * item's text, accelerator key and mnemonic. 463 */ 464 private void setMenuItemAction(JMenuItem item, TransferActionListener transferActionListener, 465 Action action, String actionCommand) { 466 String text = item.getText(); 467 KeyStroke accel = item.getAccelerator(); 468 int mnemonic = item.getMnemonic(); 469 item.setAction(action); 470 item.setText(text); 471 item.setAccelerator(accel); 472 item.setMnemonic(mnemonic); 473 item.setActionCommand(actionCommand); 474 475 item.addActionListener(transferActionListener); 476 } 477 478 /** 479 * Create a tool bar for this window. 480 */ 481 private JToolBar createToolbar() { 482 ResourceBundle resBundle = MDIApplication.getInstance().getResourceBundle(); 483 JToolBar toolbar = new JToolBar(); 484 485 // Create a "center" button that centers the view. 486 URL url = ClassLoader.getSystemResource(resBundle.getString("centerAndZoomIcon")); 487 JButton button = new JButton(new ImageIcon(url)); 488 button.setToolTipText(resBundle.getString("centerandZoomToolTip")); 489 button.addActionListener(new ActionListener() { 490 @Override 491 public void actionPerformed(ActionEvent evt) { 492 GeomSSScene scene = _canvas.getScene(); 493 scene.centerAndZoom(); 494 } 495 }); 496 toolbar.add(button); 497 498 // Create a "view angle" pop-up combo-box. 499 // Load icons for the various settings. 500 String[] iconFileNames = { 501 resBundle.getString("rightSideViewIcon"), resBundle.getString("leftSideViewIcon"), 502 resBundle.getString("topViewIcon"), resBundle.getString("bottomViewIcon"), 503 resBundle.getString("frontViewIcon"), resBundle.getString("rearViewIcon"), 504 resBundle.getString("topRightFrontViewIcon"), resBundle.getString("topLeftFrontViewIcon"), 505 resBundle.getString("topRightBackViewIcon"), resBundle.getString("topLeftBackViewIcon") 506 }; 507 ImageIcon[] icons = new ImageIcon[iconFileNames.length]; 508 for (int i = 0; i < iconFileNames.length; ++i) { 509 url = ClassLoader.getSystemResource(iconFileNames[i]); 510 icons[i] = new ImageIcon(url); 511 } 512 513 JComboBox viewOptions = new ViewAngleComboBox(icons); 514 viewOptions.setMaximumSize(viewOptions.getPreferredSize()); 515 viewOptions.setToolTipText(resBundle.getString("selectViewToolTip")); 516 toolbar.add(viewOptions); 517 518 // Create a symmetry button. 519 url = ClassLoader.getSystemResource(resBundle.getString("symmetryUnselectedIcon")); 520 JToggleButton togButton = new JToggleButton(new ImageIcon(url)); 521 url = ClassLoader.getSystemResource(resBundle.getString("symmetrySelectedIcon")); 522 togButton.setSelectedIcon(new ImageIcon(url)); 523 togButton.addActionListener(new ActionListener() { 524 @Override 525 public void actionPerformed(ActionEvent evt) { 526 JToggleButton btn = (JToggleButton)evt.getSource(); 527 GeomSSScene scene = _canvas.getScene(); 528 scene.setMirrored(btn.isSelected()); 529 } 530 }); 531 togButton.setToolTipText(resBundle.getString("symmetrySelectToolTip")); 532 toolbar.add(togButton); 533 534 // Create the projection policy button. 535 url = ClassLoader.getSystemResource(resBundle.getString("projPolicyUnselectedIcon")); 536 togButton = new JToggleButton(new ImageIcon(url)); 537 url = ClassLoader.getSystemResource(resBundle.getString("projPolicySelectedIcon")); 538 togButton.setSelectedIcon(new ImageIcon(url)); 539 togButton.addActionListener(new ActionListener() { 540 @Override 541 public void actionPerformed(ActionEvent evt) { 542 JToggleButton btn = (JToggleButton)evt.getSource(); 543 GeomSSScene scene = _canvas.getScene(); 544 scene.setProjectionPolicy((btn.isSelected() ? PARALLEL_PROJECTION : PERSPECTIVE_PROJECTION)); 545 } 546 }); 547 togButton.setToolTipText(resBundle.getString("projPolicyToolTip")); 548 toolbar.add(togButton); 549 550 // Create the render type options. 551 String[] renderIconNames = {resBundle.getString("solidWireframeIcon"), resBundle.getString("solidIcon"), 552 resBundle.getString("wireframeIcon"), resBundle.getString("stringsOnlyIcon"), 553 resBundle.getString("pointsOnlyIcon")}; 554 icons = new ImageIcon[renderIconNames.length]; 555 for (int i = 0; i < renderIconNames.length; ++i) { 556 url = ClassLoader.getSystemResource(renderIconNames[i]); 557 icons[i] = new ImageIcon(url); 558 } 559 JComboBox renderOptions = new JComboBox(icons); 560 renderOptions.addActionListener(new ActionListener() { 561 @Override 562 public void actionPerformed(ActionEvent evt) { 563 JComboBox cb = (JComboBox)evt.getSource(); 564 RenderType[] renderOptions = RenderType.values(); 565 int selected = cb.getSelectedIndex(); 566 GeomSSScene scene = _canvas.getScene(); 567 scene.setRenderType(renderOptions[selected]); 568 } 569 }); 570 renderOptions.setMaximumSize(renderOptions.getPreferredSize()); 571 renderOptions.setToolTipText(resBundle.getString("pointGeomRenderingToolTip")); 572 toolbar.add(renderOptions); 573 574 return toolbar; 575 } 576 577 /** 578 * A JComboBox that deselects the list whenever the orientation in the 3D canvas 579 * changes. 580 */ 581 private class ViewAngleComboBox extends JComboBox { 582 583 // The list of all possible view angles. 584 585 private GeomSSCanvas3D.PDViewAngle[] _vAngleOptions = GeomSSCanvas3D.PDViewAngle.values(); 586 587 // Flag used to indicate that the user has just selected an item. 588 private boolean isSelecting = false; 589 590 public ViewAngleComboBox(Object[] items) { 591 super(items); 592 593 // Listen for selection changes and change the view appropriately. 594 this.addActionListener(new ActionListener() { 595 @Override 596 public void actionPerformed(ActionEvent evt) { 597 int index = getSelectedIndex(); 598 if (index >= 0) { 599 isSelecting = true; 600 _canvas.setView(_vAngleOptions[index]); 601 isSelecting = false; 602 } 603 } 604 }); 605 606 // Listen for orientation changes and deselect the items when one happens. 607 _canvas.addTransformChangeListener(new TransformChangeListener() { 608 @Override 609 public void transformChanged(TransformChangeEvent e) { 610 if (!isSelecting) { 611 if (e.getType().equals(TransformChangeEvent.Type.ROTATE)) { 612 setSelectedIndex(-1); // Deselect the items in the menu. 613 } 614 } 615 } 616 }); 617 618 } 619 } 620 621 /** 622 * Creates, and initializes, a new application window with the specified content. 623 * Effectively creates a new instance of this program. 624 * 625 * @param name The name of this window (usually file name of data file or 626 * "Untitled"). 627 * @param newData The data set this window contains. If null is passed, an 628 * empty/default/new data set is created. 629 * @return A new application window with the specified content. 630 * @throws java.lang.NoSuchMethodException If there was a problem constructing the 631 * window's action listeners. 632 */ 633 public static MainWindow newAppWindow(String name, GeomElement newData) 634 throws NoSuchMethodException { 635 636 // If a name wasn't provided, createa default one. 637 if (isNull(name) || name.length() < 1) { 638 // Create a unique name for the new instance (an initial project name). 639 name = MDIApplication.getInstance().getResourceBundle().getString("untitled"); 640 if (_instanceCount > 0) 641 name = name + _instanceCount; 642 } 643 644 // Create an instance of this window (with a default data set if one isn't provided). 645 MainWindow appFrame = new MainWindow(name, 646 (nonNull(newData) ? newData : GeomList.newInstance(name))); 647 648 // Store the new data in the BeanShell environment. 649 if (nonNull(newData)) { 650 try { 651 appFrame._bsh.set("newData", newData); 652 } catch (Exception e) { 653 e.printStackTrace(); // Can't happen. 654 } 655 } 656 657 return appFrame; 658 } 659 660 /** 661 * Handle the user choosing the "New..." from the File menu. Creates a new, blank, 662 * document window. 663 * 664 * @param event The action event that caused this method to be called. 665 * @return A new, empty, application window. 666 */ 667 public MainWindow handleNew(ActionEvent event) { 668 return (MainWindow)(MDIApplication.getInstance().handleNew(event)); 669 } 670 671 /** 672 * Handle the user choosing "Close" from the File menu. This implementation dispatches 673 * a "Window Closing" event to this window. 674 * 675 * @param event The action event that caused this method to be called. Ignored. 676 */ 677 public void handleClose(ActionEvent event) { 678 this.dispatchEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING)); 679 } 680 681 /** 682 * Handle the user choosing "Open..." from the File menu. Lets the user choose a data 683 * file and open it. 684 * 685 * @param event The action event that caused this method to be called. Ignored. 686 */ 687 public void handleOpen(ActionEvent event) { 688 ResourceBundle resBundle = MDIApplication.getInstance().getResourceBundle(); 689 690 try { 691 // Ask the user to select a native geometry file. 692 String extension = resBundle.getString("primaryExtension"); 693 ExtFilenameFilter fnFilter = new ExtFilenameFilter(); 694 fnFilter.addExtension(extension); 695 String dir; 696 if (isNull(_fileRef)) 697 dir = MDIApplication.getInstance().getPreferences().getLastPath(); 698 else 699 dir = _fileRef.getParent(); 700 File theFile = AppUtilities.selectFile(this, FileDialog.LOAD, resBundle.getString("fileDialogLoad"), 701 dir, "", fnFilter); 702 if (isNull(theFile)) 703 return; 704 705 // Pass the inputs to the console to load the file. 706 String cwd = (String)_bsh.get("bsh.cwd"); // Get the current working directory. 707 String pathName; 708 if (theFile.getParent().equals(cwd)) 709 pathName = theFile.getName(); 710 else 711 pathName = theFile.getPath(); 712 if (AppUtilities.isWindows()) 713 pathName = pathName.replace("\\", "/"); 714 String command = "open(pathToFile(\"" + pathName + "\"));"; 715 716 // Print the command to the console as if we opened it through BSH. 717 _bsh.println(command); 718 719 // Read in the geometry and any workspace variables. 720 // This is being done rather than calling BSH to catch exceptions here. 721 XGSSGeomReader reader = new XGSSGeomReader(); 722 FastMap<String, Object> workspace = (FastMap<String, Object>)reader.readWorkspace(theFile); 723 724 // Store all the variables into the BeanShell worksapce. 725 try { 726 for (String varName : workspace.keySet()) 727 _bsh.set(varName, workspace.get(varName)); 728 } finally { 729 FastMap.recycle(workspace); 730 } 731 732 // Change the name of this app's windows to match the new file name. 733 String fileName = theFile.getName(); 734 fileName = fileName.substring(0, fileName.lastIndexOf(".")); 735 this.setTitle(fileName); 736 737 // Store a reference to the file. 738 _fileRef = theFile; 739 MDIApplication.getInstance().getPreferences().setLastPath(theFile.getParent()); 740 741 } catch (Exception e) { 742 AppUtilities.showException(this, resBundle.getString("unexpectedTitle"), 743 resBundle.getString("unexpectedMsg"), e); 744 e.printStackTrace(); 745 } 746 } 747 748 /** 749 * Handle the user choosing "Import..." from the File menu. Lets the user choose a 750 * data file and open it. 751 * 752 * @param event The action event that caused this method to be called (ignored). 753 */ 754 public void handleImport(ActionEvent event) { 755 ResourceBundle resBundle = MDIApplication.getInstance().getResourceBundle(); 756 757 try { 758 // Build up the input dialog. 759 DialogItem item = new DialogItem(resBundle.getString("inputDlgListLabel") + " ", 760 resBundle.getString("inputDlgListExample")); 761 List<DialogItem> itemList = new ArrayList(); 762 itemList.add(item); 763 item = new DialogItem(resBundle.getString("inputDlgFileLabel") + " ", 764 new File(MDIApplication.getInstance().getPreferences().getLastPath(), 765 resBundle.getString("inputDlgFileExample"))); 766 item.setLoadFile(true); 767 itemList.add(item); 768 769 // Show the dialog. 770 InputDialog dialog = new InputDialog(this, resBundle.getString("inputDlgTitle"), "", itemList); 771 itemList = dialog.getOutput(); 772 if (isNull(itemList)) 773 return; 774 775 // Get the user inputs. 776 String listName = (String)itemList.get(0).getElement(); 777 File theFile = (File)itemList.get(1).getElement(); 778 if (!theFile.exists()) { 779 String msg = MessageFormat.format(resBundle.getString("fileDoesntExistMsg"), theFile.getName()); 780 JOptionPane.showMessageDialog(this, msg, resBundle.getString("ioErrorTitle"), 781 JOptionPane.ERROR_MESSAGE); 782 return; 783 } 784 785 // Pass the inputs to the console. 786 String cwd = (String)_bsh.get("bsh.cwd"); // Get the current working directory. 787 String pathName; 788 if (theFile.getParent().equals(cwd)) 789 pathName = theFile.getName(); 790 else 791 pathName = theFile.getPath(); 792 if (AppUtilities.isWindows()) 793 pathName = pathName.replace("\\", "/"); 794 String command = listName + " = readGeomFile(pathToFile(\"" + pathName + "\"));"; 795 796 // Print the command to the console and then run it. 797 new BSHCommandRunner(command).start(); 798 799 } catch (EvalError e) { 800 e.printStackTrace(); 801 802 } catch (Exception e) { 803 AppUtilities.showException(null, resBundle.getString("unexpectedTitle"), 804 resBundle.getString("unexpectedMsg"), e); 805 e.printStackTrace(); 806 } 807 808 } 809 810 /** 811 * Method that reads in the specified file and creates a new window for displaying 812 * it's contents. 813 * 814 * @param parent Parent frame for dialogs (null is fine). 815 * @param theFile The file to be loaded and displayed. If null is passed, this method 816 * will do nothing. 817 * @throws java.lang.NoSuchMethodException if there was a problem constructing this 818 * window's action listeners. 819 */ 820 public static void newWindowFromDataFile(Frame parent, File theFile) 821 throws NoSuchMethodException { 822 823 // Read in the model data from the input file. 824 GeometryList newData = readGeometryData(parent, theFile); 825 826 if (nonNull(newData)) { 827 828 // Create an instance of an application window to get the program rolling. 829 MainWindow window = newAppWindow(theFile.getName(), newData); 830 831 // Center the geometry in the window. 832 window._canvas.getScene().centerAndZoom(); 833 834 // Display the window. 835 window.setVisible(true); 836 MDIApplication.addWindow(window); 837 838 } 839 840 } 841 842 /** 843 * Method that reads in a data file and return the resulting data structure. 844 * 845 * @param parent The parent frame for dialogs (null is fine). 846 * @param theFile The file to be read in. If <code>null</code> is passed, this 847 * method will do nothing. 848 */ 849 private static GeometryList readGeometryData(Component parent, File theFile) { 850 requireNonNull(theFile); 851 ResourceBundle resBundle = MDIApplication.getInstance().getResourceBundle(); 852 GeometryList data = null; 853 854 try { 855 data = GeomSSBatch.readGeometryData(resBundle, parent, theFile); 856 857 } catch (IOException e) { 858 String msg = e.getMessage(); 859 if (isNull(msg)) { 860 if (nonNull(e.getCause())) 861 msg = e.getCause().getMessage(); 862 } 863 JOptionPane.showMessageDialog(parent, msg, 864 resBundle.getString("ioErrorTitle"), JOptionPane.ERROR_MESSAGE); 865 e.printStackTrace(); 866 867 } catch (Exception e) { 868 AppUtilities.showException(parent, resBundle.getString("unexpectedTitle"), 869 resBundle.getString("unexpectedMsg"), e); 870 e.printStackTrace(); 871 } 872 873 return data; 874 } 875 876 /** 877 * Handle the user choosing "Source Script..." from the File menu. Lets the user 878 * choose a script file and source it. 879 * 880 * @param event The action event that caused this method to be called (ignored). 881 */ 882 public void handleSrcScript(ActionEvent event) { 883 ResourceBundle resBundle = MDIApplication.getInstance().getResourceBundle(); 884 885 try { 886 ExtFilenameFilter fnFilter = new ExtFilenameFilter(); 887 fnFilter.addExtension("bsh"); 888 String dir = MDIApplication.getInstance().getPreferences().getLastPath(); 889 File theFile = AppUtilities.selectFile(this, FileDialog.LOAD, resBundle.getString("fileDialogLoad"), 890 dir, null, fnFilter); 891 if (isNull(theFile)) 892 return; // User canceled. 893 894 // Pass the inputs to the console. 895 String cwd = (String)_bsh.get("bsh.cwd"); // Get the current working directory. 896 String command; 897 if (theFile.getParent().equals(cwd)) 898 command = "source(\"" + theFile.getName() + "\");"; 899 else 900 command = "source(\"" + theFile.getPath() + "\");"; 901 if (AppUtilities.isWindows()) 902 command = command.replace("\\", "/"); 903 904 // Print the command to the console and then run it. 905 new BSHCommandRunner(command).start(); 906 907 } catch (Exception e) { 908 _bsh.error(e); 909 e.printStackTrace(); 910 } 911 } 912 913 /** 914 * Handle the user choosing "Save" from the File menu. This saves the model to the 915 * last file used. Catches and displays to the user all exceptions. 916 * 917 * @param event The action event that caused this method to be called (ignored). 918 */ 919 public void handleSave(ActionEvent event) { 920 try { 921 // Go off and actually do the save. Catch exceptions here. 922 doSave(); 923 924 } catch (IOException e) { 925 String msg = e.getMessage(); 926 if (isNull(msg)) { 927 Throwable cause = e.getCause(); 928 if (nonNull(cause)) 929 msg = cause.getMessage(); 930 } 931 ResourceBundle resBundle = MDIApplication.getInstance().getResourceBundle(); 932 JOptionPane.showMessageDialog(this, msg, resBundle.getString("ioErrorTitle"), 933 JOptionPane.ERROR_MESSAGE); 934 935 } catch (Exception e) { 936 ResourceBundle resBundle = MDIApplication.getInstance().getResourceBundle(); 937 AppUtilities.showException(this, resBundle.getString("unexpectedTitle"), 938 resBundle.getString("unexpectedMsg"), e); 939 e.printStackTrace(); 940 } 941 } 942 943 /** 944 * Handle the user choosing "Save" from the File menu. This saves the model to the 945 * last file used. All exceptions are thrown. 946 * 947 * @return true if the user cancels, false if the model was saved. 948 */ 949 private boolean doSave() throws IOException, EvalError { 950 if (isNull(_fileRef)) 951 return doSaveAs(); 952 953 if (canWriteFile(_fileRef)) { 954 File theFile = _fileRef; 955 956 // Pass the inputs to the console to load the file. 957 String cwd = (String)_bsh.get("bsh.cwd"); // Get the current working directory. 958 String pathName; 959 if (theFile.getParent().equals(cwd)) 960 pathName = theFile.getName(); 961 else 962 pathName = theFile.getPath(); 963 if (AppUtilities.isWindows()) 964 pathName = pathName.replace("\\", "/"); 965 String command = "save(pathToFile(\"" + pathName + "\"));"; 966 967 // Print the command to the console and then run it. 968 new BSHCommandRunner(command).start(); 969 } 970 971 return false; 972 } 973 974 /** 975 * Handle the user choosing "Save As" from the File menu. This asks the user for input 976 * and then saves the model to a file. 977 * 978 * @param event The action event that caused this method to be called (ignored). 979 */ 980 public void handleSaveAs(ActionEvent event) { 981 try { 982 // Go off and actually do the Save As.... Catch exceptions here. 983 doSaveAs(); 984 985 } catch (IOException e) { 986 String msg = e.getMessage(); 987 if (isNull(msg)) { 988 Throwable cause = e.getCause(); 989 if (nonNull(cause)) 990 msg = cause.getMessage(); 991 } 992 ResourceBundle resBundle = MDIApplication.getInstance().getResourceBundle(); 993 JOptionPane.showMessageDialog(this, msg, resBundle.getString("ioErrorTitle"), 994 JOptionPane.ERROR_MESSAGE); 995 996 } catch (Exception e) { 997 ResourceBundle resBundle = MDIApplication.getInstance().getResourceBundle(); 998 AppUtilities.showException(this, resBundle.getString("unexpectedTitle"), 999 resBundle.getString("unexpectedMsg"), e); 1000 e.printStackTrace(); 1001 } 1002 1003 } 1004 1005 /** 1006 * Handle the user choosing "Save As" from the File menu. This asks the user for input 1007 * and then saves all the geometry to the selected file. This method throws all 1008 * exceptions. 1009 * 1010 * @return true if the user cancels, false if the model was saved. 1011 */ 1012 private boolean doSaveAs() throws IOException { 1013 ResourceBundle resBundle = MDIApplication.getInstance().getResourceBundle(); 1014 1015 FastMap<String, Object> vars = new FastMap(); 1016 FastMap<String, GeomElement> geom = new FastMap(); 1017 try { 1018 // Extract the workspace variables into a pair of Maps. 1019 GeomSSBatch.extractVariableMaps(_bsh, geom, vars); 1020 if (geom.isEmpty() && vars.isEmpty()) 1021 return false; // No geometry to be saved. 1022 1023 // Build the directory and filename to prompt the user with. 1024 String extension = resBundle.getString("primaryExtension"); 1025 ExtFilenameFilter fnFilter = new ExtFilenameFilter(); 1026 fnFilter.addExtension(extension); 1027 String dir; 1028 String fileName; 1029 if (nonNull(_fileRef)) { 1030 // Prompt the user with the previous name/path. 1031 dir = _fileRef.getPath(); 1032 fileName = _fileRef.getName(); 1033 } else { 1034 dir = (String)_bsh.get("bsh.cwd"); // Get the current working directory. 1035 fileName = this.getTitle() + "." + extension; 1036 } 1037 1038 // Ask the user to select a file for saving. 1039 File theFile = selectFile4Save(extension, fnFilter, dir, fileName); 1040 1041 if (canWriteFile(theFile)) { 1042 // Pass the inputs to the console to load the file. 1043 String cwd = (String)_bsh.get("bsh.cwd"); // Get the current working directory. 1044 String pathName; 1045 if (theFile.getParent().equals(cwd)) 1046 pathName = theFile.getName(); 1047 else 1048 pathName = theFile.getPath(); 1049 if (AppUtilities.isWindows()) 1050 pathName = pathName.replace("\\", "/"); 1051 String command = "save(pathToFile(\"" + pathName + "\"));"; 1052 1053 // Print the command to the console as if we were going to run it directly. 1054 _bsh.println(command); 1055 1056 // Write out the file. This is done rather than going through BSH in order to catch 1057 // exceptions here. 1058 XGSSGeomReader writer = new XGSSGeomReader(); 1059 writer.write(theFile, geom, vars); 1060 1061 // Change the name of this app's windows to match the new file name. 1062 fileName = FileUtils.getFileNameWithoutExtension(theFile); 1063 this.setTitle(fileName); 1064 1065 // Store off the file reference for later use. 1066 MDIApplication.getInstance().getPreferences().setLastPath(theFile.getParent()); 1067 _fileRef = theFile; 1068 1069 return false; 1070 } 1071 1072 } catch (bsh.UtilEvalError | bsh.EvalError e) { 1073 throw new IOException(e); 1074 } finally { 1075 FastMap.recycle(vars); 1076 FastMap.recycle(geom); 1077 } 1078 1079 return true; 1080 } 1081 1082 /** 1083 * Handle the user choosing "Save As PNG..." from the File menu. This asks the user 1084 * for input and then saves the image to a file. 1085 * 1086 * @param event The action event that caused this method to be called (ignored). 1087 */ 1088 public void handleSaveAsPNG(ActionEvent event) { 1089 1090 // Build the directory and filename to prompt the user with. 1091 String extension = "png"; 1092 String dir = MDIApplication.getInstance().getPreferences().getLastPath(); 1093 String fileName = this.getTitle() + "." + extension; 1094 ExtFilenameFilter fnFilter = new ExtFilenameFilter(extension); 1095 1096 // Ask the user to select a file for saving. 1097 File theFile = selectFile4Save(extension, fnFilter, dir, fileName); 1098 1099 if (canWriteFile(theFile)) { 1100 // Wait for the canvas to finish rendering if necessary. 1101 _canvas.waitForRendering(200); 1102 1103 // Notify the image capture listener that an image needs to be saved. 1104 _PNGObserver.setFilename(theFile.getPath()); 1105 _PNGObserver.setCaptureNextFrame(); 1106 _canvas.getView().repaint(); 1107 } 1108 } 1109 1110 /** 1111 * Handle the user choosing "Save As JPEG..." from the File menu. This asks the user 1112 * for input and then saves the image to a file. 1113 * 1114 * @param event The action event that caused this method to be called (ignored). 1115 */ 1116 public void handleSaveAsJPEG(ActionEvent event) { 1117 1118 // Build the directory and filename to prompt the user with. 1119 String extension = "jpeg"; 1120 String dir = MDIApplication.getInstance().getPreferences().getLastPath(); 1121 String fileName = this.getTitle() + "." + extension; 1122 ExtFilenameFilter fnFilter = new ExtFilenameFilter(extension); 1123 fnFilter.addExtension("jpg"); 1124 1125 // Ask the user to select a file for saving. 1126 File theFile = selectFile4Save(extension, fnFilter, dir, fileName); 1127 1128 if (canWriteFile(theFile)) { 1129 // Wait for the canvas to finish rendering if necessary. 1130 _canvas.waitForRendering(200); 1131 1132 // Notify the image capture listener that an image needs to be saved. 1133 _JPEGObserver.setFilename(theFile.getPath()); 1134 _JPEGObserver.setCaptureNextFrame(); 1135 _canvas.getView().repaint(); 1136 } 1137 } 1138 1139 /** 1140 * Asks the user to select a file for saving. 1141 * 1142 * @param fileType The file extension for the file being saved (example: "png" for a 1143 * PNG file). 1144 * @param fnFilter The file name filter to use. 1145 * @param dir The name of the directory path to prompt the user with (the default 1146 * path). 1147 * @param fileName The name of the file to prompt the user with (the default file). 1148 * @return A reference to the selected file or <code>null</code> for no file selected 1149 * (user canceled or error). Exceptions are handled in this method. 1150 */ 1151 private File selectFile4Save(String extension, ExtFilenameFilter fnFilter, String dir, String fileName) { 1152 ResourceBundle resBundle = MDIApplication.getInstance().getResourceBundle(); 1153 1154 try { 1155 // Ask the user to select a file for saving. 1156 String msg = MessageFormat.format(resBundle.getString("fileSaveDialog"), extension.toUpperCase()); 1157 File theFile = AppUtilities.selectFile4Save(this, msg, 1158 dir, fileName, fnFilter, extension, 1159 resBundle.getString("fileExists"), resBundle.getString("warningTitle")); 1160 1161 if (nonNull(theFile)) { 1162 if (theFile.exists() && !theFile.canWrite()) 1163 throw new IOException(resBundle.getString("canNotWrite2File")); 1164 1165 return theFile; 1166 } 1167 1168 } catch (IOException e) { 1169 String msg = e.getMessage(); 1170 if (isNull(msg)) { 1171 Throwable cause = e.getCause(); 1172 if (nonNull(cause)) 1173 msg = cause.getMessage(); 1174 } 1175 JOptionPane.showMessageDialog(this, msg, resBundle.getString("ioErrorTitle"), 1176 JOptionPane.ERROR_MESSAGE); 1177 1178 } catch (Exception e) { 1179 e.printStackTrace(); 1180 AppUtilities.showException(this, resBundle.getString("unexpectedTitle"), 1181 resBundle.getString("unexpectedMsg"), e); 1182 } 1183 1184 return null; 1185 } 1186 1187 /** 1188 * Displays a message to the user if a file exists but can not be written to. 1189 * 1190 * @param theFile The file to test. 1191 * @return <code>true</code> if the program can write to the file or 1192 * <code>false</code> if theFile is <code>null</code> or the file exists but 1193 * can not be written to. 1194 */ 1195 public boolean canWriteFile(File theFile) { 1196 if (isNull(theFile)) 1197 return false; 1198 if (!theFile.exists() || theFile.canWrite()) 1199 return true; 1200 1201 ResourceBundle resBundle = MDIApplication.getInstance().getResourceBundle(); 1202 JOptionPane.showMessageDialog(this, resBundle.getString("canNotWrite2File"), 1203 resBundle.getString("ioErrorTitle"), JOptionPane.ERROR_MESSAGE); 1204 return false; 1205 } 1206 1207 /** 1208 * Handle the user choosing to export to a GeomReader. Lets the user choose a data 1209 * file and open it. 1210 * 1211 * @param reader The reader to use when exporting geometry. 1212 */ 1213 public void handleExportGeom(GeomReader reader) { 1214 requireNonNull(reader); 1215 ResourceBundle resBundle = MDIApplication.getInstance().getResourceBundle(); 1216 1217 try { 1218 // Build up the input dialog. 1219 DialogItem item = new DialogItem(resBundle.getString("exptDlgListLabel") + " ", 1220 resBundle.getString("exptDlgListExample")); 1221 List<DialogItem> itemList = new ArrayList(); 1222 itemList.add(item); 1223 item = new DialogItem(reader.toString() + " " + resBundle.getString("exptDlgSaveLabel") + " ", 1224 new File(MDIApplication.getInstance().getPreferences().getLastPath(), 1225 resBundle.getString("inputDlgFileExample"))); 1226 item.setLoadFile(false); 1227 item.setFileExtension(reader.getExtension()); 1228 itemList.add(item); 1229 1230 // Show the dialog. 1231 InputDialog dialog = new InputDialog(this, resBundle.getString("inputDlgTitle"), "", itemList); 1232 itemList = dialog.getOutput(); 1233 if (isNull(itemList)) 1234 return; 1235 1236 // Get the user inputs. 1237 String listName = (String)itemList.get(0).getElement(); 1238 File theFile = (File)itemList.get(1).getElement(); 1239 1240 if (canWriteFile(theFile)) { 1241 // Pass the inputs to the console. 1242 String cwd = (String)_bsh.get("bsh.cwd"); // Get the current working directory. 1243 String writerName = reader.getClass().toString(); 1244 writerName = writerName.substring(writerName.lastIndexOf(".") + 1); 1245 String pathName; 1246 if (theFile.getParent().equals(cwd)) 1247 pathName = theFile.getName(); 1248 else 1249 pathName = theFile.getPath(); 1250 if (AppUtilities.isWindows()) 1251 pathName = pathName.replace("\\", "/"); 1252 String command = "writeGeomFile(" + listName + ", pathToFile(\"" + pathName + "\"), new " 1253 + writerName + "());"; 1254 _bsh.eval(command); 1255 _bsh.println(command); 1256 } 1257 1258 } catch (EvalError e) { 1259 e.printStackTrace(); 1260 AppUtilities.showException(this, resBundle.getString("evalErrTitle"), 1261 resBundle.getString("unexpectedMsg"), e); 1262 1263 } catch (Exception e) { 1264 e.printStackTrace(); 1265 AppUtilities.showException(this, resBundle.getString("unexpectedTitle"), 1266 resBundle.getString("unexpectedMsg"), e); 1267 } 1268 1269 } 1270 1271 /** 1272 * Handle the user choosing "Change Working Directory..." from the File menu. Displays 1273 * a File selection dialog allowing the user to select the working directory. 1274 * 1275 * @param event The action event that caused this method to be called (ignored). 1276 */ 1277 public void handleChangeCWD(ActionEvent event) { 1278 ResourceBundle resBundle = MDIApplication.getInstance().getResourceBundle(); 1279 1280 // Create a folder selection dialog asking the user to select a directory. 1281 FolderDialog fd = new FolderDialog(this, resBundle.getString("selectNewCWDText")); 1282 fd.setDirectory(MDIApplication.getInstance().getPreferences().getLastPath()); 1283 fd.setVisible(true); 1284 if (nonNull(fd.getFile())) { 1285 // The user has selected a directory. 1286 String newDir = fd.getDirectory(); 1287 1288 // MS Windows is a special case. 1289 if (AppUtilities.isWindows()) { 1290 newDir = newDir.replace("\\", "/"); 1291 } 1292 1293 // Change the CWD in BeanShell. 1294 try { 1295 String command = "cd(\"" + newDir + "\");"; 1296 _bsh.eval(command); 1297 _bsh.println(command); 1298 1299 // Save a reference to the current working directory so it can be restored later. 1300 MDIApplication.getInstance().getPreferences().setLastPath(newDir); 1301 1302 } catch (EvalError e) { 1303 String msg = e.getMessage(); 1304 JOptionPane.showMessageDialog(this, msg, resBundle.getString("evalErrTitle"), 1305 JOptionPane.ERROR_MESSAGE); 1306 } 1307 } 1308 1309 } 1310 1311 /** 1312 * Handle the user choosing "Page Setup..." from the File menu. Displays a Page Setup 1313 * dialog allowing the user to change the page settings. 1314 * 1315 * @param event The action event that caused this method to be called (ignored). 1316 */ 1317 public void handlePageSetup(ActionEvent event) { 1318 PrinterJob job = PrinterJob.getPrinterJob(); 1319 if (isNull(_pf)) 1320 _pf = job.defaultPage(); 1321 _pf = job.pageDialog(_pf); 1322 } 1323 1324 /** 1325 * Handle the user choosing "Print" from the File menu. Prints the currently displayed 1326 * plot. 1327 * 1328 * @param event The action event that caused this method to be called (ignored). 1329 */ 1330 public void handlePrint(ActionEvent event) { 1331 PrinterJob job = PrinterJob.getPrinterJob(); 1332 1333 if (isNull(_pf)) 1334 _pf = job.defaultPage(); 1335 job.setPrintable(_canvas, _pf); 1336 1337 if (job.printDialog()) { 1338 try { 1339 job.print(); 1340 1341 } catch (PrinterException e) { 1342 JOptionPane.showMessageDialog(this, e); 1343 } 1344 } 1345 1346 } 1347 1348 /** 1349 * A thread for running BSH commands that could take some time to complete. This 1350 * prevents the GUI from being held up while the command is running. 1351 */ 1352 private class BSHCommandRunner extends Thread { 1353 1354 private final String _command; 1355 1356 public BSHCommandRunner(String command) { 1357 _command = command; 1358 } 1359 1360 @Override 1361 public void run() { 1362 try { 1363 _bsh.println(_command); 1364 _bsh.eval(_command); 1365 _bsh.println("==> \"" + _command + "\" Done!"); 1366 } catch (Exception e) { 1367 _bsh.error(e); 1368 e.printStackTrace(); 1369 } 1370 } 1371 } 1372 1373 /** 1374 * A class that serves as the public interface (in BeanShell) for this application. 1375 */ 1376 private class PublicInterface implements GeomSSApp { 1377 1378 /** 1379 * Return a reference to the 3D scene. 1380 */ 1381 @Override 1382 public GeomSSScene getScene() { 1383 return _canvas.getScene(); 1384 } 1385 1386 /** 1387 * Return the parent Frame for this application. 1388 */ 1389 @Override 1390 public Frame getParentFrame() { 1391 return MainWindow.this; 1392 } 1393 1394 /** 1395 * Return a reference to the preferences for the GeomSS application. 1396 */ 1397 @Override 1398 public Preferences getPreferences() { 1399 return MDIApplication.getInstance().getPreferences(); 1400 } 1401 1402 /** 1403 * Return the resource bundle for this application containing the localized 1404 * application Strings. 1405 */ 1406 @Override 1407 public ResourceBundle getResourceBundle() { 1408 return MDIApplication.getInstance().getResourceBundle(); 1409 } 1410 1411 /** 1412 * Add the supplied window to the Windows menu of the main application. The window 1413 * should be made visible before calling this method to avoid an inconsistent user 1414 * interface state (a window listed in the "Windows" menu, but not visible). The 1415 * window will be removed from the "Windows" menu when it's "dispose" method is 1416 * called. 1417 * 1418 * @param window The window to be added to the Windows menu. 1419 */ 1420 @Override 1421 public void addToWindowsMenu(Window window) { 1422 requireNonNull(window); 1423 MDIApplication.addWindow(window); 1424 } 1425 1426 /** 1427 * Quit or exit the application after properly saving preferences, etc. 1428 */ 1429 @Override 1430 public void quit() { 1431 // Save a reference to the current working directory so it can be restored later. 1432 try { 1433 String cwd = (String)_bsh.get("bsh.cwd"); 1434 MDIApplication.getInstance().getPreferences().setLastPath(cwd); 1435 } catch (Exception e) { 1436 e.printStackTrace(); // Can't happen. 1437 } 1438 1439 // Ask Java to quit if the user selects this menu item. 1440 // The quit handler (if one is registered) will be called automatically. 1441 System.exit(0); 1442 } 1443 1444 /** 1445 * Read in a geometry file and return a GeometryList instance. All exceptions are 1446 * handled by this method. 1447 * 1448 * @param theFile The file to be read in. If <code>null</code> is passed, this 1449 * method will do nothing. 1450 * @return A GeometryList object containing the geometry read in from the file or 1451 * <code>null</code> if the user cancels the read at any point or if an 1452 * exception of any kind is thrown. 1453 */ 1454 @Override 1455 public GeometryList readGeomFile(File theFile) { 1456 return readGeometryData(MainWindow.this, theFile); 1457 } 1458 1459 /** 1460 * Write out geometry to the specified file using the specified GeometryList 1461 * instance (some GeomReader classes have specific requirements for the contents 1462 * of this list). All exceptions are handled by this method. 1463 * 1464 * @param geometry The geometry object to be written out. 1465 * @param theFile The file to be written to. 1466 * @param writer The GeomReader to use to write out the file. 1467 */ 1468 @Override 1469 public void writeGeomFile(GeometryList geometry, File theFile, GeomReader writer) { 1470 if (canWriteFile(theFile)) { 1471 ResourceBundle resBundle = MDIApplication.getInstance().getResourceBundle(); 1472 try { 1473 1474 // Write the geometry to the file. 1475 GeomSSBatch.writeGeometryData(resBundle, MainWindow.this, geometry, theFile, writer); 1476 1477 } catch (IOException e) { 1478 String msg = e.getMessage(); 1479 if (isNull(msg)) { 1480 if (nonNull(e.getCause())) 1481 msg = e.getCause().getMessage(); 1482 } 1483 JOptionPane.showMessageDialog(MainWindow.this, msg, 1484 resBundle.getString("ioErrorTitle"), JOptionPane.ERROR_MESSAGE); 1485 e.printStackTrace(); 1486 } 1487 } 1488 } 1489 1490 /** 1491 * Returns a list of all known GeomReader objects that are capable of writing to a 1492 * file. 1493 */ 1494 @Override 1495 public List<GeomReader> getAllGeomWriters() { 1496 return _geomWriters; 1497 } 1498 1499 /** 1500 * Save the entire global workspace to an XGSS file. All defined geometry and 1501 * non-geometry variables in the global workspace will be saved to the specified 1502 * file. 1503 * 1504 * @param theFile The file to write the workspace to in XGSS format. 1505 */ 1506 @Override 1507 public void saveWorkspace(File theFile) { 1508 if (canWriteFile(theFile)) { 1509 1510 ResourceBundle resBundle = MDIApplication.getInstance().getResourceBundle(); 1511 try { 1512 // Save the workspace to the specified file. 1513 GeomSSBatch.saveWorkspace(resBundle, _bsh, theFile); 1514 1515 } catch (IOException e) { 1516 String msg = e.getMessage(); 1517 if (isNull(msg)) { 1518 if (nonNull(e.getCause())) 1519 msg = e.getCause().getMessage(); 1520 } 1521 1522 JOptionPane.showMessageDialog(MainWindow.this, msg, resBundle.getString("ioErrorTitle"), 1523 JOptionPane.ERROR_MESSAGE); 1524 e.printStackTrace(); 1525 1526 } catch (Exception e) { 1527 AppUtilities.showException(MainWindow.this, resBundle.getString("unexpectedTitle"), 1528 resBundle.getString("unexpectedMsg"), e); 1529 e.printStackTrace(); 1530 } 1531 } 1532 } 1533 1534 /** 1535 * Load the specified XGSS file into the current global workspace. All the 1536 * geometry and non-geometry variables in the specified XGSS file will be loaded 1537 * into the current global workspace. Any existing variables with the same names 1538 * will be silently overwritten. 1539 * 1540 * @param theFile The XGSS file to load into the current workspace. 1541 */ 1542 @Override 1543 public void loadWorkspace(File theFile) { 1544 requireNonNull(theFile); 1545 ResourceBundle resBundle = MDIApplication.getInstance().getResourceBundle(); 1546 try { 1547 // Load in the specified file. 1548 GeomSSBatch.loadWorkspace(resBundle, _bsh, theFile); 1549 1550 } catch (IOException e) { 1551 String msg = e.getMessage(); 1552 if (isNull(msg)) { 1553 if (nonNull(e.getCause())) 1554 msg = e.getCause().getMessage(); 1555 } 1556 1557 JOptionPane.showMessageDialog(MainWindow.this, msg, resBundle.getString("ioErrorTitle"), 1558 JOptionPane.ERROR_MESSAGE); 1559 e.printStackTrace(); 1560 1561 } catch (Exception e) { 1562 AppUtilities.showException(MainWindow.this, resBundle.getString("unexpectedTitle"), 1563 resBundle.getString("unexpectedMsg"), e); 1564 e.printStackTrace(); 1565 } 1566 } 1567 1568 /** 1569 * Save a copy of the current 3D view as a PNG file. 1570 */ 1571 @Override 1572 public void saveAsPNG() { 1573 handleSaveAsPNG(null); 1574 } 1575 1576 /** 1577 * Save a copy of the current 3D view as a PNG file. 1578 * 1579 * @param file The file to be saved. 1580 */ 1581 @Override 1582 public void saveAsPNG(File file) { 1583 if (canWriteFile(file)) { 1584 // Wait for the canvas to finish rendering if necessary. 1585 _canvas.waitForRendering(200); 1586 1587 // Notify the image capture listener that an image needs to be saved. 1588 _PNGObserver.setFilename(file.getPath()); 1589 _PNGObserver.setCaptureNextFrame(); 1590 _canvas.getView().repaint(); 1591 } 1592 } 1593 1594 /** 1595 * Save a copy of the current 3D view as a JPEG file. 1596 */ 1597 @Override 1598 public void saveAsJPEG() { 1599 handleSaveAsJPEG(null); 1600 } 1601 1602 /** 1603 * Save a copy of the current 3D view as a JPEG file. 1604 * 1605 * @param file The file to be saved. 1606 */ 1607 @Override 1608 public void saveAsJPEG(File file) { 1609 if (canWriteFile(file)) { 1610 // Wait for the canvas to finish rendering if necessary. 1611 _canvas.waitForRendering(200); 1612 1613 // Notify the image capture listener that an image needs to be saved. 1614 _JPEGObserver.setFilename(file.getPath()); 1615 _JPEGObserver.setCaptureNextFrame(); 1616 _canvas.getView().repaint(); 1617 } 1618 } 1619 1620 /** 1621 * Print the current 3D view. The user will be asked to supply information on the 1622 * print settings. 1623 */ 1624 @Override 1625 public void print() { 1626 handlePrint(null); 1627 } 1628 1629 /** 1630 * Positions the input window at the location stored in the preferences using the 1631 * supplied key (with "PosX" and "PosY" appended). If the preference key isn't 1632 * found or if the returned values can not be turned into numbers, the 1633 * setLocationByPlatform() flag is set for the window. 1634 * 1635 * @param prefsKey The prefix for the preference key to use ("PosX" and "PosY" 1636 * will be appended in order to retrieve the actual preference 1637 * values). 1638 * @param window The window to be positioned using the preference values. 1639 * @see Window#setLocationByPlatform(boolean) 1640 */ 1641 @Override 1642 public void posWindowFromPrefs(String prefsKey, Window window) { 1643 requireNonNull(prefsKey); 1644 requireNonNull(window); 1645 Preferences prefs = MDIApplication.getInstance().getPreferences(); 1646 String windowPosX = prefs.get(prefsKey + "PosX"); 1647 String windowPosY = prefs.get(prefsKey + "PosY"); 1648 1649 if (isNull(windowPosX) || isNull(windowPosY)) 1650 // No existing preference for the given key. 1651 window.setLocationByPlatform(true); 1652 1653 else { 1654 try { 1655 int x = TypeFormat.parseInt(windowPosX); 1656 int y = TypeFormat.parseInt(windowPosY); 1657 window.setLocation(x,y); 1658 } catch (NumberFormatException e) { 1659 // Fall back on the standard location. 1660 window.setLocationByPlatform(true); 1661 } 1662 } 1663 } 1664 1665 /** 1666 * Save the location of the specified window in the application preferences using 1667 * the supplied key (with "PosX" and "PosY" appended). 1668 * 1669 * @param prefsKey The prefix for the preference key to use ("PosX" and "PosY" 1670 * will be appended in order to save the actual preference 1671 * values). 1672 * @param window The window for which the position is to be saved in the 1673 * preferences. 1674 */ 1675 @Override 1676 public void savePrefsWindowPos(String prefsKey, Window window) { 1677 requireNonNull(prefsKey); 1678 requireNonNull(window); 1679 Preferences prefs = MDIApplication.getInstance().getPreferences(); 1680 Rectangle bounds = window.getBounds(); 1681 prefs.set(prefsKey + "PosX", String.valueOf(bounds.x)); 1682 prefs.set(prefsKey + "PosY", String.valueOf(bounds.y)); 1683 } 1684 1685 } // end PublicInterface 1686}