# HG changeset patch # User Frank Benoit # Date 1200421880 -3600 # Node ID 4cdaacfb86494b068a4ed8de9e44c6be01db6425 # Parent c263acbfae340120ec9d912d8e78e15f218fcceb Link diff -r c263acbfae34 -r 4cdaacfb8649 dwt/widgets/Link.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwt/widgets/Link.d Tue Jan 15 19:31:20 2008 +0100 @@ -0,0 +1,745 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +module dwt.widgets.Link; + + +import dwt.DWT; +import dwt.DWTException; +import dwt.accessibility.ACC; +import dwt.accessibility.Accessible; +import dwt.accessibility.AccessibleAdapter; +import dwt.accessibility.AccessibleControlAdapter; +import dwt.accessibility.AccessibleControlEvent; +import dwt.accessibility.AccessibleEvent; +import dwt.events.SelectionEvent; +import dwt.events.SelectionListener; +import dwt.graphics.Color; +import dwt.graphics.Font; +import dwt.graphics.GC; +import dwt.graphics.GCData; +import dwt.graphics.Point; +import dwt.graphics.RGB; +import dwt.graphics.Rectangle; +import dwt.graphics.TextLayout; +import dwt.graphics.TextStyle; +import dwt.internal.gtk.OS; + +import dwt.graphics.Cursor; +import dwt.widgets.Control; +import dwt.widgets.Composite; +import dwt.widgets.TypedListener; +import dwt.widgets.Event; + +import tango.text.Text; +import tango.text.Unicode; + +alias tango.text.Text.Text!(char) Text8; +/** + * Instances of this class represent a selectable + * user interface object that displays a text with + * links. + *

+ *

+ *
Styles:
+ *
(none)
+ *
Events:
+ *
Selection
+ *
+ *

+ * IMPORTANT: This class is not intended to be subclassed. + *

