1 /* 2 * Copyright (c) 2011, 2016, 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.AWTAccessor; 29 import sun.awt.SunToolkit; 30 31 import javax.swing.*; 32 import java.awt.*; 33 import java.awt.event.*; 34 import java.awt.geom.Point2D; 35 import java.awt.image.BufferedImage; 36 import java.awt.peer.TrayIconPeer; 37 import java.beans.PropertyChangeEvent; 38 import java.beans.PropertyChangeListener; 39 import java.util.concurrent.atomic.AtomicReference; 40 41 import static sun.awt.AWTAccessor.*; 42 43 public class CTrayIcon extends CFRetainedResource implements TrayIconPeer { 44 private TrayIcon target; 45 private PopupMenu popup; 46 private JDialog messageDialog; 47 private DialogEventHandler handler; 48 49 // In order to construct MouseEvent object, we need to specify a 50 // Component target. Because TrayIcon isn't Component's subclass, 51 // we use this dummy frame instead 52 private final Frame dummyFrame; 53 54 // A bitmask that indicates what mouse buttons produce MOUSE_CLICKED events 55 // on MOUSE_RELEASE. Click events are only generated if there were no drag 56 // events between MOUSE_PRESSED and MOUSE_RELEASED for particular button 57 private static int mouseClickButtons = 0; 58 59 CTrayIcon(TrayIcon target) { 60 super(0, true); 61 62 this.messageDialog = null; 63 this.handler = null; 64 this.target = target; 65 this.popup = target.getPopupMenu(); 66 this.dummyFrame = new Frame(); 67 setPtr(createModel()); 68 69 //if no one else is creating the peer. 70 checkAndCreatePopupPeer(); 71 updateImage(); 72 } 73 74 private CPopupMenu checkAndCreatePopupPeer() { 75 CPopupMenu menuPeer = null; 76 if (popup != null) { 77 try { 78 final MenuComponentAccessor acc = getMenuComponentAccessor(); 79 menuPeer = acc.getPeer(popup); 80 if (menuPeer == null) { 81 popup.addNotify(); 82 menuPeer = acc.getPeer(popup); 83 } 112 } 113 } else { 114 return 0L; 115 } 116 } 117 118 // This method is executed on Appkit, so if ptr is not zero means that, 119 // it is still not deallocated(even if we call NSApp postRunnableEvent) 120 // and sent CFRelease to the native queue 121 return checkAndCreatePopupPeer().ptr; 122 } 123 124 /** 125 * We display tray icon message as a small dialog with OK button. 126 * This is lame, but JDK 1.6 does basically the same. There is a new 127 * kind of window in Lion, NSPopover, so perhaps it could be used it 128 * to implement better looking notifications. 129 */ 130 public void displayMessage(final String caption, final String text, 131 final String messageType) { 132 133 if (SwingUtilities.isEventDispatchThread()) { 134 displayMessageOnEDT(caption, text, messageType); 135 } else { 136 try { 137 SwingUtilities.invokeAndWait(new Runnable() { 138 public void run() { 139 displayMessageOnEDT(caption, text, messageType); 140 } 141 }); 142 } catch (Exception e) { 143 throw new AssertionError(e); 144 } 145 } 146 } 147 148 @Override 149 public void dispose() { 150 if (messageDialog != null) { 151 disposeMessageDialog(); 152 } 153 154 dummyFrame.dispose(); 155 156 if (popup != null) { 157 popup.removeNotify(); 158 } 159 160 LWCToolkit.targetDisposedPeer(target, this); 161 target = null; 162 163 super.dispose(); 164 } 165 166 @Override 167 public void setToolTip(String tooltip) { 168 execute(ptr -> nativeSetToolTip(ptr, tooltip)); 169 } 170 171 //adds tooltip to the NSStatusBar's NSButton. 172 private native void nativeSetToolTip(long trayIconModel, String tooltip); 173 259 final String cmd = target.getActionCommand(); 260 final ActionEvent event = new ActionEvent(target, 261 ActionEvent.ACTION_PERFORMED, cmd); 262 postEvent(event); 263 } 264 265 // synthesize CLICKED event 266 if (jeventType == MouseEvent.MOUSE_RELEASED) { 267 if ((mouseClickButtons & eventButtonMask) != 0) { 268 MouseEvent clickEvent = new MouseEvent(dummyFrame, 269 MouseEvent.MOUSE_CLICKED, when, jmodifiers, absX, absY, 270 absX, absY, jclickCount, isPopupTrigger, jbuttonNumber); 271 clickEvent.setSource(target); 272 postEvent(clickEvent); 273 } 274 275 mouseClickButtons &= ~eventButtonMask; 276 } 277 } 278 279 private native Point2D nativeGetIconLocation(long trayIconModel); 280 281 public void displayMessageOnEDT(String caption, String text, 282 String messageType) { 283 if (messageDialog != null) { 284 disposeMessageDialog(); 285 } 286 287 // obtain icon to show along the message 288 Icon icon = getIconForMessageType(messageType); 289 if (icon != null) { 290 icon = new ImageIcon(scaleIcon(icon, 0.75)); 291 } 292 293 // We want the message dialog text area to be about 1/8 of the screen 294 // size. There is nothing special about this value, it's just makes the 295 // message dialog to look nice 296 Dimension screenSize = java.awt.Toolkit.getDefaultToolkit().getScreenSize(); 297 int textWidth = screenSize.width / 8; 298 299 // create dialog to show 300 messageDialog = createMessageDialog(caption, text, textWidth, icon); 301 302 // finally, show the dialog to user 303 showMessageDialog(); 304 } 305 306 /** 307 * Creates dialog window used to display the message 308 */ 309 private JDialog createMessageDialog(String caption, String text, 310 int textWidth, Icon icon) { 311 JDialog dialog; 312 handler = new DialogEventHandler(); 313 314 JTextArea captionArea = null; 315 if (caption != null) { 316 captionArea = createTextArea(caption, textWidth, false, true); 317 } 318 319 JTextArea textArea = null; 320 if (text != null){ 321 textArea = createTextArea(text, textWidth, true, false); 322 } 323 324 Object[] panels = null; 325 if (captionArea != null) { 326 if (textArea != null) { 327 panels = new Object[] {captionArea, new JLabel(), textArea}; 328 } else { 329 panels = new Object[] {captionArea}; 330 } 331 } else { 332 if (textArea != null) { 333 panels = new Object[] {textArea}; 334 } 335 } 336 337 // We want message dialog with small title bar. There is a client 338 // property property that does it, however, it must be set before 339 // dialog's native window is created. This is why we create option 340 // pane and dialog separately 341 final JOptionPane op = new JOptionPane(panels); 342 op.setIcon(icon); 343 op.addPropertyChangeListener(handler); 344 345 // Make Ok button small. Most likely won't work for L&F other then Aqua 346 try { 347 JPanel buttonPanel = (JPanel)op.getComponent(1); 348 JButton ok = (JButton)buttonPanel.getComponent(0); 349 ok.putClientProperty("JComponent.sizeVariant", "small"); 350 } catch (Throwable t) { 351 // do nothing, we tried and failed, no big deal 352 } 353 354 dialog = new JDialog((Dialog) null); 355 JRootPane rp = dialog.getRootPane(); 356 357 // gives us dialog window with small title bar and not zoomable 358 rp.putClientProperty(CPlatformWindow.WINDOW_STYLE, "small"); 359 rp.putClientProperty(CPlatformWindow.WINDOW_ZOOMABLE, "false"); 360 361 dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); 362 dialog.setModal(false); 363 dialog.setModalExclusionType(Dialog.ModalExclusionType.TOOLKIT_EXCLUDE); 364 dialog.setAlwaysOnTop(true); 365 dialog.setAutoRequestFocus(false); 366 dialog.setResizable(false); 367 dialog.setContentPane(op); 368 369 dialog.addWindowListener(handler); 370 371 // suppress security warning for untrusted windows 372 AWTAccessor.getWindowAccessor().setTrayIconWindow(dialog, true); 373 374 dialog.pack(); 375 376 return dialog; 377 } 378 379 private void showMessageDialog() { 380 381 Dimension screenSize = java.awt.Toolkit.getDefaultToolkit().getScreenSize(); 382 AtomicReference<Point2D> ref = new AtomicReference<>(); 383 execute(ptr -> { 384 ref.set(nativeGetIconLocation(ptr)); 385 }); 386 Point2D iconLoc = ref.get(); 387 if (iconLoc == null) { 388 return; 389 } 390 391 int dialogY = (int)iconLoc.getY(); 392 int dialogX = (int)iconLoc.getX(); 393 if (dialogX + messageDialog.getWidth() > screenSize.width) { 394 dialogX = screenSize.width - messageDialog.getWidth(); 395 } 396 397 messageDialog.setLocation(dialogX, dialogY); 398 messageDialog.setVisible(true); 399 } 400 401 private void disposeMessageDialog() { 402 if (SwingUtilities.isEventDispatchThread()) { 403 disposeMessageDialogOnEDT(); 404 } else { 405 try { 406 SwingUtilities.invokeAndWait(new Runnable() { 407 public void run() { 408 disposeMessageDialogOnEDT(); 409 } 410 }); 411 } catch (Exception e) { 412 throw new AssertionError(e); 413 } 414 } 415 } 416 417 private void disposeMessageDialogOnEDT() { 418 if (messageDialog != null) { 419 messageDialog.removeWindowListener(handler); 420 messageDialog.removePropertyChangeListener(handler); 421 messageDialog.dispose(); 422 423 messageDialog = null; 424 handler = null; 425 } 426 } 427 428 /** 429 * Scales an icon using specified scale factor 430 * 431 * @param icon icon to scale 432 * @param scaleFactor scale factor to use 433 * @return scaled icon as BuffedredImage 434 */ 435 private static BufferedImage scaleIcon(Icon icon, double scaleFactor) { 436 if (icon == null) { 437 return null; 438 } 439 440 int w = icon.getIconWidth(); 441 int h = icon.getIconHeight(); 442 443 GraphicsEnvironment ge = 444 GraphicsEnvironment.getLocalGraphicsEnvironment(); 445 GraphicsDevice gd = ge.getDefaultScreenDevice(); 446 GraphicsConfiguration gc = gd.getDefaultConfiguration(); 463 g.drawImage(iconImage, 0, 0, scaledW, scaledH, null); 464 g.dispose(); 465 466 return scaledImage; 467 } 468 469 470 /** 471 * Gets Aqua icon used in message dialog. 472 */ 473 private static Icon getIconForMessageType(String messageType) { 474 if (messageType.equals("ERROR")) { 475 return UIManager.getIcon("OptionPane.errorIcon"); 476 } else if (messageType.equals("WARNING")) { 477 return UIManager.getIcon("OptionPane.warningIcon"); 478 } else { 479 // this is just an application icon 480 return UIManager.getIcon("OptionPane.informationIcon"); 481 } 482 } 483 484 private static JTextArea createTextArea(String text, int width, 485 boolean isSmall, boolean isBold) { 486 JTextArea textArea = new JTextArea(text); 487 488 textArea.setLineWrap(true); 489 textArea.setWrapStyleWord(true); 490 textArea.setEditable(false); 491 textArea.setFocusable(false); 492 textArea.setBorder(null); 493 textArea.setBackground(new JLabel().getBackground()); 494 495 if (isSmall) { 496 textArea.putClientProperty("JComponent.sizeVariant", "small"); 497 } 498 499 if (isBold) { 500 Font font = textArea.getFont(); 501 Font boldFont = new Font(font.getName(), Font.BOLD, font.getSize()); 502 textArea.setFont(boldFont); 503 } 504 505 textArea.setSize(width, 1); 506 507 return textArea; 508 } 509 510 /** 511 * Implements all the Listeners needed by message dialog 512 */ 513 private final class DialogEventHandler extends WindowAdapter 514 implements PropertyChangeListener { 515 516 public void windowClosing(WindowEvent we) { 517 disposeMessageDialog(); 518 } 519 520 public void propertyChange(PropertyChangeEvent e) { 521 if (messageDialog == null) { 522 return; 523 } 524 525 String prop = e.getPropertyName(); 526 Container cp = messageDialog.getContentPane(); 527 528 if (messageDialog.isVisible() && e.getSource() == cp && 529 (prop.equals(JOptionPane.VALUE_PROPERTY))) { 530 disposeMessageDialog(); 531 } 532 } 533 } 534 } 535 | 1 /* 2 * Copyright (c) 2011, 2017, 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 java.awt.AWTEvent; 29 import java.awt.Button; 30 import java.awt.Frame; 31 import java.awt.Graphics2D; 32 import java.awt.GraphicsConfiguration; 33 import java.awt.GraphicsDevice; 34 import java.awt.GraphicsEnvironment; 35 import java.awt.Image; 36 import java.awt.MediaTracker; 37 import java.awt.PopupMenu; 38 import java.awt.RenderingHints; 39 import java.awt.Toolkit; 40 import java.awt.Transparency; 41 import java.awt.TrayIcon; 42 import java.awt.event.ActionEvent; 43 import java.awt.event.MouseEvent; 44 import java.awt.geom.Point2D; 45 import java.awt.image.BufferedImage; 46 import java.awt.peer.TrayIconPeer; 47 48 import javax.swing.Icon; 49 import javax.swing.UIManager; 50 51 import sun.awt.SunToolkit; 52 53 import static sun.awt.AWTAccessor.MenuComponentAccessor; 54 import static sun.awt.AWTAccessor.getMenuComponentAccessor; 55 56 public class CTrayIcon extends CFRetainedResource implements TrayIconPeer { 57 private TrayIcon target; 58 private PopupMenu popup; 59 60 // In order to construct MouseEvent object, we need to specify a 61 // Component target. Because TrayIcon isn't Component's subclass, 62 // we use this dummy frame instead 63 private final Frame dummyFrame; 64 65 // A bitmask that indicates what mouse buttons produce MOUSE_CLICKED events 66 // on MOUSE_RELEASE. Click events are only generated if there were no drag 67 // events between MOUSE_PRESSED and MOUSE_RELEASED for particular button 68 private static int mouseClickButtons = 0; 69 70 CTrayIcon(TrayIcon target) { 71 super(0, true); 72 73 this.target = target; 74 this.popup = target.getPopupMenu(); 75 this.dummyFrame = new Frame(); 76 setPtr(createModel()); 77 78 //if no one else is creating the peer. 79 checkAndCreatePopupPeer(); 80 updateImage(); 81 } 82 83 private CPopupMenu checkAndCreatePopupPeer() { 84 CPopupMenu menuPeer = null; 85 if (popup != null) { 86 try { 87 final MenuComponentAccessor acc = getMenuComponentAccessor(); 88 menuPeer = acc.getPeer(popup); 89 if (menuPeer == null) { 90 popup.addNotify(); 91 menuPeer = acc.getPeer(popup); 92 } 121 } 122 } else { 123 return 0L; 124 } 125 } 126 127 // This method is executed on Appkit, so if ptr is not zero means that, 128 // it is still not deallocated(even if we call NSApp postRunnableEvent) 129 // and sent CFRelease to the native queue 130 return checkAndCreatePopupPeer().ptr; 131 } 132 133 /** 134 * We display tray icon message as a small dialog with OK button. 135 * This is lame, but JDK 1.6 does basically the same. There is a new 136 * kind of window in Lion, NSPopover, so perhaps it could be used it 137 * to implement better looking notifications. 138 */ 139 public void displayMessage(final String caption, final String text, 140 final String messageType) { 141 // obtain icon to show along the message 142 Icon icon = getIconForMessageType(messageType); 143 CImage cimage = null; 144 if (icon != null) { 145 BufferedImage image = scaleIcon(icon, 0.75); 146 cimage = CImage.getCreator().createFromImage(image); 147 } 148 if (cimage != null) { 149 cimage.execute(imagePtr -> { 150 execute(ptr -> nativeShowNotification(ptr, caption, text, 151 imagePtr)); 152 }); 153 } else { 154 execute(ptr -> nativeShowNotification(ptr, caption, text, 0)); 155 } 156 } 157 158 @Override 159 public void dispose() { 160 dummyFrame.dispose(); 161 162 if (popup != null) { 163 popup.removeNotify(); 164 } 165 166 LWCToolkit.targetDisposedPeer(target, this); 167 target = null; 168 169 super.dispose(); 170 } 171 172 @Override 173 public void setToolTip(String tooltip) { 174 execute(ptr -> nativeSetToolTip(ptr, tooltip)); 175 } 176 177 //adds tooltip to the NSStatusBar's NSButton. 178 private native void nativeSetToolTip(long trayIconModel, String tooltip); 179 265 final String cmd = target.getActionCommand(); 266 final ActionEvent event = new ActionEvent(target, 267 ActionEvent.ACTION_PERFORMED, cmd); 268 postEvent(event); 269 } 270 271 // synthesize CLICKED event 272 if (jeventType == MouseEvent.MOUSE_RELEASED) { 273 if ((mouseClickButtons & eventButtonMask) != 0) { 274 MouseEvent clickEvent = new MouseEvent(dummyFrame, 275 MouseEvent.MOUSE_CLICKED, when, jmodifiers, absX, absY, 276 absX, absY, jclickCount, isPopupTrigger, jbuttonNumber); 277 clickEvent.setSource(target); 278 postEvent(clickEvent); 279 } 280 281 mouseClickButtons &= ~eventButtonMask; 282 } 283 } 284 285 private native void nativeShowNotification(long trayIconModel, 286 String caption, String text, 287 long nsimage); 288 289 /** 290 * Used by the automated tests. 291 */ 292 private native Point2D nativeGetIconLocation(long trayIconModel); 293 294 /** 295 * Scales an icon using specified scale factor 296 * 297 * @param icon icon to scale 298 * @param scaleFactor scale factor to use 299 * @return scaled icon as BuffedredImage 300 */ 301 private static BufferedImage scaleIcon(Icon icon, double scaleFactor) { 302 if (icon == null) { 303 return null; 304 } 305 306 int w = icon.getIconWidth(); 307 int h = icon.getIconHeight(); 308 309 GraphicsEnvironment ge = 310 GraphicsEnvironment.getLocalGraphicsEnvironment(); 311 GraphicsDevice gd = ge.getDefaultScreenDevice(); 312 GraphicsConfiguration gc = gd.getDefaultConfiguration(); 329 g.drawImage(iconImage, 0, 0, scaledW, scaledH, null); 330 g.dispose(); 331 332 return scaledImage; 333 } 334 335 336 /** 337 * Gets Aqua icon used in message dialog. 338 */ 339 private static Icon getIconForMessageType(String messageType) { 340 if (messageType.equals("ERROR")) { 341 return UIManager.getIcon("OptionPane.errorIcon"); 342 } else if (messageType.equals("WARNING")) { 343 return UIManager.getIcon("OptionPane.warningIcon"); 344 } else { 345 // this is just an application icon 346 return UIManager.getIcon("OptionPane.informationIcon"); 347 } 348 } 349 } 350 |