]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.eclipse.swt.win32.win32.x86_64/src/org/eclipse/swt/widgets/Link.java
Remove invalid SHA-256-Digests
[simantics/platform.git] / bundles / org.eclipse.swt.win32.win32.x86_64 / src / org / eclipse / swt / widgets / Link.java
1 /*******************************************************************************
2  * Copyright (c) 2000, 2016 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *     Conrad Groth - Bug 401015 - [CSS] Add support for styling hyperlinks in Links
14  *******************************************************************************/
15 package org.eclipse.swt.widgets;
16
17 import java.util.*;
18
19 import org.eclipse.swt.*;
20 import org.eclipse.swt.events.*;
21 import org.eclipse.swt.graphics.*;
22 import org.eclipse.swt.internal.*;
23 import org.eclipse.swt.internal.win32.*;
24
25 /**
26  * Instances of this class represent a selectable
27  * user interface object that displays a text with
28  * links.
29  * <dl>
30  * <dt><b>Styles:</b></dt>
31  * <dd>(none)</dd>
32  * <dt><b>Events:</b></dt>
33  * <dd>Selection</dd>
34  * </dl>
35  * <p>
36  * IMPORTANT: This class is <em>not</em> intended to be subclassed.
37  * </p>
38  *
39  * @see <a href="http://www.eclipse.org/swt/snippets/#link">Link snippets</a>
40  * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: ControlExample</a>
41  * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
42  *
43  * @since 3.1
44  * @noextend This class is not intended to be subclassed by clients.
45  */
46 public class Link extends Control {
47         String text;
48         int linkForeground = -1;
49         String [] ids;
50         char [] mnemonics;
51         int nextFocusItem = -1;
52         static final long LinkProc;
53         static final TCHAR LinkClass = new TCHAR (0, OS.WC_LINK, true);
54         static {
55                 WNDCLASS lpWndClass = new WNDCLASS ();
56                 OS.GetClassInfo (0, LinkClass, lpWndClass);
57                 LinkProc = lpWndClass.lpfnWndProc;
58                 /*
59                 * Feature in Windows.  The SysLink window class
60                 * does not include CS_DBLCLKS.  This means that these
61                 * controls will not get double click messages such as
62                 * WM_LBUTTONDBLCLK.  The fix is to register a new
63                 * window class with CS_DBLCLKS.
64                 *
65                 * NOTE:  Screen readers look for the exact class name
66                 * of the control in order to provide the correct kind
67                 * of assistance.  Therefore, it is critical that the
68                 * new window class have the same name.  It is possible
69                 * to register a local window class with the same name
70                 * as a global class.  Since bits that affect the class
71                 * are being changed, it is possible that other native
72                 * code, other than SWT, could create a control with
73                 * this class name, and fail unexpectedly.
74                 */
75                 lpWndClass.hInstance = OS.GetModuleHandle (null);
76                 lpWndClass.style &= ~OS.CS_GLOBALCLASS;
77                 lpWndClass.style |= OS.CS_DBLCLKS;
78                 OS.RegisterClass (LinkClass, lpWndClass);
79         }
80
81 /**
82  * Constructs a new instance of this class given its parent
83  * and a style value describing its behavior and appearance.
84  * <p>
85  * The style value is either one of the style constants defined in
86  * class <code>SWT</code> which is applicable to instances of this
87  * class, or must be built by <em>bitwise OR</em>'ing together
88  * (that is, using the <code>int</code> "|" operator) two or more
89  * of those <code>SWT</code> style constants. The class description
90  * lists the style constants that are applicable to the class.
91  * Style bits are also inherited from superclasses.
92  * </p>
93  *
94  * @param parent a composite control which will be the parent of the new instance (cannot be null)
95  * @param style the style of control to construct
96  *
97  * @exception IllegalArgumentException <ul>
98  *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
99  * </ul>
100  * @exception SWTException <ul>
101  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
102  *    <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
103  * </ul>
104  *
105  * @see Widget#checkSubclass
106  * @see Widget#getStyle
107  */
108 public Link (Composite parent, int style) {
109         super (parent, style);
110 }
111
112 /**
113  * Adds the listener to the collection of listeners who will
114  * be notified when the control is selected by the user, by sending
115  * it one of the messages defined in the <code>SelectionListener</code>
116  * interface.
117  * <p>
118  * <code>widgetSelected</code> is called when the control is selected by the user.
119  * <code>widgetDefaultSelected</code> is not called.
120  * </p>
121  *
122  * @param listener the listener which should be notified
123  *
124  * @exception IllegalArgumentException <ul>
125  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
126  * </ul>
127  * @exception SWTException <ul>
128  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
129  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
130  * </ul>
131  *
132  * @see SelectionListener
133  * @see #removeSelectionListener
134  * @see SelectionEvent
135  */
136 public void addSelectionListener (SelectionListener listener) {
137         checkWidget ();
138         if (listener == null) error (SWT.ERROR_NULL_ARGUMENT);
139         TypedListener typedListener = new TypedListener (listener);
140         addListener (SWT.Selection, typedListener);
141         addListener (SWT.DefaultSelection, typedListener);
142 }
143
144 @Override
145 long callWindowProc (long hwnd, int msg, long wParam, long lParam) {
146         if (handle == 0) return 0;
147         /*
148         * Feature in Windows.  By convention, native Windows controls
149         * check for a non-NULL wParam, assume that it is an HDC and
150         * paint using that device.  The SysLink control does not.
151         * The fix is to check for an HDC and use WM_PRINTCLIENT.
152         */
153         switch (msg) {
154                 case OS.WM_PAINT:
155                         if (wParam != 0) {
156                                 OS.SendMessage (hwnd, OS.WM_PRINTCLIENT, wParam, 0);
157                                 return 0;
158                         }
159                         break;
160         }
161         return OS.CallWindowProc (LinkProc, hwnd, msg, wParam, lParam);
162 }
163
164 @Override
165 Point computeSizeInPixels (int wHint, int hHint, boolean changed) {
166         checkWidget ();
167         int width, height;
168         /*
169          * When the text is empty, LM_GETIDEALSIZE returns zero width and height,
170          * but SWT convention is to return zero width and line height.
171          */
172         if (text.isEmpty()) {
173                 long hDC = OS.GetDC (handle);
174                 long newFont = OS.SendMessage (handle, OS.WM_GETFONT, 0, 0);
175                 long oldFont = OS.SelectObject (hDC, newFont);
176                 TEXTMETRIC lptm = new TEXTMETRIC ();
177                 OS.GetTextMetrics (hDC, lptm);
178                 width = 0;
179                 height = lptm.tmHeight;
180                 if (newFont != 0) OS.SelectObject (hDC, oldFont);
181                 OS.ReleaseDC (handle, hDC);
182         } else {
183                 SIZE size = new SIZE ();
184                 int maxWidth = (wHint == SWT.DEFAULT) ? 0x7fffffff : wHint;
185                 OS.SendMessage (handle, OS.LM_GETIDEALSIZE, maxWidth, size);
186                 width = size.cx;
187                 height = size.cy;
188         }
189         if (wHint != SWT.DEFAULT) width = wHint;
190         if (hHint != SWT.DEFAULT) height = hHint;
191         int border = getBorderWidthInPixels ();
192         width += border * 2;
193         height += border * 2;
194         return new Point (width, height);
195 }
196
197 @Override
198 void createHandle () {
199         super.createHandle ();
200         state |= THEME_BACKGROUND;
201 }
202
203 @Override
204 void createWidget () {
205         super.createWidget ();
206         text = "";
207         ids = new String[0];
208         mnemonics = new char[0];
209 }
210
211 @Override
212 void enableWidget (boolean enabled) {
213         super.enableWidget (enabled);
214         /*
215          * Feature in Windows.  SysLink32 control doesn't natively
216          * provide disabled state. Emulate it with custom draw.
217          */
218         OS.InvalidateRect (handle, null, true);
219 }
220
221 int getFocusItem () {
222         LITEM item = new LITEM ();
223         item.mask = OS.LIF_ITEMINDEX | OS.LIF_STATE;
224         item.stateMask = OS.LIS_FOCUSED;
225         while (OS.SendMessage (handle, OS.LM_GETITEM, 0, item) != 0) {
226                 if ((item.state & OS.LIS_FOCUSED) != 0) {
227                         return item.iLink;
228                 }
229                 item.iLink++;
230         }
231         return -1;
232 }
233
234 /**
235  * Returns the link foreground color.
236  *
237  * @return the receiver's link foreground color.
238  *
239  * @exception SWTException <ul>
240  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
241  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
242  * </ul>
243  * @since 3.105
244  */
245 public Color getLinkForeground () {
246         checkWidget ();
247         if (linkForeground != -1) {
248                 return Color.win32_new (display, linkForeground);
249         }
250         return Color.win32_new (display, OS.GetSysColor (OS.COLOR_HOTLIGHT));
251 }
252
253 @Override
254 String getNameText () {
255         return getText ();
256 }
257
258 /**
259  * Returns the receiver's text, which will be an empty
260  * string if it has never been set.
261  *
262  * @return the receiver's text
263  *
264  * @exception SWTException <ul>
265  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
266  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
267  * </ul>
268  */
269 public String getText () {
270         checkWidget ();
271         return text;
272 }
273
274 @Override
275 boolean mnemonicHit (char key) {
276         char uckey = Character.toUpperCase (key);
277         for (int i = 0; i < mnemonics.length; i++) {
278                 if (uckey == mnemonics[i]) {
279                         nextFocusItem = i;
280                         return setFocus () && setFocusItem (i);
281                 }
282         }
283         return false;
284 }
285
286 @Override
287 boolean mnemonicMatch (char key) {
288         char uckey = Character.toUpperCase (key);
289         for (int i = 0; i < mnemonics.length; i++) {
290                 if (uckey == mnemonics[i]) {
291                         return true;
292                 }
293         }
294         return false;
295 }
296
297 void parse (String string) {
298         int length = string.length ();
299         // The shortest link length is 7 characters (<a></a>).
300         ids = new String [length / 7];
301         mnemonics = new char [length / 7];
302         int index = 0, state = 0, linkIndex = 0;
303         int linkStart = 0, linkEnd = 0, refStart = 0, refEnd = 0;
304         char mnemonic = 0;
305         while (index < length) {
306                 char c = Character.toLowerCase (string.charAt (index));
307                 switch (state) {
308                         case 0:
309                                 if (c == '<') {
310                                         state++;
311                                 } else if (c == '&') {
312                                         state = 16;
313                                 }
314                                 break;
315                         case 1:
316                                 if (c == 'a') state++;
317                                 break;
318                         case 2:
319                                 switch (c) {
320                                         case 'h':
321                                                 state = 7;
322                                                 break;
323                                         case '>':
324                                                 linkStart = index  + 1;
325                                                 state++;
326                                                 break;
327                                         default:
328                                                 if (Character.isWhitespace(c)) break;
329                                                 else state = 13;
330                                 }
331                                 break;
332                         case 3:
333                                 if (c == '<') {
334                                         linkEnd = index;
335                                         state++;
336                                 }
337                                 break;
338                         case 4:
339                                 state = c == '/' ? state + 1 : 3;
340                                 break;
341                         case 5:
342                                 state = c == 'a' ? state + 1 : 3;
343                                 break;
344                         case 6:
345                                 if (c == '>') {
346                                         if (refStart == 0) {
347                                                 refStart = linkStart;
348                                                 refEnd = linkEnd;
349                                         }
350                                         ids [linkIndex] = string.substring(refStart, refEnd);
351                                         if (mnemonic != 0) {
352                                                 mnemonics [linkIndex] = mnemonic;
353                                         }
354                                         linkIndex++;
355                                         linkStart = linkEnd = refStart = refEnd = mnemonic = 0;
356                                         state = 0;
357                                 } else {
358                                         state = 3;
359                                 }
360                                 break;
361                         case 7:
362                                 state = c == 'r' ? state + 1 : 0;
363                                 break;
364                         case 8:
365                                 state = c == 'e' ? state + 1 : 0;
366                                 break;
367                         case 9:
368                                 state = c == 'f' ? state + 1 : 0;
369                                 break;
370                         case 10:
371                                 state = c == '=' ? state + 1 : 0;
372                                 break;
373                         case 11:
374                                 if (c == '"') {
375                                         state++;
376                                         refStart = index + 1;
377                                 } else {
378                                         state = 0;
379                                 }
380                                 break;
381                         case 12:
382                                 if (c == '"') {
383                                         refEnd = index;
384                                         state = 2;
385                                 }
386                                 break;
387                         case 13:
388                                 if (Character.isWhitespace (c)) {
389                                         state = 0;
390                                 } else if (c == '='){
391                                         state++;
392                                 }
393                                 break;
394                         case 14:
395                                 state = c == '"' ? state + 1 : 0;
396                                 break;
397                         case 15:
398                                 if (c == '"') state = 2;
399                                 break;
400                         case 16:
401                                 if (c == '<') {
402                                         state = 1;
403                                 } else {
404                                         state = 0;
405                                         if (c != '&') mnemonic = Character.toUpperCase (c);
406                                 }
407                                 break;
408                         default:
409                                 state = 0;
410                                 break;
411                 }
412                 index++;
413         }
414         ids = Arrays.copyOf(ids, linkIndex);
415         mnemonics = Arrays.copyOf(mnemonics, linkIndex);
416 }
417
418 @Override
419 void releaseWidget () {
420         super.releaseWidget ();
421         ids = null;
422         mnemonics = null;
423         text = null;
424 }
425
426 /**
427  * Removes the listener from the collection of listeners who will
428  * be notified when the control is selected by the user.
429  *
430  * @param listener the listener which should no longer be notified
431  *
432  * @exception IllegalArgumentException <ul>
433  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
434  * </ul>
435  * @exception SWTException <ul>
436  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
437  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
438  * </ul>
439  *
440  * @see SelectionListener
441  * @see #addSelectionListener
442  */
443 public void removeSelectionListener (SelectionListener listener) {
444         checkWidget ();
445         if (listener == null) error (SWT.ERROR_NULL_ARGUMENT);
446         if (eventTable == null) return;
447         eventTable.unhook (SWT.Selection, listener);
448         eventTable.unhook (SWT.DefaultSelection, listener);
449 }
450
451 boolean setFocusItem (int index) {
452         int bits = 0;
453         if (index > 0) {
454                 bits = OS.GetWindowLong (handle, OS.GWL_STYLE);
455         }
456         LITEM item = new LITEM ();
457         item.mask = OS.LIF_ITEMINDEX | OS.LIF_STATE;
458         item.stateMask = OS.LIS_FOCUSED;
459         int activeIndex = getFocusItem ();
460         if (activeIndex == index) return true;
461         if (activeIndex >= 0) {
462                 /* Feature in Windows. Unfocus any element unfocus all elements.
463                  * For example if item 2 is focused and we set unfocus (state = 0)
464                  * for item 0 Windows will remove the focus state for item 2
465                  * (getFocusItem() == -1) but fail to remove the focus border around
466                  * the link. The fix is to only unfocus the element which has focus.
467                  */
468                 item.iLink = activeIndex;
469                 OS.SendMessage (handle, OS.LM_SETITEM, 0, item);
470         }
471         item.iLink = index;
472         item.state = OS.LIS_FOCUSED;
473         long result = OS.SendMessage (handle, OS.LM_SETITEM, 0, item);
474
475         if (index > 0) {
476         /* Feature in Windows. For some reason, setting the focus to
477          * any item but first causes the control to clear the WS_TABSTOP
478          * bit. The fix is always to reset the bit.
479          */
480         OS.SetWindowLong (handle, OS.GWL_STYLE, bits);
481         }
482         return result != 0;
483 }
484
485 /**
486  * Sets the link foreground color to the color specified
487  * by the argument, or to the default system color for the link
488  * if the argument is null.
489  * <p>
490  * Note: This operation is a hint and may be overridden by the platform.
491  * </p>
492  * @param color the new color (or null)
493  *
494  * @exception IllegalArgumentException <ul>
495  *    <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li>
496  * </ul>
497  * @exception SWTException <ul>
498  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
499  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
500  * </ul>
501  * @since 3.105
502  */
503 public void setLinkForeground (Color color) {
504         checkWidget ();
505         int pixel = -1;
506         if (color != null) {
507                 if (color.isDisposed()) error(SWT.ERROR_INVALID_ARGUMENT);
508                 pixel = color.handle;
509         }
510         if (pixel == linkForeground) return;
511         linkForeground = pixel;
512         OS.InvalidateRect (handle, null, true);
513 }
514
515 /**
516  * Sets the receiver's text.
517  * <p>
518  * The string can contain both regular text and hyperlinks.  A hyperlink
519  * is delimited by an anchor tag, &lt;a&gt; and &lt;/a&gt;.  Within an
520  * anchor, a single HREF attribute is supported.  When a hyperlink is
521  * selected, the text field of the selection event contains either the
522  * text of the hyperlink or the value of its HREF, if one was specified.
523  * In the rare case of identical hyperlinks within the same string, the
524  * HREF attribute can be used to distinguish between them.  The string may
525  * include the mnemonic character and line delimiters. The only delimiter
526  * the HREF attribute supports is the quotation mark ("). Text containing
527  * angle-bracket characters &lt; or &gt; may be escaped using \\, however
528  * this operation is a hint and varies from platform to platform.
529  * </p>
530  * <p>
531  * Mnemonics are indicated by an '&amp;' that causes the next
532  * character to be the mnemonic. The receiver can have a
533  * mnemonic in the text preceding each link. When the user presses a
534  * key sequence that matches the mnemonic, focus is assigned
535  * to the link that follows the text. Mnemonics in links and in
536  * the trailing text are ignored. On most platforms,
537  * the mnemonic appears underlined but may be emphasised in a
538  * platform specific manner.  The mnemonic indicator character
539  * '&amp;' can be escaped by doubling it in the string, causing
540  * a single '&amp;' to be displayed.
541  * </p>
542  *
543  * @param string the new text
544  *
545  * @exception IllegalArgumentException <ul>
546  *    <li>ERROR_NULL_ARGUMENT - if the text is null</li>
547  * </ul>
548  * @exception SWTException <ul>
549  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
550  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
551  * </ul>
552  */
553 public void setText (String string) {
554         checkWidget ();
555         if (string == null) error (SWT.ERROR_NULL_ARGUMENT);
556         if (string.equals (text)) return;
557         text = string;
558         if ((state & HAS_AUTO_DIRECTION) != 0) {
559                 updateTextDirection (AUTO_TEXT_DIRECTION);
560         }
561         TCHAR buffer = new TCHAR (getCodePage (), string, true);
562         OS.SetWindowText (handle, buffer);
563         parse(string);
564 }
565
566 @Override
567 int resolveTextDirection() {
568         return BidiUtil.resolveTextDirection(text);
569 }
570
571 @Override
572 boolean updateTextDirection(int textDirection) {
573         if (super.updateTextDirection(textDirection)) {
574                 int flags = SWT.RIGHT_TO_LEFT | SWT.LEFT_TO_RIGHT;
575                 style &= ~SWT.MIRRORED;
576                 style &= ~flags;
577                 style |= textDirection & flags;
578                 updateOrientation ();
579                 checkMirrored ();
580                 return true;
581         }
582         return false;
583 }
584
585 @Override
586 int widgetStyle () {
587         int bits = super.widgetStyle ();
588         return bits | OS.WS_TABSTOP;
589 }
590
591 @Override
592 TCHAR windowClass () {
593         return LinkClass;
594 }
595
596 @Override
597 long windowProc () {
598         return LinkProc;
599 }
600
601 @Override
602 LRESULT WM_CHAR (long wParam, long lParam) {
603         LRESULT result = super.WM_CHAR (wParam, lParam);
604         if (result != null) return result;
605         switch ((int)wParam) {
606                 case SWT.SPACE:
607                 case SWT.CR:
608                 case SWT.TAB:
609                         /*
610                         * NOTE: Call the window proc with WM_KEYDOWN rather than WM_CHAR
611                         * so that the key that was ignored during WM_KEYDOWN is processed.
612                         * This allows the application to cancel an operation that is normally
613                         * performed in WM_KEYDOWN from WM_CHAR.
614                         */
615                         long code = callWindowProc (handle, OS.WM_KEYDOWN, wParam, lParam);
616                         return new LRESULT (code);
617         }
618         return result;
619 }
620
621 @Override
622 LRESULT WM_GETDLGCODE (long wParam, long lParam) {
623         long code = callWindowProc (handle, OS.WM_GETDLGCODE, wParam, lParam);
624         int count = ids.length;
625         if (count == 0) {
626                 code |= OS.DLGC_STATIC;
627         } else if (count > 1) {
628                 int limit = (OS.GetKeyState (OS.VK_SHIFT) < 0) ? 0 : count - 1;
629                 if (getFocusItem() != limit) {
630                         code |= OS.DLGC_WANTTAB;
631                 }
632         }
633         return new LRESULT (code);
634 }
635
636 @Override
637 LRESULT WM_KEYDOWN (long wParam, long lParam) {
638         LRESULT result = super.WM_KEYDOWN (wParam, lParam);
639         if (result != null) return result;
640         switch ((int)wParam) {
641                 case OS.VK_SPACE:
642                 case OS.VK_RETURN:
643                 case OS.VK_TAB:
644                         /*
645                         * Ensure that the window proc does not process VK_SPACE,
646                         * VK_RETURN or VK_TAB so that it can be handled in WM_CHAR.
647                         * This allows the application to cancel an operation that
648                         * is normally performed in WM_KEYDOWN from WM_CHAR.
649                         */
650                         return LRESULT.ZERO;
651         }
652         return result;
653 }
654
655 @Override
656 LRESULT WM_KILLFOCUS (long wParam, long lParam) {
657         nextFocusItem = getFocusItem();
658         return super.WM_KILLFOCUS(wParam, lParam);
659 }
660
661 @Override
662 LRESULT WM_NCHITTEST (long wParam, long lParam) {
663         LRESULT result = super.WM_NCHITTEST (wParam, lParam);
664         if (result != null) return result;
665
666         /*
667         * Feature in Windows. For WM_NCHITTEST, the Syslink window proc
668         * returns HTTRANSPARENT when mouse is over plain text. As a result,
669         * mouse events are not delivered. The fix is to always return HTCLIENT.
670         */
671         return new LRESULT (OS.HTCLIENT);
672 }
673
674 @Override
675 LRESULT WM_SETCURSOR(long wParam, long lParam) {
676         LRESULT result = super.WM_SETCURSOR (wParam, lParam);
677         if (result != null) return result;
678         long fDone = callWindowProc (handle, OS.WM_SETCURSOR, wParam, lParam);
679         /* Take responsibility for cursor over plain text after overriding WM_NCHITTEST. */
680         if (fDone == 0) OS.DefWindowProc (handle, OS.WM_SETCURSOR, wParam, lParam);
681         return LRESULT.ONE;
682 }
683
684 @Override
685 LRESULT WM_SETFOCUS (long wParam, long lParam) {
686         /*
687         * Feature in Windows. Upon receiving focus, SysLink control
688         * always activates the first link. This leads to surprising
689         * behavior in multi-link controls.
690         */
691         if (ids.length > 1) {
692                 if (OS.GetKeyState (OS.VK_TAB) < 0) {
693                         if (OS.GetKeyState (OS.VK_SHIFT) < 0) {
694                                 // reverse tab; focus on last item
695                                 setFocusItem(ids.length - 1);
696                         }
697                 } else if (nextFocusItem > 0) {
698                         setFocusItem(nextFocusItem);
699                 }
700         }
701         return super.WM_SETFOCUS (wParam, lParam);
702 }
703
704 @Override
705 LRESULT wmNotifyChild (NMHDR hdr, long wParam, long lParam) {
706         switch (hdr.code) {
707                 case OS.NM_RETURN:
708                 case OS.NM_CLICK:
709                         NMLINK item = new NMLINK ();
710                         OS.MoveMemory (item, lParam, NMLINK.sizeof);
711                         Event event = new Event ();
712                         event.text = ids [item.iLink];
713                         sendSelectionEvent (SWT.Selection, event, true);
714                         break;
715                 case OS.NM_CUSTOMDRAW:
716                         NMCUSTOMDRAW nmcd = new NMCUSTOMDRAW ();
717                         OS.MoveMemory (nmcd, lParam, NMCUSTOMDRAW.sizeof);
718                         switch (nmcd.dwDrawStage) {
719                                 case OS.CDDS_PREPAINT:
720                                         if (!OS.IsWindowEnabled (handle) || linkForeground != -1) {
721                                                 return new LRESULT(OS.CDRF_NOTIFYITEMDRAW);
722                                         }
723                                         break;
724                                 case OS.CDDS_ITEMPREPAINT:
725                                         /*
726                                          * Feature in Windows.  SysLink32 control doesn't natively
727                                          * provide disabled state. Emulate it with custom draw.
728                                          */
729                                         if (!OS.IsWindowEnabled (handle)) {
730                                                 OS.SetTextColor (nmcd.hdc, OS.GetSysColor (OS.COLOR_GRAYTEXT));
731                                         }
732                                         else if (linkForeground != -1 && nmcd.dwItemSpec != -1) {
733                                                 OS.SetTextColor(nmcd.hdc, linkForeground);
734                                         }
735                                         break;
736                         }
737                         break;
738         }
739         return super.wmNotifyChild (hdr, wParam, lParam);
740 }
741 }