View Javadoc
1   /*
2    * DavMail POP/IMAP/SMTP/CalDav/LDAP Exchange Gateway
3    * Copyright (C) 2009  Mickael Guessant
4    *
5    * This program is free software; you can redistribute it and/or
6    * modify it under the terms of the GNU General Public License
7    * as published by the Free Software Foundation; either version 2
8    * of the License, or (at your option) any later version.
9    *
10   * This program is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with this program; if not, write to the Free Software
17   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18   */
19  package davmail.ui.tray;
20  
21  import davmail.BundleMessage;
22  import davmail.DavGateway;
23  import davmail.Settings;
24  import davmail.ui.AboutFrame;
25  import davmail.ui.SettingsFrame;
26  import davmail.util.IOUtil;
27  import org.apache.log4j.Level;
28  import org.apache.log4j.Logger;
29  import org.apache.log4j.lf5.LF5Appender;
30  import org.apache.log4j.lf5.LogLevel;
31  import org.apache.log4j.lf5.viewer.LogBrokerMonitor;
32  import org.eclipse.swt.SWT;
33  import org.eclipse.swt.events.DisposeEvent;
34  import org.eclipse.swt.events.DisposeListener;
35  import org.eclipse.swt.graphics.Image;
36  import org.eclipse.swt.internal.gtk.GdkRectangle;
37  import org.eclipse.swt.internal.gtk.OS;
38  import org.eclipse.swt.widgets.*;
39  
40  import javax.swing.*;
41  import java.io.ByteArrayInputStream;
42  import java.io.IOException;
43  import java.net.URL;
44  
45  /**
46   * Tray icon handler based on SWT
47   */
48  public class SwtGatewayTray implements DavGatewayTrayInterface {
49      protected SwtGatewayTray() {
50      }
51  
52      SettingsFrame settingsFrame;
53      AboutFrame aboutFrame;
54  
55      private static TrayItem trayItem;
56      private static java.awt.Image awtImage;
57      private static Image image;
58      private static Image image2;
59      private static Image inactiveImage;
60      private static Display display;
61      private static Shell shell;
62      private LogBrokerMonitor logBrokerMonitor;
63      private boolean isActive = true;
64      private boolean isReady;
65      private Error error;
66      private boolean firstMessage = true;
67  
68      private final Thread mainThread = Thread.currentThread();
69  
70      /**
71       * Return AWT Image icon for frame title.
72       *
73       * @return frame icon
74       */
75      public java.awt.Image getFrameIcon() {
76          return awtImage;
77      }
78  
79      /**
80       * Switch tray icon between active and standby icon.
81       */
82      public void switchIcon() {
83          isActive = true;
84          display.syncExec(new Runnable() {
85              public void run() {
86                  Image currentImage = trayItem.getImage();
87                  if (currentImage != null && currentImage.equals(image)) {
88                      trayItem.setImage(image2);
89                  } else {
90                      trayItem.setImage(image);
91                  }
92              }
93          });
94  
95      }
96  
97      /**
98       * Set tray icon to inactive (network down)
99       */
100     public void resetIcon() {
101         display.syncExec(new Runnable() {
102             public void run() {
103                 trayItem.setImage(image);
104             }
105         });
106     }
107 
108     /**
109      * Set tray icon to inactive (network down)
110      */
111     public void inactiveIcon() {
112         isActive = false;
113         display.syncExec(new Runnable() {
114             public void run() {
115                 trayItem.setImage(inactiveImage);
116             }
117         });
118     }
119 
120     /**
121      * Check if current tray status is inactive (network down).
122      *
123      * @return true if inactive
124      */
125     public boolean isActive() {
126         return isActive;
127     }
128 
129     /**
130      * Log and display balloon message according to log level.
131      *
132      * @param message text message
133      * @param level   log level
134      */
135     public void displayMessage(final String message, final Level level) {
136         if (trayItem != null) {
137             display.asyncExec(new Runnable() {
138                 public void run() {
139                     int messageType = 0;
140                     if (level.equals(Level.INFO)) {
141                         messageType = SWT.ICON_INFORMATION;
142                     } else if (level.equals(Level.WARN)) {
143                         messageType = SWT.ICON_WARNING;
144                     } else if (level.equals(Level.ERROR)) {
145                         messageType = SWT.ICON_ERROR;
146                     }
147                     if (messageType != 0) {
148                         final ToolTip toolTip = new ToolTip(shell, SWT.BALLOON | messageType);
149                         toolTip.setText(BundleMessage.format("UI_DAVMAIL_GATEWAY"));
150                         toolTip.setMessage(message);
151                         trayItem.setToolTip(toolTip);
152                         // Wait for tray init 1 second on first message
153                         if (firstMessage) {
154                             firstMessage = false;
155                             try {
156                                 Thread.sleep(1000);
157                             } catch (InterruptedException e) {
158                                 // ignore
159                             }
160                         }
161                         toolTip.setVisible(true);
162                     }
163                     trayItem.setToolTipText(BundleMessage.format("UI_DAVMAIL_GATEWAY") + '\n' + message);
164                 }
165             });
166         }
167     }
168 
169     /**
170      * Load image with current class loader.
171      *
172      * @param fileName image resource file name
173      * @return image
174      */
175     public static Image loadSwtImage(String fileName) {
176         Image result = null;
177         try {
178             ClassLoader classloader = DavGatewayTray.class.getClassLoader();
179             URL imageUrl = classloader.getResource(fileName);
180             if (imageUrl == null) {
181                 throw new IOException(fileName);
182             }
183             byte[] imageContent = IOUtil.readFully(imageUrl.openStream());
184             result = new Image(display, new ByteArrayInputStream(imageContent));
185 
186         } catch (IOException e) {
187             DavGatewayTray.warn(new BundleMessage("LOG_UNABLE_TO_LOAD_IMAGE"), e);
188         }
189         return result;
190     }
191 
192     /**
193      * Create tray icon and register frame listeners.
194      */
195     public void init() {
196         // register error handler to avoid application crash on concurrent X access from SWT and AWT
197         try {
198             OS.gdk_error_trap_push();
199         } catch (NoClassDefFoundError e) {
200             // ignore
201         }
202         final String systemLookAndFeelClassName = UIManager.getSystemLookAndFeelClassName();
203         try {
204             // workaround for bug when SWT and AWT both try to access Gtk
205             if (systemLookAndFeelClassName.contains("gtk")) {
206                 System.setProperty("swing.defaultlaf", UIManager.getCrossPlatformLookAndFeelClassName());
207             } else {
208                 System.setProperty("swing.defaultlaf", systemLookAndFeelClassName);
209             }
210         } catch (Exception e) {
211             DavGatewayTray.warn(new BundleMessage("LOG_UNABLE_TO_SET_LOOK_AND_FEEL"));
212         }
213 
214         new Thread("SWT") {
215             @Override
216             public void run() {
217                 try {
218                     display = new Display();
219                     shell = new Shell(display);
220 
221                     final Tray tray = display.getSystemTray();
222                     if (tray != null) {
223 
224                         trayItem = new TrayItem(tray, SWT.NONE);
225                         trayItem.setToolTipText(BundleMessage.format("UI_DAVMAIL_GATEWAY"));
226 
227                         // check if tray is indeed available
228                         if (systemLookAndFeelClassName.contains("gtk")) {
229                             GdkRectangle area = new GdkRectangle();
230                             OS.gtk_status_icon_get_geometry(trayItem.handle, 0, area, 0);
231                             if (area.x == 0 && area.y == 0) {
232                                 throw new Error("System tray not available");
233                             }
234                         }
235 
236 
237                         awtImage = DavGatewayTray.loadImage(AwtGatewayTray.TRAY_PNG);
238                         image = loadSwtImage(AwtGatewayTray.TRAY_PNG);
239                         image2 = loadSwtImage(AwtGatewayTray.TRAY_ACTIVE_PNG);
240                         inactiveImage = loadSwtImage(AwtGatewayTray.TRAY_INACTIVE_PNG);
241 
242                         trayItem.setImage(image);
243                         trayItem.addDisposeListener(new DisposeListener() {
244                             public void widgetDisposed(DisposeEvent e) {
245                                 if (image != null && !image.isDisposed()) {
246                                     image.dispose();
247                                 }
248                                 if (image2 != null && !image2.isDisposed()) {
249                                     image2.dispose();
250                                 }
251                                 if (inactiveImage != null && !inactiveImage.isDisposed()) {
252                                     inactiveImage.dispose();
253                                 }
254                             }
255                         });
256 
257                         // create a popup menu
258                         final Menu popup = new Menu(shell, SWT.POP_UP);
259                         trayItem.addListener(SWT.MenuDetect, new Listener() {
260                             public void handleEvent(Event event) {
261                                 display.asyncExec(
262                                         new Runnable() {
263                                             public void run() {
264                                                 popup.setVisible(true);
265                                             }
266                                         });
267                             }
268                         });
269 
270                         MenuItem aboutItem = new MenuItem(popup, SWT.PUSH);
271                         aboutItem.setText(BundleMessage.format("UI_ABOUT"));
272                         aboutItem.addListener(SWT.Selection, new Listener() {
273                             public void handleEvent(Event event) {
274                                 display.asyncExec(
275                                         new Runnable() {
276                                             public void run() {
277                                                 if (aboutFrame == null) {
278                                                     aboutFrame = new AboutFrame();
279                                                 }
280                                                 aboutFrame.update();
281                                                 aboutFrame.setVisible(true);
282                                                 aboutFrame.toFront();
283                                                 aboutFrame.requestFocus();
284                                             }
285                                         });
286                             }
287                         });
288 
289                         trayItem.addListener(SWT.DefaultSelection, new Listener() {
290                             public void handleEvent(Event event) {
291                                 display.asyncExec(
292                                         new Runnable() {
293                                             public void run() {
294                                                 // create frame on first call
295                                                 if (settingsFrame == null) {
296                                                     settingsFrame = new SettingsFrame();
297                                                 }
298                                                 settingsFrame.reload();
299                                                 settingsFrame.setVisible(true);
300                                                 settingsFrame.toFront();
301                                                 settingsFrame.requestFocus();
302                                             }
303                                         });
304                             }
305                         });
306 
307                         // create menu item for the default action
308                         MenuItem defaultItem = new MenuItem(popup, SWT.PUSH);
309                         defaultItem.setText(BundleMessage.format("UI_SETTINGS"));
310                         defaultItem.addListener(SWT.Selection, new Listener() {
311                             public void handleEvent(Event event) {
312                                 display.asyncExec(
313                                         new Runnable() {
314                                             public void run() {
315                                                 // create frame on first call
316                                                 if (settingsFrame == null) {
317                                                     settingsFrame = new SettingsFrame();
318                                                 }
319                                                 settingsFrame.reload();
320                                                 settingsFrame.setVisible(true);
321                                                 settingsFrame.toFront();
322                                                 settingsFrame.requestFocus();
323                                             }
324                                         });
325                             }
326                         });
327 
328                         MenuItem logItem = new MenuItem(popup, SWT.PUSH);
329                         logItem.setText(BundleMessage.format("UI_SHOW_LOGS"));
330                         logItem.addListener(SWT.Selection, new Listener() {
331                             public void handleEvent(Event event) {
332                                 display.asyncExec(
333                                         new Runnable() {
334                                             public void run() {
335 
336                                                 Logger rootLogger = Logger.getRootLogger();
337                                                 LF5Appender lf5Appender = (LF5Appender) rootLogger.getAppender("LF5Appender");
338                                                 if (lf5Appender == null) {
339                                                     logBrokerMonitor = new LogBrokerMonitor(LogLevel.getLog4JLevels()) {
340                                                         @Override
341                                                         protected void closeAfterConfirm() {
342                                                             hide();
343                                                         }
344                                                     };
345                                                     lf5Appender = new LF5Appender(logBrokerMonitor);
346                                                     lf5Appender.setName("LF5Appender");
347                                                     rootLogger.addAppender(lf5Appender);
348                                                 }
349                                                 lf5Appender.getLogBrokerMonitor().show();
350                                             }
351                                         });
352                             }
353                         });
354 
355                         MenuItem exitItem = new MenuItem(popup, SWT.PUSH);
356                         exitItem.setText(BundleMessage.format("UI_EXIT"));
357                         exitItem.addListener(SWT.Selection, new Listener() {
358                             public void handleEvent(Event event) {
359                                 DavGateway.stop();
360                             }
361                         });
362 
363                         // display settings frame on first start
364                         if (Settings.isFirstStart()) {
365                             // create frame on first call
366                             if (settingsFrame == null) {
367                                 settingsFrame = new SettingsFrame();
368                             }
369                             settingsFrame.setVisible(true);
370                             settingsFrame.toFront();
371                             settingsFrame.requestFocus();
372                         }
373 
374                         synchronized (mainThread) {
375                             // ready
376                             isReady = true;
377                             mainThread.notifyAll();
378                         }
379 
380                         while (!shell.isDisposed()) {
381                             if (!display.readAndDispatch()) {
382                                 display.sleep();
383                             }
384                         }
385                     }
386                     // dispose AWT frames
387                     if (settingsFrame != null) {
388                         settingsFrame.dispose();
389                     }
390                     if (aboutFrame != null) {
391                         aboutFrame.dispose();
392                     }
393                     if (logBrokerMonitor != null) {
394                         logBrokerMonitor.dispose();
395                     }
396                 } catch (Exception exc) {
397                     DavGatewayTray.error(exc);
398                 } catch (Error exc) {
399                     error = exc;
400                     throw exc;
401                 }
402                 // make sure we do exit
403                 System.exit(0);
404             }
405         }.start();
406         while (true) {
407             // wait for SWT init
408             try {
409                 synchronized (mainThread) {
410                     if (error != null) {
411                         throw error;
412                     }
413                     if (isReady) {
414                         break;
415                     }
416                     mainThread.wait(1000);
417                 }
418             } catch (InterruptedException e) {
419                 DavGatewayTray.error(new BundleMessage("LOG_ERROR_WAITING_FOR_SWT_INIT"), e);
420             }
421         }
422     }
423 
424     public void dispose() {
425         shell.dispose();
426     }
427 
428 }