1 /*******************************************************************************
2  * Copyright (c) 2000, 2008 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  *     IBM Corporation - initial API and implementation
10  * Port to the D programming language:
11  *     Frank Benoit <benoit@tionex.de>
12  *******************************************************************************/
13 module org.eclipse.swt.widgets.ToolTip;
14 
15 import java.lang.all;
16 
17 import org.eclipse.swt.widgets.Widget;
18 import org.eclipse.swt.widgets.TrayItem;
19 import org.eclipse.swt.widgets.Shell;
20 import org.eclipse.swt.SWT;
21 //import org.eclipse.swt.internal.*;
22 import org.eclipse.swt.internal.gtk.OS;
23 import org.eclipse.swt.graphics.Point;
24 import org.eclipse.swt.graphics.Color;
25 import org.eclipse.swt.events.SelectionListener;
26 import org.eclipse.swt.events.SelectionEvent;
27 import org.eclipse.swt.widgets.TypedListener;
28 import org.eclipse.swt.widgets.Event;
29 import org.eclipse.swt.widgets.Display;
30 
31 /**
32  * Instances of this class represent popup windows that are used
33  * to inform or warn the user.
34  * <p>
35  * <dl>
36  * <dt><b>Styles:</b></dt>
37  * <dd>BALLOON, ICON_ERROR, ICON_INFORMATION, ICON_WARNING</dd>
38  * <dt><b>Events:</b></dt>
39  * <dd>Selection</dd>
40  * </dl>
41  * </p><p>
42  * Note: Only one of the styles ICON_ERROR, ICON_INFORMATION,
43  * and ICON_WARNING may be specified.
44  * </p><p>
45  * IMPORTANT: This class is intended to be subclassed <em>only</em>
46  * within the SWT implementation.
47  * </p>
48  *
49  * @see <a href="http://www.eclipse.org/swt/snippets/#tooltips">Tool Tips snippets</a>
50  * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: ControlExample</a>
51  * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
52  *
53  * @since 3.2
54  */
55 public class ToolTip : Widget {
56     Shell parent;
57     String text, message;
58     TrayItem item;
59     int x, y;
60     int timerId;
61     void* layoutText, layoutMessage;
62     int [] borderPolygon;
63     bool spikeAbove, autohide;
64     CallbackData timerProcCallbackData;
65 
66     static const int BORDER = 5;
67     static const int PADDING = 5;
68     static const int INSET = 4;
69     static const int TIP_HEIGHT = 20;
70     static const int IMAGE_SIZE = 16;
71     static const int DELAY = 8000;
72 
73 /**
74  * Constructs a new instance of this class given its parent
75  * and a style value describing its behavior and appearance.
76  * <p>
77  * The style value is either one of the style constants defined in
78  * class <code>SWT</code> which is applicable to instances of this
79  * class, or must be built by <em>bitwise OR</em>'ing together
80  * (that is, using the <code>int</code> "|" operator) two or more
81  * of those <code>SWT</code> style constants. The class description
82  * lists the style constants that are applicable to the class.
83  * Style bits are also inherited from superclasses.
84  * </p>
85  *
86  * @param parent a composite control which will be the parent of the new instance (cannot be null)
87  * @param style the style of control to construct
88  *
89  * @exception IllegalArgumentException <ul>
90  *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
91  * </ul>
92  * @exception SWTException <ul>
93  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
94  *    <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
95  * </ul>
96  *
97  * @see SWT#ICON_ERROR
98  * @see SWT#ICON_INFORMATION
99  * @see SWT#ICON_WARNING
100  * @see Widget#checkSubclass
101  * @see Widget#getStyle
102  */
103 public this (Shell parent, int style) {
104     super (parent, checkStyle (style));
105     this.parent = parent;
106     createWidget (0);
107 }
108 
109 static int checkStyle (int style) {
110     int mask = SWT.ICON_ERROR | SWT.ICON_INFORMATION | SWT.ICON_WARNING;
111     if ((style & mask) is 0) return style;
112     return checkBits (style, SWT.ICON_INFORMATION, SWT.ICON_WARNING, SWT.ICON_ERROR, 0, 0, 0);
113 }
114 
115 /**
116  * Adds the listener to the collection of listeners who will
117  * be notified when the receiver is selected by the user, by sending
118  * it one of the messages defined in the <code>SelectionListener</code>
119  * interface.
120  * <p>
121  * <code>widgetSelected</code> is called when the receiver is selected.
122  * <code>widgetDefaultSelected</code> is not called.
123  * </p>
124  *
125  * @param listener the listener which should be notified when the receiver is selected by the user
126  *
127  * @exception IllegalArgumentException <ul>
128  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
129  * </ul>
130  * @exception SWTException <ul>
131  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
132  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
133  * </ul>
134  *
135  * @see SelectionListener
136  * @see #removeSelectionListener
137  * @see SelectionEvent
138  */
139 public void addSelectionListener (SelectionListener listener) {
140     checkWidget ();
141     if (listener is null) error (SWT.ERROR_NULL_ARGUMENT);
142     TypedListener typedListener = new TypedListener (listener);
143     addListener (SWT.Selection,typedListener);
144     addListener (SWT.DefaultSelection,typedListener);
145 }
146 
147 void configure () {
148     auto screen = OS.gdk_screen_get_default ();
149     OS.gtk_widget_realize (handle);
150     int monitorNumber = OS.gdk_screen_get_monitor_at_window (screen, OS.GTK_WIDGET_WINDOW (handle));
151     GdkRectangle* dest = new GdkRectangle ();
152     OS.gdk_screen_get_monitor_geometry (screen, monitorNumber, dest);
153     Point point = getSize (dest.width / 4);
154     int w = point.x;
155     int h = point.y;
156     point = getLocation ();
157     int x = point.x;
158     int y = point.y;
159     OS.gtk_window_resize (cast(GtkWindow*)handle, w, h + TIP_HEIGHT);
160     int[] polyline;
161     spikeAbove = dest.height >= y + h + TIP_HEIGHT;
162     if (dest.width >= x + w) {
163         if (dest.height >= y + h + TIP_HEIGHT) {
164             int t = TIP_HEIGHT;
165             polyline = [
166                 0, 5+t, 1, 5+t, 1, 3+t, 3, 1+t,  5, 1+t, 5, t,
167                 16, t, 16, 0, 35, t,
168                 w-5, t, w-5, 1+t, w-3, 1+t, w-1, 3+t, w-1, 5+t, w, 5+t,
169                 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,
170                 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,
171                 0, 5+t];
172             borderPolygon = [
173                 0, 5+t, 1, 4+t, 1, 3+t, 3, 1+t,  4, 1+t, 5, t,
174                 16, t, 16, 1, 35, t,
175                 w-6, 0+t, w-5, 1+t, w-4, 1+t, w-2, 3+t, w-2, 4+t, w-1, 5+t,
176                 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,
177                 5, h-1+t, 4, h-2+t, 3, h-2+t, 1, h-4+t, 1, h-5+t, 0, h-6+t,
178                 0, 5+t];
179             if ((parent.style & SWT.MIRRORED) !is 0) {
180                 x -= w - 36;
181                 polyline[12] = w-36;
182                 polyline[14] = w-16;
183                 polyline[16] = w-15;
184                 borderPolygon[12] = w-35;
185                 borderPolygon[14] = borderPolygon[16]  = w-16;
186             }
187             OS.gtk_window_move (cast(GtkWindow*)handle, Math.max(0, x - 17), y);
188         } else {
189             polyline = [
190                 0, 5, 1, 5, 1, 3, 3, 1,  5, 1, 5, 0,
191                 w-5, 0, w-5, 1, w-3, 1, w-1, 3, w-1, 5, w, 5,
192                 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,
193                 35, h, 16, h+TIP_HEIGHT, 16, h,
194                 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,
195                 0, 5];
196             borderPolygon = [
197                 0, 5, 1, 4, 1, 3, 3, 1,  4, 1, 5, 0,
198                 w-6, 0, w-5, 1, w-4, 1, w-2, 3, w-2, 4, w-1, 5,
199                 w-1, h-6, w-2, h-5, w-2, h-4, w-4, h-2, w-5, h-2, w-6, h-1,
200                 35, h-1, 17, h+TIP_HEIGHT-2, 17, h-1,
201                 5, h-1, 4, h-2, 3, h-2, 1, h-4, 1, h-5, 0, h-6,
202                 0, 5];
203             if ((parent.style & SWT.MIRRORED) !is 0) {
204                 x -= w - 36;
205                 polyline [42] =  polyline [44] =  w-16;
206                 polyline [46] = w-35;
207                 borderPolygon[36] = borderPolygon[38] = w-17;
208                 borderPolygon [40] = w-35;
209             }
210             OS.gtk_window_move (cast(GtkWindow*)handle, Math.max(0, x - 17), y - h - TIP_HEIGHT);
211         }
212     } else {
213         if (dest.height >= y + h + TIP_HEIGHT) {
214             int t = TIP_HEIGHT;
215             polyline = [
216                 0, 5+t, 1, 5+t, 1, 3+t, 3, 1+t,  5, 1+t, 5, t,
217                 w-35, t, w-16, 0, w-16, t,
218                 w-5, t, w-5, 1+t, w-3, 1+t, w-1, 3+t, w-1, 5+t, w, 5+t,
219                 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,
220                 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,
221                 0, 5+t];
222             borderPolygon = [
223                 0, 5+t, 1, 4+t, 1, 3+t, 3, 1+t,  4, 1+t, 5, t,
224                 w-35, t, w-17, 2, w-17, t,
225                 w-6, t, w-5, 1+t, w-4, 1+t, w-2, 3+t, w-2, 4+t, w-1, 5+t,
226                 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,
227                 5, h-1+t, 4, h-2+t, 3, h-2+t, 1, h-4+t, 1, h-5+t, 0, h-6+t,
228                 0, 5+t];
229             if ((parent.style & SWT.MIRRORED) !is 0) {
230                 x += w - 35;
231                 polyline [12] = polyline [14] = 16;
232                 polyline [16] = 35;
233                 borderPolygon[12] =  borderPolygon[14] = 16;
234                 borderPolygon [16] = 35;
235             }
236             OS.gtk_window_move (cast(GtkWindow*)handle, Math.min(dest.width - w, x - w + 17), y);
237         } else {
238             polyline = [
239                 0, 5, 1, 5, 1, 3, 3, 1,  5, 1, 5, 0,
240                 w-5, 0, w-5, 1, w-3, 1, w-1, 3, w-1, 5, w, 5,
241                 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,
242                 w-16, h, w-16, h+TIP_HEIGHT, w-35, h,
243                 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,
244                 0, 5];
245             borderPolygon = [
246                 0, 5, 1, 4, 1, 3, 3, 1,  4, 1, 5, 0,
247                 w-6, 0, w-5, 1, w-4, 1, w-2, 3, w-2, 4, w-1, 5,
248                 w-1, h-6, w-2, h-5, w-2, h-4, w-4, h-2, w-5, h-2, w-6, h-1,
249                 w-17, h-1, w-17, h+TIP_HEIGHT-2, w-36, h-1,
250                 5, h-1, 4, h-2, 3, h-2, 1, h-4, 1, h-5, 0, h-6,
251                 0, 5];
252             if ((parent.style & SWT.MIRRORED) !is 0) {
253                 x += w - 35;
254                 polyline [42] =  35;
255                 polyline [44] = polyline [46] = 16;
256                 borderPolygon[36] = 35;
257                 borderPolygon[38] = borderPolygon [40] = 17;
258             }
259             OS.gtk_window_move (cast(GtkWindow*)handle, Math.min(dest.width - w, x - w + 17), y - h - TIP_HEIGHT);
260         }
261     }
262     auto rgn = OS.gdk_region_polygon ( cast(GdkPoint*)polyline.ptr, cast(int)/*64bit*/polyline.length / 2, OS.GDK_EVEN_ODD_RULE);
263     OS.gtk_widget_realize (handle);
264     auto window = OS.GTK_WIDGET_WINDOW (handle);
265     OS.gdk_window_shape_combine_region (window, rgn, 0, 0);
266     OS.gdk_region_destroy (rgn);
267 }
268 
269 override void createHandle (int index) {
270     state |= HANDLE;
271     if ((style & SWT.BALLOON) !is 0) {
272         handle = OS.gtk_window_new (OS.GTK_WINDOW_POPUP);
273         Color background = display.getSystemColor (SWT.COLOR_INFO_BACKGROUND);
274         OS.gtk_widget_modify_bg (handle, OS.GTK_STATE_NORMAL, background.handle);
275         OS.gtk_widget_set_app_paintable (handle, true);
276     } else {
277         handle = cast(GtkWidget*)OS.gtk_tooltips_new ();
278         if (handle is null) SWT.error (SWT.ERROR_NO_HANDLES);
279         /*
280         * Bug in Solaris-GTK.  Invoking gtk_tooltips_force_window()
281         * can cause a crash in older versions of GTK.  The fix is
282         * to avoid this call if the GTK version is older than 2.2.x.
283         */
284         if (OS.GTK_VERSION >= OS.buildVERSION (2, 2, 1)) {
285             OS.gtk_tooltips_force_window (cast(GtkTooltips*)handle);
286         }
287         OS.g_object_ref (handle);
288         OS.gtk_object_sink (cast(GtkObject*)handle);
289     }
290 }
291 
292 override void createWidget (int index) {
293     super.createWidget (index);
294     text = "";
295     message = "";
296     x = y = -1;
297     autohide = true;
298 }
299 
300 override void deregister () {
301     super.deregister ();
302     if ((style & SWT.BALLOON) is 0) {
303         auto tipWindow = OS.GTK_TOOLTIPS_TIP_WINDOW (cast(GtkTooltips*)handle);
304         if (tipWindow !is null) display.removeWidget (tipWindow);
305     }
306 }
307 
308 override void destroyWidget () {
309     auto topHandle = topHandle ();
310     releaseHandle ();
311     if (topHandle !is null && (state & HANDLE) !is 0) {
312         if ((style & SWT.BALLOON) !is 0) {
313             OS.gtk_widget_destroy (topHandle);
314         } else {
315             OS.g_object_unref (topHandle);
316         }
317     }
318 }
319 
320 /**
321  * Returns <code>true</code> if the receiver is automatically
322  * hidden by the platform, and <code>false</code> otherwise.
323  *
324  * @return the receiver's auto hide state
325  *
326  * @exception SWTException <ul>
327  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
328  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
329  * </ul>
330  *
331  */
332 public bool getAutoHide () {
333     checkWidget ();
334     return autohide;
335 }
336 
337 Point getLocation () {
338     int x = this.x;
339     int y = this.y;
340     if (item !is null) {
341         auto itemHandle = item.handle;
342         OS.gtk_widget_realize (itemHandle);
343         auto window = OS.GTK_WIDGET_WINDOW (itemHandle);
344         int px, py;
345         OS.gdk_window_get_origin (cast(GdkWindow*)window, &px, &py);
346         x = px + OS.GTK_WIDGET_WIDTH (itemHandle) / 2;
347         y = py + OS.GTK_WIDGET_HEIGHT (itemHandle) / 2;
348     }
349     if (x is -1 || y is -1) {
350         int px, py;
351         OS.gdk_window_get_pointer (null, &px, &py, null);
352         x = px;
353         y = py;
354     }
355     return new Point(x, y);
356 }
357 
358 /**
359  * Returns the receiver's message, which will be an empty
360  * string if it has never been set.
361  *
362  * @return the receiver's message
363  *
364  * @exception SWTException <ul>
365  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
366  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
367  * </ul>
368  */
369 public String getMessage () {
370     checkWidget ();
371     return message;
372 }
373 
374 override String getNameText () {
375     return getText ();
376 }
377 
378 /**
379  * Returns the receiver's parent, which must be a <code>Shell</code>.
380  *
381  * @return the receiver's parent
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  */
388 public Shell getParent () {
389     checkWidget ();
390     return parent;
391 }
392 
393 Point getSize (int maxWidth) {
394     int textWidth = 0, messageWidth = 0;
395     int w, h;
396     if (layoutText !is null) {
397         OS.pango_layout_set_width (layoutText, -1);
398         OS.pango_layout_get_size (layoutText, &w, &h);
399         textWidth = OS.PANGO_PIXELS (w );
400     }
401     if (layoutMessage !is null) {
402         OS.pango_layout_set_width (layoutMessage, -1);
403         OS.pango_layout_get_size (layoutMessage, &w, &h);
404         messageWidth = OS.PANGO_PIXELS (w );
405     }
406     int messageTrim = 2 * INSET + 2 * BORDER + 2 * PADDING;
407     bool hasImage = layoutText !is null && (style & (SWT.ICON_ERROR | SWT.ICON_INFORMATION | SWT.ICON_WARNING)) !is 0;
408     int textTrim = messageTrim + (hasImage ? IMAGE_SIZE : 0);
409     int width = Math.min (maxWidth, Math.max (textWidth + textTrim, messageWidth + messageTrim));
410     int textHeight = 0, messageHeight = 0;
411     if (layoutText !is null) {
412         OS.pango_layout_set_width (layoutText, (maxWidth - textTrim) * OS.PANGO_SCALE);
413         OS.pango_layout_get_size (layoutText, &w, &h);
414         textHeight = OS.PANGO_PIXELS (h );
415     }
416     if (layoutMessage !is null) {
417         OS.pango_layout_set_width (layoutMessage, (maxWidth - messageTrim) * OS.PANGO_SCALE);
418         OS.pango_layout_get_size (layoutMessage, &w, &h);
419         messageHeight = OS.PANGO_PIXELS (h);
420     }
421     int height = 2 * BORDER + 2 * PADDING + messageHeight;
422     if (layoutText !is null) height += Math.max (IMAGE_SIZE, textHeight) + 2 * PADDING;
423     return new Point(width, height);
424 }
425 
426 /**
427  * Returns the receiver's text, which will be an empty
428  * string if it has never been set.
429  *
430  * @return the receiver's text
431  *
432  * @exception SWTException <ul>
433  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
434  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
435  * </ul>
436  */
437 public String getText () {
438     checkWidget ();
439     return text;
440 }
441 
442 /**
443  * Returns <code>true</code> if the receiver is visible, and
444  * <code>false</code> otherwise.
445  * <p>
446  * If one of the receiver's ancestors is not visible or some
447  * other condition makes the receiver not visible, this method
448  * may still indicate that it is considered visible even though
449  * it may not actually be showing.
450  * </p>
451  *
452  * @return the receiver's visibility state
453  *
454  * @exception SWTException <ul>
455  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
456  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
457  * </ul>
458  */
459 public bool getVisible () {
460     checkWidget ();
461     if ((style & SWT.BALLOON) !is 0) return OS.GTK_WIDGET_VISIBLE (handle);
462     auto tipWindow = OS.GTK_TOOLTIPS_TIP_WINDOW (cast(GtkTooltips*)handle);
463     return OS.GTK_WIDGET_VISIBLE (tipWindow);
464 }
465 
466 override int gtk_button_press_event (GtkWidget* widget, GdkEventButton* event) {
467     notifyListeners (SWT.Selection, new Event ());
468     setVisible (false);
469     return 0;
470 }
471 
472 override int gtk_expose_event (GtkWidget* widget, GdkEventExpose* event) {
473     auto window = OS.GTK_WIDGET_WINDOW (handle);
474     auto gdkGC = cast(GdkGC*)OS.gdk_gc_new (window);
475     OS.gdk_draw_polygon (window, gdkGC, 0, cast(GdkPoint*)borderPolygon.ptr, cast(int)/*64bit*/borderPolygon.length / 2);
476     int x = BORDER + PADDING;
477     int y = BORDER + PADDING;
478     if (spikeAbove) y += TIP_HEIGHT;
479     if (layoutText !is null) {
480         String buffer = null;
481         int id = style & (SWT.ICON_ERROR | SWT.ICON_INFORMATION | SWT.ICON_WARNING);
482         switch (id) {
483             case SWT.ICON_ERROR: buffer = "gtk-dialog-error"; break;
484             case SWT.ICON_INFORMATION: buffer = "gtk-dialog-info"; break;
485             case SWT.ICON_WARNING: buffer = "gtk-dialog-warning"; break;
486             default:
487         }
488         if (buffer !is null) {
489             auto style = OS.gtk_widget_get_default_style ();
490             auto pixbuf = OS.gtk_icon_set_render_icon (
491                 OS.gtk_icon_factory_lookup_default (buffer.ptr),
492                 style,
493                 OS.GTK_TEXT_DIR_NONE,
494                 OS.GTK_STATE_NORMAL,
495                 OS.GTK_ICON_SIZE_MENU,
496                 null,
497                 null);
498             OS.gdk_draw_pixbuf (window, gdkGC, pixbuf, 0, 0, x, y, IMAGE_SIZE, IMAGE_SIZE, OS.GDK_RGB_DITHER_NORMAL, 0, 0);
499             OS.g_object_unref (pixbuf);
500             x += IMAGE_SIZE;
501         }
502         x += INSET;
503         OS.gdk_draw_layout (window, gdkGC, x, y, layoutText);
504         int w, h;
505         OS.pango_layout_get_size (layoutText, &w, &h);
506         y += 2 * PADDING + Math.max (IMAGE_SIZE, OS.PANGO_PIXELS (h ));
507     }
508     if (layoutMessage !is null) {
509         x = BORDER + PADDING + INSET;
510         OS.gdk_draw_layout (window, gdkGC, x, y, layoutMessage);
511     }
512     OS.g_object_unref (gdkGC);
513     return 0;
514 }
515 
516 override int gtk_size_allocate (GtkWidget* widget, ptrdiff_t allocation) {
517     Point point = getLocation ();
518     int x = point.x;
519     int y = point.y;
520     auto screen = OS.gdk_screen_get_default ();
521     OS.gtk_widget_realize (widget);
522     int monitorNumber = OS.gdk_screen_get_monitor_at_window (screen, OS.GTK_WIDGET_WINDOW (widget));
523     GdkRectangle* dest = new GdkRectangle ();
524     OS.gdk_screen_get_monitor_geometry (screen, monitorNumber, dest);
525     int w = OS.GTK_WIDGET_WIDTH (widget);
526     int h = OS.GTK_WIDGET_HEIGHT (widget);
527     if (dest.height < y + h) y -= h;
528     if (dest.width < x + w) x -= w;
529     OS.gtk_window_move (cast(GtkWindow*)widget, x, y);
530     return 0;
531 }
532 
533 override void hookEvents () {
534     if ((style & SWT.BALLOON) !is 0) {
535         OS.g_signal_connect_closure (handle, OS.expose_event.ptr, display.closures [EXPOSE_EVENT], false);
536         OS.gtk_widget_add_events (handle, OS.GDK_BUTTON_PRESS_MASK);
537         OS.g_signal_connect_closure (handle, OS.button_press_event.ptr, display.closures [BUTTON_PRESS_EVENT], false);
538     } else {
539         auto tipWindow = OS.GTK_TOOLTIPS_TIP_WINDOW (cast(GtkTooltips*)handle);
540         if (tipWindow !is null) {
541             OS.g_signal_connect_closure (tipWindow, OS.size_allocate.ptr, display.closures [SIZE_ALLOCATE], false);
542             OS.gtk_widget_add_events (tipWindow, OS.GDK_BUTTON_PRESS_MASK);
543             OS.g_signal_connect_closure (tipWindow, OS.button_press_event.ptr, display.closures [BUTTON_PRESS_EVENT], false);
544         }
545     }
546 }
547 
548 /**
549  * Returns <code>true</code> if the receiver is visible and all
550  * of the receiver's ancestors are visible and <code>false</code>
551  * otherwise.
552  *
553  * @return the receiver's visibility state
554  *
555  * @exception SWTException <ul>
556  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
557  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
558  * </ul>
559  *
560  * @see #getVisible
561  */
562 public bool isVisible () {
563     checkWidget ();
564     return getVisible ();
565 }
566 
567 override void register () {
568     super.register ();
569     if ((style & SWT.BALLOON) is 0) {
570         auto tipWindow = OS.GTK_TOOLTIPS_TIP_WINDOW (cast(GtkTooltips*)handle);
571         if (tipWindow !is null) display.addWidget (tipWindow, this);
572     }
573 }
574 
575 override void releaseWidget () {
576     super.releaseWidget ();
577     if (layoutText !is null) OS.g_object_unref (layoutText);
578     layoutText = null;
579     if (layoutMessage !is null) OS.g_object_unref (layoutMessage);
580     layoutMessage = null;
581     if (timerId !is 0) OS.gtk_timeout_remove(timerId);
582     timerId = 0;
583     text = null;
584     message = null;
585     borderPolygon = null;
586 }
587 
588 /**
589  * Removes the listener from the collection of listeners who will
590  * be notified when the receiver is selected by the user.
591  *
592  * @param listener the listener which should no longer be notified
593  *
594  * @exception IllegalArgumentException <ul>
595  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
596  * </ul>
597  * @exception SWTException <ul>
598  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
599  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
600  * </ul>
601  *
602  * @see SelectionListener
603  * @see #addSelectionListener
604  */
605 public void removeSelectionListener (SelectionListener listener) {
606     checkWidget();
607     if (listener is null) error (SWT.ERROR_NULL_ARGUMENT);
608     if (eventTable is null) return;
609     eventTable.unhook (SWT.Selection, listener);
610     eventTable.unhook (SWT.DefaultSelection, listener);
611 }
612 
613 /**
614  * Makes the receiver hide automatically when <code>true</code>,
615  * and remain visible when <code>false</code>.
616  *
617  * @param autoHide the auto hide state
618  *
619  * @exception SWTException <ul>
620  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
621  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
622  * </ul>
623  *
624  * @see #getVisible
625  * @see #setVisible
626  */
627 public void setAutoHide (bool autohide) {
628     checkWidget ();
629     this.autohide = autohide;
630     //TODO - update when visible
631 }
632 
633 /**
634  * Sets the location of the receiver, which must be a tooltip,
635  * to the point specified by the arguments which are relative
636  * to the display.
637  * <p>
638  * Note that this is different from most widgets where the
639  * location of the widget is relative to the parent.
640  * </p>
641  *
642  * @param x the new x coordinate for the receiver
643  * @param y the new y coordinate for the receiver
644  *
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 public void setLocation (int x, int y) {
651     checkWidget ();
652     this.x = x;
653     this.y = y;
654     if ((style & SWT.BALLOON) !is 0) {
655         if (OS.GTK_WIDGET_VISIBLE (handle)) configure ();
656     } else {
657         auto tipWindow = OS.GTK_TOOLTIPS_TIP_WINDOW (cast(GtkTooltips*)handle);
658         if (OS.GTK_WIDGET_VISIBLE (tipWindow)) {
659             OS.gtk_window_move (cast(GtkWindow*)tipWindow, x, y);
660         }
661     }
662 }
663 
664 /**
665  * Sets the location of the receiver, which must be a tooltip,
666  * to the point specified by the argument which is relative
667  * to the display.
668  * <p>
669  * Note that this is different from most widgets where the
670  * location of the widget is relative to the parent.
671  * </p><p>
672  * Note that the platform window manager ultimately has control
673  * over the location of tooltips.
674  * </p>
675  *
676  * @param location the new location for the receiver
677  *
678  * @exception IllegalArgumentException <ul>
679  *    <li>ERROR_NULL_ARGUMENT - if the point is null</li>
680  * </ul>
681  * @exception SWTException <ul>
682  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
683  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
684  * </ul>
685  */
686 public void setLocation (Point location) {
687     checkWidget ();
688     if (location is null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
689     setLocation (location.x, location.y);
690 }
691 
692 /**
693  * Sets the receiver's message.
694  *
695  * @param string the new message
696  *
697  * @exception SWTException <ul>
698  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
699  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
700  * </ul>
701  */
702 public void setMessage (String string) {
703     checkWidget ();
704     // SWT extension: allow null for zero length string
705     //if (string is null) error (SWT.ERROR_NULL_ARGUMENT);
706     message = string;
707     if ((style & SWT.BALLOON) is 0) return;
708     if (layoutMessage !is null) OS.g_object_unref (layoutMessage);
709     layoutMessage = null;
710     if (message.length !is 0) {
711         layoutMessage = OS.gtk_widget_create_pango_layout (handle, message.toStringzValidPtr());
712         if (OS.GTK_VERSION >= OS.buildVERSION (2, 4, 0)) {
713             OS.pango_layout_set_auto_dir (layoutMessage, false);
714         }
715         OS.pango_layout_set_wrap (layoutMessage, OS.PANGO_WRAP_WORD_CHAR);
716     }
717     if (OS.GTK_WIDGET_VISIBLE (handle)) configure ();
718 }
719 
720 /**
721  * Sets the receiver's text.
722  *
723  * @param string the new text
724  *
725  * @exception SWTException <ul>
726  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
727  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
728  * </ul>
729  */
730 public void setText (String string) {
731     checkWidget ();
732     // SWT extension: allow null for zero length string
733     //if (string is null) error (SWT.ERROR_NULL_ARGUMENT);
734     text = string;
735     if ((style & SWT.BALLOON) is 0) return;
736     if (layoutText !is null) OS.g_object_unref (layoutText);
737     layoutText = null;
738     if (text.length !is 0) {
739         layoutText = OS.gtk_widget_create_pango_layout (handle, text.toStringzValidPtr());
740         if (OS.GTK_VERSION >= OS.buildVERSION (2, 4, 0)) {
741             OS.pango_layout_set_auto_dir (layoutText, false);
742         }
743         auto boldAttr = OS.pango_attr_weight_new (OS.PANGO_WEIGHT_BOLD);
744         boldAttr.start_index = 0;
745         boldAttr.end_index = cast(int)/*64bit*/text.length+1;
746         auto attrList = OS.pango_attr_list_new ();
747         OS.pango_attr_list_insert (attrList, boldAttr);
748         OS.pango_layout_set_attributes (layoutText, attrList);
749         OS.pango_attr_list_unref (attrList);
750         OS.pango_layout_set_wrap (layoutText, OS.PANGO_WRAP_WORD_CHAR);
751     }
752     if (OS.GTK_WIDGET_VISIBLE (handle)) configure ();
753 }
754 
755 /**
756  * Marks the receiver as visible if the argument is <code>true</code>,
757  * and marks it invisible otherwise.
758  * <p>
759  * If one of the receiver's ancestors is not visible or some
760  * other condition makes the receiver not visible, marking
761  * it visible may not actually cause it to be displayed.
762  * </p>
763  *
764  * @param visible the new visibility state
765  *
766  * @exception SWTException <ul>
767  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
768  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
769  * </ul>
770  */
771 public void setVisible (bool visible) {
772     if (timerId !is 0) OS.gtk_timeout_remove(timerId);
773     timerId = 0;
774     if (visible) {
775         if ((style & SWT.BALLOON) !is 0) {
776             configure ();
777             OS.gtk_widget_show (handle);
778         } else {
779             auto vboxHandle = parent.vboxHandle;
780             String string = text;
781             if (text.length > 0) string ~= "\n\n";
782             string ~= message;
783             char* buffer = toStringz( string );
784             OS.gtk_tooltips_set_tip (cast(GtkTooltips*)handle, vboxHandle, buffer, null);
785             auto data = OS.gtk_tooltips_data_get (vboxHandle);
786             OS.GTK_TOOLTIPS_SET_ACTIVE (cast(GtkTooltips*)handle, data);
787             OS.gtk_tooltips_set_tip (cast(GtkTooltips*)handle, vboxHandle, buffer, null);
788         }
789         if (autohide) timerId = display.doWindowTimerAdd( &timerProcCallbackData, DELAY, handle);
790     } else {
791         if ((style & SWT.BALLOON) !is 0) {
792             OS.gtk_widget_hide (handle);
793         } else {
794             auto tipWindow = OS.GTK_TOOLTIPS_TIP_WINDOW (cast(GtkTooltips*)handle);
795             OS.gtk_widget_hide (tipWindow);
796         }
797     }
798 }
799 
800 override int timerProc (GtkWidget* widget) {
801     if ((style & SWT.BALLOON) !is 0) {
802         OS.gtk_widget_hide (handle);
803     } else {
804         auto tipWindow = OS.GTK_TOOLTIPS_TIP_WINDOW (cast(GtkTooltips*)handle);
805         OS.gtk_widget_hide (tipWindow);
806     }
807     return 0;
808 }
809 
810 }