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