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 org.apache.log4j.Level;
27  import org.apache.log4j.Logger;
28  import org.eclipse.swt.SWT;
29  import org.eclipse.swt.graphics.GC;
30  import org.eclipse.swt.graphics.Image;
31  import org.eclipse.swt.graphics.ImageData;
32  import org.eclipse.swt.graphics.PaletteData;
33  import org.eclipse.swt.widgets.Display;
34  import org.eclipse.swt.widgets.Menu;
35  import org.eclipse.swt.widgets.MenuItem;
36  import org.eclipse.swt.widgets.Shell;
37  import org.eclipse.swt.widgets.ToolTip;
38  import org.eclipse.swt.widgets.Tray;
39  import org.eclipse.swt.widgets.TrayItem;
40  
41  import javax.imageio.ImageIO;
42  import javax.swing.*;
43  import java.awt.image.BufferedImage;
44  import java.io.ByteArrayInputStream;
45  import java.io.ByteArrayOutputStream;
46  import java.io.IOException;
47  import java.io.InputStream;
48  import java.net.URL;
49  import java.util.ArrayList;
50  
51  /**
52   * Tray icon handler based on SWT
53   */
54  public class SwtGatewayTray implements DavGatewayTrayInterface {
55      private static final Logger LOGGER = Logger.getLogger(SwtGatewayTray.class);
56  
57      private static final Object LOCK = new Object();
58  
59      protected SwtGatewayTray() {
60      }
61  
62      static SettingsFrame settingsFrame;
63      static AboutFrame aboutFrame;
64  
65      private static TrayItem trayItem;
66      private static ArrayList<java.awt.Image> frameIcons;
67  
68      private static Image image;
69      private static Image image2;
70      private static Image inactiveImage;
71      private static Display display;
72      private static Shell shell;
73      private boolean isActive = true;
74      private static boolean isReady = false;
75      private static Error error;
76      private boolean firstMessage = true;
77  
78      public static void initDisplay() {
79          if (!isReady) {
80              // ready
81              // start main loop, shell can be null before init
82              // dispose AWT frames
83              Thread swtThread = new Thread("SWT") {
84                  @Override
85                  public void run() {
86                      try {
87                          display = Display.getDefault();
88                          shell = new Shell(display);
89                          synchronized (LOCK) {
90                              // ready
91                              isReady = true;
92                              LOCK.notifyAll();
93                          }
94  
95                          // start main loop, shell can be null before init
96                          while (!shell.isDisposed()) {
97                              if (!display.readAndDispatch()) {
98                                  display.sleep();
99                              }
100                         }
101                         // dispose AWT frames
102                         if (settingsFrame != null) {
103                             settingsFrame.dispose();
104                         }
105                         if (aboutFrame != null) {
106                             aboutFrame.dispose();
107                         }
108                         System.exit(0);
109                     } catch (Throwable e) {
110                         LOGGER.error("Error in SWT thread", e);
111                         error = new Error(e);
112                     }
113                 }
114             };
115             swtThread.start();
116             while (true) {
117                 // wait for SWT init
118                 try {
119                     synchronized (LOCK) {
120                         if (error != null) {
121                             throw error;
122                         }
123                         if (isReady) {
124                             break;
125                         }
126                         LOCK.wait(1000);
127                     }
128                 } catch (InterruptedException e) {
129                     DavGatewayTray.error(new BundleMessage("LOG_ERROR_WAITING_FOR_SWT_INIT"), e);
130                     Thread.currentThread().interrupt();
131                 }
132             }
133         }
134     }
135 
136     /**
137      * Return AWT Image icon for frame title.
138      *
139      * @return frame icon
140      */
141     @Override
142     public java.util.List<java.awt.Image> getFrameIcons() {
143         return frameIcons;
144     }
145 
146     /**
147      * Switch tray icon between active and standby icon.
148      */
149     public void switchIcon() {
150         isActive = true;
151         display.syncExec(() -> {
152             Image currentImage = trayItem.getImage();
153             if (currentImage != null && currentImage.equals(image)) {
154                 trayItem.setImage(image2);
155             } else {
156                 trayItem.setImage(image);
157             }
158         });
159 
160     }
161 
162     /**
163      * Set tray icon to inactive (network down)
164      */
165     public void resetIcon() {
166         display.syncExec(() -> trayItem.setImage(image));
167     }
168 
169     /**
170      * Set tray icon to inactive (network down)
171      */
172     public void inactiveIcon() {
173         isActive = false;
174         display.syncExec(() -> trayItem.setImage(inactiveImage));
175     }
176 
177     /**
178      * Check if current tray status is inactive (network down).
179      *
180      * @return true if inactive
181      */
182     public boolean isActive() {
183         return isActive;
184     }
185 
186     /**
187      * Log and display balloon message according to log level.
188      *
189      * @param message text message
190      * @param level   log level
191      */
192     public void displayMessage(final String message, final Level level) {
193         if (trayItem != null) {
194             display.asyncExec(() -> {
195                 int messageType = 0;
196                 if (level.equals(Level.INFO)) {
197                     messageType = SWT.ICON_INFORMATION;
198                 } else if (level.equals(Level.WARN)) {
199                     messageType = SWT.ICON_WARNING;
200                 } else if (level.equals(Level.ERROR)) {
201                     messageType = SWT.ICON_ERROR;
202                 }
203                 if (messageType != 0) {
204                     final ToolTip toolTip = new ToolTip(shell, SWT.BALLOON | messageType);
205                     toolTip.setText(BundleMessage.format("UI_DAVMAIL_GATEWAY"));
206                     toolTip.setMessage(message);
207                     trayItem.setToolTip(toolTip);
208                     // Wait for tray init 1 second on first message
209                     if (firstMessage) {
210                         firstMessage = false;
211                         try {
212                             Thread.sleep(1000);
213                         } catch (InterruptedException e) {
214                             Thread.currentThread().interrupt();
215                         }
216                     }
217                     toolTip.setVisible(true);
218                 }
219                 trayItem.setToolTipText(BundleMessage.format("UI_DAVMAIL_GATEWAY") + '\n' + message);
220             });
221         }
222     }
223 
224     /**
225      * Load image with current class loader.
226      * No scaling
227      *
228      * @param fileName image resource file name
229      * @return image
230      */
231     public static Image loadSwtImage(String fileName) {
232         Image result = null;
233         try {
234             ClassLoader classloader = DavGatewayTray.class.getClassLoader();
235             URL imageUrl = classloader.getResource(fileName);
236             if (imageUrl == null) {
237                 throw new IOException("fileName");
238             }
239             try (InputStream inputStream = imageUrl.openStream()) {
240                 result = new Image(display, inputStream);
241 
242             }
243 
244         } catch (IOException e) {
245             DavGatewayTray.warn(new BundleMessage("LOG_UNABLE_TO_LOAD_IMAGE"), e);
246         }
247         return result;
248     }
249 
250     /**
251      * Load image with current class loader.
252      * Scale to size
253      *
254      * @param fileName image resource file name
255      * @param targetSize     target image size
256      * @return image
257      */
258     public static Image loadSwtImage(String fileName, int targetSize) {
259         int padding = 0;
260         Image result = null;
261         try {
262             ClassLoader classloader = DavGatewayTray.class.getClassLoader();
263             URL imageUrl = classloader.getResource(fileName);
264             if (imageUrl == null) {
265                 throw new IOException(fileName);
266             }
267             BufferedImage bufferedImage;
268             if (Settings.getBooleanProperty("davmail.trayGrayscale", false)) {
269                 bufferedImage = DavGatewayTray.convertGrayscale(ImageIO.read(imageUrl));
270             } else {
271                 bufferedImage = ImageIO.read(imageUrl);
272             }
273             byte[] imageBytes;
274             try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
275                 ImageIO.write(bufferedImage, "png", os);
276                 imageBytes = os.toByteArray();
277             }
278 
279             try (InputStream inputStream = new ByteArrayInputStream(imageBytes)) {
280                 Image loadedImage = new Image(display, inputStream);
281 
282                 ImageData resultImageData = new ImageData(targetSize, targetSize, 24, new PaletteData(0xff0000, 0x00ff00, 0x0000ff));
283                 // force init alpha channel
284                 resultImageData.setAlpha(0, 0, 0);
285 
286                 result = new Image(display, resultImageData);
287 
288                 // drow loaded image over transparent image
289                 GC gc = new GC(result);
290                 gc.setAntialias(SWT.ON);
291                 gc.setInterpolation(SWT.HIGH);
292                 gc.drawImage(loadedImage, 0, 0, loadedImage.getBounds().width, loadedImage.getBounds().height,
293                         padding, padding, result.getBounds().width - padding, result.getBounds().height - padding);
294                 gc.dispose();
295                 loadedImage.dispose();
296             }
297 
298         } catch (IOException e) {
299             DavGatewayTray.warn(new BundleMessage("LOG_UNABLE_TO_LOAD_IMAGE"), e);
300         }
301         return result;
302     }
303 
304     /**
305      * Create tray icon and register frame listeners.
306      */
307     public void init() {
308         try {
309             // workaround for bug when SWT and AWT both try to access Gtk
310             UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
311         } catch (Exception e) {
312             DavGatewayTray.warn(new BundleMessage("LOG_UNABLE_TO_SET_LOOK_AND_FEEL"));
313         }
314 
315         initDisplay();
316 
317         display.asyncExec(() -> {
318             try {
319                 final Tray tray = display.getSystemTray();
320                 if (tray != null) {
321 
322                     trayItem = new TrayItem(tray, SWT.NONE);
323                     trayItem.setToolTipText(BundleMessage.format("UI_DAVMAIL_GATEWAY"));
324 
325                     frameIcons = new ArrayList<>();
326                     frameIcons.add(DavGatewayTray.adjustTrayIcon(DavGatewayTray.loadImage(AwtGatewayTray.TRAY128_PNG)));
327                     frameIcons.add(DavGatewayTray.adjustTrayIcon(DavGatewayTray.loadImage(AwtGatewayTray.TRAY_PNG)));
328 
329                     image = loadSwtImage("tray128.png", 128);
330                     image2 = loadSwtImage("tray128active.png", 128);
331                     inactiveImage = loadSwtImage("tray128inactive.png", 128);
332 
333                     trayItem.setImage(image);
334                     trayItem.addDisposeListener(e -> {
335                         if (image != null && !image.isDisposed()) {
336                             image.dispose();
337                         }
338                         if (image2 != null && !image2.isDisposed()) {
339                             image2.dispose();
340                         }
341                         if (inactiveImage != null && !inactiveImage.isDisposed()) {
342                             inactiveImage.dispose();
343                         }
344                     });
345 
346                     // create a popup menu
347                     final Menu popup = new Menu(shell, SWT.POP_UP);
348                     trayItem.addListener(SWT.MenuDetect, event -> display.asyncExec(
349                             () -> popup.setVisible(true)));
350 
351                     MenuItem aboutItem = new MenuItem(popup, SWT.PUSH);
352                     aboutItem.setText(BundleMessage.format("UI_ABOUT"));
353                     aboutItem.addListener(SWT.Selection, event -> SwingUtilities.invokeLater(
354                             () -> {
355                                 if (aboutFrame == null) {
356                                     aboutFrame = new AboutFrame();
357                                 }
358                                 aboutFrame.update();
359                                 aboutFrame.setVisible(true);
360                                 aboutFrame.toFront();
361                                 aboutFrame.requestFocus();
362                             }));
363 
364                     // create menu item for the default action
365                     trayItem.addListener(SWT.DefaultSelection, event -> SwingUtilities.invokeLater(
366                             () -> openSettingsFrame()));
367 
368                     MenuItem defaultItem = new MenuItem(popup, SWT.PUSH);
369                     defaultItem.setText(BundleMessage.format("UI_SETTINGS"));
370                     defaultItem.addListener(SWT.Selection, event -> SwingUtilities.invokeLater(
371                             () -> openSettingsFrame()));
372 
373                     MenuItem exitItem = new MenuItem(popup, SWT.PUSH);
374                     exitItem.setText(BundleMessage.format("UI_EXIT"));
375                     exitItem.addListener(SWT.Selection, event -> DavGateway.stop());
376 
377                     // display settings frame on first start
378                     if (Settings.isFirstStart()) {
379                         SwingUtilities.invokeLater(() -> {
380                             // create frame on first call
381                             if (settingsFrame == null) {
382                                 settingsFrame = new SettingsFrame();
383                             }
384                             settingsFrame.setVisible(true);
385                             settingsFrame.toFront();
386                             settingsFrame.requestFocus();
387                         });
388 
389                     }
390 
391                 }
392             } catch (Exception exc) {
393                 DavGatewayTray.error(exc);
394             } catch (Error exc) {
395                 error = exc;
396                 throw exc;
397             }
398         });
399     }
400 
401     private void openSettingsFrame() {
402         // create frame on first call
403         if (settingsFrame == null) {
404             settingsFrame = new SettingsFrame();
405         }
406         settingsFrame.reload();
407         settingsFrame.setVisible(true);
408         settingsFrame.toFront();
409         settingsFrame.requestFocus();
410     }
411 
412     public void dispose() {
413         shell.dispose();
414     }
415 
416 }