+ * + * @since 3.1 + */ +public class Link : Control { + char[] text; + TextLayout layout; + Color linkColor, disabledColor; + Point [] offsets; + Point selection; + char[] [] ids; + int [] mnemonics; + int focusIndex; + + static RGB LINK_FOREGROUND; + static RGB LINK_DISABLED_FOREGROUND; + + static void static_this(){ + if( LINK_FOREGROUND is null ){ + LINK_FOREGROUND = new RGB (0, 51, 153); + } + if( LINK_DISABLED_FOREGROUND is null ){ + LINK_DISABLED_FOREGROUND = new RGB (172, 168, 153); + } + } + +/** + * Constructs a new instance of this class given its parent + * and a style value describing its behavior and appearance. + *

+ * The style value is either one of the style constants defined in + * class DWT which is applicable to instances of this + * class, or must be built by bitwise OR'ing together + * (that is, using the int "|" operator) two or more + * of those DWT style constants. The class description + * lists the style constants that are applicable to the class. + * Style bits are also inherited from superclasses. + *

+ * + * @param parent a composite control which will be the parent of the new instance (cannot be null) + * @param style the style of control to construct + * + * @exception IllegalArgumentException + * @exception DWTException + * + * @see Widget#checkSubclass + * @see Widget#getStyle + */ +public this (Composite parent, int style) { + super (parent, style); + static_this(); +} + +/** + * Adds the listener to the collection of listeners who will + * be notified when the control is selected by the user, by sending + * it one of the messages defined in the SelectionListener + * interface. + *

+ * widgetSelected is called when the control is selected by the user. + * widgetDefaultSelected is not called. + *

+ * + * @param listener the listener which should be notified + * + * @exception IllegalArgumentException + * @exception DWTException + * + * @see SelectionListener + * @see #removeSelectionListener + * @see SelectionEvent + */ +public void addSelectionListener (SelectionListener listener) { + checkWidget (); + if (listener is null) error (DWT.ERROR_NULL_ARGUMENT); + TypedListener typedListener = new TypedListener (listener); + addListener (DWT.Selection, typedListener); + addListener (DWT.DefaultSelection, typedListener); +} + +public Point computeSize (int wHint, int hHint, bool changed) { + checkWidget (); + if (wHint !is DWT.DEFAULT && wHint < 0) wHint = 0; + if (hHint !is DWT.DEFAULT && hHint < 0) hHint = 0; + int width, height; + int layoutWidth = layout.getWidth (); + //TEMPORARY CODE + if (wHint is 0) { + layout.setWidth (1); + Rectangle rect = layout.getBounds (); + width = 0; + height = rect.height; + } else { + layout.setWidth (wHint); + Rectangle rect = layout.getBounds (); + width = rect.width; + height = rect.height; + } + layout.setWidth (layoutWidth); + if (wHint !is DWT.DEFAULT) width = wHint; + if (hHint !is DWT.DEFAULT) height = hHint; + int border = getBorderWidth (); + width += border * 2; + height += border * 2; + return new Point (width, height); +} + +void createHandle(int index) { + state |= HANDLE | THEME_BACKGROUND; + handle = cast(GtkWidget*)OS.g_object_new (display.gtk_fixed_get_type (), null); + if (handle is null) DWT.error (DWT.ERROR_NO_HANDLES); + OS.gtk_fixed_set_has_window (handle, true); + OS.GTK_WIDGET_SET_FLAGS (handle, OS.GTK_CAN_FOCUS); + layout = new TextLayout (display); + linkColor = new Color (display, LINK_FOREGROUND); + disabledColor = new Color (display, LINK_DISABLED_FOREGROUND); + offsets = null; + ids = null; + mnemonics = null; + selection = new Point (-1, -1); + focusIndex = -1; +} + +void createWidget (int index) { + super.createWidget (index); + layout.setFont (getFont ()); + text = ""; + initAccessible (); +} + +void enableWidget (bool enabled) { + super.enableWidget (enabled); + if (isDisposed ()) return; + TextStyle linkStyle = new TextStyle (null, enabled ? linkColor : disabledColor, null); + linkStyle.underline = true; + for (int i = 0; i < offsets.length; i++) { + Point point = offsets [i]; + layout.setStyle (linkStyle, point.x, point.y); + } + redraw (); +} + +alias Control.fixStyle fixStyle; +void fixStyle () { + fixStyle (handle); +} + +void initAccessible () { + Accessible accessible = getAccessible (); + accessible.addAccessibleListener (new class () AccessibleAdapter { + public void getName (AccessibleEvent e) { + e.result = parse (text); + } + }); + + accessible.addAccessibleControlListener (new class () AccessibleControlAdapter { + public void getChildAtPoint (AccessibleControlEvent e) { + e.childID = ACC.CHILDID_SELF; + } + + public void getLocation (AccessibleControlEvent e) { + Rectangle rect = display.map (getParent (), null, getBounds ()); + e.x = rect.x; + e.y = rect.y; + e.width = rect.width; + e.height = rect.height; + } + + public void getChildCount (AccessibleControlEvent e) { + e.detail = 0; + } + + public void getRole (AccessibleControlEvent e) { + e.detail = ACC.ROLE_LINK; + } + + public void getState (AccessibleControlEvent e) { + e.detail = ACC.STATE_FOCUSABLE; + if (hasFocus ()) e.detail |= ACC.STATE_FOCUSED; + } + + public void getDefaultAction (AccessibleControlEvent e) { + e.result = DWT.getMessage ("SWT_Press"); //$NON-NLS-1$ + } + + public void getSelection (AccessibleControlEvent e) { + if (hasFocus ()) e.childID = ACC.CHILDID_SELF; + } + + public void getFocus (AccessibleControlEvent e) { + if (hasFocus ()) e.childID = ACC.CHILDID_SELF; + } + }); +} + +char[] getNameText () { + return getText (); +} + +Rectangle [] getRectangles (int linkIndex) { + int lineCount = layout.getLineCount (); + Rectangle [] rects = new Rectangle [lineCount]; + int [] lineOffsets = layout.getLineOffsets (); + Point point = offsets [linkIndex]; + int lineStart = 1; + while (point.x > lineOffsets [lineStart]) lineStart++; + int lineEnd = 1; + while (point.y > lineOffsets [lineEnd]) lineEnd++; + int index = 0; + if (lineStart is lineEnd) { + rects [index++] = layout.getBounds (point.x, point.y); + } else { + rects [index++] = layout.getBounds (point.x, lineOffsets [lineStart]-1); + rects [index++] = layout.getBounds (lineOffsets [lineEnd-1], point.y); + if (lineEnd - lineStart > 1) { + for (int i = lineStart; i < lineEnd - 1; i++) { + rects [index++] = layout.getLineBounds (i); + } + } + } + if (rects.length !is index) { + Rectangle [] tmp = new Rectangle [index]; + System.arraycopy (rects, 0, tmp, 0, index); + rects = tmp; + } + return rects; +} + +/** + * Returns the receiver's text, which will be an empty + * string if it has never been set. + * + * @return the receiver's text + * + * @exception DWTException + */ +public char[] getText () { + checkWidget (); + return text; +} + +override int gtk_button_press_event (GtkWidget* widget, GdkEventButton* gdkEvent) { + int /*long*/ result = super.gtk_button_press_event (widget, gdkEvent); + if (result !is 0) return result; + if (gdkEvent.button is 1 && gdkEvent.type is OS.GDK_BUTTON_PRESS) { + if (focusIndex !is -1) setFocus (); + int x = cast(int) gdkEvent.x; + int y = cast(int) gdkEvent.y; + int offset = layout.getOffset (x, y, null); + int oldSelectionX = selection.x; + int oldSelectionY = selection.y; + selection.x = offset; + selection.y = -1; + if (oldSelectionX !is -1 && oldSelectionY !is -1) { + if (oldSelectionX > oldSelectionY) { + int temp = oldSelectionX; + oldSelectionX = oldSelectionY; + oldSelectionY = temp; + } + Rectangle rect = layout.getBounds (oldSelectionX, oldSelectionY); + redraw (rect.x, rect.y, rect.width, rect.height, false); + } + for (int j = 0; j < offsets.length; j++) { + Rectangle [] rects = getRectangles (j); + for (int i = 0; i < rects.length; i++) { + Rectangle rect = rects [i]; + if (rect.contains (x, y)) { + focusIndex = j; + redraw (); + return result; + } + } + } + } + return result; +} + +override int /*long*/ gtk_button_release_event (GtkWidget* widget, GdkEventButton* gdkEvent) { + int /*long*/ result = super.gtk_button_release_event (widget, gdkEvent); + if (result !is 0) return result; + if (focusIndex is -1) return result; + if (gdkEvent.button is 1) { + int x = cast(int) gdkEvent.x; + int y = cast(int) gdkEvent.y; + Rectangle [] rects = getRectangles (focusIndex); + for (int i = 0; i < rects.length; i++) { + Rectangle rect = rects [i]; + if (rect.contains (x, y)) { + Event ev = new Event (); + ev.text = ids [focusIndex]; + sendEvent (DWT.Selection, ev); + return result; + } + } + } + return result; +} + +override int /*long*/ gtk_event_after (GtkWidget* widget, GdkEvent* event) { + int /*long*/ result = super.gtk_event_after (widget, event); + switch (event.type) { + case OS.GDK_FOCUS_CHANGE: + redraw (); + break; + } + return result; +} + +override int /*long*/ gtk_expose_event (GtkWidget* widget, GdkEventExpose* gdkEvent) { + if ((state & OBSCURED) !is 0) return 0; + GCData data = new GCData (); + data.damageRgn = gdkEvent.region; + GC gc = GC.gtk_new (this, data); + OS.gdk_gc_set_clip_region (gc.handle, gdkEvent.region); + int selStart = selection.x; + int selEnd = selection.y; + if (selStart > selEnd) { + selStart = selection.y; + selEnd = selection.x; + } + // temporary code to disable text selection + selStart = selEnd = -1; + if ((state & DISABLED) !is 0) gc.setForeground (disabledColor); + layout.draw (gc, 0, 0, selStart, selEnd, null, null); + if (hasFocus () && focusIndex !is -1) { + Rectangle [] rects = getRectangles (focusIndex); + for (int i = 0; i < rects.length; i++) { + Rectangle rect = rects [i]; + gc.drawFocus (rect.x, rect.y, rect.width, rect.height); + } + } + if (hooks (DWT.Paint) || filters (DWT.Paint)) { + Event event = new Event (); + event.count = gdkEvent.count; + event.x = gdkEvent.area.x; + event.y = gdkEvent.area.y; + event.width = gdkEvent.area.width; + event.height = gdkEvent.area.height; + event.gc = gc; + sendEvent (DWT.Paint, event); + event.gc = null; + } + gc.dispose (); + return 0; +} + +override int /*long*/ gtk_key_press_event (GtkWidget* widget, GdkEventKey* gdkEvent) { + int /*long*/ result = super.gtk_key_press_event (widget, gdkEvent); + if (result !is 0) return result; + if (focusIndex is -1) return result; + switch (gdkEvent.keyval) { + case OS.GDK_Return: + case OS.GDK_KP_Enter: + case OS.GDK_space: + Event event = new Event (); + event.text = ids [focusIndex]; + sendEvent (DWT.Selection, event); + break; + case OS.GDK_Tab: + if (focusIndex < offsets.length - 1) { + focusIndex++; + redraw (); + } + break; + case OS.GDK_ISO_Left_Tab: + if (focusIndex > 0) { + focusIndex--; + redraw (); + } + break; + } + return result; +} + +override int /*long*/ gtk_motion_notify_event (GtkWidget* widget, GdkEventMotion* gdkEvent) { + int /*long*/ result = super.gtk_motion_notify_event (widget, gdkEvent); + if (result !is 0) return result; + int x = cast(int) gdkEvent.x; + int y = cast(int) gdkEvent.y; + if ((gdkEvent.state & OS.GDK_BUTTON1_MASK) !is 0) { + int oldSelection = selection.y; + selection.y = layout.getOffset (x, y, null); + if (selection.y !is oldSelection) { + int newSelection = selection.y; + if (oldSelection > newSelection) { + int temp = oldSelection; + oldSelection = newSelection; + newSelection = temp; + } + Rectangle rect = layout.getBounds (oldSelection, newSelection); + redraw (rect.x, rect.y, rect.width, rect.height, false); + } + } else { + for (int j = 0; j < offsets.length; j++) { + Rectangle [] rects = getRectangles (j); + for (int i = 0; i < rects.length; i++) { + Rectangle rect = rects [i]; + if (rect.contains (x, y)) { + setCursor (display.getSystemCursor (DWT.CURSOR_HAND)); + return result; + } + } + } + setCursor ( cast(Cursor)null); + } + return result; +} + +void releaseWidget () { + super.releaseWidget (); + if (layout !is null) layout.dispose (); + layout = null; + if (linkColor !is null) linkColor.dispose (); + linkColor = null; + if (disabledColor !is null) disabledColor.dispose (); + disabledColor = null; + offsets = null; + ids = null; + mnemonics = null; + text = null; +} + +/** + * Removes the listener from the collection of listeners who will + * be notified when the control is selected by the user. + * + * @param listener the listener which should no longer be notified + * + * @exception IllegalArgumentException + * @exception DWTException + * + * @see SelectionListener + * @see #addSelectionListener + */ +public void removeSelectionListener (SelectionListener listener) { + checkWidget (); + if (listener is null) error (DWT.ERROR_NULL_ARGUMENT); + if (eventTable is null) return; + eventTable.unhook (DWT.Selection, listener); + eventTable.unhook (DWT.DefaultSelection, listener); +} + +char[] parse (char[] string) { + int length_ = string.length; + offsets = new Point[]( length_ / 4 ); + ids = new char[][]( length_ / 4 ); + mnemonics = new int[] ( length_ / 4 + 1 ); + Text8 result = new Text8 (); + char [] buffer = string.dup; + int index = 0, state = 0, linkIndex = 0; + int start = 0, tagStart = 0, linkStart = 0, endtagStart = 0, refStart = 0; + while (index < length_) { + char c = tango.text.Unicode.toLower (buffer [index .. index+1])[0]; + switch (state) { + case 0: + if (c is '<') { + tagStart = index; + state++; + } + break; + case 1: + if (c is 'a') state++; + break; + case 2: + switch (c) { + case 'h': + state = 7; + break; + case '>': + linkStart = index + 1; + state++; + break; + default: + if (tango.text.Unicode.isWhitespace(c)) break; + else state = 13; + } + break; + case 3: + if (c is '<') { + endtagStart = index; + state++; + } + break; + case 4: + state = c is '/' ? state + 1 : 3; + break; + case 5: + state = c is 'a' ? state + 1 : 3; + break; + case 6: + if (c is '>') { + mnemonics [linkIndex] = parseMnemonics (buffer, start, tagStart, result); + int offset = result.length (); + parseMnemonics (buffer, linkStart, endtagStart, result); + offsets [linkIndex] = new Point (offset, result.length () - 1); + if (ids [linkIndex] is null) { + ids [linkIndex] = buffer[ linkStart .. endtagStart ].dup; + } + linkIndex++; + start = tagStart = linkStart = endtagStart = refStart = index + 1; + state = 0; + } else { + state = 3; + } + break; + case 7: + state = c is 'r' ? state + 1 : 0; + break; + case 8: + state = c is 'e' ? state + 1 : 0; + break; + case 9: + state = c is 'f' ? state + 1 : 0; + break; + case 10: + state = c is '=' ? state + 1 : 0; + break; + case 11: + if (c is '"') { + state++; + refStart = index + 1; + } else { + state = 0; + } + break; + case 12: + if (c is '"') { + ids[linkIndex] = buffer[ refStart .. index ].dup; + state = 2; + } + break; + case 13: + if (tango.text.Unicode.isWhitespace (c)) { + state = 0; + } else if (c is '='){ + state++; + } + break; + case 14: + state = c is '"' ? state + 1 : 0; + break; + case 15: + if (c is '"') state = 2; + break; + default: + state = 0; + break; + } + index++; + } + if (start < length_) { + int tmp = parseMnemonics (buffer, start, tagStart, result); + int mnemonic = parseMnemonics (buffer, linkStart, index, result); + if (mnemonic is -1) mnemonic = tmp; + mnemonics [linkIndex] = mnemonic; + } else { + mnemonics [linkIndex] = -1; + } + if (offsets.length !is linkIndex) { + Point [] newOffsets = new Point [linkIndex]; + System.arraycopy (offsets, 0, newOffsets, 0, linkIndex); + offsets = newOffsets; + char[] [] newIDs = new char[] [linkIndex]; + System.arraycopy (ids, 0, newIDs, 0, linkIndex); + ids = newIDs; + int [] newMnemonics = new int [linkIndex + 1]; + System.arraycopy (mnemonics, 0, newMnemonics, 0, linkIndex + 1); + mnemonics = newMnemonics; + } + return result.toString (); +} + +int parseMnemonics (char[] buffer, int start, int end, Text8 result) { + int mnemonic = -1, index = start; + while (index < end) { + if (buffer [index] is '&') { + if (index + 1 < end && buffer [index + 1] is '&') { + result.append (buffer [index]); + index++; + } else { + mnemonic = result.length(); + } + } else { + result.append (buffer [index]); + } + index++; + } + return mnemonic; +} + +int setBounds(int x, int y, int width, int height, bool move, bool resize) { + int result = super.setBounds (x, y, width,height, move, resize); + if ((result & RESIZED) !is 0) { + layout.setWidth (width > 0 ? width : -1); + redraw (); + } + return result; +} + +void setFontDescription (PangoFontDescription* font) { + super.setFontDescription (font); + layout.setFont (Font.gtk_new (display, font)); +} + +/** + * Sets the receiver's text. + *

+ * The string can contain both regular text and hyperlinks. A hyperlink + * is delimited by an anchor tag, <A> and </A>. Within an + * anchor, a single HREF attribute is supported. When a hyperlink is + * selected, the text field of the selection event contains either the + * text of the hyperlink or the value of its HREF, if one was specified. + * In the rare case of identical hyperlinks within the same string, the + * HREF tag can be used to distinguish between them. The string may + * include the mnemonic character and line delimiters. + *

+ * + * @param string the new text + * + * @exception IllegalArgumentException + * @exception DWTException + */ +public void setText (char[] string) { + checkWidget (); + if (string is null) error (DWT.ERROR_NULL_ARGUMENT); + if (string ==/*eq*/ text) return; + text = string; + layout.setText (parse (string)); + focusIndex = offsets.length > 0 ? 0 : -1; + selection.x = selection.y = -1; + bool enabled = (state & DISABLED) is 0; + TextStyle linkStyle = new TextStyle (null, enabled ? linkColor : disabledColor, null); + linkStyle.underline = true; + for (int i = 0; i < offsets.length; i++) { + Point point = offsets [i]; + layout.setStyle (linkStyle, point.x, point.y); + } + TextStyle mnemonicStyle = new TextStyle (null, null, null); + mnemonicStyle.underline = true; + for (int i = 0; i < mnemonics.length; i++) { + int mnemonic = mnemonics [i]; + if (mnemonic !is -1) { + layout.setStyle (mnemonicStyle, mnemonic, mnemonic); + } + } + redraw (); +} + +void showWidget () { + super.showWidget (); + fixStyle (handle); +} + +override int traversalCode (int key, GdkEventKey* event) { + if (offsets.length is 0) return 0; + int bits = super.traversalCode (key, event); + if (key is OS.GDK_Tab && focusIndex < offsets.length - 1) { + return bits & ~DWT.TRAVERSE_TAB_NEXT; + } + if (key is OS.GDK_ISO_Left_Tab && focusIndex > 0) { + return bits & ~DWT.TRAVERSE_TAB_PREVIOUS; + } + return bits; +} +}