Author: tfmorris
Date: 2008-09-08 09:02:10-0700
New Revision: 15685

Added:
   trunk/src/argouml-app/src/org/argouml/util/osdep/OSXAdapter.java   
(contents, props changed)
Modified:
   trunk/src/argouml-app/src/org/argouml/ui/cmd/GenericArgoMenuBar.java
   trunk/src/argouml-app/src/org/argouml/util/osdep/OsUtil.java

Log:
RESOLVED - task 3926: Quit via cmd-Q or app menu "quit" choice doesn't prompt 
for save 
http://argouml.tigris.org/issues/show_bug.cgi?id=3926

Modified: trunk/src/argouml-app/src/org/argouml/ui/cmd/GenericArgoMenuBar.java
Url: 
http://argouml.tigris.org/source/browse/argouml/trunk/src/argouml-app/src/org/argouml/ui/cmd/GenericArgoMenuBar.java?view=diff&rev=15685&p1=trunk/src/argouml-app/src/org/argouml/ui/cmd/GenericArgoMenuBar.java&p2=trunk/src/argouml-app/src/org/argouml/ui/cmd/GenericArgoMenuBar.java&r1=15684&r2=15685
==============================================================================
--- trunk/src/argouml-app/src/org/argouml/ui/cmd/GenericArgoMenuBar.java        
(original)
+++ trunk/src/argouml-app/src/org/argouml/ui/cmd/GenericArgoMenuBar.java        
2008-09-08 09:02:10-0700
@@ -39,6 +39,7 @@
 import javax.swing.JToolBar;
 import javax.swing.KeyStroke;
 
+import org.apache.log4j.Logger;
 import org.argouml.application.helpers.ResourceLoaderWrapper;
 import org.argouml.cognitive.critics.ui.ActionOpenCritics;
 import org.argouml.cognitive.ui.ActionAutoCritique;
@@ -77,7 +78,8 @@
 import org.argouml.uml.ui.ActionSequenceDiagram;
 import org.argouml.uml.ui.ActionStateDiagram;
 import org.argouml.uml.ui.ActionUseCaseDiagram;
-import org.tigris.gef.base.AdjustPageBreaksAction;
+import org.argouml.util.osdep.OSXAdapter;
+import org.argouml.util.osdep.OsUtil;
 import org.tigris.gef.base.AlignAction;
 import org.tigris.gef.base.DistributeAction;
 import org.tigris.gef.base.ReorderAction;
@@ -113,6 +115,9 @@
  */
 public class GenericArgoMenuBar extends JMenuBar implements
         TargetListener {
+    
+    private static final Logger LOG = 
+        Logger.getLogger(GenericArgoMenuBar.class);
 
     private static List<JMenu> moduleMenus = new ArrayList<JMenu>();
 
@@ -202,6 +207,12 @@
     private Action navigateTargetForwardAction;
 
     private Action navigateTargetBackAction;
+    
+    // References to actions that we need for Mac hack
+    private ActionSettings settingsAction;
+    private ActionAboutArgoUML aboutAction;
+    private ActionExit exitAction;
+    private ActionOpenProject openAction;
 
     /**
      * The constructor.
@@ -210,6 +221,7 @@
         initActions();
         initMenus();
         initModulesUI();
+        registerForMacEvents();
     }
 
     private void initActions() {
@@ -293,7 +305,8 @@
         setMnemonic(newItem, "New");
         ShortcutMgr.assignAccelerator(newItem, ShortcutMgr.ACTION_NEW_PROJECT);
         toolbarTools.add((new ActionNew()));
-        JMenuItem openProjectItem = file.add(new ActionOpenProject());
+        openAction = new ActionOpenProject();
+        JMenuItem openProjectItem = file.add(openAction);
         setMnemonic(openProjectItem, "Open");
         ShortcutMgr.assignAccelerator(openProjectItem,
                 ShortcutMgr.ACTION_OPEN_PROJECT);
@@ -364,14 +377,17 @@
         mruList = new LastRecentlyUsedMenuList(file);
 
         // and exit menu entry starting with separator. 
-        file.addSeparator();
-        JMenuItem exitItem = file.add(new ActionExit());
-        setMnemonic(exitItem, "Exit");
-        /* The "Close window" shortcut (ALT+F4) actually can't 
-         * be registered as a shortcut, 
-         * because it closes the configuration dialog! */
-        exitItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F4,
-              InputEvent.ALT_MASK));
+        exitAction = new ActionExit();
+        if (!OsUtil.isMacOSX()) {
+            file.addSeparator();
+            JMenuItem exitItem = file.add(exitAction);
+            setMnemonic(exitItem, "Exit");
+            /* The "Close window" shortcut (ALT+F4) actually can't 
+             * be registered as a shortcut, 
+             * because it closes the configuration dialog! */
+            exitItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F4,
+                    InputEvent.ALT_MASK));
+        }
 
         fileToolbar = (new ToolBarFactory(toolbarTools)).createToolBar();
         fileToolbar.setName(Translator.localize("misc.toolbar.file"));
