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}