|
1 /* |
|
2 * Copyright 2009 Sun Microsystems, Inc. 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. Sun designates this |
|
8 * particular file as subject to the "Classpath" exception as provided |
|
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, |
|
22 * CA 95054 USA or visit www.sun.com if you need additional information or |
|
23 * have any 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.logging.Logger; |
|
35 import java.util.logging.Level; |
|
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 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 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 public static class Tooltip extends InfoWindow { |
|
151 |
|
152 public interface LiveArguments extends InfoWindow.LiveArguments { |
|
153 /** The tooltip to be displayed. */ |
|
154 String getTooltipString(); |
|
155 } |
|
156 |
|
157 private final Object target; |
|
158 private final LiveArguments liveArguments; |
|
159 |
|
160 private final Label textLabel = new Label(""); |
|
161 private final Runnable starter = new Runnable() { |
|
162 public void run() { |
|
163 display(); |
|
164 }}; |
|
165 |
|
166 private final static int TOOLTIP_SHOW_TIME = 10000; |
|
167 private final static int TOOLTIP_START_DELAY_TIME = 1000; |
|
168 private final static int TOOLTIP_MAX_LENGTH = 64; |
|
169 private final static int TOOLTIP_MOUSE_CURSOR_INDENT = 5; |
|
170 private final static Color TOOLTIP_BACKGROUND_COLOR = new Color(255, 255, 220); |
|
171 private final static Font TOOLTIP_TEXT_FONT = XWindow.getDefaultFont(); |
|
172 |
|
173 public Tooltip(Frame parent, Object target, |
|
174 LiveArguments liveArguments) |
|
175 { |
|
176 super(parent, Color.black); |
|
177 |
|
178 this.target = target; |
|
179 this.liveArguments = liveArguments; |
|
180 |
|
181 XTrayIconPeer.suppressWarningString(this); |
|
182 |
|
183 setCloser(null, TOOLTIP_SHOW_TIME); |
|
184 textLabel.setBackground(TOOLTIP_BACKGROUND_COLOR); |
|
185 textLabel.setFont(TOOLTIP_TEXT_FONT); |
|
186 add(textLabel); |
|
187 } |
|
188 |
|
189 /* |
|
190 * WARNING: this method is executed on Toolkit thread! |
|
191 */ |
|
192 private void display() { |
|
193 String tooltipString = liveArguments.getTooltipString(); |
|
194 if (tooltipString == null) { |
|
195 return; |
|
196 } else if (tooltipString.length() > TOOLTIP_MAX_LENGTH) { |
|
197 textLabel.setText(tooltipString.substring(0, TOOLTIP_MAX_LENGTH)); |
|
198 } else { |
|
199 textLabel.setText(tooltipString); |
|
200 } |
|
201 |
|
202 // Execute on EDT to avoid deadlock (see 6280857). |
|
203 SunToolkit.executeOnEventHandlerThread(target, new Runnable() { |
|
204 public void run() { |
|
205 if (liveArguments.isDisposed()) { |
|
206 return; |
|
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 Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(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 = (Message)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 |