diff --git a/src/java/davmail/tray/DavGatewayTray.java b/src/java/davmail/tray/DavGatewayTray.java index 3b56baec..9c6dbdf5 100644 --- a/src/java/davmail/tray/DavGatewayTray.java +++ b/src/java/davmail/tray/DavGatewayTray.java @@ -1,6 +1,7 @@ package davmail.tray; import davmail.Settings; +import davmail.ui.OSXFrameGatewayTray; import davmail.exchange.NetworkDownException; import org.apache.log4j.Logger; import org.apache.log4j.Priority; @@ -140,7 +141,12 @@ public class DavGatewayTray { } } if (davGatewayTray == null) { - davGatewayTray = new FrameGatewayTray(); + if (System.getProperty("os.name").toLowerCase().startsWith("mac os x")) { + // MacOS + davGatewayTray = new OSXFrameGatewayTray(); + } else { + davGatewayTray = new FrameGatewayTray(); + } davGatewayTray.init(); } } diff --git a/src/java/davmail/tray/FrameGatewayTray.java b/src/java/davmail/tray/FrameGatewayTray.java index 42bfa777..d67f2cdc 100644 --- a/src/java/davmail/tray/FrameGatewayTray.java +++ b/src/java/davmail/tray/FrameGatewayTray.java @@ -21,7 +21,9 @@ public class FrameGatewayTray implements DavGatewayTrayInterface { protected FrameGatewayTray() { } - private static JFrame mainFrame = null; + protected static JFrame mainFrame = null; + protected static AboutFrame aboutFrame; + protected static SettingsFrame settingsFrame; private static JEditorPane errorArea = null; private static JLabel errorLabel = null; private static JEditorPane messageArea = null; @@ -89,6 +91,39 @@ public class FrameGatewayTray implements DavGatewayTrayInterface { }); } + public void about() { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + aboutFrame.update(); + aboutFrame.setVisible(true); + } + }); + } + + public void preferences() { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + settingsFrame.reload(); + settingsFrame.setVisible(true); + } + }); + } + + public void showLogs() { + Logger rootLogger = Logger.getRootLogger(); + LF5Appender lf5Appender = (LF5Appender) rootLogger.getAppender("LF5Appender"); + if (lf5Appender == null) { + lf5Appender = new LF5Appender(new LogBrokerMonitor(LogLevel.getLog4JLevels()) { + protected void closeAfterConfirm() { + hide(); + } + }); + lf5Appender.setName("LF5Appender"); + rootLogger.addAppender(lf5Appender); + } + lf5Appender.getLogBrokerMonitor().show(); + } + public void init() { SwingUtilities.invokeLater(new Runnable() { public void run() { @@ -97,6 +132,56 @@ public class FrameGatewayTray implements DavGatewayTrayInterface { }); } + protected void buildMenu() { + // create a popup menu + JMenu menu = new JMenu("DavMail"); + JMenuBar menuBar = new JMenuBar(); + menuBar.add(menu); + mainFrame.setJMenuBar(menuBar); + + // create an action settingsListener to listen for settings action executed on the tray icon + ActionListener aboutListener = new ActionListener() { + public void actionPerformed(ActionEvent e) { + about(); + } + }; + // create menu item for the default action + JMenuItem aboutItem = new JMenuItem("About..."); + aboutItem.addActionListener(aboutListener); + menu.add(aboutItem); + + + // create an action settingsListener to listen for settings action executed on the tray icon + ActionListener settingsListener = new ActionListener() { + public void actionPerformed(ActionEvent e) { + preferences(); + } + }; + // create menu item for the default action + JMenuItem defaultItem = new JMenuItem("Settings..."); + defaultItem.addActionListener(settingsListener); + menu.add(defaultItem); + + JMenuItem logItem = new JMenuItem("Show logs..."); + logItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + showLogs(); + } + }); + menu.add(logItem); + + // create an action exitListener to listen for exit action executed on the tray icon + ActionListener exitListener = new ActionListener() { + public void actionPerformed(ActionEvent e) { + //noinspection CallToSystemExit + System.exit(0); + } + }; + // create menu item for the exit action + JMenuItem exitItem = new JMenuItem("Quit"); + exitItem.addActionListener(exitListener); + menu.add(exitItem); + } protected void createAndShowGUI() { // set native look and feel @@ -105,11 +190,6 @@ public class FrameGatewayTray implements DavGatewayTrayInterface { } catch (Exception e) { DavGatewayTray.warn("Unable to set system look and feel", e); } - // MacOS - if (System.getProperty("mrj.version") != null) { - System.setProperty("apple.laf.useScreenMenuBar", "true"); - } - image = DavGatewayTray.loadImage("tray.png"); image2 = DavGatewayTray.loadImage("tray2.png"); @@ -120,12 +200,6 @@ public class FrameGatewayTray implements DavGatewayTrayInterface { mainFrame.setTitle("DavMail Gateway"); mainFrame.setIconImage(image); - // create a popup menu - JMenu menu = new JMenu("DavMail"); - JMenuBar menuBar = new JMenuBar(); - menuBar.add(menu); - mainFrame.setJMenuBar(menuBar); - JPanel errorPanel = new JPanel(); errorPanel.setBorder(BorderFactory.createTitledBorder("Last message")); errorPanel.setLayout(new BoxLayout(errorPanel, BoxLayout.X_AXIS)); @@ -152,62 +226,9 @@ public class FrameGatewayTray implements DavGatewayTrayInterface { mainPanel.add(messagePanel); mainFrame.add(mainPanel); - final AboutFrame aboutFrame = new AboutFrame(); - // create an action settingsListener to listen for settings action executed on the tray icon - ActionListener aboutListener = new ActionListener() { - public void actionPerformed(ActionEvent e) { - aboutFrame.update(); - aboutFrame.setVisible(true); - } - }; - // create menu item for the default action - JMenuItem aboutItem = new JMenuItem("About..."); - aboutItem.addActionListener(aboutListener); - menu.add(aboutItem); - - final SettingsFrame settingsFrame = new SettingsFrame(); - // create an action settingsListener to listen for settings action executed on the tray icon - ActionListener settingsListener = new ActionListener() { - public void actionPerformed(ActionEvent e) { - settingsFrame.reload(); - settingsFrame.setVisible(true); - } - }; - // create menu item for the default action - JMenuItem defaultItem = new JMenuItem("Settings..."); - defaultItem.addActionListener(settingsListener); - menu.add(defaultItem); - - JMenuItem logItem = new JMenuItem("Show logs..."); - logItem.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - Logger rootLogger = Logger.getRootLogger(); - LF5Appender lf5Appender = (LF5Appender) rootLogger.getAppender("LF5Appender"); - if (lf5Appender == null) { - lf5Appender = new LF5Appender(new LogBrokerMonitor(LogLevel.getLog4JLevels()) { - protected void closeAfterConfirm() { - hide(); - } - }); - lf5Appender.setName("LF5Appender"); - rootLogger.addAppender(lf5Appender); - } - lf5Appender.getLogBrokerMonitor().show(); - } - }); - menu.add(logItem); - - // create an action exitListener to listen for exit action executed on the tray icon - ActionListener exitListener = new ActionListener() { - public void actionPerformed(ActionEvent e) { - //noinspection CallToSystemExit - System.exit(0); - } - }; - // create menu item for the exit action - JMenuItem exitItem = new JMenuItem("Exit"); - exitItem.addActionListener(exitListener); - menu.add(exitItem); + aboutFrame = new AboutFrame(); + settingsFrame = new SettingsFrame(); + buildMenu(); mainFrame.setMinimumSize(new Dimension(400, 180)); mainFrame.pack(); diff --git a/src/java/davmail/ui/OSXAdapter.java b/src/java/davmail/ui/OSXAdapter.java new file mode 100644 index 00000000..670a8b44 --- /dev/null +++ b/src/java/davmail/ui/OSXAdapter.java @@ -0,0 +1,124 @@ +package davmail.ui; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Proxy; + +/** + * Reflection based MacOS handler + */ +public class OSXAdapter implements InvocationHandler { + + 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 + public static void setQuitHandler(Object target, Method quitHandler) throws InvocationTargetException, ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException { + setHandler(new OSXAdapter("handleQuit", target, quitHandler)); + } + + // Pass this method an Object and Method equipped to display application info + // They will be called when the About menu item is selected from the application menu + public static void setAboutHandler(Object target, Method aboutHandler) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, ClassNotFoundException, InstantiationException { + boolean enableAboutMenu = (target != null && aboutHandler != null); + if (enableAboutMenu) { + setHandler(new OSXAdapter("handleAbout", target, aboutHandler)); + } + Method enableAboutMethod = macOSXApplication.getClass().getDeclaredMethod("setEnabledAboutMenu", new Class[]{boolean.class}); + enableAboutMethod.invoke(macOSXApplication, enableAboutMenu); + } + + // Pass this method an Object and a Method equipped to display application options + // They will be called when the Preferences menu item is selected from the application menu + public static void setPreferencesHandler(Object target, Method prefsHandler) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, ClassNotFoundException, InstantiationException { + boolean enablePrefsMenu = (target != null && prefsHandler != null); + if (enablePrefsMenu) { + setHandler(new OSXAdapter("handlePreferences", target, prefsHandler)); + } + Method enablePrefsMethod = macOSXApplication.getClass().getDeclaredMethod("setEnabledPreferencesMenu", new Class[]{boolean.class}); + enablePrefsMethod.invoke(macOSXApplication, enablePrefsMenu); + } + + // 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 + public static void setFileHandler(Object target, Method fileHandler) throws InvocationTargetException, ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException { + 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, filename); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + return true; + } + }); + } + + // setHandler creates a Proxy object from the passed OSXAdapter and adds it as an ApplicationListener + public static void setHandler(OSXAdapter adapter) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException { + 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, osxAdapterProxy); + } + + // 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 + 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 + public boolean callTarget(Object appleEvent) throws InvocationTargetException, IllegalAccessException { + Object result = targetMethod.invoke(targetObject, (Object[]) null); + return result == null || Boolean.valueOf(result.toString()); + } + + // InvocationHandler implementation + // This is the entry point for our proxy object; it is called every time an ApplicationListener method is invoked + 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.) + 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 + protected void setApplicationEventHandled(Object event, boolean handled) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + if (event != null) { + 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, handled); + } + } +} \ No newline at end of file diff --git a/src/java/davmail/ui/OSXFrameGatewayTray.java b/src/java/davmail/ui/OSXFrameGatewayTray.java new file mode 100644 index 00000000..c4b1919e --- /dev/null +++ b/src/java/davmail/ui/OSXFrameGatewayTray.java @@ -0,0 +1,46 @@ +package davmail.ui; + +import davmail.tray.DavGatewayTray; +import davmail.tray.FrameGatewayTray; + +import javax.swing.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +/** + * MacOSX specific frame to handle menu + */ +public class OSXFrameGatewayTray extends FrameGatewayTray { + + public boolean quit() { + return true; + } + + protected void buildMenu() { + // create a popup menu + JMenu menu = new JMenu("Logs"); + JMenuBar menuBar = new JMenuBar(); + menuBar.add(menu); + mainFrame.setJMenuBar(menuBar); + + JMenuItem logItem = new JMenuItem("Show logs..."); + logItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + showLogs(); + } + }); + menu.add(logItem); + } + + + protected void createAndShowGUI() { + System.setProperty("apple.laf.useScreenMenuBar", "true"); + super.createAndShowGUI(); + try { + OSXAdapter.setAboutHandler(this, FrameGatewayTray.class.getDeclaredMethod("about", (Class[]) null)); + OSXAdapter.setPreferencesHandler(this, FrameGatewayTray.class.getDeclaredMethod("preferences", (Class[]) null)); + } catch (Exception e) { + DavGatewayTray.error("Error while loading the OSXAdapter", e); + } + } +}