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