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.TrayItem;
14 
15 import java.lang.all;
16 
17 import org.eclipse.swt.SWT;
18 import org.eclipse.swt.widgets.Tray;
19 import org.eclipse.swt.widgets.ToolTip;
20 import org.eclipse.swt.widgets.ImageList;
21 import org.eclipse.swt.widgets.Item;
22 import org.eclipse.swt.events.SelectionListener;
23 import org.eclipse.swt.events.SelectionEvent;
24 import org.eclipse.swt.events.MenuDetectListener;
25 import org.eclipse.swt.widgets.TypedListener;
26 import org.eclipse.swt.graphics.Image;
27 import org.eclipse.swt.graphics.Rectangle;
28 import org.eclipse.swt.graphics.Region;
29 import org.eclipse.swt.internal.gtk.OS;
30 
31 version(Tango){
32     import tango.util.Convert;
33 } else { // Phobos
34     import std.conv;
35 }
36 
37 /**
38  * Instances of this class represent icons that can be placed on the
39  * system tray or task bar status area.
40  * <p>
41  * <dl>
42  * <dt><b>Styles:</b></dt>
43  * <dd>(none)</dd>
44  * <dt><b>Events:</b></dt>
45  * <dd>DefaultSelection, MenuDetect, Selection</dd>
46  * </dl>
47  * </p><p>
48  * IMPORTANT: This class is <em>not</em> intended to be subclassed.
49  * </p>
50  *
51  * @see <a href="http://www.eclipse.org/swt/snippets/#tray">Tray, TrayItem snippets</a>
52  * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
53  *
54  * @since 3.0
55  */
56 public class TrayItem : Item {
57     Tray parent;
58     ToolTip toolTip;
59     String toolTipText;
60     GtkWidget* imageHandle;
61     GtkWidget* tooltipsHandle;
62     ImageList imageList;
63 
64 /**
65  * Constructs a new instance of this class given its parent
66  * (which must be a <code>Tray</code>) and a style value
67  * describing its behavior and appearance. The item is added
68  * to the end of the items maintained by its parent.
69  * <p>
70  * The style value is either one of the style constants defined in
71  * class <code>SWT</code> which is applicable to instances of this
72  * class, or must be built by <em>bitwise OR</em>'ing together
73  * (that is, using the <code>int</code> "|" operator) two or more
74  * of those <code>SWT</code> style constants. The class description
75  * lists the style constants that are applicable to the class.
76  * Style bits are also inherited from superclasses.
77  * </p>
78  *
79  * @param parent a composite control which will be the parent of the new instance (cannot be null)
80  * @param style the style of control to construct
81  *
82  * @exception IllegalArgumentException <ul>
83  *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
84  * </ul>
85  * @exception SWTException <ul>
86  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
87  *    <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
88  * </ul>
89  *
90  * @see SWT
91  * @see Widget#checkSubclass
92  * @see Widget#getStyle
93  */
94 public this (Tray parent, int style) {
95     super (parent, style);
96     this.parent = parent;
97     createWidget (parent.getItemCount ());
98 }
99 
100 /**
101  * Adds the listener to the collection of listeners who will
102  * be notified when the platform-specific context menu trigger
103  * has occurred, by sending it one of the messages defined in
104  * the <code>MenuDetectListener</code> interface.
105  *
106  * @param listener the listener which should be notified
107  *
108  * @exception IllegalArgumentException <ul>
109  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
110  * </ul>
111  * @exception SWTException <ul>
112  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
113  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
114  * </ul>
115  *
116  * @see MenuDetectListener
117  * @see #removeMenuDetectListener
118  *
119  * @since 3.3
120  */
121 public void addMenuDetectListener (MenuDetectListener listener) {
122     checkWidget ();
123     if (listener is null) error (SWT.ERROR_NULL_ARGUMENT);
124     TypedListener typedListener = new TypedListener (listener);
125     addListener (SWT.MenuDetect, typedListener);
126 }
127 
128 /**
129  * Adds the listener to the collection of listeners who will
130  * be notified when the receiver is selected by the user, by sending
131  * it one of the messages defined in the <code>SelectionListener</code>
132  * interface.
133  * <p>
134  * <code>widgetSelected</code> is called when the receiver is selected
135  * <code>widgetDefaultSelected</code> is called when the receiver is double-clicked
136  * </p>
137  *
138  * @param listener the listener which should be notified when the receiver is selected by the user
139  *
140  * @exception IllegalArgumentException <ul>
141  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
142  * </ul>
143  * @exception SWTException <ul>
144  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
145  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
146  * </ul>
147  *
148  * @see SelectionListener
149  * @see #removeSelectionListener
150  * @see SelectionEvent
151  */
152 public void addSelectionListener(SelectionListener listener) {
153     checkWidget ();
154     if (listener is null) error (SWT.ERROR_NULL_ARGUMENT);
155     TypedListener typedListener = new TypedListener (listener);
156     addListener (SWT.Selection, typedListener);
157     addListener (SWT.DefaultSelection, typedListener);
158 }
159 
160 protected override void checkSubclass () {
161     if (!isValidSubclass ()) error (SWT.ERROR_INVALID_SUBCLASS);
162 }
163 
164 override void createWidget (int index) {
165     super.createWidget (index);
166     parent.createItem (this, index);
167 }
168 
169 override void createHandle (int index) {
170     state |= HANDLE;
171     handle = OS.gtk_plug_new (null);
172     if (handle is null) error (SWT.ERROR_NO_HANDLES);
173     imageHandle = OS.gtk_image_new ();
174     if (imageHandle is null) error (SWT.ERROR_NO_HANDLES);
175     OS.gtk_container_add (cast(GtkContainer*)handle, imageHandle);
176     OS.gtk_widget_show (handle);
177     OS.gtk_widget_show (imageHandle);
178     auto id = OS.gtk_plug_get_id (cast(GtkPlug*)handle);
179     int monitor = 0;
180     auto screen = OS.gdk_screen_get_default ();
181     if (screen !is null) {
182         monitor = OS.gdk_screen_get_number (screen);
183     }
184     auto trayAtom = OS.gdk_atom_intern (toStringz("_NET_SYSTEM_TRAY_S" ~ to!(String)(monitor)), true);
185     auto xTrayAtom = OS.gdk_x11_atom_to_xatom (trayAtom);
186     auto xDisplay = OS.GDK_DISPLAY ();
187     auto trayWindow = OS.XGetSelectionOwner (xDisplay, xTrayAtom);
188     auto messageAtom = OS.gdk_atom_intern (toStringz("_NET_SYSTEM_TRAY_OPCODE"), true);
189     auto xMessageAtom = OS.gdk_x11_atom_to_xatom (messageAtom);
190     XClientMessageEvent* event = cast(XClientMessageEvent*)OS.g_malloc (XClientMessageEvent.sizeof);
191     event.type = OS.ClientMessage;
192     event.window = trayWindow;
193     event.message_type = xMessageAtom;
194     event.format = 32;
195     event.data.l [0] = OS.GDK_CURRENT_TIME;
196     event.data.l [1] = OS.SYSTEM_TRAY_REQUEST_DOCK;
197     event.data.l [2] = id;
198     OS.XSendEvent (xDisplay, trayWindow, false, OS.NoEventMask, cast(XEvent*) event);
199     OS.g_free (event);
200 }
201 
202 override void deregister () {
203     super.deregister ();
204     display.removeWidget (imageHandle);
205 }
206 
207 override void destroyWidget () {
208     parent.destroyItem (this);
209     releaseHandle ();
210 }
211 
212 /**
213  * Returns the receiver's parent, which must be a <code>Tray</code>.
214  *
215  * @return the receiver's parent
216  *
217  * @exception SWTException <ul>
218  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
219  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
220  * </ul>
221  *
222  * @since 3.2
223  */
224 public Tray getParent () {
225     checkWidget ();
226     return parent;
227 }
228 
229 /**
230  * Returns the receiver's tool tip, or null if it has
231  * not been set.
232  *
233  * @return the receiver's tool tip text
234  *
235  * @exception SWTException <ul>
236  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
237  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
238  * </ul>
239  *
240  * @since 3.2
241  */
242 public ToolTip getToolTip () {
243     checkWidget ();
244     return toolTip;
245 }
246 
247 /**
248  * Returns the receiver's tool tip text, or null if it has
249  * not been set.
250  *
251  * @return the receiver's tool tip text
252  *
253  * @exception SWTException <ul>
254  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
255  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
256  * </ul>
257  */
258 public String getToolTipText () {
259     checkWidget ();
260     return toolTipText;
261 }
262 
263 override int gtk_button_press_event (GtkWidget* widget, GdkEventButton* event) {
264     if (event.type is OS.GDK_3BUTTON_PRESS) return 0;
265     if (event.button is 3 && event.type is OS.GDK_BUTTON_PRESS) {
266         sendEvent (SWT.MenuDetect);
267         return 0;
268     }
269     if (event.type is OS.GDK_2BUTTON_PRESS) {
270         postEvent (SWT.DefaultSelection);
271     } else {
272         postEvent (SWT.Selection);
273     }
274     return 0;
275 }
276 
277 override int gtk_size_allocate (GtkWidget* widget, ptrdiff_t allocation) {
278     if (image !is null && image.mask !is null) {
279         if (OS.gdk_drawable_get_depth (image.mask) is 1) {
280             int xoffset = cast(int) Math.floor (OS.GTK_WIDGET_X (widget) + ((OS.GTK_WIDGET_WIDTH (widget) - OS.GTK_WIDGET_REQUISITION_WIDTH (widget)) * 0.5) + 0.5);
281             int yoffset = cast(int) Math.floor (OS.GTK_WIDGET_Y (widget) + ((OS.GTK_WIDGET_HEIGHT (widget) - OS.GTK_WIDGET_REQUISITION_HEIGHT (widget)) * 0.5) + 0.5);
282             Rectangle b = image.getBounds();
283             auto gdkImage = OS.gdk_drawable_get_image (image.mask, 0, 0, b.width, b.height);
284             if (gdkImage is null) SWT.error(SWT.ERROR_NO_HANDLES);
285             byte[] maskData = (cast(byte*)gdkImage.mem)[ 0 .. gdkImage.bpl * gdkImage.height].dup;
286             Region region = new Region (display);
287             for (int y = 0; y < b.height; y++) {
288                 for (int x = 0; x < b.width; x++) {
289                     int index = (y * gdkImage.bpl) + (x >> 3);
290                     int theByte = maskData [index] & 0xFF;
291                     int mask = 1 << (x & 0x7);
292                     if ((theByte & mask) !is 0) {
293                         region.add (xoffset + x, yoffset + y, 1, 1);
294                     }
295                 }
296             }
297             OS.g_object_unref (gdkImage);
298             OS.gtk_widget_realize (handle);
299             auto window = OS.GTK_WIDGET_WINDOW (handle);
300             OS.gdk_window_shape_combine_region (window, region.handle, 0, 0);
301             region.dispose ();
302         }
303     }
304     return 0;
305 }
306 
307 override void hookEvents () {
308     int eventMask = OS.GDK_BUTTON_PRESS_MASK;
309     OS.gtk_widget_add_events (handle, eventMask);
310     OS.g_signal_connect_closure_by_id (handle, display.signalIds [BUTTON_PRESS_EVENT], 0, display.closures [BUTTON_PRESS_EVENT], false);
311     OS.g_signal_connect_closure_by_id (imageHandle, display.signalIds [SIZE_ALLOCATE], 0, display.closures [SIZE_ALLOCATE], false);
312  }
313 
314 /**
315  * Returns <code>true</code> if the receiver is visible and
316  * <code>false</code> otherwise.
317  *
318  * @return the receiver's visibility
319  *
320  * @exception SWTException <ul>
321  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
322  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
323  * </ul>
324  */
325 public bool getVisible () {
326     checkWidget ();
327     return OS.GTK_WIDGET_VISIBLE (handle);
328 }
329 
330 override void register () {
331     super.register ();
332     display.addWidget (imageHandle, this);
333 }
334 
335 override void releaseHandle () {
336     if (handle !is null) OS.gtk_widget_destroy (handle);
337     handle = imageHandle = null;
338     super.releaseHandle ();
339     parent = null;
340 }
341 
342 override void releaseWidget () {
343     super.releaseWidget ();
344     if (tooltipsHandle !is null) OS.g_object_unref (tooltipsHandle);
345     tooltipsHandle = null;
346     if (imageList !is null) imageList.dispose ();
347     imageList = null;
348     toolTipText = null;
349 }
350 
351 /**
352  * Removes the listener from the collection of listeners who will
353  * be notified when the platform-specific context menu trigger has
354  * occurred.
355  *
356  * @param listener the listener which should no longer be notified
357  *
358  * @exception IllegalArgumentException <ul>
359  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
360  * </ul>
361  * @exception SWTException <ul>
362  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
363  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
364  * </ul>
365  *
366  * @see MenuDetectListener
367  * @see #addMenuDetectListener
368  *
369  * @since 3.3
370  */
371 public void removeMenuDetectListener (MenuDetectListener listener) {
372     checkWidget ();
373     if (listener is null) error (SWT.ERROR_NULL_ARGUMENT);
374     if (eventTable is null) return;
375     eventTable.unhook (SWT.MenuDetect, listener);
376 }
377 
378 /**
379  * Removes the listener from the collection of listeners who will
380  * be notified when the receiver is selected by the user.
381  *
382  * @param listener the listener which should no longer be notified
383  *
384  * @exception IllegalArgumentException <ul>
385  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
386  * </ul>
387  * @exception SWTException <ul>
388  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
389  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
390  * </ul>
391  *
392  * @see SelectionListener
393  * @see #addSelectionListener
394  */
395 public void removeSelectionListener (SelectionListener listener) {
396     checkWidget ();
397     if (listener is null) error (SWT.ERROR_NULL_ARGUMENT);
398     if (eventTable is null) return;
399     eventTable.unhook (SWT.Selection, listener);
400     eventTable.unhook (SWT.DefaultSelection, listener);
401 }
402 
403 /**
404  * Sets the receiver's image.
405  *
406  * @param image the new image
407  *
408  * @exception IllegalArgumentException <ul>
409  *    <li>ERROR_INVALID_ARGUMENT - if the image has been disposed</li>
410  * </ul>
411  * @exception SWTException <ul>
412  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
413  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
414  * </ul>
415  */
416 public override void setImage (Image image) {
417     checkWidget ();
418     if (image !is null && image.isDisposed ()) error (SWT.ERROR_INVALID_ARGUMENT);
419     this.image = image;
420     if (image !is null) {
421         Rectangle rect = image.getBounds ();
422         OS.gtk_widget_set_size_request (handle, rect.width, rect.height);
423         if (imageList is null) imageList = new ImageList ();
424         int imageIndex = imageList.indexOf (image);
425         if (imageIndex is -1) {
426             imageIndex = imageList.add (image);
427         } else {
428             imageList.put (imageIndex, image);
429         }
430         auto pixbuf = imageList.getPixbuf (imageIndex);
431         OS.gtk_image_set_from_pixbuf (cast(GtkImage*)imageHandle, pixbuf);
432         OS.gtk_widget_show (imageHandle);
433     } else {
434         OS.gtk_widget_set_size_request (handle, 1, 1);
435         OS.gtk_image_set_from_pixbuf (cast(GtkImage*)imageHandle, null);
436         OS.gtk_widget_hide (imageHandle);
437     }
438 }
439 
440 /**
441  * Sets the receiver's tool tip to the argument, which
442  * may be null indicating that no tool tip should be shown.
443  *
444  * @param toolTip the new tool tip (or null)
445  *
446  * @exception SWTException <ul>
447  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
448  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
449  * </ul>
450  *
451  * @since 3.2
452  */
453 public void setToolTip (ToolTip toolTip) {
454     checkWidget ();
455     ToolTip oldTip = this.toolTip, newTip = toolTip;
456     if (oldTip !is null) oldTip.item = null;
457     this.toolTip = newTip;
458     if (newTip !is null) newTip.item = this;
459 }
460 
461 /**
462  * Sets the receiver's tool tip text to the argument, which
463  * may be null indicating that no tool tip text should be shown.
464  *
465  * @param value the new tool tip text (or null)
466  *
467  * @exception SWTException <ul>
468  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
469  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
470  * </ul>
471  */
472 public void setToolTipText (String string) {
473     checkWidget ();
474     toolTipText = string;
475     char* buffer = null;
476     if (string !is null && string.length > 0) {
477         buffer = toStringz( string );
478     }
479     if (tooltipsHandle is null) {
480         tooltipsHandle = cast(GtkWidget*)OS.gtk_tooltips_new ();
481         if (tooltipsHandle is null) error (SWT.ERROR_NO_HANDLES);
482         OS.g_object_ref (cast(GObject*)tooltipsHandle);
483         OS.gtk_object_sink (cast(GtkObject*)tooltipsHandle);
484     }
485     OS.gtk_tooltips_set_tip (cast(GtkTooltips*)tooltipsHandle, handle, buffer, null);
486 }
487 
488 /**
489  * Makes the receiver visible if the argument is <code>true</code>,
490  * and makes it invisible otherwise.
491  *
492  * @param visible the new visibility state
493  *
494  * @exception SWTException <ul>
495  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
496  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
497  * </ul>
498  */
499 public void setVisible (bool visible) {
500     checkWidget ();
501     if (OS.GTK_WIDGET_VISIBLE (handle) is visible) return;
502     if (visible) {
503         /*
504         * It is possible (but unlikely), that application
505         * code could have disposed the widget in the show
506         * event.  If this happens, just return.
507         */
508         sendEvent (SWT.Show);
509         if (isDisposed ()) return;
510         OS.gtk_widget_show (handle);
511     } else {
512         OS.gtk_widget_hide (handle);
513         sendEvent (SWT.Hide);
514     }
515 }
516 }