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