root/dwt/custom/CCombo.d

Revision 320:da968414c383, 61.2 kB (checked in by Frank Benoit <benoit@tionex.de>, 2 months ago)

Merge changes SWT 3.4.1

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.custom.CCombo;
14
15
16
17 import dwt.DWT;
18 import dwt.DWTException;
19 import dwt.accessibility.ACC;
20 import dwt.accessibility.AccessibleAdapter;
21 import dwt.accessibility.AccessibleControlAdapter;
22 import dwt.accessibility.AccessibleControlEvent;
23 import dwt.accessibility.AccessibleEvent;
24 import dwt.accessibility.AccessibleTextAdapter;
25 import dwt.accessibility.AccessibleTextEvent;
26 import dwt.events.ModifyListener;
27 import dwt.events.SelectionEvent;
28 import dwt.events.SelectionListener;
29 import dwt.events.VerifyListener;
30 import dwt.graphics.Color;
31 import dwt.graphics.Font;
32 import dwt.graphics.GC;
33 import dwt.graphics.Point;
34 import dwt.graphics.Rectangle;
35 import dwt.widgets.Button;
36 import dwt.widgets.Composite;
37 import dwt.widgets.Control;
38 import dwt.widgets.Display;
39 import dwt.widgets.Event;
40 import dwt.widgets.Label;
41 import dwt.widgets.Layout;
42 import dwt.widgets.List;
43 import dwt.widgets.Listener;
44 import dwt.widgets.Menu;
45 import dwt.widgets.Shell;
46 import dwt.widgets.Text;
47 import dwt.widgets.TypedListener;
48 import dwt.widgets.Widget;
49
50 static import tango.text.convert.Utf;
51 static import tango.text.Unicode;
52 static import tango.text.convert.Format;
53 import dwt.dwthelper.utils;
54 import dwt.dwthelper.Runnable;
55
56 /**
57  * The CCombo class represents a selectable user interface object
58  * that combines a text field and a list and issues notification
59  * when an item is selected from the list.
60  * <p>
61  * CCombo was written to work around certain limitations in the native
62  * combo box. Specifically, on win32, the height of a CCombo can be set;
63  * attempts to set the height of a Combo are ignored. CCombo can be used
64  * anywhere that having the increased flexibility is more important than
65  * getting native L&F, but the decision should not be taken lightly.
66  * There is no is no strict requirement that CCombo look or behave
67  * the same as the native combo box.
68  * </p>
69  * <p>
70  * Note that although this class is a subclass of <code>Composite</code>,
71  * it does not make sense to add children to it, or set a layout on it.
72  * </p>
73  * <dl>
74  * <dt><b>Styles:</b>
75  * <dd>BORDER, READ_ONLY, FLAT</dd>
76  * <dt><b>Events:</b>
77  * <dd>DefaultSelection, Modify, Selection, Verify</dd>
78  * </dl>
79  *
80  * @see <a href="http://www.eclipse.org/swt/snippets/#ccombo">CCombo snippets</a>
81  * @see <a href="http://www.eclipse.org/swt/examples.php">DWT Example: CustomControlExample</a>
82  * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
83  */
84 public final class CCombo : Composite {
85
86     alias Composite.computeSize computeSize;
87
88     Text text;
89     List list;
90     int visibleItemCount = 5;
91     Shell popup;
92     Button arrow;
93     bool hasFocus;
94     Listener listener, filter;
95     Color foreground, background;
96     Font font;
97
98 /**
99  * Constructs a new instance of this class given its parent
100  * and a style value describing its behavior and appearance.
101  * <p>
102  * The style value is either one of the style constants defined in
103  * class <code>DWT</code> which is applicable to instances of this
104  * class, or must be built by <em>bitwise OR</em>'ing together
105  * (that is, using the <code>int</code> "|" operator) two or more
106  * of those <code>DWT</code> style constants. The class description
107  * lists the style constants that are applicable to the class.
108  * Style bits are also inherited from superclasses.
109  * </p>
110  *
111  * @param parent a widget which will be the parent of the new instance (cannot be null)
112  * @param style the style of widget to construct
113  *
114  * @exception IllegalArgumentException <ul>
115  *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
116  * </ul>
117  * @exception DWTException <ul>
118  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
119  * </ul>
120  *
121  * @see DWT#BORDER
122  * @see DWT#READ_ONLY
123  * @see DWT#FLAT
124  * @see Widget#getStyle()
125  */
126 public this (Composite parent, int style) {
127     super (parent, style = checkStyle (style));
128
129     int textStyle = DWT.SINGLE;
130     if ((style & DWT.READ_ONLY) !is 0) textStyle |= DWT.READ_ONLY;
131     if ((style & DWT.FLAT) !is 0) textStyle |= DWT.FLAT;
132     text = new Text (this, textStyle);
133     int arrowStyle = DWT.ARROW | DWT.DOWN;
134     if ((style & DWT.FLAT) !is 0) arrowStyle |= DWT.FLAT;
135     arrow = new Button (this, arrowStyle);
136
137     listener = new class() Listener {
138         public void handleEvent (Event event) {
139             if (popup is event.widget) {
140                 popupEvent (event);
141                 return;
142             }
143             if (text is event.widget) {
144                 textEvent (event);
145                 return;
146             }
147             if (list is event.widget) {
148                 listEvent (event);
149                 return;
150             }
151             if (arrow is event.widget) {
152                 arrowEvent (event);
153                 return;
154             }
155             if (this.outer is event.widget) {
156                 comboEvent (event);
157                 return;
158             }
159             if (getShell () is event.widget) {
160                 getDisplay().asyncExec(new class() Runnable {
161                     public void run() {
162                         if (isDisposed()) return;
163                         handleFocus (DWT.FocusOut);
164                     }
165                 });
166             }
167         }
168     };
169     filter = new class() Listener {
170         public void handleEvent(Event event) {
171             Shell shell = (cast(Control)event.widget).getShell ();
172             if (shell is this.outer.getShell ()) {
173                 handleFocus (DWT.FocusOut);
174             }
175         }
176     };
177
178     int [] comboEvents = [DWT.Dispose, DWT.FocusIn, DWT.Move, DWT.Resize];
179     for (int i=0; i<comboEvents.length; i++) this.addListener (comboEvents [i], listener);
180
181     int [] textEvents = [DWT.DefaultSelection, DWT.KeyDown, DWT.KeyUp, DWT.MenuDetect, DWT.Modify, DWT.MouseDown, DWT.MouseUp, DWT.MouseDoubleClick, DWT.MouseWheel, DWT.Traverse, DWT.FocusIn, DWT.Verify];
182     for (int i=0; i<textEvents.length; i++) text.addListener (textEvents [i], listener);
183
184     int [] arrowEvents = [DWT.MouseDown, DWT.MouseUp, DWT.Selection, DWT.FocusIn];
185     for (int i=0; i<arrowEvents.length; i++) arrow.addListener (arrowEvents [i], listener);
186
187     createPopup(null, -1);
188     initAccessible();
189 }
190 static int checkStyle (int style) {
191     int mask = DWT.BORDER | DWT.READ_ONLY | DWT.FLAT | DWT.LEFT_TO_RIGHT | DWT.RIGHT_TO_LEFT;
192     return DWT.NO_FOCUS | (style & mask);
193 }
194 /**
195  * Adds the argument to the end of the receiver's list.
196  *
197  * @param string the new item
198  *
199  * @exception DWTException <ul>
200  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
201  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
202  * </ul>
203  *
204  * @see #add(String,int)
205  */
206 public void add (String string) {
207     checkWidget();
208     // DWT extension: allow null for zero length string
209     //if (string is null) DWT.error (DWT.ERROR_NULL_ARGUMENT);
210     list.add (string);
211 }
212 /**
213  * Adds the argument to the receiver's list at the given
214  * zero-relative index.
215  * <p>
216  * Note: To add an item at the end of the list, use the
217  * result of calling <code>getItemCount()</code> as the
218  * index or use <code>add(String)</code>.
219  * </p>
220  *
221  * @param string the new item
222  * @param index the index for the item
223  *
224  * @exception IllegalArgumentException <ul>
225  *    <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list (inclusive)</li>
226  * </ul>
227  * @exception DWTException <ul>
228  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
229  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
230  * </ul>
231  *
232  * @see #add(String)
233  */
234 public void add (String string, int index) {
235     checkWidget();
236     // DWT extension: allow null for zero length string
237     //if (string is null) DWT.error (DWT.ERROR_NULL_ARGUMENT);
238     list.add (string, index);
239 }
240 /**
241  * Adds the listener to the collection of listeners who will
242  * be notified when the receiver's text is modified, by sending
243  * it one of the messages defined in the <code>ModifyListener</code>
244  * interface.
245  *
246  * @param listener the listener which should be notified
247  *
248  * @exception IllegalArgumentException <ul>
249  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
250  * </ul>
251  * @exception DWTException <ul>
252  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
253  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
254  * </ul>
255  *
256  * @see ModifyListener
257  * @see #removeModifyListener
258  */
259 public void addModifyListener (ModifyListener listener) {
260     checkWidget();
261     if (listener is null) DWT.error (DWT.ERROR_NULL_ARGUMENT);
262     TypedListener typedListener = new TypedListener (listener);
263     addListener (DWT.Modify, typedListener);
264 }
265 /**
266  * Adds the listener to the collection of listeners who will
267  * be notified when the user changes the receiver's selection, by sending
268  * it one of the messages defined in the <code>SelectionListener</code>
269  * interface.
270  * <p>
271  * <code>widgetSelected</code> is called when the combo's list selection changes.
272  * <code>widgetDefaultSelected</code> is typically called when ENTER is pressed the combo's text area.
273  * </p>
274  *
275  * @param listener the listener which should be notified when the user changes the receiver's selection
276  *
277  * @exception IllegalArgumentException <ul>
278  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
279  * </ul>
280  * @exception DWTException <ul>
281  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
282  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
283  * </ul>
284  *
285  * @see SelectionListener
286  * @see #removeSelectionListener
287  * @see SelectionEvent
288  */
289 public void addSelectionListener(SelectionListener listener) {
290     checkWidget();
291     if (listener is null) DWT.error (DWT.ERROR_NULL_ARGUMENT);
292     TypedListener typedListener = new TypedListener (listener);
293     addListener (DWT.Selection,typedListener);
294     addListener (DWT.DefaultSelection,typedListener);
295 }
296 /**
297  * Adds the listener to the collection of listeners who will
298  * be notified when the receiver's text is verified, by sending
299  * it one of the messages defined in the <code>VerifyListener</code>
300  * interface.
301  *
302  * @param listener the listener which should be notified
303  *
304  * @exception IllegalArgumentException <ul>
305  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
306  * </ul>
307  * @exception DWTException <ul>
308  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
309  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
310  * </ul>
311  *
312  * @see VerifyListener
313  * @see #removeVerifyListener
314  *
315  * @since 3.3
316  */
317 public void addVerifyListener (VerifyListener listener) {
318     checkWidget();
319     if (listener is null) DWT.error (DWT.ERROR_NULL_ARGUMENT);
320     TypedListener typedListener = new TypedListener (listener);
321     addListener (DWT.Verify,typedListener);
322 }
323 void arrowEvent (Event event) {
324     switch (event.type) {
325         case DWT.FocusIn: {
326             handleFocus (DWT.FocusIn);
327             break;
328         }
329         case DWT.MouseDown: {
330             Event mouseEvent = new Event ();
331             mouseEvent.button = event.button;
332             mouseEvent.count = event.count;
333             mouseEvent.stateMask = event.stateMask;
334             mouseEvent.time = event.time;
335             mouseEvent.x = event.x; mouseEvent.y = event.y;
336             notifyListeners (DWT.MouseDown, mouseEvent);
337             event.doit = mouseEvent.doit;
338             break;
339         }
340         case DWT.MouseUp: {
341             Event mouseEvent = new Event ();
342             mouseEvent.button = event.button;
343             mouseEvent.count = event.count;
344             mouseEvent.stateMask = event.stateMask;
345             mouseEvent.time = event.time;
346             mouseEvent.x = event.x; mouseEvent.y = event.y;
347             notifyListeners (DWT.MouseUp, mouseEvent);
348             event.doit = mouseEvent.doit;
349             break;
350         }
351         case DWT.Selection: {
352             text.setFocus();
353             dropDown (!isDropped ());
354             break;
355         }
356         default:
357     }
358 }
359 /**
360  * Sets the selection in the receiver's text field to an empty
361  * selection starting just before the first character. If the
362  * text field is editable, this has the effect of placing the
363  * i-beam at the start of the text.
364  * <p>
365  * Note: To clear the selected items in the receiver's list,
366  * use <code>deselectAll()</code>.
367  * </p>
368  *
369  * @exception DWTException <ul>
370  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
371  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
372  * </ul>
373  *
374  * @see #deselectAll
375  */
376 public void clearSelection () {
377     checkWidget ();
378     text.clearSelection ();
379     list.deselectAll ();
380 }
381 void comboEvent (Event event) {
382     switch (event.type) {
383         case DWT.Dispose:
384             if (popup !is null && !popup.isDisposed ()) {
385                 list.removeListener (DWT.Dispose, listener);
386                 popup.dispose ();
387             }
388             Shell shell = getShell ();
389             shell.removeListener (DWT.Deactivate, listener);
390             Display display = getDisplay ();
391             display.removeFilter (DWT.FocusIn, filter);
392             popup = null;
393             text = null;
394             list = null;
395             arrow = null;
396             break;
397         case DWT.FocusIn:
398             Control focusControl = getDisplay ().getFocusControl ();
399             if (focusControl is arrow || focusControl is list) return;
400             if (isDropped()) {
401                 list.setFocus();
402             } else {
403                 text.setFocus();
404             }
405             break;
406         case DWT.Move:
407             dropDown (false);
408             break;
409         case DWT.Resize:
410             internalLayout (false);
411             break;
412         default:
413     }
414 }
415
416 public override Point computeSize (int wHint, int hHint, bool changed) {
417     checkWidget ();
418     int width = 0, height = 0;
419     String[] items = list.getItems ();
420     GC gc = new GC (text);
421     int spacer = gc.stringExtent (" ").x; //$NON-NLS-1$
422     int textWidth = gc.stringExtent (text.getText ()).x;
423     for (int i = 0; i < items.length; i++) {
424         textWidth = Math.max (gc.stringExtent (items[i]).x, textWidth);
425     }
426     gc.dispose ();
427     Point textSize = text.computeSize (DWT.DEFAULT, DWT.DEFAULT, changed);
428     Point arrowSize = arrow.computeSize (DWT.DEFAULT, DWT.DEFAULT, changed);
429     Point listSize = list.computeSize (DWT.DEFAULT, DWT.DEFAULT, changed);
430     int borderWidth = getBorderWidth ();
431
432     height = Math.max (textSize.y, arrowSize.y);
433     width = Math.max (textWidth + 2*spacer + arrowSize.x + 2*borderWidth, listSize.x);
434     if (wHint !is DWT.DEFAULT) width = wHint;
435     if (hHint !is DWT.DEFAULT) height = hHint;
436     return new Point (width + 2*borderWidth, height + 2*borderWidth);
437 }
438 /**
439  * Copies the selected text.
440  * <p>
441  * The current selection is copied to the clipboard.
442  * </p>
443  *
444  * @exception DWTException <ul>
445  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
446  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
447  * </ul>
448  *
449  * @since 3.3
450  */
451 public void copy () {
452     checkWidget ();
453     text.copy ();
454 }
455 void createPopup(String[] items, int selectionIndex) {
456     // create shell and list
457     popup = new Shell (getShell (), DWT.NO_TRIM | DWT.ON_TOP);
458     int style = getStyle ();
459     int listStyle = DWT.SINGLE | DWT.V_SCROLL;
460     if ((style & DWT.FLAT) !is 0) listStyle |= DWT.FLAT;
461     if ((style & DWT.RIGHT_TO_LEFT) !is 0) listStyle |= DWT.RIGHT_TO_LEFT;
462     if ((style & DWT.LEFT_TO_RIGHT) !is 0) listStyle |= DWT.LEFT_TO_RIGHT;
463     list = new List (popup, listStyle);
464     if (font !is null) list.setFont (font);
465     if (foreground !is null) list.setForeground (foreground);
466     if (background !is null) list.setBackground (background);
467
468     int [] popupEvents = [DWT.Close, DWT.Paint, DWT.Deactivate];
469     for (int i=0; i<popupEvents.length; i++) popup.addListener (popupEvents [i], listener);
470     int [] listEvents = [DWT.MouseUp, DWT.Selection, DWT.Traverse, DWT.KeyDown, DWT.KeyUp, DWT.FocusIn, DWT.Dispose];
471     for (int i=0; i<listEvents.length; i++) list.addListener (listEvents [i], listener);
472
473     if (items !is null) list.setItems (items);
474     if (selectionIndex !is -1) list.setSelection (selectionIndex);
475 }
476 /**
477  * Cuts the selected text.
478  * <p>
479  * The current selection is first copied to the
480  * clipboard and then deleted from the widget.
481  * </p>
482  *
483  * @exception DWTException <ul>
484  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
485  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
486  * </ul>
487  *
488  * @since 3.3
489  */
490 public void cut () {
491     checkWidget ();
492     text.cut ();
493 }
494 /**
495  * Deselects the item at the given zero-relative index in the receiver's
496  * list.  If the item at the index was already deselected, it remains
497  * deselected. Indices that are out of range are ignored.
498  *
499  * @param index the index of the item to deselect
500  *
501  * @exception DWTException <ul>
502  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
503  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
504  * </ul>
505  */
506 public void deselect (int index) {
507     checkWidget ();
508     if (0 <= index && index < list.getItemCount () &&
509             index is list.getSelectionIndex() &&
510             text.getText().equals(list.getItem(index))) {
511         text.setText("");  //$NON-NLS-1$
512         list.deselect (index);
513     }
514 }
515 /**
516  * Deselects all selected items in the receiver's list.
517  * <p>
518  * Note: To clear the selection in the receiver's text field,
519  * use <code>clearSelection()</code>.
520  * </p>
521  *
522  * @exception DWTException <ul>
523  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
524  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
525  * </ul>
526  *
527  * @see #clearSelection
528  */
529 public void deselectAll () {
530     checkWidget ();
531     text.setText("");  //$NON-NLS-1$
532     list.deselectAll ();
533 }
534 void dropDown (bool drop) {
535     if (drop is isDropped ()) return;
536     if (!drop) {
537         popup.setVisible (false);
538         if (!isDisposed () && isFocusControl()) {
539             text.setFocus();
540         }
541         return;
542     }
543     if (!isVisible()) return;
544     if (getShell() !is popup.getParent ()) {
545         String[] items = list.getItems ();
546         int selectionIndex = list.getSelectionIndex ();
547         list.removeListener (DWT.Dispose, listener);
548         popup.dispose();
549         popup = null;
550         list = null;
551         createPopup (items, selectionIndex);
552     }
553
554     Point size = getSize ();
555     int itemCount = list.getItemCount ();
556     itemCount = (itemCount is 0) ? visibleItemCount : Math.min(visibleItemCount, itemCount);
557     int itemHeight = list.getItemHeight () * itemCount;
558     Point listSize = list.computeSize (DWT.DEFAULT, itemHeight, false);
559     list.setBounds (1, 1, Math.max (size.x - 2, listSize.x), listSize.y);
560
561     int index = list.getSelectionIndex ();
562     if (index !is -1) list.setTopIndex (index);
563     Display display = getDisplay ();
564     Rectangle listRect = list.getBounds ();
565     Rectangle parentRect = display.map (getParent (), null, getBounds ());
566     Point comboSize = getSize ();
567     Rectangle displayRect = getMonitor ().getClientArea ();
568     int width = Math.max (comboSize.x, listRect.width + 2);
569     int height = listRect.height + 2;
570     int x = parentRect.x;
571     int y = parentRect.y + comboSize.y;
572     if (y + height > displayRect.y + displayRect.height) y = parentRect.y - height;
573     if (x + width > displayRect.x + displayRect.width) x = displayRect.x + displayRect.width - listRect.width;
574     popup.setBounds (x, y, width, height);
575     popup.setVisible (true);
576     if (isFocusControl()) list.setFocus ();
577 }
578 /*
579  * Return the lowercase of the first non-'&' character following
580  * an '&' character in the given string. If there are no '&'
581  * characters in the given string, return '\0'.
582  */
583 dchar _findMnemonic (String string) {
584     if (string is null) return '\0';
585     int index = 0;
586     int length = string.length;
587     do {
588         while (index < length