root/dwt/widgets/CoolBar.d

Revision 246:fd9c62a2998e, 43.7 kB (checked in by Frank Benoit <benoit@tionex.de>, 5 months ago)

Updater SWT 3.4M7 to 3.4

Line 
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 dwt.widgets.CoolBar;
14
15 import dwt.widgets.Composite;
16
17
18 import dwt.DWT;
19 import dwt.DWTException;
20 import dwt.graphics.Point;
21 import dwt.graphics.Rectangle;
22 import dwt.internal.win32.OS;
23
24 import dwt.widgets.Composite;
25 import dwt.widgets.Control;
26 import dwt.widgets.CoolItem;
27 import dwt.widgets.Event;
28
29 import dwt.dwthelper.utils;
30
31 /**
32  * Instances of this class provide an area for dynamically
33  * positioning the items they contain.
34  * <p>
35  * The item children that may be added to instances of this class
36  * must be of type <code>CoolItem</code>.
37  * </p><p>
38  * Note that although this class is a subclass of <code>Composite</code>,
39  * it does not make sense to add <code>Control</code> children to it,
40  * or set a layout on it.
41  * </p><p>
42  * <dl>
43  * <dt><b>Styles:</b></dt>
44  * <dd>FLAT, HORIZONTAL, VERTICAL</dd>
45  * <dt><b>Events:</b></dt>
46  * <dd>(none)</dd>
47  * </dl>
48  * </p><p>
49  * Note: Only one of the styles HORIZONTAL and VERTICAL may be specified.
50  * </p><p>
51  * IMPORTANT: This class is <em>not</em> intended to be subclassed.
52  * </p>
53  *
54  * @see <a href="http://www.eclipse.org/swt/snippets/#coolbar">CoolBar snippets</a>
55  * @see <a href="http://www.eclipse.org/swt/examples.php">DWT Example: ControlExample</a>
56  * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
57  */
58
59 public class CoolBar : Composite {
60
61     alias Composite.computeSize computeSize;
62     alias Composite.windowProc windowProc;
63
64     CoolItem [] items;
65     CoolItem [] originalItems;
66     bool locked;
67     bool ignoreResize;
68     private static /+const+/ WNDPROC ReBarProc;
69     static const TCHAR* ReBarClass = OS.REBARCLASSNAME.ptr;
70
71     private static bool static_this_completed = false;
72     private static void static_this() {
73         if( static_this_completed ){
74             return;
75         }
76         synchronized {
77             if( static_this_completed ){
78                 return;
79             }
80             INITCOMMONCONTROLSEX icex;
81             icex.dwSize = INITCOMMONCONTROLSEX.sizeof;
82             icex.dwICC = OS.ICC_COOL_CLASSES;
83             OS.InitCommonControlsEx (&icex);
84             WNDCLASS lpWndClass;
85             OS.GetClassInfo (null, ReBarClass, &lpWndClass);
86             ReBarProc = lpWndClass.lpfnWndProc;
87             static_this_completed = true;
88         }
89     }
90
91     static const int SEPARATOR_WIDTH = 2;
92     static const int MAX_WIDTH = 0x7FFF;
93     static const int DEFAULT_COOLBAR_WIDTH = 0;
94     static const int DEFAULT_COOLBAR_HEIGHT = 0;
95
96 /**
97  * Constructs a new instance of this class given its parent
98  * and a style value describing its behavior and appearance.
99  * <p>
100  * The style value is either one of the style constants defined in
101  * class <code>DWT</code> which is applicable to instances of this
102  * class, or must be built by <em>bitwise OR</em>'ing together
103  * (that is, using the <code>int</code> "|" operator) two or more
104  * of those <code>DWT</code> style constants. The class description
105  * lists the style constants that are applicable to the class.
106  * Style bits are also inherited from superclasses.
107  * </p>
108  *
109  * @param parent a composite control which will be the parent of the new instance (cannot be null)
110  * @param style the style of control to construct
111  *
112  * @exception IllegalArgumentException <ul>
113  *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
114  * </ul>
115  * @exception DWTException <ul>
116  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
117  *    <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
118  * </ul>
119  *
120  * @see DWT
121  * @see Widget#checkSubclass
122  * @see Widget#getStyle
123  */
124 public this (Composite parent, int style) {
125     static_this();
126     super (parent, checkStyle (style));
127     /*
128     * Ensure that either of HORIZONTAL or VERTICAL is set.
129     * NOTE: HORIZONTAL and VERTICAL have the same values
130     * as H_SCROLL and V_SCROLL so it is necessary to first
131     * clear these bits to avoid scroll bars and then reset
132     * the bits using the original style supplied by the
133     * programmer.
134     *
135     * NOTE: The CCS_VERT style cannot be applied when the
136     * widget is created because of this conflict.
137     */
138     if ((style & DWT.VERTICAL) !is 0) {
139         this.style |= DWT.VERTICAL;
140         int bits = OS.GetWindowLong (handle, OS.GWL_STYLE);
141         OS.SetWindowLong (handle, OS.GWL_STYLE, bits | OS.CCS_VERT);
142     } else {
143         this.style |= DWT.HORIZONTAL;
144     }
145 }
146
147 override int callWindowProc (HWND hwnd, int msg, int wParam, int lParam) {
148     if (handle is null) return 0;
149     return OS.CallWindowProc (ReBarProc, hwnd, msg, wParam, lParam);
150 }
151
152 static int checkStyle (int style) {
153     style |= DWT.NO_FOCUS;
154     /*
155     * Even though it is legal to create this widget
156     * with scroll bars, they serve no useful purpose
157     * because they do not automatically scroll the
158     * widget's client area.  The fix is to clear
159     * the DWT style.
160     */
161     return style & ~(DWT.H_SCROLL | DWT.V_SCROLL);
162 }
163
164 override protected void checkSubclass () {
165     if (!isValidSubclass ()) error (DWT.ERROR_INVALID_SUBCLASS);
166 }
167
168 override public Point computeSize (int wHint, int hHint, bool changed) {
169     checkWidget ();
170     int width = 0, height = 0;
171     int border = getBorderWidth ();
172     int newWidth = wHint is DWT.DEFAULT ? 0x3FFF : wHint + (border * 2);
173     int newHeight = hHint is DWT.DEFAULT ? 0x3FFF : hHint + (border * 2);
174     int count = OS.SendMessage (handle, OS.RB_GETBANDCOUNT, 0, 0);
175     if (count !is 0) {
176         ignoreResize = true;
177         bool redraw = false;
178         if (OS.IsWindowVisible (handle)) {
179             if (OS.COMCTL32_MAJOR >= 6) {
180                 redraw = true;
181                 OS.UpdateWindow (handle);
182                 OS.DefWindowProc (handle, OS.WM_SETREDRAW, 0, 0);
183             } else {
184                 redraw = drawCount is 0;
185                 if (redraw) {
186                     OS.UpdateWindow (handle);
187                     OS.SendMessage (handle, OS.WM_SETREDRAW, 0, 0);
188                 }
189             }
190         }
191         RECT oldRect;
192         OS.GetWindowRect (handle, &oldRect);
193         int oldWidth = oldRect.right - oldRect.left;
194         int oldHeight = oldRect.bottom - oldRect.top;
195         int flags = OS.SWP_NOACTIVATE | OS.SWP_NOMOVE | OS.SWP_NOREDRAW | OS.SWP_NOZORDER;
196         SetWindowPos (handle, null, 0, 0, newWidth, newHeight, flags);
197         RECT rect;
198         OS.SendMessage (handle, OS.RB_GETRECT, count - 1, &rect);
199         height = Math.max (height, rect.bottom);
200         SetWindowPos (handle, null, 0, 0, oldWidth, oldHeight, flags);
201         REBARBANDINFO rbBand;
202         rbBand.cbSize = REBARBANDINFO.sizeof;
203         rbBand.fMask = OS.RBBIM_IDEALSIZE | OS.RBBIM_STYLE;
204         int rowWidth = 0;
205         for (int i = 0; i < count; i++) {
206             OS.SendMessage(handle, OS.RB_GETBANDINFO, i, &rbBand);
207             if ((rbBand.fStyle & OS.RBBS_BREAK) !is 0) {
208                 width = Math.max(width, rowWidth);
209                 rowWidth = 0;
210             }
211             rowWidth += rbBand.cxIdeal + getMargin (i);
212         }
213         width = Math.max(width, rowWidth);
214         if (redraw) {
215             if (OS.COMCTL32_MAJOR >= 6) {
216                 OS.DefWindowProc (handle, OS.WM_SETREDRAW, 1, 0);
217             } else {
218                 OS.SendMessage (handle, OS.WM_SETREDRAW, 1, 0);
219             }
220         }
221         ignoreResize = false;
222     }
223     if (width is 0) width = DEFAULT_COOLBAR_WIDTH;
224     if (height is 0) height = DEFAULT_COOLBAR_HEIGHT;
225     if ((style & DWT.VERTICAL) !is 0) {
226         int tmp = width;
227         width = height;
228         height = tmp;
229     }
230     if (wHint !is DWT.DEFAULT) width = wHint;
231     if (hHint !is DWT.DEFAULT) height = hHint;
232     height += border * 2;
233     width += border * 2;
234     return new Point (width, height);
235 }
236
237 override void createHandle () {
238     super.createHandle ();
239     state &= ~(CANVAS | THEME_BACKGROUND);
240
241     /*
242     * Feature in Windows.  When the control is created,
243     * it does not use the default system font.  A new HFONT
244     * is created and destroyed when the control is destroyed.
245     * This means that a program that queries the font from
246     * this control, uses the font in another control and then
247     * destroys this control will have the font unexpectedly
248     * destroyed in the other control.  The fix is to assign
249     * the font ourselves each time the control is created.
250     * The control will not destroy a font that it did not
251     * create.
252     */
253     auto hFont = OS.GetStockObject (OS.SYSTEM_FONT);
254     OS.SendMessage (handle, OS.WM_SETFONT, hFont, 0);
255 }
256
257 void createItem (CoolItem item, int index) {
258     int count = OS.SendMessage (handle, OS.RB_GETBANDCOUNT, 0, 0);
259     if (!(0 <= index && index <= count)) error (DWT.ERROR_INVALID_RANGE);
260     int id = 0;
261     while (id < items.length && items [id] !is null) id++;
262     if (id is items.length) {
263         CoolItem [] newItems = new CoolItem [items.length + 4];
264         System.arraycopy (items, 0, newItems, 0, items.length);
265         items = newItems;
266     }
267     auto hHeap = OS.GetProcessHeap ();
268     auto lpText = cast(TCHAR*) OS.HeapAlloc (hHeap, OS.HEAP_ZERO_MEMORY, TCHAR.sizeof);
269     REBARBANDINFO rbBand;
270     rbBand.cbSize = REBARBANDINFO.sizeof;
271     rbBand.fMask = OS.RBBIM_TEXT | OS.RBBIM_STYLE | OS.RBBIM_ID;
272     rbBand.fStyle = OS.RBBS_VARIABLEHEIGHT | OS.RBBS_GRIPPERALWAYS;
273     if ((item.style & DWT.DROP_DOWN) !is 0) {
274         rbBand.fStyle |= OS.RBBS_USECHEVRON;
275     }
276     rbBand.lpText = lpText;
277     rbBand.wID = id;
278
279     /*
280     * Feature in Windows.  When inserting an item at end of a row,
281     * sometimes, Windows will begin to place the item on the right
282     * side of the cool bar.  The fix is to resize the new items to
283     * the maximum size and then resize the next to last item to the
284     * ideal size.
285     */
286     int lastIndex = getLastIndexOfRow (index - 1);
287     bool fixLast = index is lastIndex + 1;
288     if (fixLast) {
289         rbBand.fMask |= OS.RBBIM_SIZE;
290         rbBand.cx = MAX_WIDTH;
291     }
292
293     /*
294     * Feature in Windows. Is possible that the item at index zero
295     * has the RBBS_BREAK flag set. When a new item is inserted at
296     * position zero, the previous item at position zero moves to
297     * a new line.  The fix is to detect this case and clear the
298     * RBBS_BREAK flag on the previous item before inserting the
299     * new item.
300     */
301     if (index is 0 && count > 0) {
302         getItem (0).setWrap (false);
303     }
304
305     /* Insert the item */
306     if (OS.SendMessage (handle, OS.RB_INSERTBAND, index, &rbBand) is 0) {
307         error (DWT.ERROR_ITEM_NOT_ADDED);
308     }
309
310     /* Resize the next to last item to the ideal size */
311     if (fixLast) {
312         resizeToPreferredWidth (lastIndex);
313     }
314
315     OS.HeapFree (hHeap, 0, lpText);
316     items [item.id = id] = item;
317     int length = originalItems.length;
318     CoolItem [] newOriginals = new CoolItem [length + 1];
319     System.arraycopy (originalItems, 0, newOriginals, 0, index);
320     System.arraycopy (originalItems, index, newOriginals, index + 1, length - index);
321     newOriginals [index] = item;
322     originalItems = newOriginals;
323 }
324
325 override void createWidget () {
326     super.createWidget ();
327     items = new CoolItem [4];
328     originalItems = new CoolItem [0];
329 }
330
331 void destroyItem (CoolItem item) {
332     int index = OS.SendMessage (handle, OS.RB_IDTOINDEX, item.id, 0);
333     int count = OS.SendMessage (handle, OS.RB_GETBANDCOUNT, 0, 0);
334     if (count !is 0) {
335         int lastIndex = getLastIndexOfRow (index);
336         if (index is lastIndex) {
337             /*
338             * Feature in Windows.  If the last item in a row is
339             * given its ideal size, it will be placed at the far
340             * right hand edge of the coolbar.  It is preferred
341             * that the last item appear next to the second last
342             * item.  The fix is to size the last item of each row
343             * so that it occupies all the available space to the
344             * right in the row.
345             */
346             resizeToMaximumWidth (lastIndex - 1);
347         }
348     }
349
350     /*
351     * Feature in Windows.  When Windows removed a rebar
352     * band, it makes the band child invisible.  The fix
353     * is to show the child.
354     */
355     Control control = item.control;
356     bool wasVisible = control !is null && !control.isDisposed() && control.getVisible ();
357
358     /*
359     * When a wrapped item is being deleted, make the next
360     * item in the row wrapped in order to preserve the row.
361     * In order to avoid an unnecessary layout, temporarily
362     * ignore WM_SIZE.  If the next item is wrapped then a
363     * row will be deleted and the WM_SIZE is necessary.
364     */
365     CoolItem nextItem = null;
366     if (item.getWrap ()) {
367         if (index + 1 < count) {
368             nextItem = getItem (index + 1);
369             ignoreResize = !nextItem.getWrap ();
370         }
371     }
372     if (OS.SendMessage (handle, OS.RB_DELETEBAND, index, 0) is 0) {
373         error (DWT.ERROR_ITEM_NOT_REMOVED);
374     }
375     items [item.id] = null;
376     item.id = -1;
377     if (ignoreResize) {
378         nextItem.setWrap (true);
379         ignoreResize = false;
380     }
381
382     /* Restore the visible state of the control */
383     if (wasVisible) control.setVisible (true);
384
385     index = 0;
386     while (index < originalItems.length) {
387         if (originalItems [index] is item) break;
388         index++;
389     }
390     int length = originalItems.length - 1;
391     CoolItem [] newOriginals = new CoolItem [length];
392     System.arraycopy (originalItems, 0, newOriginals, 0, index);
393     System.arraycopy (originalItems, index + 1, newOriginals, index, length - index);
394     originalItems = newOriginals;
395 }
396
397 override void drawThemeBackground (HDC hDC, HWND hwnd, RECT* rect) {
398     if (OS.COMCTL32_MAJOR >= 6 && OS.IsAppThemed ()) {
399         if (background is -1 && (style & DWT.FLAT) !is 0) {
400             Control control = findBackgroundControl ();
401             if (control !is null && control.backgroundImage !is null) {
402                 fillBackground (hDC, control.getBackgroundPixel (), rect);
403                 return;
404             }
405         }
406     }
407     RECT rect2;
408     OS.GetClientRect (handle, &rect2);
409     OS.MapWindowPoints (handle, hwnd, cast(POINT*) &rect2, 2);
410     POINT lpPoint;
411     OS.SetWindowOrgEx (hDC, -rect2.left, -rect2.top, &lpPoint);
412     OS.SendMessage (handle, OS.WM_PRINT, hDC, OS.PRF_CLIENT | OS.PRF_ERASEBKGND);
413     OS.SetWindowOrgEx (hDC, lpPoint.x, lpPoint.y, null);
414 }
415
416 override Control findThemeControl () {
417     if ((style & DWT.FLAT) !is 0) return this;
418     return background is -1 && backgroundImage is null ? this : super.findThemeControl ();
419 }
420
421 int getMargin (int index) {
422     int margin = 0;
423     if (OS.COMCTL32_MAJOR >= 6) {
424         MARGINS margins;
425         OS.SendMessage (handle, OS.RB_GETBANDMARGINS, 0, &margins);
426         margin += margins.cxLeftWidth + margins.cxRightWidth;
427     }
428     RECT rect;
429     OS.SendMessage (handle, OS.RB_GETBANDBORDERS, index, &rect);
430     if ((style & DWT.FLAT) !is 0) {
431         /*
432         * Bug in Windows.  When the style bit  RBS_BANDBORDERS is not set
433         * the rectangle returned by RBS_BANDBORDERS is four pixels too small.
434         * The fix is to add four pixels to the result.
435         */
436         if ((style & DWT.VERTICAL) !is 0) {
437             margin += rect.top + 4;
438         } else {
439             margin += rect.left + 4;
440         }
441     } else {
442         if ((style & DWT.VERTICAL) !is 0) {
443             margin += rect.top + rect.bottom;
444         } else {
445             margin += rect.left + rect.right;
446         }
447     }
448     if ((style & DWT.FLAT) is 0) {
449         if (!isLastItemOfRow (index)) {
450             margin += CoolBar.SEPARATOR_WIDTH;
451         }
452     }
453     return margin;
454 }
455
456 /**
457  * Returns the item that is currently displayed at the given,
458  * zero-relative index. Throws an exception if the index is
459  * out of range.
460  *
461  * @param index the visual index of the item to return
462  * @return the item at the given visual index
463  *
464  * @exception IllegalArgumentException <ul>
465  *    <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list minus 1 (inclusive)</li>
466  * </ul>
467  * @exception DWTException <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 CoolItem getItem (int index) {
473     checkWidget ();
474     int count = OS.SendMessage (handle, OS.RB_GETBANDCOUNT, 0, 0);
475     if (!(0 <= index && index < count)) error (DWT.ERROR_INVALID_RANGE);
476     REBARBANDINFO rbBand;
477     rbBand.cbSize = REBARBANDINFO.sizeof;
478     rbBand.fMask = OS.RBBIM_ID;
479     OS.SendMessage (handle, OS.RB_GETBANDINFO, index, &rbBand);
480     return items [rbBand.wID];
481 }
482
483 /**
484  * Returns the number of items contained in the receiver.
485  *
486  * @return the number of items
487  *
488  * @exception DWTException <ul>
489  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
490  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
491  * </ul>
492  */
493 public int getItemCount () {
494     checkWidget ();
495     return OS.SendMessage (handle, OS.RB_GETBANDCOUNT, 0, 0);
496 }
497
498 /**
499  * Returns an array of zero-relative ints that map
500  * the creation order of the receiver's items to the
501  * order in which they are currently being displayed.
502  * <p>
503  * Specifically, the indices of the returned array represent
504  * the current visual order of the items, and the contents
505  * of the array represent the creation order of the items.
506  * </p><p>
507  * Note: This is not the actual structure used by the receiver
508  * to maintain its list of items, so modifying the array will
509  * not affect the receiver.
510  * </p>
511  *
512  * @return the current visual order of the receiver's items
513  *
514  * @exception DWTException <ul>
515  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
516  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
517  * </ul>
518  */
519 public int [] getItemOrder () {
520     checkWidget ();
521     int count = OS.SendMessage (handle, OS.RB_GETBANDCOUNT, 0, 0);
522     int [] indices = new int [count];
523     REBARBANDINFO rbBand;
524     rbBand.cbSize = REBARBANDINFO.sizeof;
525     rbBand.fMask = OS.RBBIM_ID;
526     for (int i=0; i<count; i++) {
527         OS.SendMessage (handle, OS.RB_GETBANDINFO, i, &rbBand);
528         CoolItem item = items [rbBand.wID];
529         int index = 0;
530         while (index<originalItems.length) {
531             if (originalItems [index] is item) break;
532             index++;
533         }
534         if (index is originalItems.length) error (DWT.ERROR_CANNOT_GET_ITEM);
535         indices [i] = index;
536     }
537     return indices;
538 }
539
540 /**
541  * Returns an array of <code>CoolItem</code>s in the order
542  * in which they are currently being displayed.
543  * <p>
544  * Note: This is not the actual structure used by the receiver
545  * to maintain its list of items, so modifying the array will
546  * not affect the receiver.
547  * </p>
548  *
549  * @return the receiver's items in their current visual order
550  *
551  * @exception DWTException <ul>
552  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
553  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
554  * </ul>
555  */
556 public CoolItem [] getItems () {
557     checkWidget ();
558     int count = OS.SendMessage (handle, OS.RB_GETBANDCOUNT, 0, 0);
559     CoolItem [] result = new CoolItem [count];
560     REBARBANDINFO rbBand;
561     rbBand.cbSize = REBARBANDINFO.sizeof;
562     rbBand.fMask = OS.RBBIM_ID;
563     for (int i=0; i<count; i++) {
564         OS.SendMessage (handle, OS.RB_GETBANDINFO, i, &rbBand);
565         result [i] = items [rbBand.wID];
566     }
567     return result;
568 }
569
570 /**
571  * Returns an array of points whose x and y coordinates describe
572  * the widths and heights (respectively) of the items in the receiver
573  * in the order in which they are currently being displayed.
574  *
575  * @return the receiver's item sizes in their current visual order
576  *
577  * @exception DWTException <ul>
578  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
579  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
580  * </ul>
581  */
582