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 org.eclipse.swt.widgets.Link;
14 
15 
16 import org.eclipse.swt.SWT;
17 import org.eclipse.swt.SWTException;
18 import org.eclipse.swt.accessibility.ACC;
19 import org.eclipse.swt.accessibility.Accessible;
20 import org.eclipse.swt.accessibility.AccessibleAdapter;
21 import org.eclipse.swt.accessibility.AccessibleControlAdapter;
22 import org.eclipse.swt.accessibility.AccessibleControlEvent;
23 import org.eclipse.swt.accessibility.AccessibleEvent;
24 import org.eclipse.swt.events.SelectionEvent;
25 import org.eclipse.swt.events.SelectionListener;
26 import org.eclipse.swt.graphics.Color;
27 import org.eclipse.swt.graphics.Font;
28 import org.eclipse.swt.graphics.GC;
29 import org.eclipse.swt.graphics.GCData;
30 import org.eclipse.swt.graphics.Point;
31 import org.eclipse.swt.graphics.RGB;
32 import org.eclipse.swt.graphics.Rectangle;
33 import org.eclipse.swt.graphics.TextLayout;
34 import org.eclipse.swt.graphics.TextStyle;
35 import org.eclipse.swt.internal.gtk.OS;
36 
37 import org.eclipse.swt.graphics.Cursor;
38 import org.eclipse.swt.widgets.Control;
39 import org.eclipse.swt.widgets.Composite;
40 import org.eclipse.swt.widgets.TypedListener;
41 import org.eclipse.swt.widgets.Event;
42 import java.lang.all;
43 import java.nonstandard.UnsafeUtf;
44 
45 /**
46  * Instances of this class represent a selectable
47  * user interface object that displays a text with
48  * links.
49  * <p>
50  * <dl>
51  * <dt><b>Styles:</b></dt>
52  * <dd>(none)</dd>
53  * <dt><b>Events:</b></dt>
54  * <dd>Selection</dd>
55  * </dl>
56  * <p>
57  * IMPORTANT: This class is <em>not</em> intended to be subclassed.
58  * </p>
59  *
60  * @see <a href="http://www.eclipse.org/swt/snippets/#link">Link snippets</a>
61  * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: ControlExample</a>
62  * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
63  * 
64  * @since 3.1
65  */
66 public class Link : Control {
67 
68     alias Control.computeSize computeSize;
69     alias Control.fixStyle fixStyle;
70     alias Control.setBounds setBounds;
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;
80 
81     static RGB LINK_FOREGROUND;
82     static RGB LINK_DISABLED_FOREGROUND;
83 
84     static void static_this(){
85         if( LINK_FOREGROUND is null ){
86             LINK_FOREGROUND = new RGB (0, 51, 153);
87         }
88         if( LINK_DISABLED_FOREGROUND is null ){
89             LINK_DISABLED_FOREGROUND = new RGB (172, 168, 153);
90         }
91     }
92 
93 /**
94  * Constructs a new instance of this class given its parent
95  * and a style value describing its behavior and appearance.
96  * <p>
97  * The style value is either one of the style constants defined in
98  * class <code>SWT</code> which is applicable to instances of this
99  * class, or must be built by <em>bitwise OR</em>'ing together
100  * (that is, using the <code>int</code> "|" operator) two or more
101  * of those <code>SWT</code> style constants. The class description
102  * lists the style constants that are applicable to the class.
103  * Style bits are also inherited from superclasses.
104  * </p>
105  *
106  * @param parent a composite control which will be the parent of the new instance (cannot be null)
107  * @param style the style of control to construct
108  *
109  * @exception IllegalArgumentException <ul>
110  *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
111  * </ul>
112  * @exception SWTException <ul>
113  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
114  *    <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
115  * </ul>
116  *
117  * @see Widget#checkSubclass
118  * @see Widget#getStyle
119  */
120 public this (Composite parent, int style) {
121     static_this();
122     super (parent, style);
123 }
124 
125 /**
126  * Adds the listener to the collection of listeners who will
127  * be notified when the control is selected by the user, by sending
128  * it one of the messages defined in the <code>SelectionListener</code>
129  * interface.
130  * <p>
131  * <code>widgetSelected</code> is called when the control is selected by the user.
132  * <code>widgetDefaultSelected</code> is not called.
133  * </p>
134  *
135  * @param listener the listener which should be notified
136  *
137  * @exception IllegalArgumentException <ul>
138  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
139  * </ul>
140  * @exception SWTException <ul>
141  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
142  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
143  * </ul>
144  *
145  * @see SelectionListener
146  * @see #removeSelectionListener
147  * @see SelectionEvent
148  */
149 public void addSelectionListener (SelectionListener listener) {
150     checkWidget ();
151     if (listener is null) error (SWT.ERROR_NULL_ARGUMENT);
152     TypedListener typedListener = new TypedListener (listener);
153     addListener (SWT.Selection, typedListener);
154     addListener (SWT.DefaultSelection, typedListener);
155 }
156 
157 public override Point computeSize (int wHint, int hHint, bool changed) {
158     checkWidget ();
159     if (wHint !is SWT.DEFAULT && wHint < 0) wHint = 0;
160     if (hHint !is SWT.DEFAULT && hHint < 0) hHint = 0;
161     int width, height;
162     int layoutWidth = layout.getWidth ();
163     //TEMPORARY CODE
164     if (wHint is 0) {
165         layout.setWidth (1);
166         Rectangle rect = layout.getBounds ();
167         width = 0;
168         height = rect.height;
169     } else {
170         layout.setWidth (wHint);
171         Rectangle rect = layout.getBounds ();
172         width = rect.width;
173         height = rect.height;
174     }
175     layout.setWidth (layoutWidth);
176     if (wHint !is SWT.DEFAULT) width = wHint;
177     if (hHint !is SWT.DEFAULT) height = hHint;
178     int border = getBorderWidth ();
179     width += border * 2;
180     height += border * 2;
181     return new Point (width, height);
182 }
183 
184 override void createHandle(int index) {
185     state |= HANDLE | THEME_BACKGROUND;
186     handle = cast(GtkWidget*)OS.g_object_new (display.gtk_fixed_get_type (), null);
187     if (handle is null) SWT.error (SWT.ERROR_NO_HANDLES);
188     OS.gtk_fixed_set_has_window (handle, true);
189     OS.GTK_WIDGET_SET_FLAGS (handle, OS.GTK_CAN_FOCUS);
190     layout = new TextLayout (display);
191     layout.setOrientation((style & SWT.RIGHT_TO_LEFT) !is 0? SWT.RIGHT_TO_LEFT : SWT.LEFT_TO_RIGHT);
192     linkColor = new Color (display, LINK_FOREGROUND);
193     disabledColor = new Color (display, LINK_DISABLED_FOREGROUND);
194     offsets = null;
195     ids = null;
196     mnemonics = null;
197     selection = new Point (-1, -1);
198     focusIndex = -1;
199 }
200 
201 override void createWidget (int index) {
202     super.createWidget (index);
203     layout.setFont (getFont ());
204     text = "";
205     initAccessible ();
206 }
207 
208 override void enableWidget (bool enabled) {
209     super.enableWidget (enabled);
210     if (isDisposed ()) return;
211     TextStyle linkStyle = new TextStyle (null, enabled ? linkColor : disabledColor, null);
212     linkStyle.underline = true;
213     for (int i = 0; i < offsets.length; i++) {
214         Point point = offsets [i];
215         layout.setStyle (linkStyle, point.x, point.y);
216     }
217     redraw ();
218 }
219 
220 override void fixStyle () {
221     fixStyle (handle);
222 }
223 
224 void initAccessible () {
225     Accessible accessible = getAccessible ();
226     accessible.addAccessibleListener (new class () AccessibleAdapter {
227         override
228         public void getName (AccessibleEvent e) {
229             e.result = parse (text);
230         }
231     });
232 
233     accessible.addAccessibleControlListener (new class () AccessibleControlAdapter {
234         override
235         public void getChildAtPoint (AccessibleControlEvent e) {
236             e.childID = ACC.CHILDID_SELF;
237         }
238 
239         override
240         public void getLocation (AccessibleControlEvent e) {
241             Rectangle rect = display.map (getParent (), null, getBounds ());
242             e.x = rect.x;
243             e.y = rect.y;
244             e.width = rect.width;
245             e.height = rect.height;
246         }
247 
248         override
249         public void getChildCount (AccessibleControlEvent e) {
250             e.detail = 0;
251         }
252 
253         override
254         public void getRole (AccessibleControlEvent e) {
255             e.detail = ACC.ROLE_LINK;
256         }
257 
258         override
259         public void getState (AccessibleControlEvent e) {
260             e.detail = ACC.STATE_FOCUSABLE;
261             if (hasFocus ()) e.detail |= ACC.STATE_FOCUSED;
262         }
263 
264         override
265         public void getDefaultAction (AccessibleControlEvent e) {
266             e.result = SWT.getMessage ("SWT_Press"); //$NON-NLS-1$
267         }
268 
269         override
270         public void getSelection (AccessibleControlEvent e) {
271             if (hasFocus ()) e.childID = ACC.CHILDID_SELF;
272         }
273 
274         override
275         public void getFocus (AccessibleControlEvent e) {
276             if (hasFocus ()) e.childID = ACC.CHILDID_SELF;
277         }
278     });
279 }
280 
281 override String getNameText () {
282     return getText ();
283 }
284 
285 Rectangle [] getRectangles (int linkIndex) {
286     int lineCount = layout.getLineCount ();
287     Rectangle [] rects = new Rectangle [lineCount];
288     int [] lineOffsets = layout.getLineOffsets ();
289     Point point = offsets [linkIndex];
290     int lineStart = 1;
291     while (point.x > lineOffsets [lineStart]) lineStart++;
292     int lineEnd = 1;
293     while (point.y > lineOffsets [lineEnd]) lineEnd++;
294     int index = 0;
295     if (lineStart is lineEnd) {
296         rects [index++] = layout.getBounds (point.x, point.y);
297     } else {
298         rects [index++] = layout.getBounds (point.x, lineOffsets [lineStart]-1);
299         rects [index++] = layout.getBounds (lineOffsets [lineEnd-1], point.y);
300         if (lineEnd - lineStart > 1) {
301             for (int i = lineStart; i < lineEnd - 1; i++) {
302                 rects [index++] = layout.getLineBounds (i);
303             }
304         }
305     }
306     if (rects.length !is index) {
307         Rectangle [] tmp = new Rectangle [index];
308         System.arraycopy (rects, 0, tmp, 0, index);
309         rects = tmp;
310     }
311     return rects;
312 }
313 
314 override
315 int getClientWidth () {
316     return (state & ZERO_WIDTH) !is 0 ? 0 : OS.GTK_WIDGET_WIDTH (handle);
317 }
318 
319 /**
320  * Returns the receiver's text, which will be an empty
321  * string if it has never been set.
322  *
323  * @return the receiver's text
324  *
325  * @exception SWTException <ul>
326  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
327  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
328  * </ul>
329  */
330 public String getText () {
331     checkWidget ();
332     return text;
333 }
334 
335 override int gtk_button_press_event (GtkWidget* widget, GdkEventButton* gdkEvent) {
336     int result = super.gtk_button_press_event (widget, gdkEvent);
337     if (result !is 0) return result;
338     if (gdkEvent.button is 1 && gdkEvent.type is OS.GDK_BUTTON_PRESS) {
339         if (focusIndex !is -1) setFocus ();
340         int x = cast(int) gdkEvent.x;
341         int y = cast(int) gdkEvent.y;
342         if ((style & SWT.MIRRORED) !is 0) x = getClientWidth () - x;
343         int offset = layout.getOffset (x, y, null);
344         int oldSelectionX = selection.x;
345         int oldSelectionY = selection.y;
346         selection.x = offset;
347         selection.y = -1;
348         if (oldSelectionX !is -1 && oldSelectionY !is -1) {
349             if (oldSelectionX > oldSelectionY) {
350                 int temp = oldSelectionX;
351                 oldSelectionX = oldSelectionY;
352                 oldSelectionY = temp;
353             }
354             Rectangle rect = layout.getBounds (oldSelectionX, oldSelectionY);
355             redraw (rect.x, rect.y, rect.width, rect.height, false);
356         }
357         for (int j = 0; j < offsets.length; j++) {
358             Rectangle [] rects = getRectangles (j);
359             for (int i = 0; i < rects.length; i++) {
360                 Rectangle rect = rects [i];
361                 if (rect.contains (x, y)) {
362                     focusIndex = j;
363                     redraw ();
364                     return result;
365                 }
366             }
367         }
368     }
369     return result;
370 }
371 
372 override int gtk_button_release_event (GtkWidget* widget, GdkEventButton* gdkEvent) {
373     int result = super.gtk_button_release_event (widget, gdkEvent);
374     if (result !is 0) return result;
375     if (focusIndex is -1) return result;
376     if (gdkEvent.button is 1) {
377         int x = cast(int) gdkEvent.x;
378         int y = cast(int) gdkEvent.y;
379         if ((style & SWT.MIRRORED) !is 0) x = getClientWidth () - x;
380         Rectangle [] rects = getRectangles (focusIndex);
381         for (int i = 0; i < rects.length; i++) {
382             Rectangle rect = rects [i];
383             if (rect.contains (x, y)) {
384                 Event ev = new Event ();
385                 ev.text = ids [focusIndex];
386                 sendEvent (SWT.Selection, ev);
387                 return result;
388             }
389         }
390     }
391     return result;
392 }
393 
394 override int gtk_event_after (GtkWidget* widget, GdkEvent* event) {
395     int result = super.gtk_event_after (widget, event);
396     switch (event.type) {
397         case OS.GDK_FOCUS_CHANGE:
398             redraw ();
399             break;
400         default:
401     }
402     return result;
403 }
404 
405 override int gtk_expose_event (GtkWidget* widget, GdkEventExpose* gdkEvent) {
406     if ((state & OBSCURED) !is 0) return 0;
407     GCData data = new GCData ();
408     data.damageRgn = gdkEvent.region;
409     GC gc = GC.gtk_new (this, data);
410     OS.gdk_gc_set_clip_region (gc.handle, gdkEvent.region);
411     int selStart = selection.x;
412     int selEnd = selection.y;
413     if (selStart > selEnd) {
414         selStart = selection.y;
415         selEnd = selection.x;
416     }
417     // temporary code to disable text selection
418     selStart = selEnd = -1;
419     if ((state & DISABLED) !is 0) gc.setForeground (disabledColor);
420     layout.draw (gc, 0, 0, selStart, selEnd, null, null);
421     if (hasFocus () && focusIndex !is -1) {
422         Rectangle [] rects = getRectangles (focusIndex);
423         for (int i = 0; i < rects.length; i++) {
424             Rectangle rect = rects [i];
425             gc.drawFocus (rect.x, rect.y, rect.width, rect.height);
426         }
427     }
428     if (hooks (SWT.Paint) || filters (SWT.Paint)) {
429         Event event = new Event ();
430         event.count = gdkEvent.count;
431         event.x = gdkEvent.area.x;
432         event.y = gdkEvent.area.y;
433         event.width = gdkEvent.area.width;
434         event.height = gdkEvent.area.height;
435         if ((style & SWT.MIRRORED) !is 0) event.x = getClientWidth () - event.width - event.x;
436         event.gc = gc;
437         sendEvent (SWT.Paint, event);
438         event.gc = null;
439     }
440     gc.dispose ();
441     return 0;
442 }
443 
444 override int gtk_key_press_event (GtkWidget* widget, GdkEventKey* gdkEvent) {
445     int result = super.gtk_key_press_event (widget, gdkEvent);
446     if (result !is 0) return result;
447     if (focusIndex is -1) return result;
448     switch (gdkEvent.keyval) {
449         case OS.GDK_Return:
450         case OS.GDK_KP_Enter:
451         case OS.GDK_space:
452             Event event = new Event ();
453             event.text = ids [focusIndex];
454             sendEvent (SWT.Selection, event);
455             break;
456         case OS.GDK_Tab:
457             if (focusIndex < offsets.length - 1) {
458                 focusIndex++;
459                 redraw ();
460             }
461             break;
462         case OS.GDK_ISO_Left_Tab:
463             if (focusIndex > 0) {
464                 focusIndex--;
465                 redraw ();
466             }
467             break;
468         default:
469     }
470     return result;
471 }
472 
473 override int gtk_motion_notify_event (GtkWidget* widget, GdkEventMotion* gdkEvent) {
474     int result = super.gtk_motion_notify_event (widget, gdkEvent);
475     if (result !is 0) return result;
476     int x = cast(int) gdkEvent.x;
477     int y = cast(int) gdkEvent.y;
478     if ((style & SWT.MIRRORED) !is 0) x = getClientWidth () - x;
479     if ((gdkEvent.state & OS.GDK_BUTTON1_MASK) !is 0) {
480         int oldSelection = selection.y;
481         selection.y = layout.getOffset (x, y, null);
482         if (selection.y !is oldSelection) {
483             int newSelection = selection.y;
484             if (oldSelection > newSelection) {
485                 int temp = oldSelection;
486                 oldSelection = newSelection;
487                 newSelection = temp;
488             }
489             Rectangle rect = layout.getBounds (oldSelection, newSelection);
490             redraw (rect.x, rect.y, rect.width, rect.height, false);
491         }
492     } else {
493         for (int j = 0; j < offsets.length; j++) {
494             Rectangle [] rects = getRectangles (j);
495             for (int i = 0; i < rects.length; i++) {
496                 Rectangle rect = rects [i];
497                 if (rect.contains (x, y)) {
498                     setCursor (display.getSystemCursor (SWT.CURSOR_HAND));
499                     return result;
500                 }
501             }
502         }
503         setCursor (null);
504     }
505     return result;
506 }
507 
508 override void releaseWidget () {
509     super.releaseWidget ();
510     if (layout !is null) layout.dispose ();
511     layout = null;
512     if (linkColor !is null)  linkColor.dispose ();
513     linkColor = null;
514     if (disabledColor !is null) disabledColor.dispose ();
515     disabledColor = null;
516     offsets = null;
517     ids = null;
518     mnemonics = null;
519     text = null;
520 }
521 
522 /**
523  * Removes the listener from the collection of listeners who will
524  * be notified when the control is selected by the user.
525  *
526  * @param listener the listener which should no longer be notified
527  *
528  * @exception IllegalArgumentException <ul>
529  *    <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
530  * </ul>
531  * @exception SWTException <ul>
532  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
533  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
534  * </ul>
535  *
536  * @see SelectionListener
537  * @see #addSelectionListener
538  */
539 public void removeSelectionListener (SelectionListener listener) {
540     checkWidget ();
541     if (listener is null) error (SWT.ERROR_NULL_ARGUMENT);
542     if (eventTable is null) return;
543     eventTable.unhook (SWT.Selection, listener);
544     eventTable.unhook (SWT.DefaultSelection, listener);
545 }
546 
547 String parse (String string) {
548     ptrdiff_t length_ = string.length;
549     offsets = new Point[]( length_ / 4 );
550     ids = new String[]( length_ / 4 );
551     mnemonics = new int[] ( length_ / 4 + 1 );
552     StringBuffer result = new StringBuffer ();
553     char [] buffer = string.dup;
554 
555     int index = 0, state = 0, linkIndex = 0;
556     int start = 0, tagStart = 0, linkStart = 0, endtagStart = 0, refStart = 0;
557     while (index < length_) {
558         ptrdiff_t increment;
559         dchar c = Character.toLowerCase (buffer.dcharAt (index, increment));
560 
561         switch (state) {
562             case 0:
563                 if (c is '<') {
564                     tagStart = index;
565                     state++;
566                 }
567                 break;
568             case 1:
569                 if (c is 'a') state++;
570                 break;
571             case 2:
572                 switch (c) {
573                     case 'h':
574                         state = 7;
575                         break;
576                     case '>':
577                         linkStart = index  + 1;
578                         state++;
579                         break;
580                     default:
581                         if (Character.isWhitespace(c)) break;
582                         else state = 13;
583                 }
584                 break;
585             case 3:
586                 if (c is '<') {
587                     endtagStart = index;
588                     state++;
589                 }
590                 break;
591             case 4:
592                 state = c is '/' ? state + 1 : 3;
593                 break;
594             case 5:
595                 state = c is 'a' ? state + 1 : 3;
596                 break;
597             case 6:
598                 if (c is '>') {
599                     mnemonics [linkIndex] = parseMnemonics (buffer, start, tagStart, result);
600                     int offset = result.length ();
601                     parseMnemonics (buffer, linkStart, endtagStart, result);
602                     offsets [linkIndex] = new Point (offset, result.length () - 1);
603                     if (ids [linkIndex] is null) {
604                         ids [linkIndex] = buffer[ linkStart .. endtagStart ]._idup();
605                     }
606                     linkIndex++;
607                     start = tagStart = linkStart = endtagStart = refStart = index + 1;
608                     state = 0;
609                 } else {
610                     state = 3;
611                 }
612                 break;
613             case 7:
614                 state = c is 'r' ? state + 1 : 0;
615                 break;
616             case 8:
617                 state = c is 'e' ? state + 1 : 0;
618                 break;
619             case 9:
620                 state = c is 'f' ? state + 1 : 0;
621                 break;
622             case 10:
623                 state = c is '=' ? state + 1 : 0;
624                 break;
625             case 11:
626                 if (c is '"') {
627                     state++;
628                     refStart = index + 1;
629                 } else {
630                     state = 0;
631                 }
632                 break;
633             case 12:
634                 if (c is '"') {
635                     ids[linkIndex] = buffer[ refStart .. index ]._idup();
636                     state = 2;
637                 }
638                 break;
639             case 13:
640                 if (Character.isWhitespace (c)) {
641                     state = 0;
642                 } else if (c is '='){
643                     state++;
644                 }
645                 break;
646             case 14:
647                 state = c is '"' ? state + 1 : 0;
648                 break;
649             case 15:
650                 if (c is '"') state = 2;
651                 break;
652             default:
653                 state = 0;
654                 break;
655         }
656         index+=increment;
657     }
658     if (start < length_) {
659         int tmp = parseMnemonics (buffer, start, tagStart, result);
660         int mnemonic = parseMnemonics (buffer, Math.max (tagStart, linkStart), cast(int)/*64bit*/length_, result);
661         if (mnemonic is -1) mnemonic = tmp;
662         mnemonics [linkIndex] = mnemonic;
663     } else {
664         mnemonics [linkIndex] = -1;
665     }
666     if (offsets.length !is linkIndex) {
667         Point [] newOffsets = new Point [linkIndex];
668         System.arraycopy (offsets, 0, newOffsets, 0, linkIndex);
669         offsets = newOffsets;
670         String [] newIDs = new String [linkIndex];
671         System.arraycopy (ids, 0, newIDs, 0, linkIndex);
672         ids = newIDs;
673         int [] newMnemonics = new int [linkIndex + 1];
674         System.arraycopy (mnemonics, 0, newMnemonics, 0, linkIndex + 1);
675         mnemonics = newMnemonics;
676     }
677     return result.toString ();
678 }
679 
680 int parseMnemonics (char[] buffer, int start, int end, StringBuffer result) {
681     int mnemonic = -1, index = start;
682     while (index < end) {
683         ptrdiff_t incr = 1;
684         if ( buffer[index] is '&') {
685             if (index + 1 < end && buffer [index + 1] is '&') {
686                 result.append (buffer [index]);
687                 index++;
688             } else {
689                 mnemonic = result.length();
690             }
691         } else {
692             result.append (buffer.dcharAsStringAt(index, incr));
693         }
694         index+=incr;
695     }
696     return mnemonic;
697 }
698 
699 override int setBounds(int x, int y, int width, int height, bool move, bool resize) {
700     int result = super.setBounds (x, y, width,height, move, resize);
701     if ((result & RESIZED) !is 0) {
702         layout.setWidth (width > 0 ? width : -1);
703         redraw ();
704     }
705     return result;
706 }
707 
708 override void setFontDescription (PangoFontDescription* font) {
709     super.setFontDescription (font);
710     layout.setFont (Font.gtk_new (display, font));
711 }
712 
713 /**
714  * Sets the receiver's text.
715  * <p>
716  * The string can contain both regular text and hyperlinks.  A hyperlink
717  * is delimited by an anchor tag, &lt;A&gt; and &lt;/A&gt;.  Within an
718  * anchor, a single HREF attribute is supported.  When a hyperlink is
719  * selected, the text field of the selection event contains either the
720  * text of the hyperlink or the value of its HREF, if one was specified.
721  * In the rare case of identical hyperlinks within the same string, the
722  * HREF tag can be used to distinguish between them.  The string may
723  * include the mnemonic character and line delimiters.
724  * </p>
725  *
726  * @param string the new text
727  *
728  * @exception SWTException <ul>
729  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
730  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
731  * </ul>
732  */
733 public void setText (String string) {
734     checkWidget ();
735     // SWT extension: allow null for zero length string
736     //if (string is null) error (SWT.ERROR_NULL_ARGUMENT);
737     if (string.equals(text)) return;
738     text = string;
739     layout.setText (parse (string));
740     focusIndex = offsets.length > 0 ? 0 : -1;
741     selection.x = selection.y = -1;
742     bool enabled = (state & DISABLED) is 0;
743     TextStyle linkStyle = new TextStyle (null, enabled ? linkColor : disabledColor, null);
744     linkStyle.underline = true;
745     int [] bidiSegments = new int [offsets.length*2];
746     for (int i = 0; i < offsets.length; i++) {
747         Point point = offsets [i];
748         layout.setStyle (linkStyle, point.x, point.y);
749         bidiSegments[i*2] = point.x;
750         bidiSegments[i*2+1] = point.y+1;
751     }
752     layout.setSegments (bidiSegments);
753     TextStyle mnemonicStyle = new TextStyle (null, null, null);
754     mnemonicStyle.underline = true;
755     for (int i = 0; i < mnemonics.length; i++) {
756         int mnemonic  = mnemonics [i];
757         if (mnemonic !is -1) {
758             layout.setStyle (mnemonicStyle, mnemonic, mnemonic);
759         }
760     }
761     redraw ();
762 }
763 
764 override void showWidget () {
765     super.showWidget ();
766     fixStyle (handle);
767 }
768 
769 override int traversalCode (int key, GdkEventKey* event) {
770     if (offsets.length is 0) return 0;
771     int bits = super.traversalCode (key, event);
772     if (key is OS.GDK_Tab && focusIndex < offsets.length - 1) {
773         return bits & ~SWT.TRAVERSE_TAB_NEXT;
774     }
775     if (key is OS.GDK_ISO_Left_Tab && focusIndex > 0) {
776         return bits & ~SWT.TRAVERSE_TAB_PREVIOUS;
777     }
778     return bits;
779 }
780 }