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, <A> and </A>. 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 }