001/** 002 * GeomSSGUI -- The multi-document interface 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 geomss.geom.reader.GeomReader; 021import geomss.geom.reader.GeomReaderFactory; 022import jahuwaldt.io.ExtFilenameFilter; 023import jahuwaldt.swing.*; 024import java.awt.FileDialog; 025import java.awt.Frame; 026import java.awt.desktop.*; 027import java.awt.event.ActionEvent; 028import java.io.File; 029import java.io.IOException; 030import java.io.InputStreamReader; 031import java.io.StringWriter; 032import java.net.URL; 033import java.nio.charset.Charset; 034import java.util.ArrayList; 035import java.util.List; 036import static java.util.Objects.isNull; 037import static java.util.Objects.nonNull; 038import java.util.ResourceBundle; 039import javax.swing.*; 040import javax.swing.event.HyperlinkEvent; 041import javax.swing.event.HyperlinkListener; 042 043 044/** 045 * This class represents a multi-document interface GUI application for use in this 046 * program. 047 * 048 * <p> Modified by: Joseph A. Huwaldt </p> 049 * 050 * @author Joseph A. Huwaldt, Date: May 2, 2009 051 * @version January 1, 2024 052 */ 053public class GeomSSGUI extends MDIApplication { 054 055 // A file filter that recognizes MacOS file types. 056 private final ExtFilenameFilter fnFilter = new ExtFilenameFilter(); 057 058 /** 059 * Constructor for our application that displays the GUI. Note that only one 060 * can ever be created. Attempting to instantiate more will result in an 061 * <code>IllegalStateException</code> being thrown. 062 * 063 * @param resBundle A reference to the resource bundle for this application. 064 * @throws java.lang.Exception if there is any problem constructing this GUI. 065 */ 066 public GeomSSGUI(ResourceBundle resBundle) throws Exception { 067 super(resBundle.getString("appName")); 068 069 // Set the system look and feel to hide that hideous Java LAF. 070 AppUtilities.setSystemLAF(); 071 072 // MacOS users like the menu bar to be at the top of the screen. 073 // This does nothing on non-MacOS platforms. 074 System.setProperty("apple.laf.useScreenMenuBar", "true"); 075 076 // Store the resource bundle. 077 setResourceBundle(resBundle); 078 079 // Get the list of geometry readers that are available. 080 GeomReader[] allReaders = GeomReaderFactory.getAllReaders(); 081 082 // Set up the file name filter. 083 if (nonNull(allReaders)) { 084 int numReaders = allReaders.length; 085 for (int i = 0; i < numReaders; ++i) 086 fnFilter.addExtension(allReaders[i].getExtension()); 087 088 // Add any other extensions that might be encountered that we should be able to read. 089 fnFilter.addExtension("geo"); 090 } 091 setFilenameFilter(fnFilter); 092 093 // Create the menu bar. 094 JMenuBar menuBar = createMenuBar(); 095 096 // Add the menu bar to this application. 097 // Cause the application to stay open when the last window is closed under MacOS. 098 // When the last window is closed, a menu bar will remain for the application with no 099 // window open. This is standard MacOS behavior and will do nothing on other platforms. 100 setFramelessJMenuBar(menuBar); 101 102 // Add an open files handler for MacOS. 103 MDIApplication.setOpenFileHandler(new java.awt.desktop.OpenFilesHandler() { 104 @Override 105 public void openFiles(OpenFilesEvent e) { 106 // Loop over the files to be opened and open each in turn. 107 List<File> files = e.getFiles(); 108 for (File file : files) { 109 openFile(file); 110 } 111 } 112 }); 113 114 } 115 116 //----------------------------------------------------------------------------------- 117 /** 118 * Handle the user choosing the "New..." from the File menu. Creates and returns a 119 * reference to a new document window. 120 * 121 * @param event The event that caused this method to be called (ignored). 122 * @return A new window frame for the application. 123 */ 124 @Override 125 public Frame handleNew(ActionEvent event) { 126 Frame window = null; 127 128 try { 129 // Create an instance of an application window to get the program rolling. 130 window = MainWindow.newAppWindow(null, null); 131 window.setVisible(true); 132 addWindow(window); 133 134 } catch (Exception e) { 135 ResourceBundle resBundle = getResourceBundle(); 136 AppUtilities.showException(null, resBundle.getString("unexpectedTitle"), 137 resBundle.getString("unexpectedMsg"), e); 138 e.printStackTrace(); 139 } 140 141 return window; 142 } 143 144 /** 145 * Open the specified file in a new window. 146 * 147 * @param theFile The file to be opened and read in. 148 */ 149 private void openFile(File theFile) { 150 try { 151 // Create a new application window and read in the file. 152 MainWindow.newWindowFromDataFile(null, theFile); 153 154 } catch (Exception e) { 155 e.printStackTrace(); 156 AppUtilities.showException(null, MDIApplication.getInstance().getResourceBundle().getString("unexpectedTitle"), 157 MDIApplication.getInstance().getResourceBundle().getString("unexpectedMsg"), e); 158 } 159 160 } 161 162 /** 163 * Handles the user choosing "Open" from the File menu. Allows the user to choose a 164 * geometry file to open. 165 * 166 * @param event The action event that caused this method to be called (ignored). 167 */ 168 public void handleOpen(ActionEvent event) { 169 ResourceBundle resBundle = getResourceBundle(); 170 171 try { 172 String dir = this.getPreferences().getLastPath(); 173 File theFile = AppUtilities.selectFile(null, FileDialog.LOAD, resBundle.getString("fileDialogLoad"), 174 dir, null, getFilenameFilter()); 175 if (isNull(theFile)) 176 return; // User canceled. 177 178 // Open the file. 179 openFile(theFile); 180 181 } catch (Exception e) { 182 AppUtilities.showException(null, resBundle.getString("unexpectedTitle"), 183 resBundle.getString("unexpectedMsg"), e); 184 e.printStackTrace(); 185 } 186 187 } 188 189 /** 190 * Create an about handler for use in this application. 191 * 192 * @return The about menu item for this program. 193 */ 194 public AboutHandler createAboutHandler() { 195 196 AboutHandler about = new AboutHandler() { 197 @Override 198 public void handleAbout​(AboutEvent e) { 199 ResourceBundle resBundle = getResourceBundle(); 200 201 // Load in the application's icon image. 202 Icon appIcon = getApplicationIcon(); 203 if (isNull(appIcon)) 204 // Use a generic icon since this app can't read it's custom icon. 205 appIcon = UIManager.getIcon("OptionPane.informationIcon"); 206 207 // Read in the about box text information. 208 String credits = readAboutText(resBundle.getString("aboutTextURLStr")); 209 210 String aName = resBundle.getString("appName"); 211 String aVersion = resBundle.getString("appVersion"); 212 String aModDate = resBundle.getString("appModDate"); 213 String copyright = resBundle.getString("copyright"); 214 StandardMacAboutFrame f = new StandardMacAboutFrame(aName, aVersion + " - " + aModDate); 215 f.setApplicationIcon(appIcon); 216 f.setCopyright(copyright); 217 f.setCredits(credits, "text/html"); 218 f.setHyperlinkListener(new HyperlinkListener() { 219 @Override 220 public void hyperlinkUpdate(HyperlinkEvent e) { 221 if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { 222 // Display the selected URL in the default browser for this platform. 223 try { 224 MDIApplication.browse(e.getURL().toURI()); 225 } catch (Exception err) { 226 err.printStackTrace(); 227 } 228 } 229 } 230 }); 231 f.setVisible(true); 232 } 233 }; 234 235 return about; 236 } 237 238 /** 239 * Return the application icon for this program or null if one can not be found. 240 * 241 * @return The application icon for this program. 242 */ 243 public Icon getApplicationIcon() { 244 ResourceBundle resBundle = getResourceBundle(); 245 246 // Load in the application's icon image. 247 Icon appIcon = null; 248 try { 249 URL imgURL = ClassLoader.getSystemResource(resBundle.getString("applicationIconURL")); 250 appIcon = new ImageIcon(imgURL); 251 } catch (Exception ex) { 252 ex.printStackTrace(); 253 } 254 255 return appIcon; 256 } 257 258 /** 259 * Method that handles reading in the contents of the text region of the about box 260 * from a text file. 261 */ 262 private String readAboutText(String urlStr) { 263 String text; 264 try { 265 266 URL url = ClassLoader.getSystemResource(urlStr); 267 268 InputStreamReader reader = new InputStreamReader(url.openStream(), Charset.forName("UTF-8")); 269 StringWriter writer = new StringWriter(); 270 int character = reader.read(); 271 while (character != -1) { 272 writer.write(character); 273 character = reader.read(); 274 } 275 text = writer.toString(); 276 277 } catch (IOException e) { 278 e.printStackTrace(); 279 280 // Create a standard set of credits if we didn't find the text file. 281 text = getResourceBundle().getString("standardCreditsMsg"); 282 } 283 284 return text; 285 } 286 287 /** 288 * Initializes and displays the menus associated with this application. 289 */ 290 private JMenuBar createMenuBar() throws NoSuchMethodException { 291 ResourceBundle resBundle = getResourceBundle(); 292 293 // Create a menu bar. 294 JMenuBar menuBar = new JMenuBar(); 295 296 // Set up the file menu. 297 List<String[]> menuStrings = new ArrayList(); 298 String[] row = new String[3]; 299 row[0] = resBundle.getString("newItemText"); 300 row[1] = resBundle.getString("newItemKey"); 301 row[2] = "handleNew"; 302 menuStrings.add(row); 303 row = new String[3]; 304 row[0] = resBundle.getString("openItemText"); 305 row[1] = resBundle.getString("openItemKey"); 306 row[2] = "handleOpen"; 307 menuStrings.add(row); 308 menuStrings.add(new String[3]); // Blank line 309 row = new String[3]; 310 row[0] = resBundle.getString("closeItemText"); 311 row[1] = resBundle.getString("closeItemKey"); 312 row[2] = "handleClose"; 313 menuStrings.add(row); 314 row = new String[3]; 315 row[0] = resBundle.getString("saveItemText"); 316 row[1] = resBundle.getString("saveItemKey"); 317 row[2] = null; 318 menuStrings.add(row); 319 row = new String[3]; 320 row[0] = resBundle.getString("saveAsItemText"); 321 row[1] = null; 322 row[2] = null; 323 menuStrings.add(row); 324 325 JMenu menu = AppUtilities.buildMenu(this, resBundle.getString("fileMenuText"), menuStrings); 326 menuBar.add(menu); 327 328 // Add a Quit menu item (if it isn't automatically present). 329 if (!QuitJMenuItem.isAutomaticallyPresent()) { 330 menu.addSeparator(); 331 menu.add(this.getQuitJMenuItem()); 332 } 333 334 // Add a Quit handler to ask the user if they want to save unsaved changes before quitting. 335 setQuitHandler(new QuitHandler() { 336 @Override 337 public void handleQuitRequestWith​(QuitEvent e, QuitResponse response) { 338 handleQuit(e, response); 339 } 340 }); 341 342 343 // Set up the edit menu. 344 menuStrings.clear(); 345 row = new String[3]; 346 row[0] = resBundle.getString("undoItemText"); 347 row[1] = resBundle.getString("undoItemKey"); 348 row[2] = null; 349 menuStrings.add(row); 350 row = new String[3]; 351 row[0] = resBundle.getString("redoItemText"); 352 row[1] = null; 353 row[2] = null; 354 menuStrings.add(row); 355 menuStrings.add(new String[3]); // Blank line. 356 row = new String[3]; 357 row[0] = resBundle.getString("cutItemText"); 358 row[1] = resBundle.getString("cutItemKey"); 359 row[2] = null; 360 menuStrings.add(row); 361 row = new String[3]; 362 row[0] = resBundle.getString("copyItemText"); 363 row[1] = resBundle.getString("copyItemKey"); 364 row[2] = null; 365 menuStrings.add(row); 366 row = new String[3]; 367 row[0] = resBundle.getString("pasteItemText"); 368 row[1] = resBundle.getString("pasteItemKey"); 369 row[2] = null; 370 menuStrings.add(row); 371 372 menu = AppUtilities.buildMenu(this, resBundle.getString("editMenuText"), menuStrings); 373 menuBar.add(menu); 374 375 // Register a Preferences menu item handler. 376 setPreferencesHandler(createPreferencesHandler()); 377 378 // Create a Preferences menu item (if not automatically present). 379 if (!PreferencesJMenuItem.isAutomaticallyPresent()) { 380 menu.addSeparator(); 381 menu.add(this.getPreferencesJMenuItem()); 382 } 383 384 // Register an about menu item handler. 385 setAboutHandler(createAboutHandler()); 386 387 // Create an about menu item (if not automatically present). 388 if (!AboutJMenuItem.isAutomaticallyPresent()) { 389 // Create Help menu. 390 menu = new JMenu(resBundle.getString("helpMenuText")); 391 menuBar.add(menu); 392 393 // Add the "About" item to the Help menu. 394 menu.add(this.getAboutJMenuItem()); 395 } 396 397 return menuBar; 398 } 399 400 /** 401 * Return a preferences handler that displays the preference dialog for this application. 402 * 403 * @return A preferences handler 404 */ 405 public PreferencesHandler createPreferencesHandler() { 406 PreferencesHandler handler = new PreferencesHandler() { 407 @Override 408 public void handlePreferences(PreferencesEvent e) { 409 MDIApplication.getInstance().getPreferences().showPreferenceDialog(); 410 } 411 }; 412 return handler; 413 } 414 415}