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