0001: /*******************************************************************************
0002: * Copyright (c) 2000, 2007 IBM Corporation and others.
0003: * All rights reserved. This program and the accompanying materials
0004: * are made available under the terms of the Eclipse Public License v1.0
0005: * which accompanies this distribution, and is available at
0006: * http://www.eclipse.org/legal/epl-v10.html
0007: *
0008: * Contributors:
0009: * IBM Corporation - initial API and implementation
0010: *******************************************************************************/package org.eclipse.ui.forms.widgets;
0011:
0012: import java.io.InputStream;
0013: import java.util.ArrayList;
0014: import java.util.Enumeration;
0015: import java.util.Hashtable;
0016:
0017: import org.eclipse.core.runtime.ListenerList;
0018: import org.eclipse.swt.SWT;
0019: import org.eclipse.swt.SWTException;
0020: import org.eclipse.swt.accessibility.ACC;
0021: import org.eclipse.swt.accessibility.Accessible;
0022: import org.eclipse.swt.accessibility.AccessibleAdapter;
0023: import org.eclipse.swt.accessibility.AccessibleControlAdapter;
0024: import org.eclipse.swt.accessibility.AccessibleControlEvent;
0025: import org.eclipse.swt.accessibility.AccessibleEvent;
0026: import org.eclipse.swt.custom.ScrolledComposite;
0027: import org.eclipse.swt.dnd.Clipboard;
0028: import org.eclipse.swt.dnd.TextTransfer;
0029: import org.eclipse.swt.dnd.Transfer;
0030: import org.eclipse.swt.events.DisposeEvent;
0031: import org.eclipse.swt.events.DisposeListener;
0032: import org.eclipse.swt.events.FocusEvent;
0033: import org.eclipse.swt.events.FocusListener;
0034: import org.eclipse.swt.events.MenuEvent;
0035: import org.eclipse.swt.events.MenuListener;
0036: import org.eclipse.swt.events.MouseEvent;
0037: import org.eclipse.swt.events.MouseListener;
0038: import org.eclipse.swt.events.MouseMoveListener;
0039: import org.eclipse.swt.events.MouseTrackListener;
0040: import org.eclipse.swt.events.PaintEvent;
0041: import org.eclipse.swt.events.PaintListener;
0042: import org.eclipse.swt.events.SelectionAdapter;
0043: import org.eclipse.swt.events.SelectionEvent;
0044: import org.eclipse.swt.events.SelectionListener;
0045: import org.eclipse.swt.graphics.Color;
0046: import org.eclipse.swt.graphics.Font;
0047: import org.eclipse.swt.graphics.FontMetrics;
0048: import org.eclipse.swt.graphics.GC;
0049: import org.eclipse.swt.graphics.Image;
0050: import org.eclipse.swt.graphics.Point;
0051: import org.eclipse.swt.graphics.Rectangle;
0052: import org.eclipse.swt.widgets.Canvas;
0053: import org.eclipse.swt.widgets.Composite;
0054: import org.eclipse.swt.widgets.Control;
0055: import org.eclipse.swt.widgets.Event;
0056: import org.eclipse.swt.widgets.Layout;
0057: import org.eclipse.swt.widgets.Listener;
0058: import org.eclipse.swt.widgets.Menu;
0059: import org.eclipse.swt.widgets.MenuItem;
0060: import org.eclipse.swt.widgets.TypedListener;
0061: import org.eclipse.ui.forms.HyperlinkSettings;
0062: import org.eclipse.ui.forms.events.HyperlinkEvent;
0063: import org.eclipse.ui.forms.events.IHyperlinkListener;
0064: import org.eclipse.ui.internal.forms.Messages;
0065: import org.eclipse.ui.internal.forms.widgets.ControlSegment;
0066: import org.eclipse.ui.internal.forms.widgets.FormTextModel;
0067: import org.eclipse.ui.internal.forms.widgets.FormUtil;
0068: import org.eclipse.ui.internal.forms.widgets.IFocusSelectable;
0069: import org.eclipse.ui.internal.forms.widgets.IHyperlinkSegment;
0070: import org.eclipse.ui.internal.forms.widgets.ImageSegment;
0071: import org.eclipse.ui.internal.forms.widgets.Locator;
0072: import org.eclipse.ui.internal.forms.widgets.Paragraph;
0073: import org.eclipse.ui.internal.forms.widgets.ParagraphSegment;
0074: import org.eclipse.ui.internal.forms.widgets.SelectionData;
0075: import org.eclipse.ui.internal.forms.widgets.TextSegment;
0076:
0077: /**
0078: * This class is a read-only text control that is capable of rendering wrapped
0079: * text. Text can be rendered as-is or by parsing the formatting XML tags.
0080: * Independently, words that start with http:// can be converted into hyperlinks
0081: * on the fly.
0082: * <p>
0083: * When configured to use formatting XML, the control requires the root element
0084: * <code>form</code> to be used. The following tags can be children of the
0085: * <code>form</code> element:
0086: * </p>
0087: * <ul>
0088: * <li><b>p </b>- for defining paragraphs. The following attributes are
0089: * allowed:
0090: * <ul>
0091: * <li><b>vspace </b>- if set to 'false', no vertical space will be added
0092: * (default is 'true')</li>
0093: * </ul>
0094: * </li>
0095: * <li><b>li </b>- for defining list items. The following attributes are
0096: * allowed:
0097: * <ul>
0098: * <li><b>vspace </b>- the same as with the <b>p </b> tag</li>
0099: * <li><b>style </b>- could be 'bullet' (default), 'text' and 'image'</li>
0100: * <li><b>value </b>- not used for 'bullet'. For text, it is the value of the
0101: * text that is rendered as a bullet. For image, it is the href of the image to
0102: * be rendered as a bullet.</li>
0103: * <li><b>indent </b>- the number of pixels to indent the text in the list item
0104: * </li>
0105: * <li><b>bindent </b>- the number of pixels to indent the bullet itself</li>
0106: * </ul>
0107: * </li>
0108: * </ul>
0109: * <p>
0110: * Text in paragraphs and list items will be wrapped according to the width of
0111: * the control. The following tags can appear as children of either <b>p </b> or
0112: * <b>li </b> elements:
0113: * <ul>
0114: * <li><b>img </b>- to render an image. Element accepts attribute 'href' that
0115: * is a key to the <code>Image</code> set using 'setImage' method. Vertical
0116: * position of image relative to surrounding text is optionally controlled by
0117: * the attribute <b>align</b> that can have values <b>top</b>, <b>middle</b>
0118: * and <b>bottom</b></li>
0119: * <li><b>a </b>- to render a hyperlink. Element accepts attribute 'href' that
0120: * will be provided to the hyperlink listeners via HyperlinkEvent object. The
0121: * element also accepts 'nowrap' attribute (default is false). When set to
0122: * 'true', the hyperlink will not be wrapped. Hyperlinks automatically created
0123: * when 'http://' is encountered in text are not wrapped.</li>
0124: * <li><b>b </b>- the enclosed text will use bold font.</li>
0125: * <li><b>br </b>- forced line break (no attributes).</li>
0126: * <li><b>span </b>- the enclosed text will have the color and font specified
0127: * in the element attributes. Color is provided using 'color' attribute and is a
0128: * key to the Color object set by 'setColor' method. Font is provided using
0129: * 'font' attribute and is a key to the Font object set by 'setFont' method. As with
0130: * hyperlinks, it is possible to block wrapping by setting 'nowrap' to true
0131: * (false by default).
0132: * </li>
0133: * <li><b>control (new in 3.1)</b> - to place a control that is a child of the
0134: * text control. Element accepts attribute 'href' that is a key to the Control
0135: * object set using 'setControl' method. Optionally, attribute 'fill' can be set
0136: * to <code>true</code> to make the control fill the entire width of the text.
0137: * Form text is not responsible for creating or disposing controls, it only
0138: * places them relative to the surrounding text. Similar to <b>img</b>,
0139: * vertical position of the control can be set using the <b>align</b>
0140: * attribute. In addition, <b>width</b> and <b>height</b> attributes can
0141: * be used to force the dimensions of the control. If not used,
0142: * the preferred control size will be used.
0143: * </ul>
0144: * <p>
0145: * None of the elements can nest. For example, you cannot have <b>b </b> inside
0146: * a <b>span </b>. This was done to keep everything simple and transparent.
0147: * Since 3.1, an exception to this rule has been added to support nesting images
0148: * and text inside the hyperlink tag (<b>a</b>). Image enclosed in the
0149: * hyperlink tag acts as a hyperlink, can be clicked on and can accept and
0150: * render selection focus. When both text and image is enclosed, selection and
0151: * rendering will affect both as a single hyperlink.
0152: * </p>
0153: * <p>
0154: * Since 3.1, it is possible to select text. Text selection can be
0155: * programmatically accessed and also copied to clipboard. Non-textual objects
0156: * (images, controls etc.) in the selection range are ignored.
0157: * <p>
0158: * Care should be taken when using this control. Form text is not an HTML
0159: * browser and should not be treated as such. If you need complex formatting
0160: * capabilities, use Browser widget. If you need editing capabilities and
0161: * font/color styles of text segments is all you need, use StyleText widget.
0162: * Finally, if all you need is to wrap text, use SWT Label widget and create it
0163: * with SWT.WRAP style.
0164: *
0165: * @see FormToolkit
0166: * @see TableWrapLayout
0167: * @since 3.0
0168: */
0169: public class FormText extends Canvas {
0170: /**
0171: * The object ID to be used when registering action to handle URL hyperlinks
0172: * (those that should result in opening the web browser). Value is
0173: * "urlHandler".
0174: */
0175: public static final String URL_HANDLER_ID = "urlHandler"; //$NON-NLS-1$
0176:
0177: /**
0178: * Value of the horizontal margin (default is 0).
0179: */
0180: public int marginWidth = 0;
0181:
0182: /**
0183: * Value of tue vertical margin (default is 1).
0184: */
0185: public int marginHeight = 1;
0186:
0187: // private fields
0188: //TODO We should remove the dependency on Platform
0189: private static final boolean DEBUG_TEXT = false;//"true".equalsIgnoreCase(Platform.getDebugOption(FormUtil.DEBUG_TEXT));
0190: private static final boolean DEBUG_TEXTSIZE = false;//"true".equalsIgnoreCase(Platform.getDebugOption(FormUtil.DEBUG_TEXTSIZE));
0191:
0192: private static final boolean DEBUG_FOCUS = false;//"true".equalsIgnoreCase(Platform.getDebugOption(FormUtil.DEBUG_FOCUS));
0193:
0194: private boolean hasFocus;
0195:
0196: private boolean paragraphsSeparated = true;
0197:
0198: private FormTextModel model;
0199:
0200: private ListenerList listeners;
0201:
0202: private Hashtable resourceTable = new Hashtable();
0203:
0204: private IHyperlinkSegment entered;
0205:
0206: private IHyperlinkSegment armed;
0207:
0208: private boolean mouseFocus = false;
0209:
0210: private boolean controlFocusTransfer = false;
0211:
0212: private boolean inSelection = false;
0213:
0214: private SelectionData selData;
0215:
0216: private static final String INTERNAL_MENU = "__internal_menu__"; //$NON-NLS-1$
0217:
0218: private static final String CONTROL_KEY = "__segment__"; //$NON-NLS-1$
0219:
0220: private class FormTextLayout extends Layout implements
0221: ILayoutExtension {
0222: public FormTextLayout() {
0223: }
0224:
0225: public int computeMaximumWidth(Composite parent, boolean changed) {
0226: return computeSize(parent, SWT.DEFAULT, SWT.DEFAULT,
0227: changed).x;
0228: }
0229:
0230: public int computeMinimumWidth(Composite parent, boolean changed) {
0231: return computeSize(parent, 5, SWT.DEFAULT, true).x;
0232: }
0233:
0234: /*
0235: * @see Layout#computeSize(Composite, int, int, boolean)
0236: */
0237: public Point computeSize(Composite composite, int wHint,
0238: int hHint, boolean changed) {
0239: long start = 0;
0240:
0241: if (DEBUG_TEXT)
0242: start = System.currentTimeMillis();
0243: int innerWidth = wHint;
0244: if (innerWidth != SWT.DEFAULT)
0245: innerWidth -= marginWidth * 2;
0246: Point textSize = computeTextSize(innerWidth);
0247: int textWidth = textSize.x + 2 * marginWidth;
0248: int textHeight = textSize.y + 2 * marginHeight;
0249: Point result = new Point(textWidth, textHeight);
0250: if (DEBUG_TEXT) {
0251: long stop = System.currentTimeMillis();
0252: System.out
0253: .println("FormText computeSize: " + (stop - start) //$NON-NLS-1$
0254: + "ms"); //$NON-NLS-1$
0255: }
0256: if (DEBUG_TEXTSIZE) {
0257: System.out
0258: .println("FormText (" + model.getAccessibleText() + "), computeSize: wHint=" + wHint + ", result=" + result); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
0259: }
0260: return result;
0261: }
0262:
0263: private Point computeTextSize(int wHint) {
0264: Paragraph[] paragraphs = model.getParagraphs();
0265: GC gc = new GC(FormText.this );
0266: gc.setFont(getFont());
0267: Locator loc = new Locator();
0268: int width = wHint != SWT.DEFAULT ? wHint : 0;
0269: FontMetrics fm = gc.getFontMetrics();
0270: int lineHeight = fm.getHeight();
0271: boolean selectableInTheLastRow = false;
0272: for (int i = 0; i < paragraphs.length; i++) {
0273: Paragraph p = paragraphs[i];
0274: if (i > 0 && getParagraphsSeparated()
0275: && p.getAddVerticalSpace())
0276: loc.y += getParagraphSpacing(lineHeight);
0277: loc.rowHeight = 0;
0278: loc.indent = p.getIndent();
0279: loc.x = p.getIndent();
0280: ParagraphSegment[] segments = p.getSegments();
0281: if (segments.length > 0) {
0282: selectableInTheLastRow = false;
0283: int pwidth = 0;
0284: for (int j = 0; j < segments.length; j++) {
0285: ParagraphSegment segment = segments[j];
0286: segment.advanceLocator(gc, wHint, loc,
0287: resourceTable, false);
0288: if (wHint != SWT.DEFAULT) {
0289: width = Math.max(width, loc.width);
0290: } else {
0291: pwidth += loc.width;
0292: }
0293: if (segment instanceof IFocusSelectable)
0294: selectableInTheLastRow = true;
0295: }
0296: if (wHint == SWT.DEFAULT)
0297: width = Math.max(width, pwidth);
0298: loc.y += loc.rowHeight;
0299: } else {
0300: // empty new line
0301: loc.y += lineHeight;
0302: }
0303: }
0304: gc.dispose();
0305: if (selectableInTheLastRow)
0306: loc.y += 1;
0307: return new Point(width, loc.y);
0308: }
0309:
0310: protected void layout(Composite composite, boolean flushCache) {
0311: long start = 0;
0312:
0313: if (DEBUG_TEXT) {
0314: start = System.currentTimeMillis();
0315: }
0316: selData = null;
0317: Rectangle carea = composite.getClientArea();
0318: if (DEBUG_TEXTSIZE) {
0319: System.out
0320: .println("FormText layout (" + model.getAccessibleText() + "), carea=" + carea); //$NON-NLS-1$ //$NON-NLS-2$
0321: }
0322: GC gc = new GC(composite);
0323: gc.setFont(getFont());
0324: ensureBoldFontPresent(getFont());
0325: gc.setForeground(getForeground());
0326: gc.setBackground(getBackground());
0327:
0328: Locator loc = new Locator();
0329: loc.marginWidth = marginWidth;
0330: loc.marginHeight = marginHeight;
0331: loc.x = marginWidth;
0332: loc.y = marginHeight;
0333: FontMetrics fm = gc.getFontMetrics();
0334: int lineHeight = fm.getHeight();
0335:
0336: Paragraph[] paragraphs = model.getParagraphs();
0337: IHyperlinkSegment selectedLink = getSelectedLink();
0338: for (int i = 0; i < paragraphs.length; i++) {
0339: Paragraph p = paragraphs[i];
0340: if (i > 0 && paragraphsSeparated
0341: && p.getAddVerticalSpace())
0342: loc.y += getParagraphSpacing(lineHeight);
0343: loc.indent = p.getIndent();
0344: loc.resetCaret();
0345: loc.rowHeight = 0;
0346: p.layout(gc, carea.width, loc, lineHeight,
0347: resourceTable, selectedLink);
0348: }
0349: gc.dispose();
0350: if (DEBUG_TEXT) {
0351: long stop = System.currentTimeMillis();
0352: System.out
0353: .println("FormText.layout: " + (stop - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
0354: }
0355: }
0356: }
0357:
0358: /**
0359: * Contructs a new form text widget in the provided parent and using the
0360: * styles.
0361: *
0362: * @param parent
0363: * form text parent control
0364: * @param style
0365: * the widget style
0366: */
0367: public FormText(Composite parent, int style) {
0368: super (parent, SWT.NO_BACKGROUND | SWT.WRAP | style);
0369: setLayout(new FormTextLayout());
0370: model = new FormTextModel();
0371: addDisposeListener(new DisposeListener() {
0372: public void widgetDisposed(DisposeEvent e) {
0373: model.dispose();
0374: disposeResourceTable(true);
0375: }
0376: });
0377: addPaintListener(new PaintListener() {
0378: public void paintControl(PaintEvent e) {
0379: paint(e);
0380: }
0381: });
0382: addListener(SWT.KeyDown, new Listener() {
0383: public void handleEvent(Event e) {
0384: if (e.character == '\r') {
0385: activateSelectedLink();
0386: return;
0387: }
0388: }
0389: });
0390: addListener(SWT.Traverse, new Listener() {
0391: public void handleEvent(Event e) {
0392: if (DEBUG_FOCUS)
0393: System.out.println("Traversal: " + e); //$NON-NLS-1$
0394: switch (e.detail) {
0395: case SWT.TRAVERSE_PAGE_NEXT:
0396: case SWT.TRAVERSE_PAGE_PREVIOUS:
0397: case SWT.TRAVERSE_ARROW_NEXT:
0398: case SWT.TRAVERSE_ARROW_PREVIOUS:
0399: e.doit = false;
0400: return;
0401: }
0402: if (!model.hasFocusSegments()) {
0403: e.doit = true;
0404: return;
0405: }
0406: if (e.detail == SWT.TRAVERSE_TAB_NEXT)
0407: e.doit = advance(true);
0408: else if (e.detail == SWT.TRAVERSE_TAB_PREVIOUS)
0409: e.doit = advance(false);
0410: else if (e.detail != SWT.TRAVERSE_RETURN)
0411: e.doit = true;
0412: }
0413: });
0414: addFocusListener(new FocusListener() {
0415: public void focusGained(FocusEvent e) {
0416: if (!hasFocus) {
0417: hasFocus = true;
0418: if (DEBUG_FOCUS) {
0419: System.out.println("FormText: focus gained"); //$NON-NLS-1$
0420: }
0421: if (!mouseFocus && !controlFocusTransfer) {
0422: handleFocusChange();
0423: }
0424: }
0425: }
0426:
0427: public void focusLost(FocusEvent e) {
0428: if (DEBUG_FOCUS) {
0429: System.out.println("FormText: focus lost"); //$NON-NLS-1$
0430: }
0431: if (hasFocus) {
0432: hasFocus = false;
0433: if (!controlFocusTransfer)
0434: handleFocusChange();
0435: }
0436: }
0437: });
0438: addMouseListener(new MouseListener() {
0439: public void mouseDoubleClick(MouseEvent e) {
0440: }
0441:
0442: public void mouseDown(MouseEvent e) {
0443: // select a link
0444: handleMouseClick(e, true);
0445: }
0446:
0447: public void mouseUp(MouseEvent e) {
0448: // activate a link
0449: handleMouseClick(e, false);
0450: }
0451: });
0452: addMouseTrackListener(new MouseTrackListener() {
0453: public void mouseEnter(MouseEvent e) {
0454: handleMouseMove(e);
0455: }
0456:
0457: public void mouseExit(MouseEvent e) {
0458: if (entered != null) {
0459: exitLink(entered, e.stateMask);
0460: paintLinkHover(entered, false);
0461: entered = null;
0462: setCursor(null);
0463: }
0464: }
0465:
0466: public void mouseHover(MouseEvent e) {
0467: handleMouseHover(e);
0468: }
0469: });
0470: addMouseMoveListener(new MouseMoveListener() {
0471: public void mouseMove(MouseEvent e) {
0472: handleMouseMove(e);
0473: }
0474: });
0475: initAccessible();
0476: ensureBoldFontPresent(getFont());
0477: createMenu();
0478: // we will handle traversal of controls, if any
0479: setTabList(new Control[] {});
0480: }
0481:
0482: /**
0483: * Test for focus.
0484: *
0485: * @return <samp>true </samp> if the widget has focus.
0486: */
0487: public boolean getFocus() {
0488: return hasFocus;
0489: }
0490:
0491: /**
0492: * Test if the widget is currently processing the text it is about to
0493: * render.
0494: *
0495: * @return <samp>true </samp> if the widget is still loading the text,
0496: * <samp>false </samp> otherwise.
0497: * @deprecated not used any more - returns <code>false</code>
0498: */
0499: public boolean isLoading() {
0500: return false;
0501: }
0502:
0503: /**
0504: * Returns the text that will be shown in the control while the real content
0505: * is loading.
0506: *
0507: * @return loading text message
0508: * @deprecated loading text is not used since 3.1
0509: */
0510: public String getLoadingText() {
0511: return null;
0512: }
0513:
0514: /**
0515: * Sets the text that will be shown in the control while the real content is
0516: * loading. This is significant when content to render is loaded from the
0517: * input stream that was created from a remote URL, and the time to load the
0518: * entire content is nontrivial.
0519: *
0520: * @param loadingText
0521: * loading text message
0522: * @deprecated use setText(loadingText, false, false);
0523: */
0524: public void setLoadingText(String loadingText) {
0525: setText(loadingText, false, false);
0526: }
0527:
0528: /**
0529: * If paragraphs are separated, spacing will be added between them.
0530: * Otherwise, new paragraphs will simply start on a new line with no
0531: * spacing.
0532: *
0533: * @param value
0534: * <samp>true </samp> if paragraphs are separated, </samp> false
0535: * </samp> otherwise.
0536: */
0537: public void setParagraphsSeparated(boolean value) {
0538: paragraphsSeparated = value;
0539: }
0540:
0541: /**
0542: * Tests if there is some inter-paragraph spacing.
0543: *
0544: * @return <samp>true </samp> if paragraphs are separated, <samp>false
0545: * </samp> otherwise.
0546: */
0547: public boolean getParagraphsSeparated() {
0548: return paragraphsSeparated;
0549: }
0550:
0551: /**
0552: * Registers the image referenced by the provided key.
0553: * <p>
0554: * For <samp>img </samp> tags, an object of a type <samp>Image </samp> must
0555: * be registered using the key equivalent to the value of the <samp>href
0556: * </samp> attribute used in the tag.
0557: *
0558: * @param key
0559: * unique key that matches the value of the <samp>href </samp>
0560: * attribute.
0561: * @param image
0562: * an object of a type <samp>Image </samp>.
0563: */
0564: public void setImage(String key, Image image) {
0565: resourceTable.put("i." + key, image); //$NON-NLS-1$
0566: }
0567:
0568: /**
0569: * Registers the color referenced by the provided key.
0570: * <p>
0571: * For <samp>span </samp> tags, an object of a type <samp>Color </samp> must
0572: * be registered using the key equivalent to the value of the <samp>color
0573: * </samp> attribute.
0574: *
0575: * @param key
0576: * unique key that matches the value of the <samp>color </samp>
0577: * attribute.
0578: * @param color
0579: * an object of the type <samp>Color </samp> or <samp>null</samp>
0580: * if the key needs to be cleared.
0581: */
0582: public void setColor(String key, Color color) {
0583: String fullKey = "c." + key; //$NON-NLS-1$
0584: if (color == null)
0585: resourceTable.remove(fullKey);
0586: else
0587: resourceTable.put(fullKey, color);
0588: }
0589:
0590: /**
0591: * Registers the font referenced by the provided key.
0592: * <p>
0593: * For <samp>span </samp> tags, an object of a type <samp>Font </samp> must
0594: * be registered using the key equivalent to the value of the <samp>font
0595: * </samp> attribute.
0596: *
0597: * @param key
0598: * unique key that matches the value of the <samp>font </samp>
0599: * attribute.
0600: * @param font
0601: * an object of the type <samp>Font </samp> or <samp>null</samp>
0602: * if the key needs to be cleared.
0603: */
0604: public void setFont(String key, Font font) {
0605: String fullKey = "f." + key; //$NON-NLS-1$
0606: if (font == null)
0607: resourceTable.remove(fullKey);
0608: else
0609: resourceTable.put(fullKey, font);
0610: model.clearCache(fullKey);
0611: }
0612:
0613: /**
0614: * Registers the control referenced by the provided key.
0615: * <p>
0616: * For <samp>control</samp> tags, an object of a type <samp>Control</samp>
0617: * must be registered using the key equivalent to the value of the
0618: * <samp>control</samp> attribute.
0619: *
0620: * @param key
0621: * unique key that matches the value of the <samp>control</samp>
0622: * attribute.
0623: * @param control
0624: * an object of the type <samp>Control</samp> or <samp>null</samp>
0625: * if the existing control at the specified key needs to be
0626: * removed.
0627: * @since 3.1
0628: */
0629: public void setControl(String key, Control control) {
0630: String fullKey = "o." + key; //$NON-NLS-1$
0631: if (control == null)
0632: resourceTable.remove(fullKey);
0633: else
0634: resourceTable.put(fullKey, control);
0635: }
0636:
0637: /**
0638: * Sets the font to use to render the default text (text that does not have
0639: * special font property assigned). Bold font will be constructed from this
0640: * font.
0641: *
0642: * @param font
0643: * the default font to use
0644: */
0645: public void setFont(Font font) {
0646: super .setFont(font);
0647: model.clearCache(null);
0648: Font boldFont = (Font) resourceTable
0649: .get(FormTextModel.BOLD_FONT_ID);
0650: if (boldFont != null) {
0651: boldFont.dispose();
0652: resourceTable.remove(FormTextModel.BOLD_FONT_ID);
0653: }
0654: ensureBoldFontPresent(getFont());
0655: }
0656:
0657: /**
0658: * Sets the provided text. Text can be rendered as-is, or by parsing the
0659: * formatting tags. Optionally, sections of text starting with http:// will
0660: * be converted to hyperlinks.
0661: *
0662: * @param text
0663: * the text to render
0664: * @param parseTags
0665: * if <samp>true </samp>, formatting tags will be parsed.
0666: * Otherwise, text will be rendered as-is.
0667: * @param expandURLs
0668: * if <samp>true </samp>, URLs found in the untagged text will be
0669: * converted into hyperlinks.
0670: */
0671: public void setText(String text, boolean parseTags,
0672: boolean expandURLs) {
0673: disposeResourceTable(false);
0674: entered = null;
0675: if (parseTags)
0676: model.parseTaggedText(text, expandURLs);
0677: else
0678: model.parseRegularText(text, expandURLs);
0679: hookControlSegmentFocus();
0680: layout();
0681: redraw();
0682: }
0683:
0684: /**
0685: * Sets the contents of the stream. Optionally, URLs in untagged text can be
0686: * converted into hyperlinks. The caller is responsible for closing the
0687: * stream.
0688: *
0689: * @param is
0690: * stream to render
0691: * @param expandURLs
0692: * if <samp>true </samp>, URLs found in untagged text will be
0693: * converted into hyperlinks.
0694: */
0695: public void setContents(InputStream is, boolean expandURLs) {
0696: entered = null;
0697: disposeResourceTable(false);
0698: model.parseInputStream(is, expandURLs);
0699: hookControlSegmentFocus();
0700: layout();
0701: redraw();
0702: }
0703:
0704: private void hookControlSegmentFocus() {
0705: Paragraph[] paragraphs = model.getParagraphs();
0706: if (paragraphs == null)
0707: return;
0708: Listener listener = new Listener() {
0709: public void handleEvent(Event e) {
0710: switch (e.type) {
0711: case SWT.FocusIn:
0712: if (!controlFocusTransfer)
0713: syncControlSegmentFocus((Control) e.widget);
0714: break;
0715: case SWT.Traverse:
0716: if (DEBUG_FOCUS)
0717: System.out.println("Control traversal: " + e); //$NON-NLS-1$
0718: switch (e.detail) {
0719: case SWT.TRAVERSE_PAGE_NEXT:
0720: case SWT.TRAVERSE_PAGE_PREVIOUS:
0721: case SWT.TRAVERSE_ARROW_NEXT:
0722: case SWT.TRAVERSE_ARROW_PREVIOUS:
0723: e.doit = false;
0724: return;
0725: }
0726: Control c = (Control) e.widget;
0727: ControlSegment segment = (ControlSegment) c
0728: .getData(CONTROL_KEY);
0729: if (e.detail == SWT.TRAVERSE_TAB_NEXT)
0730: e.doit = advanceControl(c, segment, true);
0731: else if (e.detail == SWT.TRAVERSE_TAB_PREVIOUS)
0732: e.doit = advanceControl(c, segment, false);
0733: if (!e.doit)
0734: e.detail = SWT.TRAVERSE_NONE;
0735: break;
0736: }
0737: }
0738: };
0739: for (int i = 0; i < paragraphs.length; i++) {
0740: Paragraph p = paragraphs[i];
0741: ParagraphSegment[] segments = p.getSegments();
0742: for (int j = 0; j < segments.length; j++) {
0743: if (segments[j] instanceof ControlSegment) {
0744: ControlSegment cs = (ControlSegment) segments[j];
0745: Control c = cs.getControl(resourceTable);
0746: if (c != null) {
0747: if (c.getData(CONTROL_KEY) == null) {
0748: // first time - hook
0749: c.setData(CONTROL_KEY, cs);
0750: attachTraverseListener(c, listener);
0751: }
0752: }
0753: }
0754: }
0755: }
0756: }
0757:
0758: private void attachTraverseListener(Control c, Listener listener) {
0759: if (c instanceof Composite) {
0760: Composite parent = (Composite) c;
0761: Control[] children = parent.getChildren();
0762: for (int i = 0; i < children.length; i++) {
0763: attachTraverseListener(children[i], listener);
0764: }
0765: if (c instanceof Canvas) {
0766: // If Canvas, the control iteself can accept
0767: // traverse events and should be monitored
0768: c.addListener(SWT.Traverse, listener);
0769: c.addListener(SWT.FocusIn, listener);
0770: }
0771: } else {
0772: c.addListener(SWT.Traverse, listener);
0773: c.addListener(SWT.FocusIn, listener);
0774: }
0775: }
0776:
0777: /**
0778: * If we click on the control randomly, our internal book-keeping will be
0779: * off. We need to update the model and mark the control segment and
0780: * currently selected. Hyperlink that may have had focus must also be
0781: * exited.
0782: *
0783: * @param control
0784: * the control that got focus
0785: */
0786: private void syncControlSegmentFocus(Control control) {
0787: ControlSegment cs = null;
0788:
0789: while (control != null) {
0790: cs = (ControlSegment) control.getData(CONTROL_KEY);
0791: if (cs != null)
0792: break;
0793: control = control.getParent();
0794: }
0795: if (cs == null)
0796: return;
0797: IFocusSelectable current = model.getSelectedSegment();
0798: // If the model and the control match, all is well
0799: if (current == cs)
0800: return;
0801: IHyperlinkSegment oldLink = null;
0802: if (current != null && current instanceof IHyperlinkSegment) {
0803: oldLink = (IHyperlinkSegment) current;
0804: exitLink(oldLink, SWT.NULL);
0805: }
0806: if (DEBUG_FOCUS)
0807: System.out
0808: .println("Sync control: " + cs + ", oldLink=" + oldLink); //$NON-NLS-1$ //$NON-NLS-2$
0809: model.select(cs);
0810: if (oldLink != null)
0811: paintFocusTransfer(oldLink, null);
0812: // getAccessible().setFocus(model.getSelectedSegmentIndex());
0813: }
0814:
0815: private boolean advanceControl(Control c, ControlSegment segment,
0816: boolean next) {
0817: Composite parent = c.getParent();
0818: if (parent == this ) {
0819: // segment-level control
0820: IFocusSelectable nextSegment = model
0821: .getNextFocusSegment(next);
0822: if (nextSegment != null) {
0823: controlFocusTransfer = true;
0824: super .forceFocus();
0825: controlFocusTransfer = false;
0826: model.select(segment);
0827: return advance(next);
0828: }
0829: // nowhere to go
0830: return setFocusToNextSibling(this , next);
0831: }
0832: if (setFocusToNextSibling(c, next))
0833: return true;
0834: // still here - must go one level up
0835: segment = (ControlSegment) parent.getData(CONTROL_KEY);
0836: return advanceControl(parent, segment, next);
0837: }
0838:
0839: private boolean setFocusToNextSibling(Control c, boolean next) {
0840: Composite parent = c.getParent();
0841: Control[] children = parent.getTabList();
0842: for (int i = 0; i < children.length; i++) {
0843: Control child = children[i];
0844: if (child == c) {
0845: // here
0846: if (next) {
0847: for (int j = i + 1; j < children.length; j++) {
0848: Control nc = children[j];
0849: if (nc.setFocus())
0850: return false;
0851: }
0852: } else {
0853: for (int j = i - 1; j >= 0; j--) {
0854: Control pc = children[j];
0855: if (pc.setFocus())
0856: return false;
0857: }
0858: }
0859: }
0860: }
0861: return false;
0862: }
0863:
0864: /**
0865: * Controls whether whitespace inside paragraph and list items is
0866: * normalized. Note that the new value will not affect the current text in
0867: * the control, only subsequent calls to <code>setText</code> or
0868: * <code>setContents</code>.
0869: * <p>
0870: * If normalized:
0871: * <ul>
0872: * <li>all white space characters will be condensed into at most one when
0873: * between words.</li>
0874: * <li>new line characters will be ignored and replaced with one white
0875: * space character</li>
0876: * <li>white space characters after the opening tags and before the closing
0877: * tags will be trimmed</li>
0878: *
0879: * @param value
0880: * <code>true</code> if whitespace is normalized,
0881: * <code>false</code> otherwise.
0882: */
0883: public void setWhitespaceNormalized(boolean value) {
0884: model.setWhitespaceNormalized(value);
0885: }
0886:
0887: /**
0888: * Tests whether whitespace inside paragraph and list item is normalized.
0889: *
0890: * @see #setWhitespaceNormalized(boolean)
0891: * @return <code>true</code> if whitespace is normalized,
0892: * <code>false</code> otherwise.
0893: */
0894: public boolean isWhitespaceNormalized() {
0895: return model.isWhitespaceNormalized();
0896: }
0897:
0898: /**
0899: * Disposes the internal menu if created and sets the menu provided as a
0900: * parameter.
0901: *
0902: * @param menu
0903: * the menu to associate with this text control
0904: */
0905: public void setMenu(Menu menu) {
0906: Menu currentMenu = super .getMenu();
0907: if (currentMenu != null
0908: && INTERNAL_MENU.equals(currentMenu.getData())) {
0909: // internal menu set
0910: if (menu != null) {
0911: currentMenu.dispose();
0912: super .setMenu(menu);
0913: }
0914: } else
0915: super .setMenu(menu);
0916: }
0917:
0918: private void createMenu() {
0919: Menu menu = new Menu(this );
0920: final MenuItem copyItem = new MenuItem(menu, SWT.PUSH);
0921: copyItem.setText(Messages.FormText_copy);
0922:
0923: SelectionListener listener = new SelectionAdapter() {
0924: public void widgetSelected(SelectionEvent e) {
0925: if (e.widget == copyItem) {
0926: copy();
0927: }
0928: }
0929: };
0930: copyItem.addSelectionListener(listener);
0931: menu.addMenuListener(new MenuListener() {
0932: public void menuShown(MenuEvent e) {
0933: copyItem.setEnabled(canCopy());
0934: }
0935:
0936: public void menuHidden(MenuEvent e) {
0937: }
0938: });
0939: menu.setData(INTERNAL_MENU);
0940: super .setMenu(menu);
0941: }
0942:
0943: /**
0944: * Returns the hyperlink settings that are in effect for this control.
0945: *
0946: * @return current hyperlinks settings
0947: */
0948: public HyperlinkSettings getHyperlinkSettings() {
0949: return model.getHyperlinkSettings();
0950: }
0951:
0952: /**
0953: * Sets the hyperlink settings to be used for this control. Settings will
0954: * affect things like hyperlink color, rendering style, cursor etc.
0955: *
0956: * @param settings
0957: * hyperlink settings for this control
0958: */
0959: public void setHyperlinkSettings(HyperlinkSettings settings) {
0960: model.setHyperlinkSettings(settings);
0961: }
0962:
0963: /**
0964: * Adds a listener that will handle hyperlink events.
0965: *
0966: * @param listener
0967: * the listener to add
0968: */
0969: public void addHyperlinkListener(IHyperlinkListener listener) {
0970: if (listeners == null)
0971: listeners = new ListenerList();
0972: listeners.add(listener);
0973: }
0974:
0975: /**
0976: * Removes the hyperlink listener.
0977: *
0978: * @param listener
0979: * the listener to remove
0980: */
0981: public void removeHyperlinkListener(IHyperlinkListener listener) {
0982: if (listeners == null)
0983: return;
0984: listeners.remove(listener);
0985: }
0986:
0987: /**
0988: * Adds a selection listener. A Selection event is sent by the widget when
0989: * the selection has changed.
0990: * <p>
0991: * <code>widgetDefaultSelected</code> is not called for FormText.
0992: * </p>
0993: *
0994: * @param listener
0995: * the listener
0996: * @exception SWTException
0997: * <ul>
0998: * <li>ERROR_WIDGET_DISPOSED - if the receiver has been
0999: * disposed</li>
1000: * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
1001: * thread that created the receiver</li>
1002: * </ul>
1003: * @exception IllegalArgumentException
1004: * <ul>
1005: * <li>ERROR_NULL_ARGUMENT when listener is null</li>
1006: * </ul>
1007: * @since 3.1
1008: */
1009: public void addSelectionListener(SelectionListener listener) {
1010: checkWidget();
1011: if (listener == null) {
1012: SWT.error(SWT.ERROR_NULL_ARGUMENT);
1013: }
1014: TypedListener typedListener = new TypedListener(listener);
1015: addListener(SWT.Selection, typedListener);
1016: }
1017:
1018: /**
1019: * Removes the specified selection listener.
1020: * <p>
1021: *
1022: * @param listener
1023: * the listener
1024: * @exception SWTException
1025: * <ul>
1026: * <li>ERROR_WIDGET_DISPOSED - if the receiver has been
1027: * disposed</li>
1028: * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
1029: * thread that created the receiver</li>
1030: * </ul>
1031: * @exception IllegalArgumentException
1032: * <ul>
1033: * <li>ERROR_NULL_ARGUMENT when listener is null</li>
1034: * </ul>
1035: * @since 3.1
1036: */
1037: public void removeSelectionListener(SelectionListener listener) {
1038: checkWidget();
1039: if (listener == null) {
1040: SWT.error(SWT.ERROR_NULL_ARGUMENT);
1041: }
1042: removeListener(SWT.Selection, listener);
1043: }
1044:
1045: /**
1046: * Returns the selected text.
1047: * <p>
1048: *
1049: * @return selected text, or an empty String if there is no selection.
1050: * @exception SWTException
1051: * <ul>
1052: * <li>ERROR_WIDGET_DISPOSED - if the receiver has been
1053: * disposed</li>
1054: * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
1055: * thread that created the receiver</li>
1056: * </ul>
1057: * @since 3.1
1058: */
1059:
1060: public String getSelectionText() {
1061: checkWidget();
1062: if (selData != null)
1063: return selData.getSelectionText();
1064: return ""; //$NON-NLS-1$
1065: }
1066:
1067: /**
1068: * Tests if the text is selected and can be copied into the clipboard.
1069: *
1070: * @return <code>true</code> if the selected text can be copied into the
1071: * clipboard, <code>false</code> otherwise.
1072: * @since 3.1
1073: */
1074: public boolean canCopy() {
1075: return selData != null && selData.canCopy();
1076: }
1077:
1078: /**
1079: * Copies the selected text into the clipboard. Does nothing if no text is
1080: * selected or the text cannot be copied for any other reason.
1081: *
1082: * @since 3.1
1083: */
1084:
1085: public void copy() {
1086: if (!canCopy())
1087: return;
1088: Clipboard clipboard = new Clipboard(getDisplay());
1089: Object[] o = new Object[] { getSelectionText() };
1090: Transfer[] t = new Transfer[] { TextTransfer.getInstance() };
1091: clipboard.setContents(o, t);
1092: clipboard.dispose();
1093: }
1094:
1095: /**
1096: * Returns the reference of the hyperlink that currently has keyboard focus,
1097: * or <code>null</code> if there are no hyperlinks in the receiver or no
1098: * hyperlink has focus at the moment.
1099: *
1100: * @return href of the selected hyperlink or <code>null</code> if none
1101: * selected.
1102: * @since 3.1
1103: */
1104: public Object getSelectedLinkHref() {
1105: IHyperlinkSegment link = getSelectedLink();
1106: return link != null ? link.getHref() : null;
1107: }
1108:
1109: /**
1110: * Returns the text of the hyperlink that currently has keyboard focus, or
1111: * <code>null</code> if there are no hyperlinks in the receiver or no
1112: * hyperlink has focus at the moment.
1113: *
1114: * @return text of the selected hyperlink or <code>null</code> if none
1115: * selected.
1116: * @since 3.1
1117: */
1118: public String getSelectedLinkText() {
1119: IHyperlinkSegment link = getSelectedLink();
1120: return link != null ? link.getText() : null;
1121: }
1122:
1123: private IHyperlinkSegment getSelectedLink() {
1124: IFocusSelectable segment = model.getSelectedSegment();
1125: if (segment != null && segment instanceof IHyperlinkSegment)
1126: return (IHyperlinkSegment) segment;
1127: return null;
1128: }
1129:
1130: private void initAccessible() {
1131: Accessible accessible = getAccessible();
1132: accessible.addAccessibleListener(new AccessibleAdapter() {
1133: public void getName(AccessibleEvent e) {
1134: if (e.childID == ACC.CHILDID_SELF)
1135: e.result = model.getAccessibleText();
1136: else {
1137: int linkCount = model.getHyperlinkCount();
1138: if (e.childID >= 0 && e.childID < linkCount) {
1139: IHyperlinkSegment link = model
1140: .getHyperlink(e.childID);
1141: e.result = link.getText();
1142: }
1143: }
1144: }
1145:
1146: public void getHelp(AccessibleEvent e) {
1147: e.result = getToolTipText();
1148: int linkCount = model.getHyperlinkCount();
1149: if (e.result == null && e.childID >= 0
1150: && e.childID < linkCount) {
1151: IHyperlinkSegment link = model
1152: .getHyperlink(e.childID);
1153: e.result = link.getText();
1154: }
1155: }
1156: });
1157: accessible
1158: .addAccessibleControlListener(new AccessibleControlAdapter() {
1159: public void getChildAtPoint(AccessibleControlEvent e) {
1160: Point pt = toControl(new Point(e.x, e.y));
1161: IHyperlinkSegment link = model.findHyperlinkAt(
1162: pt.x, pt.y);
1163: if (link != null)
1164: e.childID = model.indexOf(link);
1165: else
1166: e.childID = ACC.CHILDID_SELF;
1167: }
1168:
1169: public void getLocation(AccessibleControlEvent e) {
1170: Rectangle location = null;
1171: if (e.childID != ACC.CHILDID_SELF
1172: && e.childID != ACC.CHILDID_NONE) {
1173: int index = e.childID;
1174: IHyperlinkSegment link = model
1175: .getHyperlink(index);
1176: if (link != null) {
1177: location = link.getBounds();
1178: }
1179: }
1180: if (location == null) {
1181: location = getBounds();
1182: }
1183: Point pt = toDisplay(new Point(location.x,
1184: location.y));
1185: e.x = pt.x;
1186: e.y = pt.y;
1187: e.width = location.width;
1188: e.height = location.height;
1189: }
1190:
1191: public void getFocus(AccessibleControlEvent e) {
1192: int childID = ACC.CHILDID_NONE;
1193:
1194: if (isFocusControl()) {
1195: int selectedIndex = model
1196: .getSelectedSegmentIndex();
1197: if (selectedIndex == -1) {
1198: childID = ACC.CHILDID_SELF;
1199: } else {
1200: childID = selectedIndex;
1201: }
1202: }
1203: e.childID = childID;
1204: }
1205:
1206: public void getDefaultAction(
1207: AccessibleControlEvent e) {
1208: if (model.getHyperlinkCount() > 0) {
1209: e.result = SWT.getMessage("SWT_Press"); //$NON-NLS-1$
1210: }
1211: }
1212:
1213: public void getChildCount(AccessibleControlEvent e) {
1214: e.detail = model.getHyperlinkCount();
1215: }
1216:
1217: public void getRole(AccessibleControlEvent e) {
1218: int role = 0;
1219: int childID = e.childID;
1220: int linkCount = model.getHyperlinkCount();
1221: if (childID == ACC.CHILDID_SELF) {
1222: if (linkCount > 0) {
1223: role = ACC.ROLE_LINK;
1224: } else {
1225: role = ACC.ROLE_TEXT;
1226: }
1227: } else if (childID >= 0 && childID < linkCount) {
1228: role = ACC.ROLE_LINK;
1229: }
1230: e.detail = role;
1231: }
1232:
1233: public void getSelection(AccessibleControlEvent e) {
1234: int selectedIndex = model
1235: .getSelectedSegmentIndex();
1236: e.childID = (selectedIndex == -1) ? ACC.CHILDID_NONE
1237: : selectedIndex;
1238: }
1239:
1240: public void getState(AccessibleControlEvent e) {
1241: int linkCount = model.getHyperlinkCount();
1242: int selectedIndex = model
1243: .getSelectedSegmentIndex();
1244: int state = 0;
1245: int childID = e.childID;
1246: if (childID == ACC.CHILDID_SELF) {
1247: state = ACC.STATE_NORMAL;
1248: } else if (childID >= 0 && childID < linkCount) {
1249: state = ACC.STATE_SELECTABLE;
1250: if (isFocusControl()) {
1251: state |= ACC.STATE_FOCUSABLE;
1252: }
1253: if (selectedIndex == childID) {
1254: state |= ACC.STATE_SELECTED;
1255: if (isFocusControl()) {
1256: state |= ACC.STATE_FOCUSED;
1257: }
1258: }
1259: }
1260: state |= ACC.STATE_READONLY;
1261: e.detail = state;
1262: }
1263:
1264: public void getChildren(AccessibleControlEvent e) {
1265: int linkCount = model.getHyperlinkCount();
1266: Object[] children = new Object[linkCount];
1267: for (int i = 0; i < linkCount; i++) {
1268: children[i] = new Integer(i);
1269: }
1270: e.children = children;
1271: }
1272:
1273: public void getValue(AccessibleControlEvent e) {
1274: // e.result = model.getAccessibleText();
1275: }
1276: });
1277: }
1278:
1279: private void startSelection(MouseEvent e) {
1280: inSelection = true;
1281: selData = new SelectionData(e);
1282: redraw();
1283: Form form = FormUtil.getForm(this );
1284: if (form != null)
1285: form.setSelectionText(this );
1286: }
1287:
1288: private void endSelection(MouseEvent e) {
1289: inSelection = false;
1290: if (selData != null) {
1291: if (!selData.isEnclosed())
1292: selData = null;
1293: else
1294: computeSelection();
1295: }
1296: notifySelectionChanged();
1297: }
1298:
1299: private void computeSelection() {
1300: GC gc = new GC(this );
1301: Paragraph[] paragraphs = model.getParagraphs();
1302: IHyperlinkSegment selectedLink = getSelectedLink();
1303: if (getDisplay().getFocusControl() != this )
1304: selectedLink = null;
1305: for (int i = 0; i < paragraphs.length; i++) {
1306: Paragraph p = paragraphs[i];
1307: if (i > 0)
1308: selData.markNewLine();
1309: p
1310: .computeSelection(gc, resourceTable, selectedLink,
1311: selData);
1312: }
1313: gc.dispose();
1314: }
1315:
1316: void clearSelection() {
1317: selData = null;
1318: if (!isDisposed()) {
1319: redraw();
1320: notifySelectionChanged();
1321: }
1322: }
1323:
1324: private void notifySelectionChanged() {
1325: Event event = new Event();
1326: event.widget = this ;
1327: event.display = this .getDisplay();
1328: event.type = SWT.Selection;
1329: notifyListeners(SWT.Selection, event);
1330: getAccessible().selectionChanged();
1331: }
1332:
1333: private void handleDrag(MouseEvent e) {
1334: if (selData != null) {
1335: ScrolledComposite scomp = FormUtil
1336: .getScrolledComposite(this );
1337: if (scomp != null) {
1338: FormUtil.ensureVisible(scomp, this , e);
1339: }
1340: selData.update(e);
1341: redraw();
1342: }
1343: }
1344:
1345: private void handleMouseClick(MouseEvent e, boolean down) {
1346: if (DEBUG_FOCUS)
1347: System.out.println("FormText: mouse click(" + down + ")"); //$NON-NLS-1$ //$NON-NLS-2$
1348: if (down) {
1349: // select a hyperlink
1350: mouseFocus = true;
1351: IHyperlinkSegment segmentUnder = model.findHyperlinkAt(e.x,
1352: e.y);
1353: if (segmentUnder != null) {
1354: IHyperlinkSegment oldLink = getSelectedLink();
1355: if (getDisplay().getFocusControl() != this ) {
1356: setFocus();
1357: }
1358: model.selectLink(segmentUnder);
1359: enterLink(segmentUnder, e.stateMask);
1360: paintFocusTransfer(oldLink, segmentUnder);
1361: }
1362: if (e.button == 1) {
1363: startSelection(e);
1364: armed = segmentUnder;
1365: } else {
1366: }
1367: } else {
1368: if (e.button == 1) {
1369: endSelection(e);
1370: IHyperlinkSegment segmentUnder = model.findHyperlinkAt(
1371: e.x, e.y);
1372: if (segmentUnder != null && armed == segmentUnder
1373: && selData == null) {
1374: activateLink(segmentUnder, e.stateMask);
1375: armed = null;
1376: }
1377: }
1378: mouseFocus = false;
1379: }
1380: }
1381:
1382: private void handleMouseHover(MouseEvent e) {
1383: }
1384:
1385: private void updateTooltipText(ParagraphSegment segment) {
1386: String tooltipText = null;
1387: if (segment != null) {
1388: tooltipText = segment.getTooltipText();
1389: }
1390: String currentTooltipText = getToolTipText();
1391:
1392: if ((currentTooltipText != null && tooltipText == null)
1393: || (currentTooltipText == null && tooltipText != null))
1394: setToolTipText(tooltipText);
1395: }
1396:
1397: private void handleMouseMove(MouseEvent e) {
1398: if (inSelection) {
1399: handleDrag(e);
1400: return;
1401: }
1402: ParagraphSegment segmentUnder = model.findSegmentAt(e.x, e.y);
1403: updateTooltipText(segmentUnder);
1404: if (segmentUnder == null) {
1405: if (entered != null) {
1406: exitLink(entered, e.stateMask);
1407: paintLinkHover(entered, false);
1408: entered = null;
1409: }
1410: setCursor(null);
1411: } else {
1412: if (segmentUnder instanceof IHyperlinkSegment) {
1413: IHyperlinkSegment linkUnder = (IHyperlinkSegment) segmentUnder;
1414: if (entered != null && linkUnder != entered) {
1415: // Special case: links are so close that there are 0 pixels between.
1416: // Must exit the link before entering the next one.
1417: exitLink(entered, e.stateMask);
1418: paintLinkHover(entered, false);
1419: entered = null;
1420: }
1421: if (entered == null) {
1422: entered = linkUnder;
1423: enterLink(linkUnder, e.stateMask);
1424: paintLinkHover(entered, true);
1425: setCursor(model.getHyperlinkSettings()
1426: .getHyperlinkCursor());
1427: }
1428: } else {
1429: if (entered != null) {
1430: exitLink(entered, e.stateMask);
1431: paintLinkHover(entered, false);
1432: entered = null;
1433: }
1434: if (segmentUnder instanceof TextSegment)
1435: setCursor(model.getHyperlinkSettings()
1436: .getTextCursor());
1437: else
1438: setCursor(null);
1439: }
1440: }
1441: }
1442:
1443: private boolean advance(boolean next) {
1444: if (DEBUG_FOCUS)
1445: System.out.println("Advance: next=" + next); //$NON-NLS-1$
1446: IFocusSelectable current = model.getSelectedSegment();
1447: if (current != null && current instanceof IHyperlinkSegment)
1448: exitLink((IHyperlinkSegment) current, SWT.NULL);
1449: IFocusSelectable newSegment = null;
1450: boolean valid = false;
1451: // get the next segment that can accept focus. Links
1452: // can always accept focus but controls may not
1453: while (!valid) {
1454: if (!model.traverseFocusSelectableObjects(next))
1455: break;
1456: newSegment = model.getSelectedSegment();
1457: if (newSegment == null)
1458: break;
1459: valid = setControlFocus(next, newSegment);
1460: }
1461: IHyperlinkSegment newLink = newSegment instanceof IHyperlinkSegment ? (IHyperlinkSegment) newSegment
1462: : null;
1463: if (valid)
1464: enterLink(newLink, SWT.NULL);
1465: IHyperlinkSegment oldLink = current instanceof IHyperlinkSegment ? (IHyperlinkSegment) current
1466: : null;
1467: if (oldLink != null || newLink != null)
1468: paintFocusTransfer(oldLink, newLink);
1469: if (newLink != null)
1470: ensureVisible(newLink);
1471: if (newLink != null)
1472: getAccessible().setFocus(model.getSelectedSegmentIndex());
1473: return !valid;
1474: }
1475:
1476: private boolean setControlFocus(boolean next,
1477: IFocusSelectable selectable) {
1478: controlFocusTransfer = true;
1479: boolean result = selectable.setFocus(resourceTable, next);
1480: controlFocusTransfer = false;
1481: return result;
1482: }
1483:
1484: private void handleFocusChange() {
1485: if (DEBUG_FOCUS) {
1486: System.out
1487: .println("Handle focus change: hasFocus=" + hasFocus //$NON-NLS-1$
1488: + ", mouseFocus=" + mouseFocus); //$NON-NLS-1$
1489: }
1490: if (hasFocus) {
1491: boolean advance = true;
1492: if (!mouseFocus) {
1493: // if (model.restoreSavedLink() == false)
1494: boolean valid = false;
1495: IFocusSelectable selectable = null;
1496: while (!valid) {
1497: if (!model.traverseFocusSelectableObjects(advance))
1498: break;
1499: selectable = model.getSelectedSegment();
1500: if (selectable == null)
1501: break;
1502: valid = setControlFocus(advance, selectable);
1503: }
1504: if (selectable == null)
1505: setFocusToNextSibling(this , true);
1506: else
1507: ensureVisible(selectable);
1508: if (selectable instanceof IHyperlinkSegment) {
1509: enterLink((IHyperlinkSegment) selectable, SWT.NULL);
1510: paintFocusTransfer(null,
1511: (IHyperlinkSegment) selectable);
1512: }
1513: }
1514: } else {
1515: paintFocusTransfer(getSelectedLink(), null);
1516: model.selectLink(null);
1517: }
1518: }
1519:
1520: private void enterLink(IHyperlinkSegment link, int stateMask) {
1521: if (link == null || listeners == null)
1522: return;
1523: int size = listeners.size();
1524: HyperlinkEvent he = new HyperlinkEvent(this , link.getHref(),
1525: link.getText(), stateMask);
1526: Object[] listenerList = listeners.getListeners();
1527: for (int i = 0; i < size; i++) {
1528: IHyperlinkListener listener = (IHyperlinkListener) listenerList[i];
1529: listener.linkEntered(he);
1530: }
1531: }
1532:
1533: private void exitLink(IHyperlinkSegment link, int stateMask) {
1534: if (link == null || listeners == null)
1535: return;
1536: int size = listeners.size();
1537: HyperlinkEvent he = new HyperlinkEvent(this , link.getHref(),
1538: link.getText(), stateMask);
1539: Object[] listenerList = listeners.getListeners();
1540: for (int i = 0; i < size; i++) {
1541: IHyperlinkListener listener = (IHyperlinkListener) listenerList[i];
1542: listener.linkExited(he);
1543: }
1544: }
1545:
1546: private void paintLinkHover(IHyperlinkSegment link, boolean hover) {
1547: GC gc = new GC(this );
1548: HyperlinkSettings settings = getHyperlinkSettings();
1549: Color newFg = hover ? settings.getActiveForeground() : settings
1550: .getForeground();
1551: if (newFg != null)
1552: gc.setForeground(newFg);
1553: gc.setBackground(getBackground());
1554: gc.setFont(getFont());
1555: boolean selected = (link == getSelectedLink());
1556: ((ParagraphSegment) link).paint(gc, hover, resourceTable,
1557: selected, selData, null);
1558: gc.dispose();
1559: }
1560:
1561: private void activateSelectedLink() {
1562: IHyperlinkSegment link = getSelectedLink();
1563: if (link != null)
1564: activateLink(link, SWT.NULL);
1565: }
1566:
1567: private void activateLink(IHyperlinkSegment link, int stateMask) {
1568: setCursor(model.getHyperlinkSettings().getBusyCursor());
1569: if (listeners != null) {
1570: int size = listeners.size();
1571: HyperlinkEvent e = new HyperlinkEvent(this , link.getHref(),
1572: link.getText(), stateMask);
1573: Object[] listenerList = listeners.getListeners();
1574: for (int i = 0; i < size; i++) {
1575: IHyperlinkListener listener = (IHyperlinkListener) listenerList[i];
1576: listener.linkActivated(e);
1577: }
1578: }
1579: if (!isDisposed() && model.linkExists(link)) {
1580: setCursor(model.getHyperlinkSettings().getHyperlinkCursor());
1581: }
1582: }
1583:
1584: private void ensureBoldFontPresent(Font regularFont) {
1585: Font boldFont = (Font) resourceTable
1586: .get(FormTextModel.BOLD_FONT_ID);
1587: if (boldFont != null)
1588: return;
1589: boldFont = FormUtil.createBoldFont(getDisplay(), regularFont);
1590: resourceTable.put(FormTextModel.BOLD_FONT_ID, boldFont);
1591: }
1592:
1593: private void paint(PaintEvent e) {
1594: GC gc = e.gc;
1595: gc.setFont(getFont());
1596: ensureBoldFontPresent(getFont());
1597: gc.setForeground(getForeground());
1598: gc.setBackground(getBackground());
1599: repaint(gc, e.x, e.y, e.width, e.height);
1600: }
1601:
1602: private void repaint(GC gc, int x, int y, int width, int height) {
1603: Image textBuffer = new Image(getDisplay(), width, height);
1604: Color bg = getBackground();
1605: Color fg = getForeground();
1606: if (!getEnabled()) {
1607: bg = getDisplay().getSystemColor(
1608: SWT.COLOR_WIDGET_BACKGROUND);
1609: fg = getDisplay().getSystemColor(
1610: SWT.COLOR_WIDGET_NORMAL_SHADOW);
1611: }
1612: GC textGC = new GC(textBuffer, gc.getStyle());
1613: textGC.setForeground(fg);
1614: textGC.setBackground(bg);
1615: textGC.setFont(getFont());
1616: textGC.fillRectangle(0, 0, width, height);
1617: Rectangle repaintRegion = new Rectangle(x, y, width, height);
1618:
1619: Paragraph[] paragraphs = model.getParagraphs();
1620: IHyperlinkSegment selectedLink = getSelectedLink();
1621: if (getDisplay().getFocusControl() != this )
1622: selectedLink = null;
1623: for (int i = 0; i < paragraphs.length; i++) {
1624: Paragraph p = paragraphs[i];
1625: p.paint(textGC, repaintRegion, resourceTable, selectedLink,
1626: selData);
1627: }
1628: textGC.dispose();
1629: gc.drawImage(textBuffer, x, y);
1630: textBuffer.dispose();
1631: }
1632:
1633: private int getParagraphSpacing(int lineHeight) {
1634: return lineHeight / 2;
1635: }
1636:
1637: private void paintFocusTransfer(IHyperlinkSegment oldLink,
1638: IHyperlinkSegment newLink) {
1639: GC gc = new GC(this );
1640: Color bg = getBackground();
1641: Color fg = getForeground();
1642: gc.setFont(getFont());
1643: if (oldLink != null) {
1644: gc.setBackground(bg);
1645: gc.setForeground(fg);
1646: oldLink.paintFocus(gc, bg, fg, false, null);
1647: }
1648: if (newLink != null) {
1649: // ensureVisible(newLink);
1650: gc.setBackground(bg);
1651: gc.setForeground(fg);
1652: newLink.paintFocus(gc, bg, fg, true, null);
1653: }
1654: gc.dispose();
1655: }
1656:
1657: private void ensureVisible(IFocusSelectable segment) {
1658: if (mouseFocus) {
1659: mouseFocus = false;
1660: return;
1661: }
1662: if (segment == null)
1663: return;
1664: Rectangle bounds = segment.getBounds();
1665: ScrolledComposite scomp = FormUtil.getScrolledComposite(this );
1666: if (scomp == null)
1667: return;
1668: Point origin = FormUtil.getControlLocation(scomp, this );
1669: origin.x += bounds.x;
1670: origin.y += bounds.y;
1671: FormUtil.ensureVisible(scomp, origin, new Point(bounds.width,
1672: bounds.height));
1673: }
1674:
1675: /**
1676: * Overrides the method by fully trusting the layout manager (computed width
1677: * or height may be larger than the provider width or height hints). Callers
1678: * should be prepared that the computed width is larger than the provided
1679: * wHint.
1680: *
1681: * @see org.eclipse.swt.widgets.Composite#computeSize(int, int, boolean)
1682: */
1683: public Point computeSize(int wHint, int hHint, boolean changed) {
1684: checkWidget();
1685: Point size;
1686: FormTextLayout layout = (FormTextLayout) getLayout();
1687: if (wHint == SWT.DEFAULT || hHint == SWT.DEFAULT) {
1688: size = layout.computeSize(this , wHint, hHint, changed);
1689: } else {
1690: size = new Point(wHint, hHint);
1691: }
1692: Rectangle trim = computeTrim(0, 0, size.x, size.y);
1693: if (DEBUG_TEXTSIZE)
1694: System.out.println("FormText Computed size: " + trim); //$NON-NLS-1$
1695: return new Point(trim.width, trim.height);
1696: }
1697:
1698: private void disposeResourceTable(boolean disposeBoldFont) {
1699: if (disposeBoldFont) {
1700: Font boldFont = (Font) resourceTable
1701: .get(FormTextModel.BOLD_FONT_ID);
1702: if (boldFont != null) {
1703: boldFont.dispose();
1704: resourceTable.remove(FormTextModel.BOLD_FONT_ID);
1705: }
1706: }
1707: ArrayList imagesToRemove = new ArrayList();
1708: for (Enumeration enm = resourceTable.keys(); enm
1709: .hasMoreElements();) {
1710: String key = (String) enm.nextElement();
1711: if (key.startsWith(ImageSegment.SEL_IMAGE_PREFIX)) {
1712: Object obj = resourceTable.get(key);
1713: if (obj instanceof Image) {
1714: Image image = (Image) obj;
1715: if (!image.isDisposed()) {
1716: image.dispose();
1717: imagesToRemove.add(key);
1718: }
1719: }
1720: }
1721: }
1722: for (int i = 0; i < imagesToRemove.size(); i++) {
1723: resourceTable.remove(imagesToRemove.get(i));
1724: }
1725: }
1726:
1727: /*
1728: * (non-Javadoc)
1729: *
1730: * @see org.eclipse.swt.widgets.Control#setEnabled(boolean)
1731: */
1732: public void setEnabled(boolean enabled) {
1733: super .setEnabled(enabled);
1734: redraw();
1735: }
1736:
1737: /* (non-Javadoc)
1738: * @see org.eclipse.swt.widgets.Control#setFocus()
1739: */
1740: public boolean setFocus() {
1741: FormUtil.setFocusScrollingEnabled(this , false);
1742: boolean result = super .setFocus();
1743: FormUtil.setFocusScrollingEnabled(this , true);
1744: return result;
1745: }
1746: }
|