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]
