root/dwt/widgets/Link.d

Revision 246:fd9c62a2998e, 35.9 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.Link;
14
15 import dwt.DWT;
16 import dwt.DWTException;
17 import dwt.accessibility.ACC;
18 import dwt.accessibility.Accessible;
19 import dwt.accessibility.AccessibleAdapter;
20 import dwt.accessibility.AccessibleControlAdapter;
21 import dwt.accessibility.AccessibleControlEvent;
22 import dwt.accessibility.AccessibleEvent;
23 import dwt.events.SelectionEvent;
24 import dwt.events.SelectionListener;
25 import dwt.graphics.Color;
26 import dwt.graphics.Font;
27 import dwt.graphics.GC;
28 import dwt.graphics.GCData;
29 import dwt.graphics.Point;
30 import dwt.graphics.RGB;
31 import dwt.graphics.Rectangle;
32 import dwt.graphics.TextLayout;
33 import dwt.graphics.TextStyle;
34 import dwt.internal.win32.OS;
35
36 import dwt.widgets.Control;
37 import dwt.widgets.Composite;
38 import dwt.widgets.TypedListener;
39 import dwt.widgets.Event;
40
41 import dwt.dwthelper.utils;
42
43 static import tango.text.Text;
44 alias tango.text.Text.Text!(char) StringBuffer;
45
46 /**
47  * Instances of this class represent a selectable
48  * user interface object that displays a text with
49  * links.
50  * <p>
51  * <dl>
52  * <dt><b>Styles:</b></dt>
53  * <dd>(none)</dd>
54  * <dt><b>Events:</b></dt>
55  * <dd>Selection</dd>
56  * </dl>
57  * <p>
58  * IMPORTANT: This class is <em>not</em> intended to be subclassed.
59  * </p>
60  *
61  * @see <a href="http://www.eclipse.org/swt/snippets/#link">Link snippets</a>
62  * @see <a href="http://www.eclipse.org/swt/examples.php">DWT Example: ControlExample</a>
63  * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
64  *
65  * @since 3.1
66  */
67 public class Link : Control {
68
69     alias Control.computeSize computeSize;
70     alias Control.windowProc windowProc;
71
72     String text;
73     TextLayout layout;
74     Color linkColor, disabledColor;
75     Point [] offsets;
76     Point selection;
77     String [] ids;
78     int [] mnemonics;
79     int focusIndex, mouseDownIndex;
80     HFONT font;
81     static /+const+/ RGB LINK_FOREGROUND;
82     static /+const+/ WNDPROC LinkProc;
83     static const TCHAR[] LinkClass = OS.WC_LINK;
84
85     private static bool static_this_completed = false;
86     private static void static_this() {
87         if( static_this_completed ){
88             return;
89         }
90         synchronized {
91             if( static_this_completed ){
92                 return;
93             }
94             LINK_FOREGROUND = new RGB (0, 51, 153);
95             if (OS.COMCTL32_MAJOR >= 6) {
96                 WNDCLASS lpWndClass;
97                 OS.GetClassInfo (null, LinkClass.ptr, &lpWndClass);
98                 LinkProc = lpWndClass.lpfnWndProc;
99             /*
100             * Feature in Windows.  The SysLink window class
101             * does not include CS_DBLCLKS.  This means that these
102             * controls will not get double click messages such as
103             * WM_LBUTTONDBLCLK.  The fix is to register a new
104             * window class with CS_DBLCLKS.
105             *
106             * NOTE:  Screen readers look for the exact class name
107             * of the control in order to provide the correct kind
108             * of assistance.  Therefore, it is critical that the
109             * new window class have the same name.  It is possible
110             * to register a local window class with the same name
111             * as a global class.  Since bits that affect the class
112             * are being changed, it is possible that other native
113             * code, other than DWT, could create a control with
114             * this class name, and fail unexpectedly.
115             */
116             auto hInstance = OS.GetModuleHandle (null);
117             auto hHeap = OS.GetProcessHeap ();
118             lpWndClass.hInstance = hInstance;
119             lpWndClass.style &= ~OS.CS_GLOBALCLASS;
120             lpWndClass.style |= OS.CS_DBLCLKS;
121             int byteCount = LinkClass.length * TCHAR.sizeof;
122             auto lpszClassName = cast(TCHAR*) OS.HeapAlloc (hHeap, OS.HEAP_ZERO_MEMORY, byteCount);
123             OS.MoveMemory (lpszClassName, LinkClass.ptr, byteCount);
124             lpWndClass.lpszClassName = lpszClassName;
125             OS.RegisterClass (&lpWndClass);
126             OS.HeapFree (hHeap, 0, lpszClassName);
127             } else {
128                 LinkProc = null;
129             }
130             static_this_completed = true;
131         }
132     }
133
134 /**
135  * Constructs a new instance of this class given its parent
136  * and a style value describing its behavior and appearance.
137  * <p>
138  * The style value is either one of the style constants defined in
139  * class <code>DWT</code> which is applicable to instances of this
140  * class, or must be built by <em>bitwise OR</em>'ing together
141  * (that is, using the <code>int</code> "|" operator) two or more
142  * of those <code>DWT</code> style constants. The class description
143  * lists the style constants that are applicable to the class.
144  * Style bits are also inherited from superclasses.
145  * </p>
146  *
147  * @param parent a composite control which will be the parent of the new instance (cannot be null)
148  * @param style the style of control to construct
149  *
150  * @exception IllegalArgumentException <ul>
151  *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
152  * </ul>
153  * @exception DWTException <ul>
154  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
155  *    <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
156  * </ul>
157  *
158  * @see Widget#checkSubclass
159  * @see Widget#getStyle
160  */
161 public this (Composite parent, int style) {
162     static_this();
163     super (parent, style);
164 }
165
166 /**
167  * Adds the listener to the collection of listeners who will
168  * be notified when the control is selected by the user, by sending
169  * it one of the messages defined in the <code>SelectionListener</code>
170  * interface.
171  * <p>
172  * <code>widgetSelected</code> is called when the control is selected by the user.
173  * <code>widgetDefaultSelected</code> is not called.
174  * </p>
175  *
176  * @param listener the listener which should be notified
177  *
178  * @exception IllegalArgumentException <ul>
179  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
180  * </ul>
181  * @exception DWTException <ul>
182  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
183  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
184  * </ul>
185  *
186  * @see SelectionListener
187  * @see #removeSelectionListener
188  * @see SelectionEvent
189  */
190 public void addSelectionListener (SelectionListener listener) {
191     checkWidget ();
192     if (listener is null) error (DWT.ERROR_NULL_ARGUMENT);
193     TypedListener typedListener = new TypedListener (listener);
194     addListener (DWT.Selection, typedListener);
195     addListener (DWT.DefaultSelection, typedListener);
196 }
197
198 override int callWindowProc (HWND hwnd, int msg, int wParam, int lParam) {
199     if (handle is null) return 0;
200     if (LinkProc !is null) {
201         /*
202         * Feature in Windows.  By convention, native Windows controls
203         * check for a non-NULL wParam, assume that it is an HDC and
204         * paint using that device.  The SysLink control does not.
205         * The fix is to check for an HDC and use WM_PRINTCLIENT.
206         */
207         switch (msg) {
208             case OS.WM_PAINT:
209                 if (wParam !is 0) {
210                     OS.SendMessage (hwnd, OS.WM_PRINTCLIENT, wParam, 0);
211                     return 0;
212                 }
213                 break;
214             default:
215         }
216         return OS.CallWindowProc (LinkProc, hwnd, msg, wParam, lParam);
217     }
218     return OS.DefWindowProc (hwnd, msg, wParam, lParam);
219 }
220
221 override public Point computeSize (int wHint, int hHint, bool changed) {
222     checkWidget ();
223     if (wHint !is DWT.DEFAULT && wHint < 0) wHint = 0;
224     if (hHint !is DWT.DEFAULT && hHint < 0) hHint = 0;
225     int width, height;
226     if (OS.COMCTL32_MAJOR >= 6) {
227         auto hDC = OS.GetDC (handle);
228         auto newFont = cast(HFONT) OS.SendMessage (handle, OS.WM_GETFONT, 0, 0);
229         auto oldFont = OS.SelectObject (hDC, newFont);
230         if (text.length > 0) {
231             TCHAR[] buffer = StrToTCHARs (getCodePage (), parse (text));
232             RECT rect;
233             int flags = OS.DT_CALCRECT | OS.DT_NOPREFIX;
234             if (wHint !is DWT.DEFAULT) {
235                 flags |= OS.DT_WORDBREAK;
236                 rect.right = wHint;
237             }
238             OS.DrawText (hDC, buffer.ptr, buffer.length, &rect, flags);
239             width = rect.right - rect.left;
240             height = rect.bottom;
241         } else {
242             TEXTMETRIC lptm;
243             OS.GetTextMetrics (hDC, &lptm);
244             width = 0;
245             height = lptm.tmHeight;
246         }
247         if (newFont !is null) OS.SelectObject (hDC, oldFont);
248         OS.ReleaseDC (handle, hDC);
249     } else {
250         int layoutWidth = layout.getWidth ();
251         //TEMPORARY CODE
252         if (wHint is 0) {
253             layout.setWidth (1);
254             Rectangle rect = layout.getBounds ();
255             width = 0;
256             height = rect.height;
257         } else {
258             layout.setWidth (wHint);
259             Rectangle rect = layout.getBounds ();
260             width = rect.width;
261             height = rect.height;
262         }
263         layout.setWidth (layoutWidth);
264     }
265     if (wHint !is DWT.DEFAULT) width = wHint;
266     if (hHint !is DWT.DEFAULT) height = hHint;
267     int border = getBorderWidth ();
268     width += border * 2;
269     height += border * 2;
270     return new Point (width, height);
271 }
272
273 override void createHandle () {
274     super.createHandle ();
275     state |= THEME_BACKGROUND;
276     if (OS.COMCTL32_MAJOR < 6) {
277         layout = new TextLayout (display);
278         if (!OS.IsWinCE && OS.WIN32_VERSION >= OS.VERSION (4, 10)) {
279             linkColor = Color.win32_new (display, OS.GetSysColor (OS.COLOR_HOTLIGHT));
280         } else {
281             linkColor = new Color (display, LINK_FOREGROUND);
282         }
283         disabledColor = Color.win32_new (display, OS.GetSysColor (OS.COLOR_GRAYTEXT));
284         offsets = new Point [0];
285         ids = new String [0];
286         mnemonics = new int [0];
287         selection = new Point (-1, -1);
288         focusIndex = mouseDownIndex = -1;
289     }
290 }
291
292 override void createWidget () {
293     super.createWidget ();
294     text = "";
295     if (OS.COMCTL32_MAJOR < 6) {
296         if ((style & DWT.MIRRORED) !is 0) {
297             layout.setOrientation (DWT.RIGHT_TO_LEFT);
298         }
299         initAccessible ();
300     }
301 }
302
303 void drawWidget (GC gc, RECT* rect) {
304     drawBackground (gc.handle, rect);
305     int selStart = selection.x;
306     int selEnd = selection.y;
307     if (selStart > selEnd) {
308         selStart = selection.y;
309         selEnd = selection.x;
310     }
311     // temporary code to disable text selection
312     selStart = selEnd = -1;
313     if (!OS.IsWindowEnabled (handle)) gc.setForeground (disabledColor);
314     layout.draw (gc, 0, 0, selStart, selEnd, null, null);
315     if (hasFocus () && focusIndex !is -1) {
316         Rectangle [] rects = getRectangles (focusIndex);
317         for (int i = 0; i < rects.length; i++) {
318             Rectangle rectangle = rects [i];
319             gc.drawFocus (rectangle.x, rectangle.y, rectangle.width, rectangle.height);
320         }
321     }
322     if (hooks (DWT.Paint) || filters (DWT.Paint)) {
323         Event event = new Event ();
324         event.gc = gc;
325         event.x = rect.left;
326         event.y = rect.top;
327         event.width = rect.right - rect.left;
328         event.height = rect.bottom - rect.top;
329         sendEvent (DWT.Paint, event);
330         event.gc = null;
331     }
332 }
333
334 override void enableWidget (bool enabled) {
335     if (OS.COMCTL32_MAJOR >= 6) {
336         LITEM item;
337         item.mask = OS.LIF_ITEMINDEX | OS.LIF_STATE;
338         item.stateMask = OS.LIS_ENABLED;
339         item.state = enabled ? OS.LIS_ENABLED : 0;
340         while (OS.SendMessage (handle, OS.LM_SETITEM, 0, &item) !is 0) {
341             item.iLink++;
342         }
343     } else {
344         TextStyle linkStyle = new TextStyle (null, enabled ? linkColor : disabledColor, null);
345         linkStyle.underline = true;
346         for (int i = 0; i < offsets.length; i++) {
347             Point point = offsets [i];
348             layout.setStyle (linkStyle, point.x, point.y);
349         }
350         redraw ();
351     }
352     /*
353     * Feature in Windows.  For some reason, setting
354     * LIS_ENABLED state using LM_SETITEM causes the
355     * SysLink to become enabled.  To be specific,
356     * calling IsWindowEnabled() returns true.  The
357     * fix is disable the SysLink after LM_SETITEM.
358     */
359     super.enableWidget (enabled);
360 }
361
362 void initAccessible () {
363     Accessible accessible = getAccessible ();
364     accessible.addAccessibleListener (new class() AccessibleAdapter {
365         public void getName (AccessibleEvent e) {
366             e.result = parse (text);
367         }
368     });
369
370     accessible.addAccessibleControlListener (new class() AccessibleControlAdapter {
371         public void getChildAtPoint (AccessibleControlEvent e) {
372             e.childID = ACC.CHILDID_SELF;
373         }
374
375         public void getLocation (AccessibleControlEvent e) {
376             Rectangle rect = display.map (getParent (), null, getBounds ());
377             e.x = rect.x;
378             e.y = rect.y;
379             e.width = rect.width;
380             e.height = rect.height;
381         }
382
383         public void getChildCount (AccessibleControlEvent e) {
384             e.detail = 0;
385         }
386
387         public void getRole (AccessibleControlEvent e) {
388             e.detail = ACC.ROLE_LINK;
389         }
390
391         public void getState (AccessibleControlEvent e) {
392             e.detail = ACC.STATE_FOCUSABLE;
393             if (hasFocus ()) e.detail |= ACC.STATE_FOCUSED;
394         }
395
396         public void getDefaultAction (AccessibleControlEvent e) {
397             e.result = DWT.getMessage ("SWT_Press"); //$NON-NLS-1$
398         }
399
400         public void getSelection (AccessibleControlEvent e) {
401             if (hasFocus ()) e.childID = ACC.CHILDID_SELF;
402         }
403
404         public void getFocus (AccessibleControlEvent e) {
405             if (hasFocus ()) e.childID = ACC.CHILDID_SELF;
406         }
407     });
408 }
409
410 override String getNameText () {
411     return getText ();
412 }
413
414 Rectangle [] getRectangles (int linkIndex) {
415     int lineCount = layout.getLineCount ();
416     Rectangle [] rects = new Rectangle [lineCount];
417     int [] lineOffsets = layout.getLineOffsets ();
418     Point point = offsets [linkIndex];
419     int lineStart = 1;
420     while (point.x > lineOffsets [lineStart]) lineStart++;
421     int lineEnd = 1;
422     while (point.y > lineOffsets [lineEnd]) lineEnd++;
423     int index = 0;
424     if (lineStart is lineEnd) {
425         rects [index++] = layout.getBounds (point.x, point.y);
426     } else {
427         rects [index++] = layout.getBounds (point.x, lineOffsets [lineStart]-1);
428         rects [index++] = layout.getBounds (lineOffsets [lineEnd-1], point.y);
429         if (lineEnd - lineStart > 1) {
430             for (int i = lineStart; i < lineEnd - 1; i++) {
431                 rects [index++] = layout.getLineBounds (i);
432             }
433         }
434     }
435     if (rects.length !is index) {
436         Rectangle [] tmp = new Rectangle [index];
437         System.arraycopy (rects, 0, tmp, 0, index);
438         rects = tmp;
439     }
440     return rects;
441 }
442
443 /**
444  * Returns the receiver's text, which will be an empty
445  * string if it has never been set.
446  *
447  * @return the receiver's text
448  *
449  * @exception DWTException <ul>
450  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
451  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
452  * </ul>
453  */
454 public String getText () {
455     checkWidget ();
456     return text;
457 }
458
459 String parse (String string) {
460     int length_ = string.length;
461     offsets = new Point [length_ / 4];
462     ids = new String [length_ / 4];
463     mnemonics = new int [length_ / 4 + 1];
464     StringBuffer result = new StringBuffer ();
465     char [] buffer = new char [length_];
466     string.getChars (0, string.length, buffer, 0);
467     int index = 0, state = 0, linkIndex = 0;
468     int start = 0, tagStart = 0, linkStart = 0, endtagStart = 0, refStart = 0;
469
470     while (index < length_) {
471
472         // instead Character.toLower
473         // only needed for the control symbols, which are always ascii
474         char c = buffer[index];
475         if( c >= 'A' && c <= 'Z' ){
476             c -= ( 'A' - 'a' );
477         }
478
479         // only simple ascii whitespace needed
480         bool isWhitespace(char w ){
481             switch(w){
482                 case ' ': return true;
483                 default : return false;
484             }
485         }
486
487         switch (state) {
488             case 0:
489                 if (c is '<') {
490                     tagStart = index;
491                     state++;
492                 }
493                 break;
494             case 1:
495                 if (c is 'a') state++;
496                 break;
497             case 2:
498                 switch (c) {
499                     case 'h':
500                         state = 7;
501                         break;
502                     case '>':
503                         linkStart = index  + 1;
504                         state++;
505                         break;
506                     default:
507                         if (isWhitespace(c)) break;
508                         else state = 13;
509                 }
510                 break;
511             case 3:
512                 if (c is '<') {
513                     endtagStart = index;
514                     state++;
515                 }
516                 break;
517             case 4:
518                 state = c is '/' ? state + 1 : 3;
519                 break;
520             case 5:
521                 state = c is 'a' ? state + 1 : 3;
522                 break;
523             case 6:
524                 if (c is '>') {
525                     mnemonics [linkIndex] = parseMnemonics (buffer, start, tagStart, result);
526                     int offset = result.length;
527                     parseMnemonics (buffer, linkStart, endtagStart, result);
528                     offsets [linkIndex] = new Point (offset, result.length - 1);
529                     if (ids [linkIndex] is null) {
530                         ids [linkIndex] = buffer[ linkStart .. endtagStart ].dup;
531                     }
532                     linkIndex++;
533                     start = tagStart = linkStart = endtagStart = refStart = index + 1;
534                     state = 0;
535                 } else {
536                     state = 3;
537                 }
538                 break;
539             case 7:
540                 state = c is 'r' ? state + 1 : 0;
541                 break;
542             case 8:
543                 state = c is 'e' ? state + 1 : 0;
544                 break;
545             case 9:
546                 state = c is 'f' ? state + 1 : 0;
547                 break;
548             case 10:
549                 state = c is '=' ? state + 1 : 0;
550                 break;
551             case 11:
552                 if (c is '"') {
553                     state++