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