1 /*******************************************************************************
2 * Copyright (c) 2000, 2016 IBM Corporation and others.
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/
9 * SPDX-License-Identifier: EPL-2.0
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;
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.*;
26 * Instances of this class represent a selectable
27 * user interface object that displays a text with
30 * <dt><b>Styles:</b></dt>
32 * <dt><b>Events:</b></dt>
36 * IMPORTANT: This class is <em>not</em> intended to be subclassed.
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>
44 * @noextend This class is not intended to be subclassed by clients.
46 public class Link extends Control {
48 int linkForeground = -1;
51 int nextFocusItem = -1;
52 static final long LinkProc;
53 static final TCHAR LinkClass = new TCHAR (0, OS.WC_LINK, true);
55 WNDCLASS lpWndClass = new WNDCLASS ();
56 OS.GetClassInfo (0, LinkClass, lpWndClass);
57 LinkProc = lpWndClass.lpfnWndProc;
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.
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.
75 lpWndClass.hInstance = OS.GetModuleHandle (null);
76 lpWndClass.style &= ~OS.CS_GLOBALCLASS;
77 lpWndClass.style |= OS.CS_DBLCLKS;
78 OS.RegisterClass (LinkClass, lpWndClass);
82 * Constructs a new instance of this class given its parent
83 * and a style value describing its behavior and appearance.
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.
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
97 * @exception IllegalArgumentException <ul>
98 * <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
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>
105 * @see Widget#checkSubclass
106 * @see Widget#getStyle
108 public Link (Composite parent, int style) {
109 super (parent, style);
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>
118 * <code>widgetSelected</code> is called when the control is selected by the user.
119 * <code>widgetDefaultSelected</code> is not called.
122 * @param listener the listener which should be notified
124 * @exception IllegalArgumentException <ul>
125 * <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
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>
132 * @see SelectionListener
133 * @see #removeSelectionListener
134 * @see SelectionEvent
136 public void addSelectionListener (SelectionListener listener) {
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);
145 long callWindowProc (long hwnd, int msg, long wParam, long lParam) {
146 if (handle == 0) return 0;
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.
156 OS.SendMessage (hwnd, OS.WM_PRINTCLIENT, wParam, 0);
161 return OS.CallWindowProc (LinkProc, hwnd, msg, wParam, lParam);
165 Point computeSizeInPixels (int wHint, int hHint, boolean changed) {
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.
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);
179 height = lptm.tmHeight;
180 if (newFont != 0) OS.SelectObject (hDC, oldFont);
181 OS.ReleaseDC (handle, hDC);
183 SIZE size = new SIZE ();
184 int maxWidth = (wHint == SWT.DEFAULT) ? 0x7fffffff : wHint;
185 OS.SendMessage (handle, OS.LM_GETIDEALSIZE, maxWidth, size);
189 if (wHint != SWT.DEFAULT) width = wHint;
190 if (hHint != SWT.DEFAULT) height = hHint;
191 int border = getBorderWidthInPixels ();
193 height += border * 2;
194 return new Point (width, height);
198 void createHandle () {
199 super.createHandle ();
200 state |= THEME_BACKGROUND;
204 void createWidget () {
205 super.createWidget ();
208 mnemonics = new char[0];
212 void enableWidget (boolean enabled) {
213 super.enableWidget (enabled);
215 * Feature in Windows. SysLink32 control doesn't natively
216 * provide disabled state. Emulate it with custom draw.
218 OS.InvalidateRect (handle, null, true);
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) {
235 * Returns the link foreground color.
237 * @return the receiver's link foreground color.
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>
245 public Color getLinkForeground () {
247 if (linkForeground != -1) {
248 return Color.win32_new (display, linkForeground);
250 return Color.win32_new (display, OS.GetSysColor (OS.COLOR_HOTLIGHT));
254 String getNameText () {
259 * Returns the receiver's text, which will be an empty
260 * string if it has never been set.
262 * @return the receiver's text
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>
269 public String getText () {
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]) {
280 return setFocus () && setFocusItem (i);
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]) {
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;
305 while (index < length) {
306 char c = Character.toLowerCase (string.charAt (index));
311 } else if (c == '&') {
316 if (c == 'a') state++;
324 linkStart = index + 1;
328 if (Character.isWhitespace(c)) break;
339 state = c == '/' ? state + 1 : 3;
342 state = c == 'a' ? state + 1 : 3;
347 refStart = linkStart;
350 ids [linkIndex] = string.substring(refStart, refEnd);
352 mnemonics [linkIndex] = mnemonic;
355 linkStart = linkEnd = refStart = refEnd = mnemonic = 0;
362 state = c == 'r' ? state + 1 : 0;
365 state = c == 'e' ? state + 1 : 0;
368 state = c == 'f' ? state + 1 : 0;
371 state = c == '=' ? state + 1 : 0;
376 refStart = index + 1;
388 if (Character.isWhitespace (c)) {
390 } else if (c == '='){
395 state = c == '"' ? state + 1 : 0;
398 if (c == '"') state = 2;
405 if (c != '&') mnemonic = Character.toUpperCase (c);
414 ids = Arrays.copyOf(ids, linkIndex);
415 mnemonics = Arrays.copyOf(mnemonics, linkIndex);
419 void releaseWidget () {
420 super.releaseWidget ();
427 * Removes the listener from the collection of listeners who will
428 * be notified when the control is selected by the user.
430 * @param listener the listener which should no longer be notified
432 * @exception IllegalArgumentException <ul>
433 * <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
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>
440 * @see SelectionListener
441 * @see #addSelectionListener
443 public void removeSelectionListener (SelectionListener listener) {
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);
451 boolean setFocusItem (int index) {
454 bits = OS.GetWindowLong (handle, OS.GWL_STYLE);
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.
468 item.iLink = activeIndex;
469 OS.SendMessage (handle, OS.LM_SETITEM, 0, item);
472 item.state = OS.LIS_FOCUSED;
473 long result = OS.SendMessage (handle, OS.LM_SETITEM, 0, item);
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.
480 OS.SetWindowLong (handle, OS.GWL_STYLE, bits);
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.
490 * Note: This operation is a hint and may be overridden by the platform.
492 * @param color the new color (or null)
494 * @exception IllegalArgumentException <ul>
495 * <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li>
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>
503 public void setLinkForeground (Color color) {
507 if (color.isDisposed()) error(SWT.ERROR_INVALID_ARGUMENT);
508 pixel = color.handle;
510 if (pixel == linkForeground) return;
511 linkForeground = pixel;
512 OS.InvalidateRect (handle, null, true);
516 * Sets the receiver's text.
518 * The string can contain both regular text and hyperlinks. A hyperlink
519 * is delimited by an anchor tag, <a> and </a>. 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 < or > may be escaped using \\, however
528 * this operation is a hint and varies from platform to platform.
531 * Mnemonics are indicated by an '&' 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 * '&' can be escaped by doubling it in the string, causing
540 * a single '&' to be displayed.
543 * @param string the new text
545 * @exception IllegalArgumentException <ul>
546 * <li>ERROR_NULL_ARGUMENT - if the text is null</li>
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>
553 public void setText (String string) {
555 if (string == null) error (SWT.ERROR_NULL_ARGUMENT);
556 if (string.equals (text)) return;
558 if ((state & HAS_AUTO_DIRECTION) != 0) {
559 updateTextDirection (AUTO_TEXT_DIRECTION);
561 TCHAR buffer = new TCHAR (getCodePage (), string, true);
562 OS.SetWindowText (handle, buffer);
567 int resolveTextDirection() {
568 return BidiUtil.resolveTextDirection(text);
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;
577 style |= textDirection & flags;
578 updateOrientation ();
587 int bits = super.widgetStyle ();
588 return bits | OS.WS_TABSTOP;
592 TCHAR windowClass () {
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) {
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.
615 long code = callWindowProc (handle, OS.WM_KEYDOWN, wParam, lParam);
616 return new LRESULT (code);
622 LRESULT WM_GETDLGCODE (long wParam, long lParam) {
623 long code = callWindowProc (handle, OS.WM_GETDLGCODE, wParam, lParam);
624 int count = ids.length;
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;
633 return new LRESULT (code);
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) {
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.
656 LRESULT WM_KILLFOCUS (long wParam, long lParam) {
657 nextFocusItem = getFocusItem();
658 return super.WM_KILLFOCUS(wParam, lParam);
662 LRESULT WM_NCHITTEST (long wParam, long lParam) {
663 LRESULT result = super.WM_NCHITTEST (wParam, lParam);
664 if (result != null) return result;
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.
671 return new LRESULT (OS.HTCLIENT);
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);
685 LRESULT WM_SETFOCUS (long wParam, long lParam) {
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.
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);
697 } else if (nextFocusItem > 0) {
698 setFocusItem(nextFocusItem);
701 return super.WM_SETFOCUS (wParam, lParam);
705 LRESULT wmNotifyChild (NMHDR hdr, long wParam, long lParam) {
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);
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);
724 case OS.CDDS_ITEMPREPAINT:
726 * Feature in Windows. SysLink32 control doesn't natively
727 * provide disabled state. Emulate it with custom draw.
729 if (!OS.IsWindowEnabled (handle)) {
730 OS.SetTextColor (nmcd.hdc, OS.GetSysColor (OS.COLOR_GRAYTEXT));
732 else if (linkForeground != -1 && nmcd.dwItemSpec != -1) {
733 OS.SetTextColor(nmcd.hdc, linkForeground);
739 return super.wmNotifyChild (hdr, wParam, lParam);