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.Sash;
14 
15 import java.lang.all;
16 
17 
18 
19 import org.eclipse.swt.SWT;
20 import org.eclipse.swt.SWTException;
21 import org.eclipse.swt.events.SelectionEvent;
22 import org.eclipse.swt.events.SelectionListener;
23 import org.eclipse.swt.graphics.Point;
24 import org.eclipse.swt.internal.gtk.OS;
25 
26 import org.eclipse.swt.widgets.Control;
27 import org.eclipse.swt.widgets.Composite;
28 import org.eclipse.swt.widgets.TypedListener;
29 import org.eclipse.swt.widgets.Event;
30 
31 /**
32  * Instances of the receiver represent a selectable user interface object
33  * that allows the user to drag a rubber banded outline of the sash within
34  * the parent control.
35  * <dl>
36  * <dt><b>Styles:</b></dt>
37  * <dd>HORIZONTAL, VERTICAL, SMOOTH</dd>
38  * <dt><b>Events:</b></dt>
39  * <dd>Selection</dd>
40  * </dl>
41  * <p>
42  * Note: Only one of the styles HORIZONTAL and VERTICAL may be specified.
43  * </p><p>
44  * IMPORTANT: This class is intended to be subclassed <em>only</em>
45  * within the SWT implementation.
46  * </p>
47  *
48  * @see <a href="http://www.eclipse.org/swt/snippets/#sash">Sash snippets</a>
49  * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: ControlExample</a>
50  * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
51  */
52 public class Sash : Control {
53 
54     alias Control.computeSize computeSize;
55     alias Control.setCursor setCursor;
56 
57     bool dragging;
58     int startX, startY, lastX, lastY;
59     GtkWidget* defaultCursor;
60 
61     private const static int INCREMENT = 1;
62     private const static int PAGE_INCREMENT = 9;
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#HORIZONTAL
89  * @see SWT#VERTICAL
90  * @see Widget#checkSubclass
91  * @see Widget#getStyle
92  */
93 public this (Composite parent, int style) {
94     super (parent, checkStyle (style));
95 }
96 
97 /**
98  * Adds the listener to the collection of listeners who will
99  * be notified when the control is selected by the user, by sending
100  * it one of the messages defined in the <code>SelectionListener</code>
101  * interface.
102  * <p>
103  * When <code>widgetSelected</code> is called, the x, y, width, and height fields of the event object are valid.
104  * If the receiver is being dragged, the event object detail field contains the value <code>SWT.DRAG</code>.
105  * <code>widgetDefaultSelected</code> is not called.
106  * </p>
107  *
108  * @param listener the listener which should be notified when the control is selected by the user
109  *
110  * @exception IllegalArgumentException <ul>
111  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
112  * </ul>
113  * @exception SWTException <ul>
114  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
115  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
116  * </ul>
117  *
118  * @see SelectionListener
119  * @see #removeSelectionListener
120  * @see SelectionEvent
121  */
122 public void addSelectionListener (SelectionListener listener) {
123     checkWidget ();
124     if (listener is null) error (SWT.ERROR_NULL_ARGUMENT);
125     TypedListener typedListener = new TypedListener (listener);
126     addListener (SWT.Selection,typedListener);
127     addListener (SWT.DefaultSelection,typedListener);
128 }
129 
130 static int checkStyle (int style) {
131     return checkBits (style, SWT.HORIZONTAL, SWT.VERTICAL, 0, 0, 0, 0);
132 }
133 
134 public override Point computeSize (int wHint, int hHint, bool changed) {
135     checkWidget ();
136     if (wHint !is SWT.DEFAULT && wHint < 0) wHint = 0;
137     if (hHint !is SWT.DEFAULT && hHint < 0) hHint = 0;
138     int border = getBorderWidth ();
139     int width = border * 2, height = border * 2;
140     if ((style & SWT.HORIZONTAL) !is 0) {
141         width += DEFAULT_WIDTH;  height += 3;
142     } else {
143         width += 3; height += DEFAULT_HEIGHT;
144     }
145     if (wHint !is SWT.DEFAULT) width = wHint + (border * 2);
146     if (hHint !is SWT.DEFAULT) height = hHint + (border * 2);
147     return new Point (width, height);
148 }
149 
150 override void createHandle (int index) {
151     state |= HANDLE | THEME_BACKGROUND;
152     handle = cast(GtkWidget*)OS.g_object_new (display.gtk_fixed_get_type (), null);
153     if (handle is null) error (SWT.ERROR_NO_HANDLES);
154     OS.gtk_fixed_set_has_window (handle, true);
155     OS.GTK_WIDGET_SET_FLAGS (handle, OS.GTK_CAN_FOCUS);
156     int type = (style & SWT.VERTICAL) !is 0 ? OS.GDK_SB_H_DOUBLE_ARROW : OS.GDK_SB_V_DOUBLE_ARROW;
157     defaultCursor = cast(GtkWidget*)OS.gdk_cursor_new (type);
158 }
159 
160 void drawBand (int x, int y, int width, int height) {
161     if ((style & SWT.SMOOTH) !is 0) return;
162     auto window = OS.GTK_WIDGET_WINDOW (parent.paintHandle());
163     if (window is null) return;
164     char [] bits = cast(String)[ cast(byte)-86, 85, -86, 85, -86, 85, -86, 85 ];
165     auto stipplePixmap = OS.gdk_bitmap_create_from_data (cast(GdkDrawable*)window, bits.ptr, 8, 8);
166     auto gc = OS.gdk_gc_new (window);
167     auto colormap = OS.gdk_colormap_get_system();
168     GdkColor color;
169     OS.gdk_color_white (colormap, &color);
170     OS.gdk_gc_set_foreground (gc, &color);
171     OS.gdk_gc_set_stipple (gc, stipplePixmap);
172     OS.gdk_gc_set_subwindow (gc, OS.GDK_INCLUDE_INFERIORS);
173     OS.gdk_gc_set_fill (gc, OS.GDK_STIPPLED);
174     OS.gdk_gc_set_function (gc, OS.GDK_XOR);
175     OS.gdk_draw_rectangle (window, gc, 1, x, y, width, height);
176     OS.g_object_unref (stipplePixmap);
177     OS.g_object_unref (gc);
178 }
179 
180 override int gtk_button_press_event (GtkWidget* widget, GdkEventButton* gdkEvent) {
181     auto result = super.gtk_button_press_event (widget, gdkEvent);
182     if (result !is 0) return result;
183     int button = gdkEvent.button;
184     if (button !is 1) return 0;
185     if (gdkEvent.type is OS.GDK_2BUTTON_PRESS) return 0;
186     if (gdkEvent.type is OS.GDK_3BUTTON_PRESS) return 0;
187     auto window = OS.GTK_WIDGET_WINDOW (widget);
188     int origin_x, origin_y;
189     OS.gdk_window_get_origin (window, &origin_x, &origin_y);
190     startX = cast(int) (gdkEvent.x_root - origin_x );
191     startY = cast(int) (gdkEvent.y_root - origin_y );
192     int x = OS.GTK_WIDGET_X (handle);
193     int y = OS.GTK_WIDGET_Y (handle);
194     int width = OS.GTK_WIDGET_WIDTH (handle);
195     int height = OS.GTK_WIDGET_HEIGHT (handle);
196     lastX = x;
197     lastY = y;
198     Event event = new Event ();
199     event.time = gdkEvent.time;
200     event.x = lastX;
201     event.y = lastY;
202     event.width = width;
203     event.height = height;
204     if ((style & SWT.SMOOTH) is 0) {
205         event.detail = SWT.DRAG;
206     }
207     if ((parent.style & SWT.MIRRORED) !is 0) event.x = parent.getClientWidth () - width  - event.x;
208     sendEvent (SWT.Selection, event);
209     if (isDisposed ()) return 0;
210     if (event.doit) {
211         dragging = true;
212         lastX = event.x;
213         lastY = event.y;
214         if ((parent.style & SWT.MIRRORED) !is 0) lastX = parent.getClientWidth () - width  - lastX;
215         parent.update (true, (style & SWT.SMOOTH) is 0);
216         drawBand (lastX, event.y, width, height);
217         if ((style & SWT.SMOOTH) !is 0) {
218             setBounds (event.x, event.y, width, height);
219             // widget could be disposed at this point
220         }
221     }
222     return result;
223 }
224 
225 override int gtk_button_release_event (GtkWidget* widget, GdkEventButton* gdkEvent) {
226     auto result = super.gtk_button_release_event (widget, gdkEvent);
227     if (result !is 0) return result;
228     int button = gdkEvent.button;
229     if (button !is 1) return 0;
230     if (!dragging) return 0;
231     dragging = false;
232     int width = OS.GTK_WIDGET_WIDTH (handle);
233     int height = OS.GTK_WIDGET_HEIGHT (handle);
234     Event event = new Event ();
235     event.time = gdkEvent.time;
236     event.x = lastX;
237     event.y = lastY;
238     event.width = width;
239     event.height = height;
240     drawBand (lastX, lastY, width, height);
241     if ((parent.style & SWT.MIRRORED) !is 0) event.x = parent.getClientWidth () - width  - event.x;
242     sendEvent (SWT.Selection, event);
243     if (isDisposed ()) return result;
244     if (event.doit) {
245         if ((style & SWT.SMOOTH) !is 0) {
246             setBounds (event.x, event.y, width, height);
247             // widget could be disposed at this point
248         }
249     }
250     return result;
251 }
252 
253 override int gtk_focus_in_event (GtkWidget* widget, GdkEventFocus* event) {
254     auto result = super.gtk_focus_in_event (widget, event);
255     if (result !is 0) return result;
256     // widget could be disposed at this point
257     if (handle !is null) {
258             lastX = OS.GTK_WIDGET_X (handle);
259             lastY = OS.GTK_WIDGET_Y (handle);
260     }
261     return 0;
262 }
263 
264 override int gtk_key_press_event (GtkWidget* widget, GdkEventKey* gdkEvent) {
265     auto result = super.gtk_key_press_event (widget, gdkEvent);
266     if (result !is 0) return result;
267     int keyval = gdkEvent.keyval;
268     switch (keyval) {
269         case OS.GDK_Left:
270         case OS.GDK_Right:
271         case OS.GDK_Up:
272         case OS.GDK_Down:
273             int xChange = 0, yChange = 0;
274             int stepSize = PAGE_INCREMENT;
275             if ((gdkEvent.state & OS.GDK_CONTROL_MASK) !is 0) stepSize = INCREMENT;
276             if ((style & SWT.VERTICAL) !is 0) {
277                 if (keyval is OS.GDK_Up || keyval is OS.GDK_Down) break;
278                 xChange = keyval is OS.GDK_Left ? -stepSize : stepSize;
279             } else {
280                 if (keyval is OS.GDK_Left ||keyval is OS.GDK_Right) break;
281                 yChange = keyval is OS.GDK_Up ? -stepSize : stepSize;
282             }
283 
284             int width = OS.GTK_WIDGET_WIDTH (handle);
285             int height = OS.GTK_WIDGET_HEIGHT (handle);
286             int parentBorder = 0;
287             int parentWidth = OS.GTK_WIDGET_WIDTH (parent.handle);
288             int parentHeight = OS.GTK_WIDGET_HEIGHT (parent.handle);
289             int newX = lastX, newY = lastY;
290             if ((style & SWT.VERTICAL) !is 0) {
291                 newX = Math.min (Math.max (0, lastX + xChange - parentBorder - startX), parentWidth - width);
292             } else {
293                 newY = Math.min (Math.max (0, lastY + yChange - parentBorder - startY), parentHeight - height);
294             }
295             if (newX is lastX && newY is lastY) return result;
296 
297             /* Ensure that the pointer image does not change */
298             auto window = OS.GTK_WIDGET_WINDOW (handle);
299             int grabMask = OS.GDK_POINTER_MOTION_MASK | OS.GDK_BUTTON_RELEASE_MASK;
300             auto gdkCursor = cursor !is null ? cursor.handle : cast(GdkCursor*)defaultCursor;
301             int ptrGrabResult = OS.gdk_pointer_grab (window, false, grabMask, window, gdkCursor, OS.GDK_CURRENT_TIME);
302 
303             /* The event must be sent because its doit flag is used. */
304             Event event = new Event ();
305             event.time = gdkEvent.time;
306             event.x = newX;
307             event.y = newY;
308             event.width = width;
309             event.height = height;
310             if ((parent.style & SWT.MIRRORED) !is 0) event.x = parent.getClientWidth () - width  - event.x;
311             sendEvent (SWT.Selection, event);
312             if (ptrGrabResult is OS.GDK_GRAB_SUCCESS) OS.gdk_pointer_ungrab (OS.GDK_CURRENT_TIME);
313             if (isDisposed ()) break;
314 
315             if (event.doit) {
316                 lastX = event.x;
317                 lastY = event.y;
318                 if ((parent.style & SWT.MIRRORED) !is 0) lastX = parent.getClientWidth () - width  - lastX;
319                 if ((style & SWT.SMOOTH) !is 0) {
320                     setBounds (event.x, event.y, width, height);
321                     if (isDisposed ()) break;
322                 }
323                 int cursorX = event.x, cursorY = event.y;
324                 if ((style & SWT.VERTICAL) !is 0) {
325                     cursorY += height / 2;
326                 } else {
327                     cursorX += width / 2;
328                 }
329                 display.setCursorLocation (parent.toDisplay (cursorX, cursorY));
330             }
331             break;
332         default:
333     }
334 
335     return result;
336 }
337 
338 override int gtk_motion_notify_event (GtkWidget* widget, GdkEventMotion* gdkEvent) {
339     auto result = super.gtk_motion_notify_event (widget, gdkEvent);
340     if (result !is 0) return result;
341     if (!dragging) return 0;
342     int eventX, eventY, eventState;
343     if (gdkEvent.is_hint !is 0) {
344         int pointer_x, pointer_y, mask;
345         OS.gdk_window_get_pointer (gdkEvent.window, &pointer_x, &pointer_y, &mask);
346         eventX = pointer_x ;
347         eventY = pointer_y ;
348         eventState = mask ;
349     } else {
350         int origin_x, origin_y;
351         OS.gdk_window_get_origin (gdkEvent.window, &origin_x, &origin_y);
352         eventX = cast(int)(gdkEvent.x_root - origin_x);
353         eventY = cast(int)(gdkEvent.y_root - origin_y);
354         eventState = gdkEvent.state;
355     }
356     if ((eventState & OS.GDK_BUTTON1_MASK) is 0) return 0;
357     int x = OS.GTK_WIDGET_X (handle);
358     int y = OS.GTK_WIDGET_Y (handle);
359     int width = OS.GTK_WIDGET_WIDTH (handle);
360     int height = OS.GTK_WIDGET_HEIGHT (handle);
361     int parentBorder = 0;
362     int parentWidth = OS.GTK_WIDGET_WIDTH (parent.handle);
363     int parentHeight = OS.GTK_WIDGET_HEIGHT (parent.handle);
364     int newX = lastX, newY = lastY;
365     if ((style & SWT.VERTICAL) !is 0) {
366         newX = Math.min (Math.max (0, eventX + x - startX - parentBorder), parentWidth - width);
367     } else {
368         newY = Math.min (Math.max (0, eventY + y - startY - parentBorder), parentHeight - height);
369     }
370     if (newX is lastX && newY is lastY) return 0;
371     drawBand (lastX, lastY, width, height);
372 
373     Event event = new Event ();
374     event.time = gdkEvent.time;
375     event.x = newX;
376     event.y = newY;
377     event.width = width;
378     event.height = height;
379     if ((style & SWT.SMOOTH) is 0) {
380         event.detail = SWT.DRAG;
381     }
382     if ((parent.style & SWT.MIRRORED) !is 0) event.x = parent.getClientWidth() - width  - event.x;
383     sendEvent (SWT.Selection, event);
384     if (isDisposed ()) return 0;
385     if (event.doit) {
386         lastX = event.x;
387         lastY = event.y;
388         if ((parent.style & SWT.MIRRORED) !is 0) lastX = parent.getClientWidth () - width  - lastX;
389     }
390     parent.update (true, (style & SWT.SMOOTH) is 0);
391     drawBand (lastX, lastY, width, height);
392     if ((style & SWT.SMOOTH) !is 0) {
393         setBounds (event.x, lastY, width, height);
394         // widget could be disposed at this point
395     }
396     return result;
397 }
398 
399 override int gtk_realize (GtkWidget* widget) {
400     gtk_setCursor (cursor !is null ? cursor.handle : null);
401     return super.gtk_realize (widget);
402 }
403 
404 override void hookEvents () {
405     super.hookEvents ();
406     OS.gtk_widget_add_events (handle, OS.GDK_POINTER_MOTION_HINT_MASK);
407 }
408 
409 override void releaseWidget () {
410     super.releaseWidget ();
411     if (defaultCursor !is null) OS.gdk_cursor_destroy (cast(GdkCursor*)defaultCursor);
412     defaultCursor = null;
413 }
414 
415 /**
416  * Removes the listener from the collection of listeners who will
417  * be notified when the control is selected by the user.
418  *
419  * @param listener the listener which should no longer be notified
420  *
421  * @exception IllegalArgumentException <ul>
422  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
423  * </ul>
424  * @exception SWTException <ul>
425  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
426  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
427  * </ul>
428  *
429  * @see SelectionListener
430  * @see #addSelectionListener
431  */
432 public void removeSelectionListener(SelectionListener listener) {
433     checkWidget();
434     if (listener is null) error (SWT.ERROR_NULL_ARGUMENT);
435     if (eventTable is null) return;
436     eventTable.unhook (SWT.Selection, listener);
437     eventTable.unhook (SWT.DefaultSelection,listener);
438 }
439 
440 override void gtk_setCursor (GdkCursor* cursor) {
441     super.gtk_setCursor (cursor !is null ? cursor : cast(GdkCursor*)defaultCursor);
442 }
443 
444 override int traversalCode (int key, GdkEventKey* event) {
445     return 0;
446 }
447 
448 }