@@ -458,10 +474,13 @@
         ShortcutMgr.assignAccelerator(edit.add(new ActionPerspectiveConfig()),
                 ShortcutMgr.ACTION_PERSPECTIVE_CONFIG);
 
-        JMenuItem settingsItem = edit.add(new ActionSettings());
-        setMnemonic(settingsItem, "Settings");
-        ShortcutMgr
-                .assignAccelerator(settingsItem, ShortcutMgr.ACTION_SETTINGS);
+        settingsAction = new ActionSettings();
+        if (!OsUtil.isMacOSX()) {
+            JMenuItem settingsItem = edit.add(settingsAction);
+            setMnemonic(settingsItem, "Settings");
+            ShortcutMgr.assignAccelerator(settingsItem,
+                    ShortcutMgr.ACTION_SETTINGS);
+        }
     }
 
 
@@ -904,12 +923,16 @@
         setMnemonic(systemInfo, "System Information");
         ShortcutMgr.assignAccelerator(systemInfo,
                 ShortcutMgr.ACTION_SYSTEM_INFORMATION);
-        help.addSeparator();
-        JMenuItem aboutArgoUML = help.add(new ActionAboutArgoUML());
-        setMnemonic(aboutArgoUML, "About ArgoUML");
-        ShortcutMgr.assignAccelerator(aboutArgoUML,
-                ShortcutMgr.ACTION_ABOUT_ARGOUML);
-
+        
+        aboutAction = new ActionAboutArgoUML();
+        if (!OsUtil.isMacOSX()) {
+            help.addSeparator();
+            JMenuItem aboutArgoUML = help.add(aboutAction);
+            setMnemonic(aboutArgoUML, "About ArgoUML");
+            ShortcutMgr.assignAccelerator(aboutArgoUML,
+                    ShortcutMgr.ACTION_ABOUT_ARGOUML);
+        }
+        
         // setHelpMenu(help);
         add(help);
     }
@@ -1081,4 +1104,53 @@
     public static void registerCreateDiagramAction(Action action) {
         moduleCreateDiagramActions.add(action);
     }
+    
+    private void registerForMacEvents() {
+        if (OsUtil.isMacOSX()) {
+            try {
+                // Generate and register the OSXAdapter, passing the methods 
+                // we wish to use as delegates for various
+                // com.apple.eawt.ApplicationListener methods
+                OSXAdapter.setQuitHandler(this, getClass().getDeclaredMethod(
+                        "macQuit", (Class[]) null));
+                OSXAdapter.setAboutHandler(this, getClass().getDeclaredMethod(
+                        "macAbout", (Class[]) null));
+                OSXAdapter.setPreferencesHandler(this, getClass()
+                        .getDeclaredMethod("macPreferences", (Class[]) null));
+                OSXAdapter.setFileHandler(this, getClass().getDeclaredMethod(
+                        "macOpenFile", new Class[] {String.class}));
+            } catch (Exception e) {
+                LOG.error("Error while loading the OSXAdapter:", e);
+            }
+        }
+    }
+    
+    /**
+     * Internal use only.  Do not use.
+     */
+    public void macQuit() {
+        exitAction.actionPerformed(null);
+    }
+    
+    /**
+     * Internal use only.  Do not use.
+     */
+    public void macAbout() {
+        aboutAction.actionPerformed(null);
+    }
+    
+    /**
+     * Internal use only.  Do not use.
+     */
+    public void macPreferences() {
+        settingsAction.actionPerformed(null);
+    }
+    
+    /**
+     * Internal use only.  Do not use.
+     * @param filename name of file to be opened
+     */
+    public void macOpenFile(String filename) {
+        openAction.doCommand(filename);
+    }
 }

