1 /* 2 * Copyright (c) 2009, 2015, 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.awt.X11; 27 28 import java.awt.*; 29 import java.awt.event.*; 30 import java.awt.peer.TrayIconPeer; 31 import sun.awt.*; 32 33 import java.awt.image.*; 34 import java.text.BreakIterator; 35 import java.util.concurrent.ArrayBlockingQueue; 36 import java.security.AccessController; 37 import java.security.PrivilegedAction; 38 import java.lang.reflect.InvocationTargetException; 39 40 /** 41 * An utility window class. This is a base class for Tooltip and Balloon. 42 */ 43 @SuppressWarnings("serial") // JDK-implementation class 44 public abstract class InfoWindow extends Window { 45 private Container container; 46 private Closer closer; 47 48 protected InfoWindow(Frame parent, Color borderColor) { 49 super(parent); 50 setType(Window.Type.POPUP); 51 container = new Container() { 52 @Override 53 public Insets getInsets() { 54 return new Insets(1, 1, 1, 1); 55 } 56 }; 57 setLayout(new BorderLayout()); 58 setBackground(borderColor); 59 add(container, BorderLayout.CENTER); 60 container.setLayout(new BorderLayout()); 61 62 closer = new Closer(); 63 } 64 65 public Component add(Component c) { 66 container.add(c, BorderLayout.CENTER); 67 return c; 68 } 69 70 protected void setCloser(Runnable action, int time) { 71 closer.set(action, time); 72 } 73 74 // Must be executed on EDT. 75 @SuppressWarnings("deprecation") 76 protected void show(Point corner, int indent) { 77 assert SunToolkit.isDispatchThreadForAppContext(this); 78 79 pack(); 80 81 Dimension size = getSize(); 82 Rectangle scrSize = getGraphicsConfiguration().getBounds(); 83 84 if (corner.x < scrSize.width/2 && corner.y < scrSize.height/2) { // 1st square 85 setLocation(corner.x + indent, corner.y + indent); 86 87 } else if (corner.x >= scrSize.width/2 && corner.y < scrSize.height/2) { // 2nd square 88 setLocation(corner.x - indent - size.width, corner.y + indent); 89 90 } else if (corner.x < scrSize.width/2 && corner.y >= scrSize.height/2) { // 3rd square 91 setLocation(corner.x + indent, corner.y - indent - size.height); 92 93 } else if (corner.x >= scrSize.width/2 && corner.y >= scrSize.height/2) { // 4th square 94 setLocation(corner.x - indent - size.width, corner.y - indent - size.height); 95 } 96 97 super.show(); 98 closer.schedule(); 99 } 100 101 @SuppressWarnings("deprecation") 102 public void hide() { 103 closer.close(); 104 } 105 106 private class Closer implements Runnable { 107 Runnable action; 108 int time; 109 110 public void run() { 111 doClose(); 112 } 113 114 void set(Runnable action, int time) { 115 this.action = action; 116 this.time = time; 117 } 118 119 void schedule() { 120 XToolkit.schedule(this, time); 121 } 122 123 void close() { 124 XToolkit.remove(this); 125 doClose(); 126 } 127 128 // WARNING: this method may be executed on Toolkit thread. 129 @SuppressWarnings("deprecation") 130 private void doClose() { 131 SunToolkit.executeOnEventHandlerThread(InfoWindow.this, new Runnable() { 132 public void run() { 133 InfoWindow.super.hide(); 134 invalidate(); 135 if (action != null) { 136 action.run(); 137 } 138 } 139 }); 140 } 141 } 142 143 144 private interface LiveArguments { 145 /** Whether the target of the InfoWindow is disposed. */ 146 boolean isDisposed(); 147 148 /** The bounds of the target of the InfoWindow. */ 149 Rectangle getBounds(); 150 } 151 152 @SuppressWarnings("serial") // JDK-implementation class 153 public static class Tooltip extends InfoWindow { 154 155 public interface LiveArguments extends InfoWindow.LiveArguments { 156 /** The tooltip to be displayed. */ 157 String getTooltipString(); 158 } 159 160 private final Object target; 161 private final LiveArguments liveArguments; 162 163 private final Label textLabel = new Label(""); 164 private final Runnable starter = new Runnable() { 165 public void run() { 166 display(); 167 }}; 168 169 private static final int TOOLTIP_SHOW_TIME = 10000; 170 private static final int TOOLTIP_START_DELAY_TIME = 1000; 171 private static final int TOOLTIP_MAX_LENGTH = 64; 172 private static final int TOOLTIP_MOUSE_CURSOR_INDENT = 5; 173 private static final Color TOOLTIP_BACKGROUND_COLOR = new Color(255, 255, 220); 174 private static final Font TOOLTIP_TEXT_FONT = XWindow.getDefaultFont(); 175 176 public Tooltip(Frame parent, Object target, 177 LiveArguments liveArguments) 178 { 179 super(parent, Color.black); 180 181 this.target = target; 182 this.liveArguments = liveArguments; 183 184 XTrayIconPeer.suppressWarningString(this); 185 186 setCloser(null, TOOLTIP_SHOW_TIME); 187 textLabel.setBackground(TOOLTIP_BACKGROUND_COLOR); 188 textLabel.setFont(TOOLTIP_TEXT_FONT); 189 add(textLabel); 190 } 191 192 /* 193 * WARNING: this method is executed on Toolkit thread! 194 */ 195 private void display() { 196 // Execute on EDT to avoid deadlock (see 6280857). 197 SunToolkit.executeOnEventHandlerThread(target, new Runnable() { 198 public void run() { 199 if (liveArguments.isDisposed()) { 200 return; 201 } 202 203 String tooltipString = liveArguments.getTooltipString(); 204 if (tooltipString == null) { 205 return; 206 } else if (tooltipString.length() > TOOLTIP_MAX_LENGTH) { 207 textLabel.setText(tooltipString.substring(0, TOOLTIP_MAX_LENGTH)); 208 } else { 209 textLabel.setText(tooltipString); 210 } 211 212 Point pointer = AccessController.doPrivileged( 213 new PrivilegedAction<Point>() { 214 public Point run() { 215 if (!isPointerOverTrayIcon(liveArguments.getBounds())) { 216 return null; 217 } 218 return MouseInfo.getPointerInfo().getLocation(); 219 } 220 }); 221 if (pointer == null) { 222 return; 223 } 224 show(new Point(pointer.x, pointer.y), TOOLTIP_MOUSE_CURSOR_INDENT); 225 } 226 }); 227 } 228 229 public void enter() { 230 XToolkit.schedule(starter, TOOLTIP_START_DELAY_TIME); 231 } 232 233 public void exit() { 234 XToolkit.remove(starter); 235 if (isVisible()) { 236 hide(); 237 } 238 } 239 240 private boolean isPointerOverTrayIcon(Rectangle trayRect) { 241 Point p = MouseInfo.getPointerInfo().getLocation(); 242 return !(p.x < trayRect.x || p.x > (trayRect.x + trayRect.width) || 243 p.y < trayRect.y || p.y > (trayRect.y + trayRect.height)); 244 } 245 } 246 247 @SuppressWarnings("serial") // JDK-implementation class 248 public static class Balloon extends InfoWindow { 249 250 public interface LiveArguments extends InfoWindow.LiveArguments { 251 /** The action to be performed upon clicking the baloon. */ 252 String getActionCommand(); 253 } 254 255 private final LiveArguments liveArguments; 256 private final Object target; 257 258 private static final int BALLOON_SHOW_TIME = 10000; 259 private static final int BALLOON_TEXT_MAX_LENGTH = 256; 260 private static final int BALLOON_WORD_LINE_MAX_LENGTH = 16; 261 private static final int BALLOON_WORD_LINE_MAX_COUNT = 4; 262 private static final int BALLOON_ICON_WIDTH = 32; 263 private static final int BALLOON_ICON_HEIGHT = 32; 264 private static final int BALLOON_TRAY_ICON_INDENT = 0; 265 private static final Color BALLOON_CAPTION_BACKGROUND_COLOR = new Color(200, 200 ,255); 266 private static final Font BALLOON_CAPTION_FONT = new Font(Font.DIALOG, Font.BOLD, 12); 267 268 private Panel mainPanel = new Panel(); 269 private Panel captionPanel = new Panel(); 270 private Label captionLabel = new Label(""); 271 private Button closeButton = new Button("X"); 272 private Panel textPanel = new Panel(); 273 private XTrayIconPeer.IconCanvas iconCanvas = new XTrayIconPeer.IconCanvas(BALLOON_ICON_WIDTH, BALLOON_ICON_HEIGHT); 274 private Label[] lineLabels = new Label[BALLOON_WORD_LINE_MAX_COUNT]; 275 private ActionPerformer ap = new ActionPerformer(); 276 277 private Image iconImage; 278 private Image errorImage; 279 private Image warnImage; 280 private Image infoImage; 281 private boolean gtkImagesLoaded; 282 283 private Displayer displayer = new Displayer(); 284 285 public Balloon(Frame parent, Object target, LiveArguments liveArguments) { 286 super(parent, new Color(90, 80 ,190)); 287 this.liveArguments = liveArguments; 288 this.target = target; 289 290 XTrayIconPeer.suppressWarningString(this); 291 292 setCloser(new Runnable() { 293 public void run() { 294 if (textPanel != null) { 295 textPanel.removeAll(); 296 textPanel.setSize(0, 0); 297 iconCanvas.setSize(0, 0); 298 XToolkit.awtLock(); 299 try { 300 displayer.isDisplayed = false; 301 XToolkit.awtLockNotifyAll(); 302 } finally { 303 XToolkit.awtUnlock(); 304 } 305 } 306 } 307 }, BALLOON_SHOW_TIME); 308 309 add(mainPanel); 310 311 captionLabel.setFont(BALLOON_CAPTION_FONT); 312 captionLabel.addMouseListener(ap); 313 314 captionPanel.setLayout(new BorderLayout()); 315 captionPanel.add(captionLabel, BorderLayout.WEST); 316 captionPanel.add(closeButton, BorderLayout.EAST); 317 captionPanel.setBackground(BALLOON_CAPTION_BACKGROUND_COLOR); 318 captionPanel.addMouseListener(ap); 319 320 closeButton.addActionListener(new ActionListener() { 321 public void actionPerformed(ActionEvent e) { 322 hide(); 323 } 324 }); 325 326 mainPanel.setLayout(new BorderLayout()); 327 mainPanel.setBackground(Color.white); 328 mainPanel.add(captionPanel, BorderLayout.NORTH); 329 mainPanel.add(iconCanvas, BorderLayout.WEST); 330 mainPanel.add(textPanel, BorderLayout.CENTER); 331 332 iconCanvas.addMouseListener(ap); 333 334 for (int i = 0; i < BALLOON_WORD_LINE_MAX_COUNT; i++) { 335 lineLabels[i] = new Label(); 336 lineLabels[i].addMouseListener(ap); 337 lineLabels[i].setBackground(Color.white); 338 } 339 340 displayer.thread.start(); 341 } 342 343 public void display(String caption, String text, String messageType) { 344 if (!gtkImagesLoaded) { 345 loadGtkImages(); 346 } 347 displayer.display(caption, text, messageType); 348 } 349 350 private void _display(String caption, String text, String messageType) { 351 captionLabel.setText(caption); 352 353 BreakIterator iter = BreakIterator.getWordInstance(); 354 if (text != null) { 355 iter.setText(text); 356 int start = iter.first(), end; 357 int nLines = 0; 358 359 do { 360 end = iter.next(); 361 362 if (end == BreakIterator.DONE || 363 text.substring(start, end).length() >= 50) 364 { 365 lineLabels[nLines].setText(text.substring(start, end == BreakIterator.DONE ? 366 iter.last() : end)); 367 textPanel.add(lineLabels[nLines++]); 368 start = end; 369 } 370 if (nLines == BALLOON_WORD_LINE_MAX_COUNT) { 371 if (end != BreakIterator.DONE) { 372 lineLabels[nLines - 1].setText( 373 new String(lineLabels[nLines - 1].getText() + " ...")); 374 } 375 break; 376 } 377 } while (end != BreakIterator.DONE); 378 379 380 textPanel.setLayout(new GridLayout(nLines, 1)); 381 } 382 383 if ("ERROR".equals(messageType)) { 384 iconImage = errorImage; 385 } else if ("WARNING".equals(messageType)) { 386 iconImage = warnImage; 387 } else if ("INFO".equals(messageType)) { 388 iconImage = infoImage; 389 } else { 390 iconImage = null; 391 } 392 393 if (iconImage != null) { 394 Dimension tpSize = textPanel.getSize(); 395 iconCanvas.setSize(BALLOON_ICON_WIDTH, (BALLOON_ICON_HEIGHT > tpSize.height ? 396 BALLOON_ICON_HEIGHT : tpSize.height)); 397 iconCanvas.validate(); 398 } 399 400 SunToolkit.executeOnEventHandlerThread(target, new Runnable() { 401 public void run() { 402 if (liveArguments.isDisposed()) { 403 return; 404 } 405 Point parLoc = getParent().getLocationOnScreen(); 406 Dimension parSize = getParent().getSize(); 407 show(new Point(parLoc.x + parSize.width/2, parLoc.y + parSize.height/2), 408 BALLOON_TRAY_ICON_INDENT); 409 if (iconImage != null) { 410 iconCanvas.updateImage(iconImage); // call it after the show(..) above 411 } 412 } 413 }); 414 } 415 416 public void dispose() { 417 displayer.thread.interrupt(); 418 super.dispose(); 419 } 420 421 private void loadGtkImages() { 422 if (!gtkImagesLoaded) { 423 errorImage = (Image)Toolkit.getDefaultToolkit().getDesktopProperty( 424 "gtk.icon.gtk-dialog-error.6.rtl"); 425 warnImage = (Image)Toolkit.getDefaultToolkit().getDesktopProperty( 426 "gtk.icon.gtk-dialog-warning.6.rtl"); 427 infoImage = (Image)Toolkit.getDefaultToolkit().getDesktopProperty( 428 "gtk.icon.gtk-dialog-info.6.rtl"); 429 gtkImagesLoaded = true; 430 } 431 } 432 433 private class ActionPerformer extends MouseAdapter { 434 public void mouseClicked(MouseEvent e) { 435 // hide the balloon by any click 436 hide(); 437 if (e.getButton() == MouseEvent.BUTTON1) { 438 ActionEvent aev = new ActionEvent(target, ActionEvent.ACTION_PERFORMED, 439 liveArguments.getActionCommand(), 440 e.getWhen(), e.getModifiers()); 441 XToolkit.postEvent(XToolkit.targetToAppContext(aev.getSource()), aev); 442 } 443 } 444 } 445 446 private class Displayer implements Runnable { 447 final int MAX_CONCURRENT_MSGS = 10; 448 449 ArrayBlockingQueue<Message> messageQueue = new ArrayBlockingQueue<Message>(MAX_CONCURRENT_MSGS); 450 boolean isDisplayed; 451 final Thread thread; 452 453 Displayer() { 454 this.thread = new Thread(null, this, "Displayer", 0, false); 455 this.thread.setDaemon(true); 456 } 457 458 @Override 459 public void run() { 460 while (true) { 461 Message msg = null; 462 try { 463 msg = messageQueue.take(); 464 } catch (InterruptedException e) { 465 return; 466 } 467 468 /* 469 * Wait till the previous message is displayed if any 470 */ 471 XToolkit.awtLock(); 472 try { 473 while (isDisplayed) { 474 try { 475 XToolkit.awtLockWait(); 476 } catch (InterruptedException e) { 477 return; 478 } 479 } 480 isDisplayed = true; 481 } finally { 482 XToolkit.awtUnlock(); 483 } 484 _display(msg.caption, msg.text, msg.messageType); 485 } 486 } 487 488 void display(String caption, String text, String messageType) { 489 messageQueue.offer(new Message(caption, text, messageType)); 490 } 491 } 492 493 private static class Message { 494 String caption, text, messageType; 495 496 Message(String caption, String text, String messageType) { 497 this.caption = caption; 498 this.text = text; 499 this.messageType = messageType; 500 } 501 } 502 } 503 } 504