1 /* 2 * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.lwawt.macosx; 27 28 import sun.awt.SunToolkit; 29 30 import javax.swing.*; 31 import java.awt.*; 32 import java.awt.event.*; 33 import java.awt.geom.Point2D; 34 import java.awt.image.BufferedImage; 35 import java.awt.peer.TrayIconPeer; 36 import java.beans.PropertyChangeEvent; 37 import java.beans.PropertyChangeListener; 38 39 public class CTrayIcon extends CFRetainedResource implements TrayIconPeer { 40 private TrayIcon target; 41 private PopupMenu popup; 42 private JDialog messageDialog; 43 private DialogEventHandler handler; 44 45 CTrayIcon(TrayIcon target) { 46 super(0, true); 47 48 this.messageDialog = null; 49 this.handler = null; 50 this.target = target; 51 this.popup = target.getPopupMenu(); 52 setPtr(createModel()); 53 54 //if no one else is creating the peer. 55 checkAndCreatePopupPeer(); 56 updateImage(); 57 } 58 59 private CPopupMenu checkAndCreatePopupPeer() { 60 CPopupMenu menuPeer = null; 61 if (popup != null) { 62 try { 63 menuPeer = (CPopupMenu)popup.getPeer(); 64 if (menuPeer == null) { 65 popup.addNotify(); 66 } 67 }catch (Exception e){ 68 e.printStackTrace(); 69 } 70 } 71 return menuPeer; 72 } 73 74 private long createModel() { 75 return nativeCreate(); 76 } 77 78 private long getModel() { 79 return ptr; 80 } 81 82 private native long nativeCreate(); 83 84 //invocation from the AWTTrayIcon.m 85 public long getPopupMenuModel(){ 86 if(popup == null) { 87 return 0L; 88 } 89 return checkAndCreatePopupPeer().getModel(); 90 } 91 92 /** 93 * We display tray icon message as a small dialog with OK button. 94 * This is lame, but JDK 1.6 does basically the same. There is a new 95 * kind of window in Lion, NSPopover, so perhaps it could be used it 96 * to implement better looking notifications. 97 */ 98 public void displayMessage(final String caption, final String text, 99 final String messageType) { 100 101 if (SwingUtilities.isEventDispatchThread()) { 102 displayMessageOnEDT(caption, text, messageType); 103 } else { 104 try { 105 SwingUtilities.invokeAndWait(new Runnable() { 106 public void run() { 107 displayMessageOnEDT(caption, text, messageType); 108 } 109 }); 110 } catch (Exception e) { 111 throw new AssertionError(e); 112 } 113 } 114 } 115 116 @Override 117 public void dispose() { 118 if (messageDialog != null) { 119 disposeMessageDialog(); 120 } 121 122 LWCToolkit.targetDisposedPeer(target, this); 123 target = null; 124 125 super.dispose(); 126 } 127 128 @Override 129 public void setToolTip(String tooltip) { 130 nativeSetToolTip(getModel(), tooltip); 131 } 132 133 //adds tooltip to the NSStatusBar's NSButton. 134 private native void nativeSetToolTip(long trayIconModel, String tooltip); 135 136 @Override 137 public void showPopupMenu(int x, int y) { 138 //Not used. The popupmenu is shown from the native code. 139 } 140 141 @Override 142 public void updateImage() { 143 Image image = target.getImage(); 144 if (image == null) return; 145 146 MediaTracker tracker = new MediaTracker(new Button("")); 147 tracker.addImage(image, 0); 148 try { 149 tracker.waitForAll(); 150 } catch (InterruptedException ignore) { } 151 152 if (image.getWidth(null) <= 0 || 153 image.getHeight(null) <= 0) 154 { 155 return; 156 } 157 158 CImage cimage = CImage.getCreator().createFromImage(image); 159 setNativeImage(getModel(), cimage.ptr, target.isImageAutoSize()); 160 } 161 162 private native void setNativeImage(final long model, final long nsimage, final boolean autosize); 163 164 //invocation from the AWTTrayIcon.m 165 public void performAction() { 166 SunToolkit.executeOnEventHandlerThread(target, new Runnable() { 167 public void run() { 168 final String cmd = target.getActionCommand(); 169 final ActionEvent event = new ActionEvent(target, ActionEvent.ACTION_PERFORMED, cmd); 170 SunToolkit.postEvent(SunToolkit.targetToAppContext(target), event); 171 } 172 }); 173 } 174 175 private native Point2D nativeGetIconLocation(long trayIconModel); 176 177 public void displayMessageOnEDT(String caption, String text, 178 String messageType) { 179 if (messageDialog != null) { 180 disposeMessageDialog(); 181 } 182 183 // obtain icon to show along the message 184 Icon icon = getIconForMessageType(messageType); 185 if (icon != null) { 186 icon = new ImageIcon(scaleIcon(icon, 0.75)); 187 } 188 189 // We want the message dialog text area to be about 1/8 of the screen 190 // size. There is nothing special about this value, it's just makes the 191 // message dialog to look nice 192 Dimension screenSize = java.awt.Toolkit.getDefaultToolkit().getScreenSize(); 193 int textWidth = screenSize.width / 8; 194 195 // create dialog to show 196 messageDialog = createMessageDialog(caption, text, textWidth, icon); 197 198 // finally, show the dialog to user 199 showMessageDialog(); 200 } 201 202 /** 203 * Creates dialog window used to display the message 204 */ 205 private JDialog createMessageDialog(String caption, String text, 206 int textWidth, Icon icon) { 207 JDialog dialog; 208 handler = new DialogEventHandler(); 209 210 JTextArea captionArea = null; 211 if (caption != null) { 212 captionArea = createTextArea(caption, textWidth, false, true); 213 } 214 215 JTextArea textArea = null; 216 if (text != null){ 217 textArea = createTextArea(text, textWidth, true, false); 218 } 219 220 Object[] panels = null; 221 if (captionArea != null) { 222 if (textArea != null) { 223 panels = new Object[] {captionArea, new JLabel(), textArea}; 224 } else { 225 panels = new Object[] {captionArea}; 226 } 227 } else { 228 if (textArea != null) { 229 panels = new Object[] {textArea}; 230 } 231 } 232 233 // We want message dialog with small title bar. There is a client 234 // property property that does it, however, it must be set before 235 // dialog's native window is created. This is why we create option 236 // pane and dialog separately 237 final JOptionPane op = new JOptionPane(panels); 238 op.setIcon(icon); 239 op.addPropertyChangeListener(handler); 240 241 // Make Ok button small. Most likely won't work for L&F other then Aqua 242 try { 243 JPanel buttonPanel = (JPanel)op.getComponent(1); 244 JButton ok = (JButton)buttonPanel.getComponent(0); 245 ok.putClientProperty("JComponent.sizeVariant", "small"); 246 } catch (Throwable t) { 247 // do nothing, we tried and failed, no big deal 248 } 249 250 dialog = new JDialog((Dialog) null); 251 JRootPane rp = dialog.getRootPane(); 252 253 // gives us dialog window with small title bar and not zoomable 254 rp.putClientProperty(CPlatformWindow.WINDOW_STYLE, "small"); 255 rp.putClientProperty(CPlatformWindow.WINDOW_ZOOMABLE, "false"); 256 257 dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); 258 dialog.setModal(false); 259 dialog.setResizable(false); 260 dialog.setContentPane(op); 261 262 dialog.addWindowListener(handler); 263 264 dialog.pack(); 265 266 return dialog; 267 } 268 269 private void showMessageDialog() { 270 271 Dimension screenSize = java.awt.Toolkit.getDefaultToolkit().getScreenSize(); 272 Point2D iconLoc = nativeGetIconLocation(getModel()); 273 274 int dialogY = (int)iconLoc.getY(); 275 int dialogX = (int)iconLoc.getX(); 276 if (dialogX + messageDialog.getWidth() > screenSize.width) { 277 dialogX = screenSize.width - messageDialog.getWidth(); 278 } 279 280 messageDialog.setLocation(dialogX, dialogY); 281 messageDialog.setVisible(true); 282 } 283 284 private void disposeMessageDialog() { 285 if (SwingUtilities.isEventDispatchThread()) { 286 disposeMessageDialogOnEDT(); 287 } else { 288 try { 289 SwingUtilities.invokeAndWait(new Runnable() { 290 public void run() { 291 disposeMessageDialogOnEDT(); 292 } 293 }); 294 } catch (Exception e) { 295 throw new AssertionError(e); 296 } 297 } 298 } 299 300 private void disposeMessageDialogOnEDT() { 301 if (messageDialog != null) { 302 messageDialog.removeWindowListener(handler); 303 messageDialog.removePropertyChangeListener(handler); 304 messageDialog.dispose(); 305 306 messageDialog = null; 307 handler = null; 308 } 309 } 310 311 /** 312 * Scales an icon using specified scale factor 313 * 314 * @param icon icon to scale 315 * @param scaleFactor scale factor to use 316 * @return scaled icon as BuffedredImage 317 */ 318 private static BufferedImage scaleIcon(Icon icon, double scaleFactor) { 319 if (icon == null) { 320 return null; 321 } 322 323 int w = icon.getIconWidth(); 324 int h = icon.getIconHeight(); 325 326 GraphicsEnvironment ge = 327 GraphicsEnvironment.getLocalGraphicsEnvironment(); 328 GraphicsDevice gd = ge.getDefaultScreenDevice(); 329 GraphicsConfiguration gc = gd.getDefaultConfiguration(); 330 331 // convert icon into image 332 BufferedImage iconImage = gc.createCompatibleImage(w, h, 333 Transparency.TRANSLUCENT); 334 Graphics2D g = iconImage.createGraphics(); 335 icon.paintIcon(null, g, 0, 0); 336 g.dispose(); 337 338 // and scale it nicely 339 int scaledW = (int) (w * scaleFactor); 340 int scaledH = (int) (h * scaleFactor); 341 BufferedImage scaledImage = gc.createCompatibleImage(scaledW, scaledH, 342 Transparency.TRANSLUCENT); 343 g = scaledImage.createGraphics(); 344 g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, 345 RenderingHints.VALUE_INTERPOLATION_BILINEAR); 346 g.drawImage(iconImage, 0, 0, scaledW, scaledH, null); 347 g.dispose(); 348 349 return scaledImage; 350 } 351 352 353 /** 354 * Gets Aqua icon used in message dialog. 355 */ 356 private static Icon getIconForMessageType(String messageType) { 357 if (messageType.equals("ERROR")) { 358 return UIManager.getIcon("OptionPane.errorIcon"); 359 } else if (messageType.equals("WARNING")) { 360 return UIManager.getIcon("OptionPane.warningIcon"); 361 } else { 362 // this is just an application icon 363 return UIManager.getIcon("OptionPane.informationIcon"); 364 } 365 } 366 367 private static JTextArea createTextArea(String text, int width, 368 boolean isSmall, boolean isBold) { 369 JTextArea textArea = new JTextArea(text); 370 371 textArea.setLineWrap(true); 372 textArea.setWrapStyleWord(true); 373 textArea.setEditable(false); 374 textArea.setFocusable(false); 375 textArea.setBorder(null); 376 textArea.setBackground(new JLabel().getBackground()); 377 378 if (isSmall) { 379 textArea.putClientProperty("JComponent.sizeVariant", "small"); 380 } 381 382 if (isBold) { 383 Font font = textArea.getFont(); 384 Font boldFont = new Font(font.getName(), Font.BOLD, font.getSize()); 385 textArea.setFont(boldFont); 386 } 387 388 textArea.setSize(width, 1); 389 390 return textArea; 391 } 392 393 /** 394 * Implements all the Listeners needed by message dialog 395 */ 396 private final class DialogEventHandler extends WindowAdapter 397 implements PropertyChangeListener { 398 399 public void windowClosing(WindowEvent we) { 400 disposeMessageDialog(); 401 } 402 403 public void propertyChange(PropertyChangeEvent e) { 404 if (messageDialog == null) { 405 return; 406 } 407 408 String prop = e.getPropertyName(); 409 Container cp = messageDialog.getContentPane(); 410 411 if (messageDialog.isVisible() && e.getSource() == cp && 412 (prop.equals(JOptionPane.VALUE_PROPERTY))) { 413 disposeMessageDialog(); 414 } 415 } 416 } 417 } 418