Added: trunk/src/argouml-app/src/org/argouml/util/osdep/OSXAdapter.java
Url: 
http://argouml.tigris.org/source/browse/argouml/trunk/src/argouml-app/src/org/argouml/util/osdep/OSXAdapter.java?view=auto&rev=15685
==============================================================================
--- (empty file)
+++ trunk/src/argouml-app/src/org/argouml/util/osdep/OSXAdapter.java    
2008-09-08 09:02:10-0700
@@ -0,0 +1,292 @@
+/* CHECKSTYLE:OFF - imported code
+
+File: OSXAdapter.java
+
+Abstract: Hooks existing preferences/about/quit functionality from an
+    existing Java app into handlers for the Mac OS X application menu.
+    Uses a Proxy object to dynamically implement the 
+    com.apple.eawt.ApplicationListener interface and register it with the
+    com.apple.eawt.Application object.  This allows the complete project
+    to be both built and run on any platform without any stubs or 
+    placeholders. Useful for developers looking to implement Mac OS X 
+    features while supporting multiple platforms with minimal impact.
+                       
+Version: 2.0
+
+Disclaimer: IMPORTANT:  This Apple software is supplied to you by 
+Apple Inc. ("Apple") in consideration of your agreement to the
+following terms, and your use, installation, modification or
+redistribution of this Apple software constitutes acceptance of these
+terms.  If you do not agree with these terms, please do not use,
+install, modify or redistribute this Apple software.
+
+In consideration of your agreement to abide by the following terms, and
+subject to these terms, Apple grants you a personal, non-exclusive
+license, under Apple's copyrights in this original Apple software (the
+"Apple Software"), to use, reproduce, modify and redistribute the Apple
+Software, with or without modifications, in source and/or binary forms;
+provided that if you redistribute the Apple Software in its entirety and
+without modifications, you must retain this notice and the following
+text and disclaimers in all such redistributions of the Apple Software. 
+Neither the name, trademarks, service marks or logos of Apple Inc. 
+may be used to endorse or promote products derived from the Apple
+Software without specific prior written permission from Apple.  Except
+as expressly stated in this notice, no other rights or licenses, express
+or implied, are granted by Apple herein, including but not limited to
+any patent rights that may be infringed by your derivative works or by
+other works in which the Apple Software may be incorporated.
+
+The Apple Software is provided by Apple on an "AS IS" basis.  APPLE
+MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
+THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND
+OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
+
+IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
+OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,
+MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED
+AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),
+STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+
+Copyright © 2003-2007 Apple, Inc., All Rights Reserved
+
+*/
+
+package org.argouml.util.osdep;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Hooks existing preferences/about/quit functionality from an existing Java 
app
+ * into handlers for the Mac OS X application menu. Uses a Proxy object to
+ * dynamically implement the com.apple.eawt.ApplicationListener interface and
+ * register it with the com.apple.eawt.Application object. This allows the
+ * complete project to be both built and run on any platform without any stubs
+ * or placeholders. Useful for developers looking to implement Mac OS X 
features
+ * while supporting multiple platforms with minimal impact.<p>
+ * 
+ * Modified by Tom Morris for ArgoUML as follows:
+ * - changed package
+ * - added Javadoc
+ * - disabled checkstyle warnings
+ * - switched error logging to use log4j instead of println
+ * 
+ * @author Apple, Inc.
+ * @version 2.0
+ */
+public class OSXAdapter implements InvocationHandler {
+
+    private static final Logger LOG = Logger.getLogger(OSXAdapter.class);
+    
+    protected Object targetObject;
+    protected Method targetMethod;
+    protected String proxySignature;
+    
+    static Object macOSXApplication;
+
+    /**
+     * Pass this method an Object and Method equipped to perform application
+     * shutdown logic The method passed should return a boolean stating whether
+     * or not the quit should occur
+     * 
+     * @param target object containing the method
+     * @param quitHandler method to handle quit
+     */
+    public static void setQuitHandler(Object target, Method quitHandler) {
+        setHandler(new OSXAdapter("handleQuit", target, quitHandler));
+    }
+    
+    /**
+     * Set handler for About action. It will be called when the About menu item
+     * is selected from the application menu.
+     * 
+     * @param target object containing the method
+     * @param aboutHandler method to invoke to handle About menu item
+     */
+    public static void setAboutHandler(Object target, Method aboutHandler) {
+        boolean enableAboutMenu = (target != null && aboutHandler != null);
+        if (enableAboutMenu) {
+            setHandler(new OSXAdapter("handleAbout", target, aboutHandler));
+        }
+        // If we're setting a handler, enable the About menu item by calling
+        // com.apple.eawt.Application reflectively
+        try {
+            Method enableAboutMethod = 
macOSXApplication.getClass().getDeclaredMethod("setEnabledAboutMenu", new 
Class[] { boolean.class });
+            enableAboutMethod.invoke(macOSXApplication, new Object[] { 
Boolean.valueOf(enableAboutMenu) });
+        } catch (Exception ex) {
+            LOG.error("OSXAdapter could not access the About Menu", ex);
+        }
+    }
+    
+    /**
+     * Set handler for Mac preferences item. It will be called when the
+     * Preferences menu item is selected from the application menu.
+     * 
+     * @param target object containing the method
+     * @param prefsHandler method to handle preferences action
+     */
+    public static void setPreferencesHandler(Object target, Method 
prefsHandler) {
+        boolean enablePrefsMenu = (target != null && prefsHandler != null);
+        if (enablePrefsMenu) {
+            setHandler(new OSXAdapter("handlePreferences", target, 
prefsHandler));
+        }
+        // If we're setting a handler, enable the Preferences menu item by 
calling
+        // com.apple.eawt.Application reflectively
+        try {
+            Method enablePrefsMethod = 
macOSXApplication.getClass().getDeclaredMethod("setEnabledPreferencesMenu", new 
Class[] { boolean.class });
+            enablePrefsMethod.invoke(macOSXApplication, new Object[] { 
Boolean.valueOf(enablePrefsMenu) });
+        } catch (Exception ex) {
+            LOG.error("OSXAdapter could not access the About Menu");
+            ex.printStackTrace();
+        }
+    }
+    
+
+    /**
+     * Pass this method an Object and a Method equipped to handle document
+     * events from the Finder Documents are registered with the Finder via the
+     * CFBundleDocumentTypes dictionary in the application bundle's Info.plist
+     * 
+     * @param target object containing method
+     * @param fileHandler method to invoke to open a new file
+     */
+    public static void setFileHandler(Object target, Method fileHandler) {
+        setHandler(new OSXAdapter("handleOpenFile", target, fileHandler) {
+            // Override OSXAdapter.callTarget to send information on the
+            // file to be opened
+            public boolean callTarget(Object appleEvent) {
+                if (appleEvent != null) {
+                    try {
+                        Method getFilenameMethod = 
appleEvent.getClass().getDeclaredMethod("getFilename", (Class[])null);
+                        String filename = (String) 
getFilenameMethod.invoke(appleEvent, (Object[])null);
+                        this.targetMethod.invoke(this.targetObject, new 
Object[] { filename });
+                    } catch (Exception ex) {
+                        
+                    }
+                }
+                return true;
+            }
+        });
+    }
+    
+    /**
+     * Creates a Proxy object from the passed OSXAdapter and adds it as an
+     * ApplicationListener
+     * 
+     * @param adapter an instance of this class
+     */
+    public static void setHandler(OSXAdapter adapter) {
+        try {
+            Class applicationClass = 
Class.forName("com.apple.eawt.Application");
+            if (macOSXApplication == null) {
+                macOSXApplication = 
applicationClass.getConstructor((Class[])null).newInstance((Object[])null);
+            }
+            Class applicationListenerClass = 
Class.forName("com.apple.eawt.ApplicationListener");
+            Method addListenerMethod = 
applicationClass.getDeclaredMethod("addApplicationListener", new Class[] { 
applicationListenerClass });
+            // Create a proxy object around this handler that can be 
reflectively added as an Apple ApplicationListener
+            Object osxAdapterProxy = 
Proxy.newProxyInstance(OSXAdapter.class.getClassLoader(), new Class[] { 
applicationListenerClass }, adapter);
+            addListenerMethod.invoke(macOSXApplication, new Object[] { 
osxAdapterProxy });
+        } catch (ClassNotFoundException cnfe) {
+            LOG.error("This version of Mac OS X does not support the Apple 
EAWT.  ApplicationEvent handling has been disabled (" + cnfe + ")");
+        } catch (Exception ex) {  // Likely a NoSuchMethodException or an 
IllegalAccessException loading/invoking eawt.Application methods
+            LOG.error("Mac OS X Adapter could not talk to EAWT:");
+            ex.printStackTrace();
+        }
+    }
+
+    /**
+     * Each OSXAdapter has the name of the EAWT method it intends to listen for
+     * (handleAbout, for example), the Object that will ultimately perform the
+     * task, and the Method to be called on that Object
+     * 
+     * @param proxySignature
+     * @param target
+     * @param handler
+     */
+    protected OSXAdapter(String proxySignature, Object target, Method handler) 
{
+        this.proxySignature = proxySignature;
+        this.targetObject = target;
+        this.targetMethod = handler;
+    }
+    
+
+    /**
+     * Override this method to perform any operations on the event that comes
+     * with the various callbacks. See setFileHandler above for an example
+     * 
+     * @param appleEvent the AppleEvent
+     * @return boolean result of method invocation
+     * @throws InvocationTargetException
+     * @throws IllegalAccessException
+     */
+    public boolean callTarget(Object appleEvent)
+        throws InvocationTargetException, IllegalAccessException {
+        Object result = targetMethod.invoke(targetObject, (Object[])null);
+        if (result == null) {
+            return true;
+        }
+        return Boolean.valueOf(result.toString()).booleanValue();
+    }
+    
+    /**
+     * InvocationHandler implementation This is the entry point for our proxy
+     * object; it is called every time an ApplicationListener method is invoked
+     * 
+     * @param proxy
+     * @param method
+     * @param args
+     * @return null
+     * @throws Throwable
+     * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object,
+     *      java.lang.reflect.Method, java.lang.Object[])
+     */
+    public Object invoke (Object proxy, Method method, Object[] args) throws 
Throwable {
+        if (isCorrectMethod(method, args)) {
+            boolean handled = callTarget(args[0]);
+            setApplicationEventHandled(args[0], handled);
+        }
+        // All of the ApplicationListener methods are void; return null 
regardless of what happens
+        return null;
+    }
+    
+    /**
+     * Compare the method that was called to the intended method when the
+     * OSXAdapter instance was created (e.g. handleAbout, handleQuit,
+     * handleOpenFile, etc.)
+     * 
+     * @param method method to be invoked
+     * @param args argumnets
+     * @return a boolean result of the method invocation
+     */
+    protected boolean isCorrectMethod(Method method, Object[] args) {
+        return (targetMethod != null && 
proxySignature.equals(method.getName()) && args.length == 1);
+    }
+    
+    /**
+     * It is important to mark the ApplicationEvent as handled and cancel the
+     * default behavior This method checks for a boolean result from the proxy
+     * method and sets the event accordingly
+     * 
+     * @param event
+     * @param handled
+     */
+    protected void setApplicationEventHandled(Object event, boolean handled) {
+        if (event != null) {
+            try {
+                Method setHandledMethod = 
event.getClass().getDeclaredMethod("setHandled", new Class[] { boolean.class });
+                // If the target method returns a boolean, use that as a hint
+                setHandledMethod.invoke(event, new Object[] { 
Boolean.valueOf(handled) });
+            } catch (Exception ex) {
+                LOG.error("OSXAdapter was unable to handle an 
ApplicationEvent: " + event, ex);
+            }
+        }
+    }
+}
\ No newline at end of file

Modified: trunk/src/argouml-app/src/org/argouml/util/osdep/OsUtil.java
Url: 
http://argouml.tigris.org/source/browse/argouml/trunk/src/argouml-app/src/org/argouml/util/osdep/OsUtil.java?view=diff&rev=15685&p1=trunk/src/argouml-app/src/org/argouml/util/osdep/OsUtil.java&p2=trunk/src/argouml-app/src/org/argouml/util/osdep/OsUtil.java&r1=15684&r2=15685
==============================================================================
--- trunk/src/argouml-app/src/org/argouml/util/osdep/OsUtil.java        
(original)
+++ trunk/src/argouml-app/src/org/argouml/util/osdep/OsUtil.java        
2008-09-08 09:02:10-0700
@@ -58,6 +58,16 @@
     }
 
     /**
+     * Check whether this is a Mac running OS X.
+     *
+     * @return true if this is a Mac running OS X
+     */
+    public static boolean isMacOSX() {
+        return (System.getProperty("os.name").toLowerCase()
+                .startsWith("mac os x"));
+    }
+    
+    /**
      * Check whether we deal with a Sun Java.
      *
      * @return true if this is a Sun Java

---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to