1 /*******************************************************************************
2 * Copyright (c) 2000, 2018 IBM Corporation and others.
3 *
4 * This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License 2.0
6 * which accompanies this distribution, and is available at
7 * https://www.eclipse.org/legal/epl-2.0/
8 *
9 * SPDX-License-Identifier: EPL-2.0
10 *
11 * Contributors:
12 * IBM Corporation - initial API and implementation
13 *******************************************************************************/
14 package org.eclipse.swt.widgets;
15
16
17 import org.eclipse.swt.*;
18 import org.eclipse.swt.events.*;
19 import org.eclipse.swt.graphics.*;
20 import org.eclipse.swt.internal.*;
21 import org.eclipse.swt.internal.cairo.*;
22 import org.eclipse.swt.internal.gtk.*;
23
24 /**
25 * Instances of this class represent popup windows that are used
26 * to inform or warn the user.
27 * <dl>
28 * <dt><b>Styles:</b></dt>
29 * <dd>BALLOON, ICON_ERROR, ICON_INFORMATION, ICON_WARNING</dd>
30 * <dt><b>Events:</b></dt>
31 * <dd>Selection</dd>
32 * </dl>
33 * <p>
34 * Note: Only one of the styles ICON_ERROR, ICON_INFORMATION,
35 * and ICON_WARNING may be specified.
36 * </p><p>
37 * IMPORTANT: This class is <em>not</em> intended to be subclassed.
38 * </p>
39 *
40 * @see <a href="http://www.eclipse.org/swt/snippets/#tooltips">Tool Tips snippets</a>
41 * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: ControlExample</a>
42 * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
43 *
44 * @since 3.2
45 * @noextend This class is not intended to be subclassed by clients.
46 */
47 public class ToolTip extends Widget {
48 Shell parent;
49 String text, message;
50 TrayItem item;
51 int x, y, timerId;
52 long layoutText = 0, layoutMessage = 0;
53 long provider;
54 int [] borderPolygon;
55 boolean spikeAbove, autohide;
56
57 static final int BORDER = 5;
58 static final int PADDING = 5;
59 static final int INSET = 4;
60 static final int TIP_HEIGHT = 20;
61 static final int IMAGE_SIZE = 16;
62 static final int DELAY = 8000;
63
64 /**
65 * Constructs a new instance of this class given its parent
66 * and a style value describing its behavior and appearance.
67 * <p>
68 * The style value is either one of the style constants defined in
69 * class <code>SWT</code> which is applicable to instances of this
70 * class, or must be built by <em>bitwise OR</em>'ing together
71 * (that is, using the <code>int</code> "|" operator) two or more
72 * of those <code>SWT</code> style constants. The class description
73 * lists the style constants that are applicable to the class.
74 * Style bits are also inherited from superclasses.
75 * </p>
76 *
77 * @param parent a composite control which will be the parent of the new instance (cannot be null)
78 * @param style the style of control to construct
79 *
80 * @exception IllegalArgumentException <ul>
81 * <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
82 * </ul>
83 * @exception SWTException <ul>
84 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
85 * <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
86 * </ul>
87 *
88 * @see SWT#BALLOON
89 * @see SWT#ICON_ERROR
90 * @see SWT#ICON_INFORMATION
91 * @see SWT#ICON_WARNING
92 * @see Widget#checkSubclass
93 * @see Widget#getStyle
94 */
ToolTip(Shell parent, int style)95 public ToolTip (Shell parent, int style) {
96 super (parent, checkStyle (style));
97 this.parent = parent;
98 createWidget (0);
99 parent.addToolTip (this);
100 }
101
checkStyle(int style)102 static int checkStyle (int style) {
103 int mask = SWT.ICON_ERROR | SWT.ICON_INFORMATION | SWT.ICON_WARNING;
104 if ((style & mask) == 0) return style;
105 return checkBits (style, SWT.ICON_INFORMATION, SWT.ICON_WARNING, SWT.ICON_ERROR, 0, 0, 0);
106 }
107
108 /**
109 * Adds the listener to the collection of listeners who will
110 * be notified when the receiver is selected by the user, by sending
111 * it one of the messages defined in the <code>SelectionListener</code>
112 * interface.
113 * <p>
114 * <code>widgetSelected</code> is called when the receiver is selected.
115 * <code>widgetDefaultSelected</code> is not called.
116 * </p>
117 *
118 * @param listener the listener which should be notified when the receiver is selected by the user
119 *
120 * @exception IllegalArgumentException <ul>
121 * <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
122 * </ul>
123 * @exception SWTException <ul>
124 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
125 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
126 * </ul>
127 *
128 * @see SelectionListener
129 * @see #removeSelectionListener
130 * @see SelectionEvent
131 */
addSelectionListener(SelectionListener listener)132 public void addSelectionListener (SelectionListener listener) {
133 checkWidget ();
134 if (listener == null) error (SWT.ERROR_NULL_ARGUMENT);
135 TypedListener typedListener = new TypedListener (listener);
136 addListener (SWT.Selection,typedListener);
137 addListener (SWT.DefaultSelection,typedListener);
138 }
139
configure()140 void configure () {
141 GTK.gtk_widget_realize (handle);
142 /*
143 * Feature in GTK: using gdk_screen_get_monitor_at_window() does not accurately get the correct monitor on the machine.
144 * Using gdk_screen_get_monitor_at_point() returns it correctly. Using getLocation() on point will get
145 * the coordinates for where the tooltip should appear on the host.
146 */
147 Point point = getLocation();
148 boolean multipleMonitors;
149 int monitorNumber;
150 GdkRectangle dest = new GdkRectangle ();
151 if (GTK.GTK_VERSION >= OS.VERSION(3, 22, 0)) {
152 long display = GDK.gdk_display_get_default();
153 multipleMonitors = GDK.gdk_display_get_n_monitors(display) > 1;
154 long monitor = GDK.gdk_display_get_monitor_at_point(display, point.x, point.y);
155 GDK.gdk_monitor_get_geometry (monitor, dest);
156 } else {
157 long screen = GDK.gdk_screen_get_default ();
158 multipleMonitors = GDK.gdk_screen_get_n_monitors(screen) > 1;
159 monitorNumber = GDK.gdk_screen_get_monitor_at_point(screen, point.x, point.y);
160 GDK.gdk_screen_get_monitor_geometry(screen, monitorNumber, dest);
161 }
162 point = getSize (dest.width / 4);
163 int w = point.x;
164 int h = point.y;
165 point = getLocation ();
166 int x = point.x;
167 int y = point.y;
168 GTK.gtk_window_resize (handle, w, h + TIP_HEIGHT);
169 int[] polyline;
170 Rectangle bounds = display.getBounds();
171 int width = bounds != null && GTK.GTK4 ? bounds.width : GDK.gdk_screen_width();
172 spikeAbove = dest.height >= y + h + TIP_HEIGHT;
173 if ((dest.width >= x + w) || (multipleMonitors && width >= x + w)) {
174 if (dest.height >= y + h + TIP_HEIGHT) {
175 int t = TIP_HEIGHT;
176 polyline = new int[] {
177 0, 5+t, 1, 5+t, 1, 3+t, 3, 1+t, 5, 1+t, 5, t,
178 16, t, 16, 0, 35, t,
179 w-5, t, w-5, 1+t, w-3, 1+t, w-1, 3+t, w-1, 5+t, w, 5+t,
180 w, h-5+t, w-1, h-5+t, w-1, h-3+t, w-2, h-3+t, w-2, h-2+t, w-3, h-2+t, w-3, h-1+t, w-5, h-1+t, w-5, h+t,
181 5, h+t, 5, h-1+t, 3, h-1+t, 3, h-2+t, 2, h-2+t, 2, h-3+t, 1, h-3+t, 1, h-5+t, 0, h-5+t,
182 0, 5+t};
183 borderPolygon = new int[] {
184 0, 5+t, 1, 4+t, 1, 3+t, 3, 1+t, 4, 1+t, 5, t,
185 16, t, 16, 1, 35, t,
186 w-6, 0+t, w-5, 1+t, w-4, 1+t, w-2, 3+t, w-2, 4+t, w-1, 5+t,
187 w-1, h-6+t, w-2, h-5+t, w-2, h-4+t, w-4, h-2+t, w-5, h-2+t, w-6, h-1+t,
188 5, h-1+t, 4, h-2+t, 3, h-2+t, 1, h-4+t, 1, h-5+t, 0, h-6+t,
189 0, 5+t};
190 if ((parent.style & SWT.MIRRORED) != 0) {
191 x -= w - 36;
192 polyline[12] = w-36;
193 polyline[14] = w-16;
194 polyline[16] = w-15;
195 borderPolygon[12] = w-35;
196 borderPolygon[14] = borderPolygon[16] = w-16;
197 }
198 GTK.gtk_window_move (handle, Math.max(0, x - 17), y);
199 } else {
200 polyline = new int[] {
201 0, 5, 1, 5, 1, 3, 3, 1, 5, 1, 5, 0,
202 w-5, 0, w-5, 1, w-3, 1, w-1, 3, w-1, 5, w, 5,
203 w, h-5, w-1, h-5, w-1, h-3, w-2, h-3, w-2, h-2, w-3, h-2, w-3, h-1, w-5, h-1, w-5, h,
204 35, h, 16, h+TIP_HEIGHT, 16, h,
205 5, h, 5, h-1, 3, h-1, 3, h-2, 2, h-2, 2, h-3, 1, h-3, 1, h-5, 0, h-5,
206 0, 5};
207 borderPolygon = new int[] {
208 0, 5, 1, 4, 1, 3, 3, 1, 4, 1, 5, 0,
209 w-6, 0, w-5, 1, w-4, 1, w-2, 3, w-2, 4, w-1, 5,
210 w-1, h-6, w-2, h-5, w-2, h-4, w-4, h-2, w-5, h-2, w-6, h-1,
211 35, h-1, 17, h+TIP_HEIGHT-2, 17, h-1,
212 5, h-1, 4, h-2, 3, h-2, 1, h-4, 1, h-5, 0, h-6,
213 0, 5};
214 if ((parent.style & SWT.MIRRORED) != 0) {
215 x -= w - 36;
216 polyline [42] = polyline [44] = w-16;
217 polyline [46] = w-35;
218 borderPolygon[36] = borderPolygon[38] = w-17;
219 borderPolygon [40] = w-35;
220 }
221 GTK.gtk_window_move (handle, Math.max(0, x - 17), y - h - TIP_HEIGHT);
222 }
223 } else {
224 if (dest.height >= y + h + TIP_HEIGHT) {
225 int t = TIP_HEIGHT;
226 polyline = new int[] {
227 0, 5+t, 1, 5+t, 1, 3+t, 3, 1+t, 5, 1+t, 5, t,
228 w-35, t, w-16, 0, w-16, t,
229 w-5, t, w-5, 1+t, w-3, 1+t, w-1, 3+t, w-1, 5+t, w, 5+t,
230 w, h-5+t, w-1, h-5+t, w-1, h-3+t, w-2, h-3+t, w-2, h-2+t, w-3, h-2+t, w-3, h-1+t, w-5, h-1+t, w-5, h+t,
231 5, h+t, 5, h-1+t, 3, h-1+t, 3, h-2+t, 2, h-2+t, 2, h-3+t, 1, h-3+t, 1, h-5+t, 0, h-5+t,
232 0, 5+t};
233 borderPolygon = new int[] {
234 0, 5+t, 1, 4+t, 1, 3+t, 3, 1+t, 4, 1+t, 5, t,
235 w-35, t, w-17, 2, w-17, t,
236 w-6, t, w-5, 1+t, w-4, 1+t, w-2, 3+t, w-2, 4+t, w-1, 5+t,
237 w-1, h-6+t, w-2, h-5+t, w-2, h-4+t, w-4, h-2+t, w-5, h-2+t, w-6, h-1+t,
238 5, h-1+t, 4, h-2+t, 3, h-2+t, 1, h-4+t, 1, h-5+t, 0, h-6+t,
239 0, 5+t};
240 if ((parent.style & SWT.MIRRORED) != 0) {
241 x += w - 35;
242 polyline [12] = polyline [14] = 16;
243 polyline [16] = 35;
244 borderPolygon[12] = borderPolygon[14] = 16;
245 borderPolygon [16] = 35;
246 }
247 GTK.gtk_window_move (handle, Math.max(dest.width- w, x - w + 17), y);
248 } else {
249 polyline = new int[] {
250 0, 5, 1, 5, 1, 3, 3, 1, 5, 1, 5, 0,
251 w-5, 0, w-5, 1, w-3, 1, w-1, 3, w-1, 5, w, 5,
252 w, h-5, w-1, h-5, w-1, h-3, w-2, h-3, w-2, h-2, w-3, h-2, w-3, h-1, w-5, h-1, w-5, h,
253 w-16, h, w-16, h+TIP_HEIGHT, w-35, h,
254 5, h, 5, h-1, 3, h-1, 3, h-2, 2, h-2, 2, h-3, 1, h-3, 1, h-5, 0, h-5,
255 0, 5};
256 borderPolygon = new int[] {
257 0, 5, 1, 4, 1, 3, 3, 1, 4, 1, 5, 0,
258 w-6, 0, w-5, 1, w-4, 1, w-2, 3, w-2, 4, w-1, 5,
259 w-1, h-6, w-2, h-5, w-2, h-4, w-4, h-2, w-5, h-2, w-6, h-1,
260 w-17, h-1, w-17, h+TIP_HEIGHT-2, w-36, h-1,
261 5, h-1, 4, h-2, 3, h-2, 1, h-4, 1, h-5, 0, h-6,
262 0, 5};
263 if ((parent.style & SWT.MIRRORED) != 0) {
264 x += w - 35;
265 polyline [42] = 35;
266 polyline [44] = polyline [46] = 16;
267 borderPolygon[36] = 35;
268 borderPolygon[38] = borderPolygon [40] = 17;
269 }
270 GTK.gtk_window_move (handle, Math.max(dest.width - w, x - w + 17), y - h - TIP_HEIGHT);
271 }
272 }
273 GTK.gtk_widget_realize(handle);
274 Region region = new Region (display);
275 region.add(DPIUtil.autoScaleDown(polyline));
276 GTK.gtk_widget_shape_combine_region (handle, region.handle);
277 region.dispose ();
278 }
279
280 @Override
createHandle(int index)281 void createHandle (int index) {
282 if ((style & SWT.BALLOON) != 0) {
283 state |= HANDLE;
284 handle = GTK.gtk_window_new (GTK.GTK_WINDOW_POPUP);
285 Color background = display.getSystemColor (SWT.COLOR_INFO_BACKGROUND);
286 long context = GTK.gtk_widget_get_style_context (handle);
287 GdkRGBA bgRGBA = background.handle;
288 String css = "window {background-color: " + display.gtk_rgba_to_css_string(bgRGBA) + ";}";
289 gtk_css_provider_load_from_css (context, css);
290 GTK.gtk_style_context_invalidate (context);
291 GTK.gtk_window_set_type_hint (handle, GDK.GDK_WINDOW_TYPE_HINT_TOOLTIP);
292 }
293 }
294
gtk_css_provider_load_from_css(long context, String css)295 void gtk_css_provider_load_from_css (long context, String css) {
296 /* Utility function. */
297 //@param css : a 'css java' string like "{\nbackground: red;\n}".
298 if (provider == 0) {
299 provider = GTK.gtk_css_provider_new ();
300 GTK.gtk_style_context_add_provider (context, provider, GTK.GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
301 OS.g_object_unref (provider);
302 }
303 if (GTK.GTK4) {
304 GTK.gtk_css_provider_load_from_data (provider, Converter.wcsToMbcs (css, true), -1);
305 } else {
306 GTK.gtk_css_provider_load_from_data (provider, Converter.wcsToMbcs (css, true), -1, null);
307 }
308 }
309
310 @Override
createWidget(int index)311 void createWidget (int index) {
312 super.createWidget (index);
313 text = "";
314 message = "";
315 x = y = -1;
316 autohide = true;
317 }
318
319 @Override
destroyWidget()320 void destroyWidget () {
321 long topHandle = topHandle ();
322 if (parent != null) parent.removeTooTip (this);
323 releaseHandle ();
324 if (topHandle != 0 && (state & HANDLE) != 0) {
325 if ((style & SWT.BALLOON) != 0) {
326 GTK.gtk_widget_destroy (topHandle);
327 } else {
328 OS.g_object_unref (topHandle);
329 }
330 }
331 }
332
333 /**
334 * Returns <code>true</code> if the receiver is automatically
335 * hidden by the platform, and <code>false</code> otherwise.
336 *
337 * @return the receiver's auto hide state
338 *
339 * @exception SWTException <ul>
340 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
341 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
342 * </ul>
343 *
344 */
getAutoHide()345 public boolean getAutoHide () {
346 checkWidget ();
347 return autohide;
348 }
349
getLocation()350 Point getLocation () {
351 int x = this.x;
352 int y = this.y;
353 if (item != null) {
354 long itemHandle = item.handle;
355 GdkRectangle area = new GdkRectangle ();
356 GTK.gtk_status_icon_get_geometry (itemHandle, 0, area, 0);
357 x = area.x + area.width / 2;
358 y = area.y + area.height / 2;
359 }
360 if (x == -1 || y == -1) {
361 int [] px = new int [1], py = new int [1];
362 if (GTK.GTK4) {
363 /*
364 * TODO: calling gdk_window_get_device_position() with a 0
365 * for the GdkWindow uses gdk_get_default_root_window(),
366 * which doesn't exist on GTK4.
367 */
368 } else {
369 display.gdk_window_get_device_position (0, px, py, null);
370 }
371 x = px [0];
372 y = py [0];
373 }
374 return new Point(x, y);
375 }
376
377 /**
378 * Returns the receiver's message, which will be an empty
379 * string if it has never been set.
380 *
381 * @return the receiver's message
382 *
383 * @exception SWTException <ul>
384 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
385 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
386 * </ul>
387 */
getMessage()388 public String getMessage () {
389 checkWidget ();
390 return message;
391 }
392
393 @Override
getNameText()394 String getNameText () {
395 return getText ();
396 }
397
398 /**
399 * Returns the receiver's parent, which must be a <code>Shell</code>.
400 *
401 * @return the receiver's parent
402 *
403 * @exception SWTException <ul>
404 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
405 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
406 * </ul>
407 */
getParent()408 public Shell getParent () {
409 checkWidget ();
410 return parent;
411 }
412
getSize(int maxWidth)413 Point getSize (int maxWidth) {
414 int textWidth = 0, messageWidth = 0;
415 int [] w = new int [1], h = new int [1];
416 if (layoutText != 0) {
417 OS.pango_layout_set_width (layoutText, -1);
418 OS.pango_layout_get_pixel_size (layoutText, w, h);
419 textWidth = w [0];
420 }
421 if (layoutMessage != 0) {
422 OS.pango_layout_set_width (layoutMessage, -1);
423 OS.pango_layout_get_pixel_size (layoutMessage, w, h);
424 messageWidth = w [0];
425 }
426 int messageTrim = 2 * INSET + 2 * BORDER + 2 * PADDING;
427 boolean hasImage = layoutText != 0 && (style & (SWT.ICON_ERROR | SWT.ICON_INFORMATION | SWT.ICON_WARNING)) != 0;
428 int textTrim = messageTrim + (hasImage ? IMAGE_SIZE : 0);
429 int width = Math.min (maxWidth, Math.max (textWidth + textTrim, messageWidth + messageTrim));
430 int textHeight = 0, messageHeight = 0;
431 if (layoutText != 0) {
432 OS.pango_layout_set_width (layoutText, (maxWidth - textTrim) * OS.PANGO_SCALE);
433 OS.pango_layout_get_pixel_size (layoutText, w, h);
434 textHeight = h [0];
435 }
436 if (layoutMessage != 0) {
437 OS.pango_layout_set_width (layoutMessage, (maxWidth - messageTrim) * OS.PANGO_SCALE);
438 OS.pango_layout_get_pixel_size (layoutMessage, w, h);
439 messageHeight = h [0];
440 }
441 int height = 2 * BORDER + 2 * PADDING + messageHeight;
442 if (layoutText != 0) height += Math.max (IMAGE_SIZE, textHeight) + 2 * PADDING;
443 return new Point(width, height);
444 }
445
446 /**
447 * Returns the receiver's text, which will be an empty
448 * string if it has never been set.
449 *
450 * @return the receiver's text
451 *
452 * @exception SWTException <ul>
453 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
454 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
455 * </ul>
456 */
getText()457 public String getText () {
458 checkWidget ();
459 return text;
460 }
461
462 /**
463 * Returns <code>true</code> if the receiver is visible, and
464 * <code>false</code> otherwise.
465 * <p>
466 * If one of the receiver's ancestors is not visible or some
467 * other condition makes the receiver not visible, this method
468 * may still indicate that it is considered visible even though
469 * it may not actually be showing.
470 * </p>
471 *
472 * @return the receiver's visibility state
473 *
474 * @exception SWTException <ul>
475 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
476 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
477 * </ul>
478 */
getVisible()479 public boolean getVisible () {
480 checkWidget ();
481 if ((style & SWT.BALLOON) != 0) return GTK.gtk_widget_get_visible (handle);
482 return false;
483 }
484
485 @Override
gtk_event(long widget, long event)486 long gtk_event (long widget, long event) {
487 if (!GTK.GTK4) return 0;
488 int eventType = GDK.gdk_event_get_event_type(event);
489 switch (eventType) {
490 case GDK.GDK4_BUTTON_PRESS: {
491 return gtk_button_press_event(widget, event);
492 }
493 case GDK.GDK4_BUTTON_RELEASE: {
494 return gtk_button_release_event(widget, event);
495 }
496 }
497 return 0;
498 }
499
500 @Override
gtk_button_press_event(long widget, long event)501 long gtk_button_press_event (long widget, long event) {
502 sendSelectionEvent (SWT.Selection, null, true);
503 setVisible (false);
504 return 0;
505 }
506
drawTooltip(long cairo)507 void drawTooltip (long cairo) {
508 int x = BORDER + PADDING;
509 int y = BORDER + PADDING;
510 if (cairo == 0) error (SWT.ERROR_NO_HANDLES);
511 int count = borderPolygon.length / 2;
512 if (count != 0) {
513 Cairo.cairo_set_line_width(cairo, 1);
514 Cairo.cairo_move_to(cairo, borderPolygon[0], borderPolygon[1]);
515 for (int i=1,j=2; i<count; i++,j+=2) {
516 Cairo.cairo_line_to(cairo, borderPolygon[j]+0.5, borderPolygon[j+1]+0.5);
517 }
518 Cairo.cairo_close_path(cairo);
519 Cairo.cairo_stroke(cairo);
520 }
521 if (spikeAbove) y += TIP_HEIGHT;
522 if (layoutText != 0) {
523 byte[] buffer = null;
524 int id = style & (SWT.ICON_ERROR | SWT.ICON_INFORMATION | SWT.ICON_WARNING);
525 switch (id) {
526 case SWT.ICON_ERROR: buffer = Converter.wcsToMbcs ("dialog-error", true); break;
527 case SWT.ICON_INFORMATION: buffer = Converter.wcsToMbcs ("dialog-information", true); break;
528 case SWT.ICON_WARNING: buffer = Converter.wcsToMbcs ("dialog-warning", true); break;
529 }
530 if (buffer != null) {
531 long pixbuf = GTK.gtk_icon_theme_load_icon(GTK.gtk_icon_theme_get_default(), buffer, GTK.GTK_ICON_SIZE_MENU, 0, 0);
532 GDK.gdk_cairo_set_source_pixbuf(cairo, pixbuf, x, y);
533 Cairo.cairo_paint (cairo);
534 OS.g_object_unref (pixbuf);
535 x += IMAGE_SIZE;
536 }
537 x += INSET;
538 int [] w = new int [1], h = new int [1];
539 Color foreground = display.getSystemColor (SWT.COLOR_INFO_FOREGROUND);
540 GDK.gdk_cairo_set_source_rgba(cairo,foreground.handle);
541 Cairo.cairo_move_to(cairo, x,y );
542 OS.pango_cairo_show_layout(cairo, layoutText);
543 OS.pango_layout_get_pixel_size (layoutText, w, h);
544 y += 2 * PADDING + Math.max (IMAGE_SIZE, h [0]);
545 }
546 if (layoutMessage != 0) {
547 x = BORDER + PADDING + INSET;
548 Color foreground = display.getSystemColor (SWT.COLOR_INFO_FOREGROUND);
549 GDK.gdk_cairo_set_source_rgba(cairo,foreground.handle);
550 Cairo.cairo_move_to(cairo, x, y);
551 OS.pango_cairo_show_layout(cairo, layoutMessage);
552 }
553 }
554
555 @Override
gtk_draw(long widget, long cairo)556 long gtk_draw (long widget, long cairo) {
557 if ((state & OBSCURED) != 0) return 0;
558 drawTooltip (cairo);
559 return 0;
560 }
561
562 @Override
gtk_size_allocate(long widget, long allocation)563 long gtk_size_allocate (long widget, long allocation) {
564 Point point = getLocation ();
565 int x = point.x;
566 int y = point.y;
567 GTK.gtk_widget_realize (widget);
568 GdkRectangle dest = new GdkRectangle ();
569 if (GTK.GTK_VERSION >= OS.VERSION(3, 22, 0)) {
570 long display = GDK.gdk_display_get_default();
571 long monitor = GDK.gdk_display_get_monitor_at_point(display, x, y);
572 GDK.gdk_monitor_get_geometry(monitor, dest);
573 } else {
574 long screen = GDK.gdk_screen_get_default ();
575 int monitorNumber = GDK.gdk_screen_get_monitor_at_point(screen, point.x, point.y);
576 GDK.gdk_screen_get_monitor_geometry (screen, monitorNumber, dest);
577 }
578 GtkAllocation widgetAllocation = new GtkAllocation ();
579 GTK.gtk_widget_get_allocation (widget, widgetAllocation);
580 int w = widgetAllocation.width;
581 int h = widgetAllocation.height;
582 if (dest.height < y + h) y -= h;
583 if (dest.width < x + w) x -= w;
584 GTK.gtk_window_move (widget, x, y);
585 return 0;
586 }
587
588 @Override
hookEvents()589 void hookEvents () {
590 if ((style & SWT.BALLOON) != 0) {
591 OS.g_signal_connect_closure_by_id (handle, display.signalIds [EXPOSE_EVENT], 0, display.getClosure (EXPOSE_EVENT), true);
592 OS.g_signal_connect_closure_by_id (handle, display.signalIds [EXPOSE_EVENT_INVERSE], 0, display.getClosure (EXPOSE_EVENT_INVERSE), true);
593 GTK.gtk_widget_add_events (handle, GDK.GDK_BUTTON_PRESS_MASK);
594 if (GTK.GTK4) {
595 OS.g_signal_connect_closure_by_id (handle, display.signalIds [EVENT], 0, display.getClosure (EVENT), false);
596 } else {
597 OS.g_signal_connect_closure (handle, OS.button_press_event, display.getClosure (BUTTON_PRESS_EVENT), false);
598 }
599 }
600 }
601
602 /**
603 * Returns <code>true</code> if the receiver is visible and all
604 * of the receiver's ancestors are visible and <code>false</code>
605 * otherwise.
606 *
607 * @return the receiver's visibility state
608 *
609 * @exception SWTException <ul>
610 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
611 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
612 * </ul>
613 *
614 * @see #getVisible
615 */
isVisible()616 public boolean isVisible () {
617 checkWidget ();
618 return getVisible ();
619 }
620
621 @Override
releaseWidget()622 void releaseWidget () {
623 super.releaseWidget ();
624 setVisible(false);
625 if (layoutText != 0) OS.g_object_unref (layoutText);
626 layoutText = 0;
627 if (layoutMessage != 0) OS.g_object_unref (layoutMessage);
628 layoutMessage = 0;
629 if (timerId != 0) OS.g_source_remove(timerId);
630 timerId = 0;
631 text = null;
632 message = null;
633 borderPolygon = null;
634 }
635
636 /**
637 * Removes the listener from the collection of listeners who will
638 * be notified when the receiver is selected by the user.
639 *
640 * @param listener the listener which should no longer be notified
641 *
642 * @exception IllegalArgumentException <ul>
643 * <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
644 * </ul>
645 * @exception SWTException <ul>
646 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
647 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
648 * </ul>
649 *
650 * @see SelectionListener
651 * @see #addSelectionListener
652 */
removeSelectionListener(SelectionListener listener)653 public void removeSelectionListener (SelectionListener listener) {
654 checkWidget();
655 if (listener == null) error (SWT.ERROR_NULL_ARGUMENT);
656 if (eventTable == null) return;
657 eventTable.unhook (SWT.Selection, listener);
658 eventTable.unhook (SWT.DefaultSelection, listener);
659 }
660
661 /**
662 * Makes the receiver hide automatically when <code>true</code>,
663 * and remain visible when <code>false</code>.
664 *
665 * @param autoHide the auto hide state
666 *
667 * @exception SWTException <ul>
668 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
669 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
670 * </ul>
671 *
672 * @see #getVisible
673 * @see #setVisible
674 */
setAutoHide(boolean autoHide)675 public void setAutoHide (boolean autoHide) {
676 checkWidget ();
677 this.autohide = autoHide;
678 //TODO - update when visible
679 }
680
681 /**
682 * Sets the location of the receiver, which must be a tooltip,
683 * to the point specified by the arguments which are relative
684 * to the display.
685 * <p>
686 * Note that this is different from most widgets where the
687 * location of the widget is relative to the parent.
688 * </p>
689 *
690 * @param x the new x coordinate for the receiver
691 * @param y the new y coordinate for the receiver
692 *
693 * @exception SWTException <ul>
694 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
695 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
696 * </ul>
697 */
setLocation(int x, int y)698 public void setLocation (int x, int y) {
699 checkWidget ();
700 setLocation (new Point (x, y));
701 }
702
setLocationInPixels(int x, int y)703 void setLocationInPixels (int x, int y) {
704 checkWidget ();
705 this.x = x;
706 this.y = y;
707 if ((style & SWT.BALLOON) != 0) {
708 if (GTK.gtk_widget_get_visible (handle)) configure ();
709 }
710 }
711 /**
712 * Sets the location of the receiver, which must be a tooltip,
713 * to the point specified by the argument which is relative
714 * to the display.
715 * <p>
716 * Note that this is different from most widgets where the
717 * location of the widget is relative to the parent.
718 * </p><p>
719 * Note that the platform window manager ultimately has control
720 * over the location of tooltips.
721 * </p>
722 *
723 * @param location the new location for the receiver
724 *
725 * @exception IllegalArgumentException <ul>
726 * <li>ERROR_NULL_ARGUMENT - if the point is null</li>
727 * </ul>
728 * @exception SWTException <ul>
729 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
730 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
731 * </ul>
732 */
setLocation(Point location)733 public void setLocation (Point location) {
734 checkWidget ();
735 setLocationInPixels(DPIUtil.autoScaleUp(location));
736 }
737
setLocationInPixels(Point location)738 void setLocationInPixels (Point location) {
739 checkWidget ();
740 if (location == null) error (SWT.ERROR_NULL_ARGUMENT);
741 setLocationInPixels (location.x, location.y);
742 }
743
744 /**
745 * Sets the receiver's message.
746 *
747 * @param string the new message
748 *
749 * @exception IllegalArgumentException <ul>
750 * <li>ERROR_NULL_ARGUMENT - if the text is null</li>
751 * </ul>
752 * @exception SWTException <ul>
753 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
754 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
755 * </ul>
756 */
setMessage(String string)757 public void setMessage (String string) {
758 checkWidget ();
759 if (string == null) error (SWT.ERROR_NULL_ARGUMENT);
760 message = string;
761 if ((style & SWT.BALLOON) == 0) return;
762 if (layoutMessage != 0) OS.g_object_unref (layoutMessage);
763 layoutMessage = 0;
764 if (message.length () != 0) {
765 byte [] buffer = Converter.wcsToMbcs (message, true);
766 layoutMessage = GTK.gtk_widget_create_pango_layout (handle, buffer);
767 OS.pango_layout_set_auto_dir (layoutMessage, false);
768 OS.pango_layout_set_wrap (layoutMessage, OS.PANGO_WRAP_WORD_CHAR);
769 }
770 if (GTK.gtk_widget_get_visible (handle)) configure ();
771 }
772
773 /**
774 * Sets the receiver's text.
775 *
776 * @param string the new text
777 *
778 * @exception IllegalArgumentException <ul>
779 * <li>ERROR_NULL_ARGUMENT - if the text is null</li>
780 * </ul>
781 * @exception SWTException <ul>
782 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
783 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
784 * </ul>
785 */
setText(String string)786 public void setText (String string) {
787 checkWidget ();
788 if (string == null) error (SWT.ERROR_NULL_ARGUMENT);
789 text = string;
790 if ((style & SWT.BALLOON) == 0) return;
791 if (layoutText != 0) OS.g_object_unref (layoutText);
792 layoutText = 0;
793 if (text.length () != 0) {
794 byte [] buffer = Converter.wcsToMbcs (text, true);
795 layoutText = GTK.gtk_widget_create_pango_layout (handle, buffer);
796 OS.pango_layout_set_auto_dir (layoutText, false);
797 long boldAttr = OS.pango_attr_weight_new (OS.PANGO_WEIGHT_BOLD);
798 PangoAttribute attribute = new PangoAttribute ();
799 OS.memmove (attribute, boldAttr, PangoAttribute.sizeof);
800 attribute.start_index = 0;
801 attribute.end_index = buffer.length;
802 OS.memmove (boldAttr, attribute, PangoAttribute.sizeof);
803 long attrList = OS.pango_attr_list_new ();
804 OS.pango_attr_list_insert (attrList, boldAttr);
805 OS.pango_layout_set_attributes (layoutText, attrList);
806 OS.pango_attr_list_unref (attrList);
807 OS.pango_layout_set_wrap (layoutText, OS.PANGO_WRAP_WORD_CHAR);
808 }
809 if (GTK.gtk_widget_get_visible (handle)) configure ();
810 }
811
812 /**
813 * Marks the receiver as visible if the argument is <code>true</code>,
814 * and marks it invisible otherwise.
815 * <p>
816 * If one of the receiver's ancestors is not visible or some
817 * other condition makes the receiver not visible, marking
818 * it visible may not actually cause it to be displayed.
819 * </p>
820 *
821 * @param visible the new visibility state
822 *
823 * @exception SWTException <ul>
824 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
825 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
826 * </ul>
827 */
setVisible(boolean visible)828 public void setVisible (boolean visible) {
829 checkWidget ();
830 if (timerId != 0) OS.g_source_remove(timerId);
831 timerId = 0;
832 if (visible) {
833 if ((style & SWT.BALLOON) != 0) {
834 configure ();
835 GTK.gtk_widget_show (handle);
836 } else {
837 long vboxHandle = parent.vboxHandle;
838 StringBuilder string = new StringBuilder (text);
839 if (text.length () > 0) string.append ("\n\n");
840 string.append (message);
841 byte [] buffer = Converter.wcsToMbcs (string.toString(), true);
842 GTK.gtk_widget_set_tooltip_text(vboxHandle, buffer);
843 }
844 if (autohide) timerId = OS.g_timeout_add (DELAY, display.windowTimerProc, handle);
845 } else {
846 if ((style & SWT.BALLOON) != 0) {
847 GTK.gtk_widget_hide (handle);
848 } else {
849 long vboxHandle = parent.vboxHandle;
850 byte[] buffer = Converter.wcsToMbcs("", true);
851 GTK.gtk_widget_set_tooltip_text(vboxHandle, buffer);
852 }
853 }
854 }
855
856 @Override
timerProc(long widget)857 long timerProc (long widget) {
858 if ((style & SWT.BALLOON) != 0) {
859 GTK.gtk_widget_hide (handle);
860 }
861 return 0;
862 }
863
864 }
865