KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > ui > forms > widgets > FormText


1 /*******************************************************************************
2  * Copyright (c) 2000, 2007 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  *******************************************************************************/

11 package org.eclipse.ui.forms.widgets;
12
13 import java.io.InputStream JavaDoc;
14 import java.util.ArrayList JavaDoc;
15 import java.util.Enumeration JavaDoc;
16 import java.util.Hashtable JavaDoc;
17
18 import org.eclipse.core.runtime.ListenerList;
19 import org.eclipse.swt.SWT;
20 import org.eclipse.swt.SWTException;
21 import org.eclipse.swt.accessibility.ACC;
22 import org.eclipse.swt.accessibility.Accessible;
23 import org.eclipse.swt.accessibility.AccessibleAdapter;
24 import org.eclipse.swt.accessibility.AccessibleControlAdapter;
25 import org.eclipse.swt.accessibility.AccessibleControlEvent;
26 import org.eclipse.swt.accessibility.AccessibleEvent;
27 import org.eclipse.swt.custom.ScrolledComposite;
28 import org.eclipse.swt.dnd.Clipboard;
29 import org.eclipse.swt.dnd.TextTransfer;
30 import org.eclipse.swt.dnd.Transfer;
31 import org.eclipse.swt.events.DisposeEvent;
32 import org.eclipse.swt.events.DisposeListener;
33 import org.eclipse.swt.events.FocusEvent;
34 import org.eclipse.swt.events.FocusListener;
35 import org.eclipse.swt.events.MenuEvent;
36 import org.eclipse.swt.events.MenuListener;
37 import org.eclipse.swt.events.MouseEvent;
38 import org.eclipse.swt.events.MouseListener;
39 import org.eclipse.swt.events.MouseMoveListener;
40 import org.eclipse.swt.events.MouseTrackListener;
41 import org.eclipse.swt.events.PaintEvent;
42 import org.eclipse.swt.events.PaintListener;
43 import org.eclipse.swt.events.SelectionAdapter;
44 import org.eclipse.swt.events.SelectionEvent;
45 import org.eclipse.swt.events.SelectionListener;
46 import org.eclipse.swt.graphics.Color;
47 import org.eclipse.swt.graphics.Font;
48 import org.eclipse.swt.graphics.FontMetrics;
49 import org.eclipse.swt.graphics.GC;
50 import org.eclipse.swt.graphics.Image;
51 import org.eclipse.swt.graphics.Point;
52 import org.eclipse.swt.graphics.Rectangle;
53 import org.eclipse.swt.widgets.Canvas;
54 import org.eclipse.swt.widgets.Composite;
55 import org.eclipse.swt.widgets.Control;
56 import org.eclipse.swt.widgets.Event;
57 import org.eclipse.swt.widgets.Layout;
58 import org.eclipse.swt.widgets.Listener;
59 import org.eclipse.swt.widgets.Menu;
60 import org.eclipse.swt.widgets.MenuItem;
61 import org.eclipse.swt.widgets.TypedListener;
62 import org.eclipse.ui.forms.HyperlinkSettings;
63 import org.eclipse.ui.forms.events.HyperlinkEvent;
64 import org.eclipse.ui.forms.events.IHyperlinkListener;
65 import org.eclipse.ui.internal.forms.Messages;
66 import org.eclipse.ui.internal.forms.widgets.ControlSegment;
67 import org.eclipse.ui.internal.forms.widgets.FormTextModel;
68 import org.eclipse.ui.internal.forms.widgets.FormUtil;
69 import org.eclipse.ui.internal.forms.widgets.IFocusSelectable;
70 import org.eclipse.ui.internal.forms.widgets.IHyperlinkSegment;
71 import org.eclipse.ui.internal.forms.widgets.ImageSegment;
72 import org.eclipse.ui.internal.forms.widgets.Locator;
73 import org.eclipse.ui.internal.forms.widgets.Paragraph;
74 import org.eclipse.ui.internal.forms.widgets.ParagraphSegment;
75 import org.eclipse.ui.internal.forms.widgets.SelectionData;
76 import org.eclipse.ui.internal.forms.widgets.TextSegment;
77
78 /**
79  * This class is a read-only text control that is capable of rendering wrapped
80  * text. Text can be rendered as-is or by parsing the formatting XML tags.
81  * Independently, words that start with http:// can be converted into hyperlinks
82  * on the fly.
83  * <p>
84  * When configured to use formatting XML, the control requires the root element
85  * <code>form</code> to be used. The following tags can be children of the
86  * <code>form</code> element:
87  * </p>
88  * <ul>
89  * <li><b>p </b>- for defining paragraphs. The following attributes are
90  * allowed:
91  * <ul>
92  * <li><b>vspace </b>- if set to 'false', no vertical space will be added
93  * (default is 'true')</li>
94  * </ul>
95  * </li>
96  * <li><b>li </b>- for defining list items. The following attributes are
97  * allowed:
98  * <ul>
99  * <li><b>vspace </b>- the same as with the <b>p </b> tag</li>
100  * <li><b>style </b>- could be 'bullet' (default), 'text' and 'image'</li>
101  * <li><b>value </b>- not used for 'bullet'. For text, it is the value of the
102  * text that is rendered as a bullet. For image, it is the href of the image to
103  * be rendered as a bullet.</li>
104  * <li><b>indent </b>- the number of pixels to indent the text in the list item
105  * </li>
106  * <li><b>bindent </b>- the number of pixels to indent the bullet itself</li>
107  * </ul>
108  * </li>
109  * </ul>
110  * <p>
111  * Text in paragraphs and list items will be wrapped according to the width of
112  * the control. The following tags can appear as children of either <b>p </b> or
113  * <b>li </b> elements:
114  * <ul>
115  * <li><b>img </b>- to render an image. Element accepts attribute 'href' that
116  * is a key to the <code>Image</code> set using 'setImage' method. Vertical
117  * position of image relative to surrounding text is optionally controlled by
118  * the attribute <b>align</b> that can have values <b>top</b>, <b>middle</b>
119  * and <b>bottom</b></li>
120  * <li><b>a </b>- to render a hyperlink. Element accepts attribute 'href' that
121  * will be provided to the hyperlink listeners via HyperlinkEvent object. The
122  * element also accepts 'nowrap' attribute (default is false). When set to
123  * 'true', the hyperlink will not be wrapped. Hyperlinks automatically created
124  * when 'http://' is encountered in text are not wrapped.</li>
125  * <li><b>b </b>- the enclosed text will use bold font.</li>
126  * <li><b>br </b>- forced line break (no attributes).</li>
127  * <li><b>span </b>- the enclosed text will have the color and font specified
128  * in the element attributes. Color is provided using 'color' attribute and is a
129  * key to the Color object set by 'setColor' method. Font is provided using
130  * 'font' attribute and is a key to the Font object set by 'setFont' method. As with
131  * hyperlinks, it is possible to block wrapping by setting 'nowrap' to true
132  * (false by default).
133  * </li>
134  * <li><b>control (new in 3.1)</b> - to place a control that is a child of the
135  * text control. Element accepts attribute 'href' that is a key to the Control
136  * object set using 'setControl' method. Optionally, attribute 'fill' can be set
137  * to <code>true</code> to make the control fill the entire width of the text.
138  * Form text is not responsible for creating or disposing controls, it only
139  * places them relative to the surrounding text. Similar to <b>img</b>,
140  * vertical position of the control can be set using the <b>align</b>
141  * attribute. In addition, <b>width</b> and <b>height</b> attributes can
142  * be used to force the dimensions of the control. If not used,
143  * the preferred control size will be used.
144  * </ul>
145  * <p>
146  * None of the elements can nest. For example, you cannot have <b>b </b> inside
147  * a <b>span </b>. This was done to keep everything simple and transparent.
148  * Since 3.1, an exception to this rule has been added to support nesting images
149  * and text inside the hyperlink tag (<b>a</b>). Image enclosed in the
150  * hyperlink tag acts as a hyperlink, can be clicked on and can accept and
151  * render selection focus. When both text and image is enclosed, selection and
152  * rendering will affect both as a single hyperlink.
153  * </p>
154  * <p>
155  * Since 3.1, it is possible to select text. Text selection can be
156  * programmatically accessed and also copied to clipboard. Non-textual objects
157  * (images, controls etc.) in the selection range are ignored.
158  * <p>
159  * Care should be taken when using this control. Form text is not an HTML
160  * browser and should not be treated as such. If you need complex formatting
161  * capabilities, use Browser widget. If you need editing capabilities and
162  * font/color styles of text segments is all you need, use StyleText widget.
163  * Finally, if all you need is to wrap text, use SWT Label widget and create it
164  * with SWT.WRAP style.
165  *
166  * @see FormToolkit
167  * @see TableWrapLayout
168  * @since 3.0
169  */

170 public class FormText extends Canvas {
171     /**
172      * The object ID to be used when registering action to handle URL hyperlinks
173      * (those that should result in opening the web browser). Value is
174      * "urlHandler".
175      */

176     public static final String JavaDoc URL_HANDLER_ID = "urlHandler"; //$NON-NLS-1$
177

178     /**
179      * Value of the horizontal margin (default is 0).
180      */

181     public int marginWidth = 0;
182
183     /**
184      * Value of tue vertical margin (default is 1).
185      */

186     public int marginHeight = 1;
187
188     // private fields
189
//TODO We should remove the dependency on Platform
190
private static final boolean DEBUG_TEXT = false;//"true".equalsIgnoreCase(Platform.getDebugOption(FormUtil.DEBUG_TEXT));
191
private static final boolean DEBUG_TEXTSIZE = false;//"true".equalsIgnoreCase(Platform.getDebugOption(FormUtil.DEBUG_TEXTSIZE));
192

193     private static final boolean DEBUG_FOCUS = false;//"true".equalsIgnoreCase(Platform.getDebugOption(FormUtil.DEBUG_FOCUS));
194

195     private boolean hasFocus;
196
197     private boolean paragraphsSeparated = true;
198
199     private FormTextModel model;
200
201     private ListenerList listeners;
202
203     private Hashtable JavaDoc resourceTable = new Hashtable JavaDoc();
204
205     private IHyperlinkSegment entered;
206
207     private IHyperlinkSegment armed;
208
209     private boolean mouseFocus = false;
210
211     private boolean controlFocusTransfer = false;
212
213     private boolean inSelection = false;
214
215     private SelectionData selData;
216
217     private static final String JavaDoc INTERNAL_MENU = "__internal_menu__"; //$NON-NLS-1$
218

219     private static final String JavaDoc CONTROL_KEY = "__segment__"; //$NON-NLS-1$
220

221     private class FormTextLayout extends Layout implements ILayoutExtension {
222         public FormTextLayout() {
223         }
224
225         public int computeMaximumWidth(Composite parent, boolean changed) {
226             return computeSize(parent, SWT.DEFAULT, SWT.DEFAULT, changed).x;
227         }
228
229         public int computeMinimumWidth(Composite parent, boolean changed) {
230             return computeSize(parent, 5, SWT.DEFAULT, true).x;
231         }
232
233         /*
234          * @see Layout#computeSize(Composite, int, int, boolean)
235          */

236         public Point computeSize(Composite composite, int wHint, int hHint,
237                 boolean changed) {
238             long start = 0;
239
240             if (DEBUG_TEXT)
241                 start = System.currentTimeMillis();
242             int innerWidth = wHint;
243             if (innerWidth != SWT.DEFAULT)
244                 innerWidth -= marginWidth * 2;
245             Point textSize = computeTextSize(innerWidth);
246             int textWidth = textSize.x + 2 * marginWidth;
247             int textHeight = textSize.y + 2 * marginHeight;
248             Point result = new Point(textWidth, textHeight);
249             if (DEBUG_TEXT) {
250                 long stop = System.currentTimeMillis();
251                 System.out.println("FormText computeSize: " + (stop - start) //$NON-NLS-1$
252
+ "ms"); //$NON-NLS-1$
253
}
254             if (DEBUG_TEXTSIZE) {
255                 System.out.println("FormText ("+model.getAccessibleText()+"), computeSize: wHint="+wHint+", result="+result); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
256
}
257             return result;
258         }
259
260         private Point computeTextSize(int wHint) {
261             Paragraph[] paragraphs = model.getParagraphs();
262             GC gc = new GC(FormText.this);
263             gc.setFont(getFont());
264             Locator loc = new Locator();
265             int width = wHint != SWT.DEFAULT ? wHint : 0;
266             FontMetrics fm = gc.getFontMetrics();
267             int lineHeight = fm.getHeight();
268             boolean selectableInTheLastRow = false;
269             for (int i = 0; i < paragraphs.length; i++) {
270                 Paragraph p = paragraphs[i];
271                 if (i > 0 && getParagraphsSeparated()
272                         && p.getAddVerticalSpace())
273                     loc.y += getParagraphSpacing(lineHeight);
274                 loc.rowHeight = 0;
275                 loc.indent = p.getIndent();
276                 loc.x = p.getIndent();
277                 ParagraphSegment[] segments = p.getSegments();
278                 if (segments.length > 0) {
279                     selectableInTheLastRow = false;
280                     int pwidth = 0;
281                     for (int j = 0; j < segments.length; j++) {
282                         ParagraphSegment segment = segments[j];
283                         segment.advanceLocator(gc, wHint, loc, resourceTable,
284                                 false);
285                         if (wHint != SWT.DEFAULT) {
286                             width = Math.max(width, loc.width);
287                         } else {
288                             pwidth += loc.width;
289                         }
290                         if (segment instanceof IFocusSelectable)
291                             selectableInTheLastRow = true;
292                     }
293                     if (wHint == SWT.DEFAULT)
294                         width = Math.max(width, pwidth);
295                     loc.y += loc.rowHeight;
296                 } else {
297                     // empty new line
298
loc.y += lineHeight;
299                 }
300             }
301             gc.dispose();
302             if (selectableInTheLastRow)
303                 loc.y += 1;
304             return new Point(width, loc.y);
305         }
306
307         protected void layout(Composite composite, boolean flushCache) {
308             long start = 0;
309
310             if (DEBUG_TEXT) {
311                 start = System.currentTimeMillis();
312             }
313             selData = null;
314             Rectangle carea = composite.getClientArea();
315             if (DEBUG_TEXTSIZE) {
316                 System.out.println("FormText layout ("+model.getAccessibleText()+"), carea="+carea); //$NON-NLS-1$ //$NON-NLS-2$
317
}
318             GC gc = new GC(composite);
319             gc.setFont(getFont());
320             ensureBoldFontPresent(getFont());
321             gc.setForeground(getForeground());
322             gc.setBackground(getBackground());
323
324             Locator loc = new Locator();
325             loc.marginWidth = marginWidth;
326             loc.marginHeight = marginHeight;
327             loc.x = marginWidth;
328             loc.y = marginHeight;
329             FontMetrics fm = gc.getFontMetrics();
330             int lineHeight = fm.getHeight();
331
332             Paragraph[] paragraphs = model.getParagraphs();
333             IHyperlinkSegment selectedLink = getSelectedLink();
334             for (int i = 0; i < paragraphs.length; i++) {
335                 Paragraph p = paragraphs[i];
336                 if (i > 0 && paragraphsSeparated && p.getAddVerticalSpace())
337                     loc.y += getParagraphSpacing(lineHeight);
338                 loc.indent = p.getIndent();
339                 loc.resetCaret();
340                 loc.rowHeight = 0;
341                 p.layout(gc, carea.width, loc, lineHeight, resourceTable,
342                         selectedLink);
343             }
344             gc.dispose();
345             if (DEBUG_TEXT) {
346                 long stop = System.currentTimeMillis();
347                 System.out.println("FormText.layout: " + (stop - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$
348
}
349         }
350     }
351
352     /**
353      * Contructs a new form text widget in the provided parent and using the
354      * styles.
355      *
356      * @param parent
357      * form text parent control
358      * @param style
359      * the widget style
360      */

361     public FormText(Composite parent, int style) {
362         super(parent, SWT.NO_BACKGROUND | SWT.WRAP | style);
363         setLayout(new FormTextLayout());
364         model = new FormTextModel();
365         addDisposeListener(new DisposeListener() {
366             public void widgetDisposed(DisposeEvent e) {
367                 model.dispose();
368                 disposeResourceTable(true);
369             }
370         });
371         addPaintListener(new PaintListener() {
372             public void paintControl(PaintEvent e) {
373                 paint(e);
374             }
375         });
376         addListener(SWT.KeyDown, new Listener() {
377             public void handleEvent(Event e) {
378                 if (e.character == '\r') {
379                     activateSelectedLink();
380                     return;
381                 }
382             }
383         });
384         addListener(SWT.Traverse, new Listener() {
385             public void handleEvent(Event e) {
386                 if (DEBUG_FOCUS)
387                     System.out.println("Traversal: " + e); //$NON-NLS-1$
388
switch (e.detail) {
389                 case SWT.TRAVERSE_PAGE_NEXT:
390                 case SWT.TRAVERSE_PAGE_PREVIOUS:
391                 case SWT.TRAVERSE_ARROW_NEXT:
392                 case SWT.TRAVERSE_ARROW_PREVIOUS:
393                     e.doit = false;
394                     return;
395                 }
396                 if (!model.hasFocusSegments()) {
397                     e.doit = true;
398                     return;
399                 }
400                 if (e.detail == SWT.TRAVERSE_TAB_NEXT)
401                     e.doit = advance(true);
402                 else if (e.detail == SWT.TRAVERSE_TAB_PREVIOUS)
403                     e.doit = advance(false);
404                 else if (e.detail != SWT.TRAVERSE_RETURN)
405                     e.doit = true;
406             }
407         });
408         addFocusListener(new FocusListener() {
409             public void focusGained(FocusEvent e) {
410                 if (!hasFocus) {
411                     hasFocus = true;
412                     if (DEBUG_FOCUS) {
413                         System.out.println("FormText: focus gained"); //$NON-NLS-1$
414
}
415                     if (!mouseFocus && !controlFocusTransfer) {
416                         handleFocusChange();
417                     }
418                 }
419             }
420
421             public void focusLost(FocusEvent e) {
422                 if (DEBUG_FOCUS) {
423                     System.out.println("FormText: focus lost"); //$NON-NLS-1$
424
}
425                 if (hasFocus) {
426                     hasFocus = false;
427                     if (!controlFocusTransfer)
428                         handleFocusChange();
429                 }
430             }
431         });
432         addMouseListener(new MouseListener() {
433             public void mouseDoubleClick(MouseEvent e) {
434             }
435
436             public void mouseDown(MouseEvent e) {
437                 // select a link
438
handleMouseClick(e, true);
439             }
440
441             public void mouseUp(MouseEvent e) {
442                 // activate a link
443
handleMouseClick(e, false);
444             }
445         });
446         addMouseTrackListener(new MouseTrackListener() {
447             public void mouseEnter(MouseEvent e) {
448                 handleMouseMove(e);
449             }
450
451             public void mouseExit(MouseEvent e) {
452                 if (entered != null) {
453                     exitLink(entered, e.stateMask);
454                     paintLinkHover(entered, false);
455                     entered = null;
456                     setCursor(null);
457                 }
458             }
459
460             public void mouseHover(MouseEvent e) {
461                 handleMouseHover(e);
462             }
463         });
464         addMouseMoveListener(new MouseMoveListener() {
465             public void mouseMove(MouseEvent e) {
466                 handleMouseMove(e);
467             }
468         });
469         initAccessible();
470         ensureBoldFontPresent(getFont());
471         createMenu();
472         // we will handle traversal of controls, if any
473
setTabList(new Control[] {});
474     }
475
476     /**
477      * Test for focus.
478      *
479      * @return <samp>true </samp> if the widget has focus.
480      */

481     public boolean getFocus() {
482         return hasFocus;
483     }
484
485     /**
486      * Test if the widget is currently processing the text it is about to
487      * render.
488      *
489      * @return <samp>true </samp> if the widget is still loading the text,
490      * <samp>false </samp> otherwise.
491      * @deprecated not used any more - returns <code>false</code>
492      */

493     public boolean isLoading() {
494         return false;
495     }
496
497     /**
498      * Returns the text that will be shown in the control while the real content
499      * is loading.
500      *
501      * @return loading text message
502      * @deprecated loading text is not used since 3.1
503      */

504     public String JavaDoc getLoadingText() {
505         return null;
506     }
507
508     /**
509      * Sets the text that will be shown in the control while the real content is
510      * loading. This is significant when content to render is loaded from the
511      * input stream that was created from a remote URL, and the time to load the
512      * entire content is nontrivial.
513      *
514      * @param loadingText
515      * loading text message
516      * @deprecated use setText(loadingText, false, false);
517      */

518     public void setLoadingText(String JavaDoc loadingText) {
519         setText(loadingText, false, false);
520     }
521
522     /**
523      * If paragraphs are separated, spacing will be added between them.
524      * Otherwise, new paragraphs will simply start on a new line with no
525      * spacing.
526      *
527      * @param value
528      * <samp>true </samp> if paragraphs are separated, </samp> false
529      * </samp> otherwise.
530      */

531     public void setParagraphsSeparated(boolean value) {
532         paragraphsSeparated = value;
533     }
534
535     /**
536      * Tests if there is some inter-paragraph spacing.
537      *
538      * @return <samp>true </samp> if paragraphs are separated, <samp>false
539      * </samp> otherwise.
540      */

541     public boolean getParagraphsSeparated() {
542         return paragraphsSeparated;
543     }
544
545     /**
546      * Registers the image referenced by the provided key.
547      * <p>
548      * For <samp>img </samp> tags, an object of a type <samp>Image </samp> must
549      * be registered using the key equivalent to the value of the <samp>href
550      * </samp> attribute used in the tag.
551      *
552      * @param key
553      * unique key that matches the value of the <samp>href </samp>
554      * attribute.
555      * @param image
556      * an object of a type <samp>Image </samp>.
557      */

558     public void setImage(String JavaDoc key, Image image) {
559         resourceTable.put("i." + key, image); //$NON-NLS-1$
560
}
561
562     /**
563      * Registers the color referenced by the provided key.
564      * <p>
565      * For <samp>span </samp> tags, an object of a type <samp>Color </samp> must
566      * be registered using the key equivalent to the value of the <samp>color
567      * </samp> attribute.
568      *
569      * @param key
570      * unique key that matches the value of the <samp>color </samp>
571      * attribute.
572      * @param color
573      * an object of the type <samp>Color </samp> or <samp>null</samp>
574      * if the key needs to be cleared.
575      */

576     public void setColor(String JavaDoc key, Color color) {
577         String JavaDoc fullKey = "c." + key; //$NON-NLS-1$
578
if (color == null)
579             resourceTable.remove(fullKey);
580         else
581             resourceTable.put(fullKey, color);
582     }
583
584     /**
585      * Registers the font referenced by the provided key.
586      * <p>
587      * For <samp>span </samp> tags, an object of a type <samp>Font </samp> must
588      * be registered using the key equivalent to the value of the <samp>font
589      * </samp> attribute.
590      *
591      * @param key
592      * unique key that matches the value of the <samp>font </samp>
593      * attribute.
594      * @param font
595      * an object of the type <samp>Font </samp> or <samp>null</samp>
596      * if the key needs to be cleared.
597      */

598     public void setFont(String JavaDoc key, Font font) {
599         String JavaDoc fullKey = "f." + key; //$NON-NLS-1$
600
if (font == null)
601             resourceTable.remove(fullKey);
602         else
603             resourceTable.put(fullKey, font);
604         model.clearCache(fullKey);
605     }
606
607     /**
608      * Registers the control referenced by the provided key.
609      * <p>
610      * For <samp>control</samp> tags, an object of a type <samp>Control</samp>
611      * must be registered using the key equivalent to the value of the
612      * <samp>control</samp> attribute.
613      *
614      * @param key
615      * unique key that matches the value of the <samp>control</samp>
616      * attribute.
617      * @param control
618      * an object of the type <samp>Control</samp> or <samp>null</samp>
619      * if the existing control at the specified key needs to be
620      * removed.
621      * @since 3.1
622      */

623     public void setControl(String JavaDoc key, Control control) {
624         String JavaDoc fullKey = "o." + key; //$NON-NLS-1$
625
if (control == null)
626             resourceTable.remove(fullKey);
627         else
628             resourceTable.put(fullKey, control);
629     }
630
631     /**
632      * Sets the font to use to render the default text (text that does not have
633      * special font property assigned). Bold font will be constructed from this
634      * font.
635      *
636      * @param font
637      * the default font to use
638      */

639     public void setFont(Font font) {
640         super.setFont(font);
641         model.clearCache(null);
642         Font boldFont = (Font) resourceTable.get(FormTextModel.BOLD_FONT_ID);
643         if (boldFont != null) {
644             boldFont.dispose();
645             resourceTable.remove(FormTextModel.BOLD_FONT_ID);
646         }
647         ensureBoldFontPresent(getFont());
648     }
649
650     /**
651      * Sets the provided text. Text can be rendered as-is, or by parsing the
652      * formatting tags. Optionally, sections of text starting with http:// will
653      * be converted to hyperlinks.
654      *
655      * @param text
656      * the text to render
657      * @param parseTags
658      * if <samp>true </samp>, formatting tags will be parsed.
659      * Otherwise, text will be rendered as-is.
660      * @param expandURLs
661      * if <samp>true </samp>, URLs found in the untagged text will be
662      * converted into hyperlinks.
663      */

664     public void setText(String JavaDoc text, boolean parseTags, boolean expandURLs) {
665         disposeResourceTable(false);
666         entered = null;
667         if (parseTags)
668             model.parseTaggedText(text, expandURLs);
669         else
670             model.parseRegularText(text, expandURLs);
671         hookControlSegmentFocus();
672         layout();
673         redraw();
674     }
675
676     /**
677      * Sets the contents of the stream. Optionally, URLs in untagged text can be
678      * converted into hyperlinks. The caller is responsible for closing the
679      * stream.
680      *
681      * @param is
682      * stream to render
683      * @param expandURLs
684      * if <samp>true </samp>, URLs found in untagged text will be
685      * converted into hyperlinks.
686      */

687     public void setContents(InputStream JavaDoc is, boolean expandURLs) {
688         entered = null;
689         disposeResourceTable(false);
690         model.parseInputStream(is, expandURLs);
691         hookControlSegmentFocus();
692         layout();
693         redraw();
694     }
695
696     private void hookControlSegmentFocus() {
697         Paragraph[] paragraphs = model.getParagraphs();
698         if (paragraphs == null)
699             return;
700         Listener listener = new Listener() {
701             public void handleEvent(Event e) {
702                 switch (e.type) {
703                 case SWT.FocusIn:
704                     if (!controlFocusTransfer)
705                         syncControlSegmentFocus((Control) e.widget);
706                     break;
707                 case SWT.Traverse:
708                     if (DEBUG_FOCUS)
709                         System.out.println("Control traversal: " + e); //$NON-NLS-1$
710
switch (e.detail) {
711                     case SWT.TRAVERSE_PAGE_NEXT:
712                     case SWT.TRAVERSE_PAGE_PREVIOUS:
713                     case SWT.TRAVERSE_ARROW_NEXT:
714                     case SWT.TRAVERSE_ARROW_PREVIOUS:
715                         e.doit = false;
716                         return;
717                     }
718                     Control c = (Control) e.widget;
719                     ControlSegment segment = (ControlSegment) c
720                             .getData(CONTROL_KEY);
721                     if (e.detail == SWT.TRAVERSE_TAB_NEXT)
722                         e.doit = advanceControl(c, segment, true);
723                     else if (e.detail == SWT.TRAVERSE_TAB_PREVIOUS)
724                         e.doit = advanceControl(c, segment, false);
725                     if (!e.doit)
726                         e.detail = SWT.TRAVERSE_NONE;
727                     break;
728                 }
729             }
730         };
731         for (int i = 0; i < paragraphs.length; i++) {
732             Paragraph p = paragraphs[i];
733             ParagraphSegment[] segments = p.getSegments();
734             for (int j = 0; j < segments.length; j++) {
735                 if (segments[j] instanceof ControlSegment) {
736                     ControlSegment cs = (ControlSegment) segments[j];
737                     Control c = cs.getControl(resourceTable);
738                     if (c != null) {
739                         if (c.getData(CONTROL_KEY) == null) {
740                             // first time - hook
741
c.setData(CONTROL_KEY, cs);
742                             attachTraverseListener(c, listener);
743                         }
744                     }
745                 }
746             }
747         }
748     }
749
750     private void attachTraverseListener(Control c, Listener listener) {
751         if (c instanceof Composite) {
752             Composite parent = (Composite) c;
753             Control[] children = parent.getChildren();
754             for (int i = 0; i < children.length; i++) {
755                 attachTraverseListener(children[i], listener);
756             }
757             if (c instanceof Canvas) {
758                 // If Canvas, the control iteself can accept
759
// traverse events and should be monitored
760
c.addListener(SWT.Traverse, listener);
761                 c.addListener(SWT.FocusIn, listener);
762             }
763         } else {
764             c.addListener(SWT.Traverse, listener);
765             c.addListener(SWT.FocusIn, listener);
766         }
767     }
768
769     /**
770      * If we click on the control randomly, our internal book-keeping will be
771      * off. We need to update the model and mark the control segment and
772      * currently selected. Hyperlink that may have had focus must also be
773      * exited.
774      *
775      * @param control
776      * the control that got focus
777      */

778     private void syncControlSegmentFocus(Control control) {
779         ControlSegment cs = null;
780
781         while (control != null) {
782             cs = (ControlSegment) control.getData(CONTROL_KEY);
783             if (cs != null)
784                 break;
785             control = control.getParent();
786         }
787         if (cs == null)
788             return;
789         IFocusSelectable current = model.getSelectedSegment();
790         // If the model and the control match, all is well
791
if (current == cs)
792             return;
793         IHyperlinkSegment oldLink = null;
794         if (current != null && current instanceof IHyperlinkSegment) {
795             oldLink = (IHyperlinkSegment) current;
796             exitLink(oldLink, SWT.NULL);
797         }
798         if (DEBUG_FOCUS)
799             System.out.println("Sync control: " + cs + ", oldLink=" + oldLink); //$NON-NLS-1$ //$NON-NLS-2$
800
model.select(cs);
801         if (oldLink != null)
802             paintFocusTransfer(oldLink, null);
803         // getAccessible().setFocus(model.getSelectedSegmentIndex());
804
}
805
806     private boolean advanceControl(Control c, ControlSegment segment,
807             boolean next) {
808         Composite parent = c.getParent();
809         if (parent == this) {
810             // segment-level control
811
IFocusSelectable nextSegment = model.getNextFocusSegment(next);
812             if (nextSegment != null) {
813                 controlFocusTransfer = true;
814                 super.forceFocus();
815                 controlFocusTransfer = false;
816                 model.select(segment);
817                 return advance(next);
818             }
819             // nowhere to go
820
return setFocusToNextSibling(this, next);
821         }
822         if (setFocusToNextSibling(c, next))
823             return true;
824         // still here - must go one level up
825
segment = (ControlSegment) parent.getData(CONTROL_KEY);
826         return advanceControl(parent, segment, next);
827     }
828
829     private boolean setFocusToNextSibling(Control c, boolean next) {
830         Composite parent = c.getParent();
831         Control[] children = parent.getTabList();
832         for (int i = 0; i < children.length; i++) {
833             Control child = children[i];
834             if (child == c) {
835                 // here
836
if (next) {
837                     for (int j = i + 1; j < children.length; j++) {
838                         Control nc = children[j];
839                         if (nc.setFocus())
840                             return false;
841                     }
842                 } else {
843                     for (int j = i - 1; j >= 0; j--) {
844                         Control pc = children[j];
845                         if (pc.setFocus())
846                             return false;
847                     }
848                 }
849             }
850         }
851         return false;
852     }
853
854     /**
855      * Controls whether whitespace inside paragraph and list items is
856      * normalized. Note that the new value will not affect the current text in
857      * the control, only subsequent calls to <code>setText</code> or
858      * <code>setContents</code>.
859      * <p>
860      * If normalized:
861      * <ul>
862      * <li>all white space characters will be condensed into at most one when
863      * between words.</li>
864      * <li>new line characters will be ignored and replaced with one white
865      * space character</li>
866      * <li>white space characters after the opening tags and before the closing
867      * tags will be trimmed</li>
868      *
869      * @param value
870      * <code>true</code> if whitespace is normalized,
871      * <code>false</code> otherwise.
872      */

873     public void setWhitespaceNormalized(boolean value) {
874         model.setWhitespaceNormalized(value);
875     }
876
877     /**
878      * Tests whether whitespace inside paragraph and list item is normalized.
879      *
880      * @see #setWhitespaceNormalized(boolean)
881      * @return <code>true</code> if whitespace is normalized,
882      * <code>false</code> otherwise.
883      */

884     public boolean isWhitespaceNormalized() {
885         return model.isWhitespaceNormalized();
886     }
887
888     /**
889      * Disposes the internal menu if created and sets the menu provided as a
890      * parameter.
891      *
892      * @param menu
893      * the menu to associate with this text control
894      */

895     public void setMenu(Menu menu) {
896         Menu currentMenu = super.getMenu();
897         if (currentMenu != null && INTERNAL_MENU.equals(currentMenu.getData())) {
898             // internal menu set
899
if (menu != null) {
900                 currentMenu.dispose();
901                 super.setMenu(menu);
902             }
903         } else
904             super.setMenu(menu);
905     }
906
907     private void createMenu() {
908         Menu menu = new Menu(this);
909         final MenuItem copyItem = new MenuItem(menu, SWT.PUSH);
910         copyItem.setText(Messages.FormText_copy);
911
912         SelectionListener listener = new SelectionAdapter() {
913             public void widgetSelected(SelectionEvent e) {
914                 if (e.widget == copyItem) {
915                     copy();
916                 }
917             }
918         };
919         copyItem.addSelectionListener(listener);
920         menu.addMenuListener(new MenuListener() {
921             public void menuShown(MenuEvent e) {
922                 copyItem.setEnabled(canCopy());
923             }
924
925             public void menuHidden(MenuEvent e) {
926             }
927         });
928         menu.setData(INTERNAL_MENU);
929         super.setMenu(menu);
930     }
931
932     /**
933      * Returns the hyperlink settings that are in effect for this control.
934      *
935      * @return current hyperlinks settings
936      */

937     public HyperlinkSettings getHyperlinkSettings() {
938         return model.getHyperlinkSettings();
939     }
940
941     /**
942      * Sets the hyperlink settings to be used for this control. Settings will
943      * affect things like hyperlink color, rendering style, cursor etc.
944      *
945      * @param settings
946      * hyperlink settings for this control
947      */

948     public void setHyperlinkSettings(HyperlinkSettings settings) {
949         model.setHyperlinkSettings(settings);
950     }
951
952     /**
953      * Adds a listener that will handle hyperlink events.
954      *
955      * @param listener
956      * the listener to add
957      */

958     public void addHyperlinkListener(IHyperlinkListener listener) {
959         if (listeners == null)
960             listeners = new ListenerList();
961         listeners.add(listener);
962     }
963
964     /**
965      * Removes the hyperlink listener.
966      *
967      * @param listener
968      * the listener to remove
969      */

970     public void removeHyperlinkListener(IHyperlinkListener listener) {
971         if (listeners == null)
972             return;
973         listeners.remove(listener);
974     }
975
976     /**
977      * Adds a selection listener. A Selection event is sent by the widget when
978      * the selection has changed.
979      * <p>
980      * <code>widgetDefaultSelected</code> is not called for FormText.
981      * </p>
982      *
983      * @param listener
984      * the listener
985      * @exception SWTException
986      * <ul>
987      * <li>ERROR_WIDGET_DISPOSED - if the receiver has been
988      * disposed</li>
989      * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
990      * thread that created the receiver</li>
991      * </ul>
992      * @exception IllegalArgumentException
993      * <ul>
994      * <li>ERROR_NULL_ARGUMENT when listener is null</li>
995      * </ul>
996      * @since 3.1
997      */

998     public void addSelectionListener(SelectionListener listener) {
999         checkWidget();
1000        if (listener == null) {
1001            SWT.error(SWT.ERROR_NULL_ARGUMENT);
1002        }
1003        TypedListener typedListener = new TypedListener(listener);
1004        addListener(SWT.Selection, typedListener);
1005    }
1006
1007    /**
1008     * Removes the specified selection listener.
1009     * <p>
1010     *
1011     * @param listener
1012     * the listener
1013     * @exception SWTException
1014     * <ul>
1015     * <li>ERROR_WIDGET_DISPOSED - if the receiver has been
1016     * disposed</li>
1017     * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
1018     * thread that created the receiver</li>
1019     * </ul>
1020     * @exception IllegalArgumentException
1021     * <ul>
1022     * <li>ERROR_NULL_ARGUMENT when listener is null</li>
1023     * </ul>
1024     * @since 3.1
1025     */

1026    public void removeSelectionListener(SelectionListener listener) {
1027        checkWidget();
1028        if (listener == null) {
1029            SWT.error(SWT.ERROR_NULL_ARGUMENT);
1030        }
1031        removeListener(SWT.Selection, listener);
1032    }
1033
1034    /**
1035     * Returns the selected text.
1036     * <p>
1037     *
1038     * @return selected text, or an empty String if there is no selection.
1039     * @exception SWTException
1040     * <ul>
1041     * <li>ERROR_WIDGET_DISPOSED - if the receiver has been
1042     * disposed</li>
1043     * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
1044     * thread that created the receiver</li>
1045     * </ul>
1046     * @since 3.1
1047     */

1048
1049    public String JavaDoc getSelectionText() {
1050        checkWidget();
1051        if (selData != null)
1052            return selData.getSelectionText();
1053        return ""; //$NON-NLS-1$
1054
}
1055
1056    /**
1057     * Tests if the text is selected and can be copied into the clipboard.
1058     *
1059     * @return <code>true</code> if the selected text can be copied into the
1060     * clipboard, <code>false</code> otherwise.
1061     * @since 3.1
1062     */

1063    public boolean canCopy() {
1064        return selData != null && selData.canCopy();
1065    }
1066
1067    /**
1068     * Copies the selected text into the clipboard. Does nothing if no text is
1069     * selected or the text cannot be copied for any other reason.
1070     *
1071     * @since 3.1
1072     */

1073
1074    public void copy() {
1075        if (!canCopy())
1076            return;
1077        Clipboard clipboard = new Clipboard(getDisplay());
1078        Object JavaDoc[] o = new Object JavaDoc[] { getSelectionText() };
1079        Transfer[] t = new Transfer[] { TextTransfer.getInstance() };
1080        clipboard.setContents(o, t);
1081        clipboard.dispose();
1082    }
1083
1084    /**
1085     * Returns the reference of the hyperlink that currently has keyboard focus,
1086     * or <code>null</code> if there are no hyperlinks in the receiver or no
1087     * hyperlink has focus at the moment.
1088     *
1089     * @return href of the selected hyperlink or <code>null</code> if none
1090     * selected.
1091     * @since 3.1
1092     */

1093    public Object JavaDoc getSelectedLinkHref() {
1094        IHyperlinkSegment link = getSelectedLink();
1095        return link != null ? link.getHref() : null;
1096    }
1097
1098    /**
1099     * Returns the text of the hyperlink that currently has keyboard focus, or
1100     * <code>null</code> if there are no hyperlinks in the receiver or no
1101     * hyperlink has focus at the moment.
1102     *
1103     * @return text of the selected hyperlink or <code>null</code> if none
1104     * selected.
1105     * @since 3.1
1106     */

1107    public String JavaDoc getSelectedLinkText() {
1108        IHyperlinkSegment link = getSelectedLink();
1109        return link != null ? link.getText() : null;
1110    }
1111
1112    private IHyperlinkSegment getSelectedLink() {
1113        IFocusSelectable segment = model.getSelectedSegment();
1114        if (segment != null && segment instanceof IHyperlinkSegment)
1115            return (IHyperlinkSegment) segment;
1116        return null;
1117    }
1118
1119    private void initAccessible() {
1120        Accessible accessible = getAccessible();
1121        accessible.addAccessibleListener(new AccessibleAdapter() {
1122            public void getName(AccessibleEvent e) {
1123                if (e.childID == ACC.CHILDID_SELF)
1124                    e.result = model.getAccessibleText();
1125                else {
1126                    int linkCount = model.getHyperlinkCount();
1127                    if (e.childID >= 0 && e.childID < linkCount) {
1128                        IHyperlinkSegment link = model.getHyperlink(e.childID);
1129                        e.result = link.getText();
1130                    }
1131                }
1132            }
1133
1134            public void getHelp(AccessibleEvent e) {
1135                e.result = getToolTipText();
1136                int linkCount = model.getHyperlinkCount();
1137                if (e.result == null && e.childID >= 0 && e.childID < linkCount) {
1138                    IHyperlinkSegment link = model.getHyperlink(e.childID);
1139                    e.result = link.getText();
1140                }
1141            }
1142        });
1143        accessible.addAccessibleControlListener(new AccessibleControlAdapter() {
1144            public void getChildAtPoint(AccessibleControlEvent e) {
1145                Point pt = toControl(new Point(e.x, e.y));
1146                IHyperlinkSegment link = model.findHyperlinkAt(pt.x, pt.y);
1147                if (link != null)
1148                    e.childID = model.indexOf(link);
1149                else
1150                    e.childID = ACC.CHILDID_SELF;
1151            }
1152
1153            public void getLocation(AccessibleControlEvent e) {
1154                Rectangle location = null;
1155                if (e.childID != ACC.CHILDID_SELF
1156                        && e.childID != ACC.CHILDID_NONE) {
1157                    int index = e.childID;
1158                    IHyperlinkSegment link = model.getHyperlink(index);
1159                    if (link != null) {
1160                        location = link.getBounds();
1161                    }
1162                }
1163                if (location == null) {
1164                    location = getBounds();
1165                }
1166                Point pt = toDisplay(new Point(location.x, location.y));
1167                e.x = pt.x;
1168                e.y = pt.y;
1169                e.width = location.width;
1170                e.height = location.height;
1171            }
1172
1173            public void getFocus(AccessibleControlEvent e) {
1174                int childID = ACC.CHILDID_NONE;
1175
1176                if (isFocusControl()) {
1177                    int selectedIndex = model.getSelectedSegmentIndex();
1178                    if (selectedIndex == -1) {
1179                        childID = ACC.CHILDID_SELF;
1180                    } else {
1181                        childID = selectedIndex;
1182                    }
1183                }
1184                e.childID = childID;
1185            }
1186            
1187            public void getDefaultAction (AccessibleControlEvent e) {
1188                if (model.getHyperlinkCount() > 0) {
1189                    e.result = SWT.getMessage ("SWT_Press"); //$NON-NLS-1$
1190
}
1191            }
1192
1193            public void getChildCount(AccessibleControlEvent e) {
1194                e.detail = model.getHyperlinkCount();
1195            }
1196
1197            public void getRole(AccessibleControlEvent e) {
1198                int role = 0;
1199                int childID = e.childID;
1200                int linkCount = model.getHyperlinkCount();
1201                if (childID == ACC.CHILDID_SELF) {
1202                    if (linkCount > 0) {
1203                        role = ACC.ROLE_LINK;
1204                    } else {
1205                        role = ACC.ROLE_TEXT;
1206                    }
1207                } else if (childID >= 0 && childID < linkCount) {
1208                    role = ACC.ROLE_LINK;
1209                }
1210                e.detail = role;
1211            }
1212
1213            public void getSelection(AccessibleControlEvent e) {
1214                int selectedIndex = model.getSelectedSegmentIndex();
1215                e.childID = (selectedIndex == -1) ? ACC.CHILDID_NONE
1216                        : selectedIndex;
1217            }
1218
1219            public void getState(AccessibleControlEvent e) {
1220                int linkCount = model.getHyperlinkCount();
1221                int selectedIndex = model.getSelectedSegmentIndex();
1222                int state = 0;
1223                int childID = e.childID;
1224                if (childID == ACC.CHILDID_SELF) {
1225                    state = ACC.STATE_NORMAL;
1226                } else if (childID >= 0 && childID < linkCount) {
1227                    state = ACC.STATE_SELECTABLE;
1228                    if (isFocusControl()) {
1229                        state |= ACC.STATE_FOCUSABLE;
1230                    }
1231                    if (selectedIndex == childID) {
1232                        state |= ACC.STATE_SELECTED;
1233                        if (isFocusControl()) {
1234                            state |= ACC.STATE_FOCUSED;
1235                        }
1236                    }
1237                }
1238                state |= ACC.STATE_READONLY;
1239                e.detail = state;
1240            }
1241
1242            public void getChildren(AccessibleControlEvent e) {
1243                int linkCount = model.getHyperlinkCount();
1244                Object JavaDoc[] children = new Object JavaDoc[linkCount];
1245                for (int i = 0; i < linkCount; i++) {
1246                    children[i] = new Integer JavaDoc(i);
1247                }
1248                e.children = children;
1249            }
1250
1251            public void getValue(AccessibleControlEvent e) {
1252                // e.result = model.getAccessibleText();
1253
}
1254        });
1255    }
1256
1257    private void startSelection(MouseEvent e) {
1258        inSelection = true;
1259        selData = new SelectionData(e);
1260        redraw();
1261        Form form = FormUtil.getForm(this);
1262        if (form != null)
1263            form.setSelectionText(this);
1264    }
1265
1266    private void endSelection(MouseEvent e) {
1267        inSelection = false;
1268        if (selData != null) {
1269            if (!selData.isEnclosed())
1270                selData = null;
1271            else
1272                computeSelection();
1273        }
1274        notifySelectionChanged();
1275    }
1276
1277    private void computeSelection() {
1278        GC gc = new GC(this);
1279        Paragraph[] paragraphs = model.getParagraphs();
1280        IHyperlinkSegment selectedLink = getSelectedLink();
1281        if (getDisplay().getFocusControl() != this)
1282            selectedLink = null;
1283        for (int i = 0; i < paragraphs.length; i++) {
1284            Paragraph p = paragraphs[i];
1285            if (i > 0)
1286                selData.markNewLine();
1287            p.computeSelection(gc, resourceTable, selectedLink, selData);
1288        }
1289        gc.dispose();
1290    }
1291
1292    void clearSelection() {
1293        selData = null;
1294        if (!isDisposed()) {
1295            redraw();
1296            notifySelectionChanged();
1297        }
1298    }
1299
1300    private void notifySelectionChanged() {
1301        Event event = new Event();
1302        event.widget = this;
1303        event.display = this.getDisplay();
1304        event.type = SWT.Selection;
1305        notifyListeners(SWT.Selection, event);
1306        getAccessible().selectionChanged();
1307    }
1308
1309    private void handleDrag(MouseEvent e) {
1310        if (selData != null) {
1311            ScrolledComposite scomp = FormUtil.getScrolledComposite(this);
1312            if (scomp != null) {
1313                FormUtil.ensureVisible(scomp, this, e);
1314            }
1315            selData.update(e);
1316            redraw();
1317        }
1318    }
1319
1320    private void handleMouseClick(MouseEvent e, boolean down) {
1321        if (DEBUG_FOCUS)
1322            System.out.println("FormText: mouse click(" + down + ")"); //$NON-NLS-1$ //$NON-NLS-2$
1323
if (down) {
1324            // select a hyperlink
1325
mouseFocus = true;
1326            IHyperlinkSegment segmentUnder = model.findHyperlinkAt(e.x, e.y);
1327            if (segmentUnder != null) {
1328                IHyperlinkSegment oldLink = getSelectedLink();
1329                if (getDisplay().getFocusControl() != this) {
1330                    setFocus();
1331                }
1332                model.selectLink(segmentUnder);
1333                enterLink(segmentUnder, e.stateMask);
1334                paintFocusTransfer(oldLink, segmentUnder);
1335            }
1336            if (e.button == 1) {
1337                startSelection(e);
1338                armed = segmentUnder;
1339            }
1340            else {
1341            }
1342        } else {
1343            if (e.button == 1) {
1344                endSelection(e);
1345                IHyperlinkSegment segmentUnder = model
1346                        .findHyperlinkAt(e.x, e.y);
1347                if (segmentUnder != null && armed == segmentUnder && selData == null) {
1348                    activateLink(segmentUnder, e.stateMask);
1349                    armed = null;
1350                }
1351            }
1352            mouseFocus = false;
1353        }
1354    }
1355
1356    private void handleMouseHover(MouseEvent e) {
1357    }
1358
1359    private void updateTooltipText(ParagraphSegment segment) {
1360        String JavaDoc tooltipText = null;
1361        if (segment != null) {
1362            tooltipText = segment.getTooltipText();
1363        }
1364        String JavaDoc currentTooltipText = getToolTipText();
1365
1366        if ((currentTooltipText != null && tooltipText == null)
1367                || (currentTooltipText == null && tooltipText != null))
1368            setToolTipText(tooltipText);
1369    }
1370
1371    private void handleMouseMove(MouseEvent e) {
1372        if (inSelection) {
1373            handleDrag(e);
1374            return;
1375        }
1376        ParagraphSegment segmentUnder = model.findSegmentAt(e.x, e.y);
1377        updateTooltipText(segmentUnder);
1378        if (segmentUnder == null) {
1379            if (entered != null) {
1380                exitLink(entered, e.stateMask);
1381                paintLinkHover(entered, false);
1382                entered = null;
1383            }
1384            setCursor(null);
1385        } else {
1386            if (segmentUnder instanceof IHyperlinkSegment) {
1387                IHyperlinkSegment linkUnder = (IHyperlinkSegment) segmentUnder;
1388                if (entered!=null && linkUnder!=entered) {
1389                    // Special case: links are so close that there are 0 pixels between.
1390
// Must exit the link before entering the next one.
1391
exitLink(entered, e.stateMask);
1392                    paintLinkHover(entered, false);
1393                    entered = null;
1394                }
1395                if (entered == null) {
1396                    entered = linkUnder;
1397                    enterLink(linkUnder, e.stateMask);
1398                    paintLinkHover(entered, true);
1399                    setCursor(model.getHyperlinkSettings().getHyperlinkCursor());
1400                }
1401            } else {
1402                if (entered != null) {
1403                    exitLink(entered, e.stateMask);
1404                    paintLinkHover(entered, false);
1405                    entered = null;
1406                }
1407                if (segmentUnder instanceof TextSegment)
1408                    setCursor(model.getHyperlinkSettings().getTextCursor());
1409                else
1410                    setCursor(null);
1411            }
1412        }
1413    }
1414
1415    private boolean advance(boolean next) {
1416        if (DEBUG_FOCUS)
1417            System.out.println("Advance: next=" + next); //$NON-NLS-1$
1418
IFocusSelectable current = model.getSelectedSegment();
1419        if (current != null && current instanceof IHyperlinkSegment)
1420            exitLink((IHyperlinkSegment) current, SWT.NULL);
1421        IFocusSelectable newSegment = null;
1422        boolean valid = false;
1423        // get the next segment that can accept focus. Links
1424
// can always accept focus but controls may not
1425
while (!valid) {
1426            if (!model.traverseFocusSelectableObjects(next))
1427                break;
1428            newSegment = model.getSelectedSegment();
1429            if (newSegment == null)
1430                break;
1431            valid = setControlFocus(next, newSegment);
1432        }
1433        IHyperlinkSegment newLink = newSegment instanceof IHyperlinkSegment ? (IHyperlinkSegment) newSegment
1434                : null;
1435        if (valid)
1436            enterLink(newLink, SWT.NULL);
1437        IHyperlinkSegment oldLink = current instanceof IHyperlinkSegment ? (IHyperlinkSegment) current
1438                : null;
1439        if (oldLink != null || newLink != null)
1440            paintFocusTransfer(oldLink, newLink);
1441        if (newLink != null)
1442            ensureVisible(newLink);
1443        if (newLink != null)
1444            getAccessible().setFocus(model.getSelectedSegmentIndex());
1445        return !valid;
1446    }
1447
1448    private boolean setControlFocus(boolean next, IFocusSelectable selectable) {
1449        controlFocusTransfer = true;
1450        boolean result = selectable.setFocus(resourceTable, next);
1451        controlFocusTransfer = false;
1452        return result;
1453    }
1454
1455    private void handleFocusChange() {
1456        if (DEBUG_FOCUS) {
1457            System.out.println("Handle focus change: hasFocus=" + hasFocus //$NON-NLS-1$
1458
+ ", mouseFocus=" + mouseFocus); //$NON-NLS-1$
1459
}
1460        if (hasFocus) {
1461            boolean advance = true;
1462            if (!mouseFocus) {
1463                // if (model.restoreSavedLink() == false)
1464
boolean valid = false;
1465                IFocusSelectable selectable = null;
1466                while (!valid) {
1467                    if (!model.traverseFocusSelectableObjects(advance))
1468                        break;
1469                    selectable = model.getSelectedSegment();
1470                    if (selectable == null)
1471                        break;
1472                    valid = setControlFocus(advance, selectable);
1473                }
1474                if (selectable != null)
1475                    ensureVisible(selectable);
1476                if (selectable instanceof IHyperlinkSegment) {
1477                    enterLink((IHyperlinkSegment) selectable, SWT.NULL);
1478                    paintFocusTransfer(null, (IHyperlinkSegment) selectable);
1479                }
1480            }
1481        } else {
1482            paintFocusTransfer(getSelectedLink(), null);
1483            model.selectLink(null);
1484        }
1485    }
1486
1487    private void enterLink(IHyperlinkSegment link, int stateMask) {
1488        if (link == null || listeners == null)
1489            return;
1490        int size = listeners.size();
1491        HyperlinkEvent he = new HyperlinkEvent(this, link.getHref(), link
1492                .getText(), stateMask);
1493        Object JavaDoc [] listenerList = listeners.getListeners();
1494        for (int i = 0; i < size; i++) {
1495            IHyperlinkListener listener = (IHyperlinkListener) listenerList[i];
1496            listener.linkEntered(he);
1497        }
1498    }
1499
1500    private void exitLink(IHyperlinkSegment link, int stateMask) {
1501        if (link == null || listeners == null)
1502            return;
1503        int size = listeners.size();
1504        HyperlinkEvent he = new HyperlinkEvent(this, link.getHref(), link
1505                .getText(), stateMask);
1506        Object JavaDoc [] listenerList = listeners.getListeners();
1507        for (int i = 0; i < size; i++) {
1508            IHyperlinkListener listener = (IHyperlinkListener) listenerList[i];
1509            listener.linkExited(he);
1510        }
1511    }
1512
1513    private void paintLinkHover(IHyperlinkSegment link, boolean hover) {
1514        GC gc = new GC(this);
1515        HyperlinkSettings settings = getHyperlinkSettings();
1516        Color newFg = hover ? settings.getActiveForeground() : settings
1517                .getForeground();
1518        if (newFg != null)
1519            gc.setForeground(newFg);
1520        gc.setBackground(getBackground());
1521        gc.setFont(getFont());
1522        boolean selected = (link == getSelectedLink());
1523        ((ParagraphSegment) link).paint(gc, hover, resourceTable, selected,
1524                selData, null);
1525        gc.dispose();
1526    }
1527
1528    private void activateSelectedLink() {
1529        IHyperlinkSegment link = getSelectedLink();
1530        if (link != null)
1531            activateLink(link, SWT.NULL);
1532    }
1533
1534    private void activateLink(IHyperlinkSegment link, int stateMask) {
1535        setCursor(model.getHyperlinkSettings().getBusyCursor());
1536        if (listeners != null) {
1537            int size = listeners.size();
1538            HyperlinkEvent e = new HyperlinkEvent(this, link.getHref(), link
1539                    .getText(), stateMask);
1540            Object JavaDoc [] listenerList = listeners.getListeners();
1541            for (int i = 0; i < size; i++) {
1542                IHyperlinkListener listener = (IHyperlinkListener) listenerList[i];
1543                listener.linkActivated(e);
1544            }
1545        }
1546        if (!isDisposed() && model.linkExists(link)) {
1547            setCursor(model.getHyperlinkSettings().getHyperlinkCursor());
1548        }
1549    }
1550
1551    private void ensureBoldFontPresent(Font regularFont) {
1552        Font boldFont = (Font) resourceTable.get(FormTextModel.BOLD_FONT_ID);
1553        if (boldFont != null)
1554            return;
1555        boldFont = FormUtil.createBoldFont(getDisplay(), regularFont);
1556        resourceTable.put(FormTextModel.BOLD_FONT_ID, boldFont);
1557    }
1558
1559    private void paint(PaintEvent e) {
1560        GC gc = e.gc;
1561        gc.setFont(getFont());
1562        ensureBoldFontPresent(getFont());
1563        gc.setForeground(getForeground());
1564        gc.setBackground(getBackground());
1565        repaint(gc, e.x, e.y, e.width, e.height);
1566    }
1567
1568    private void repaint(GC gc, int x, int y, int width, int height) {
1569        Image textBuffer = new Image(getDisplay(), width, height);
1570        Color bg = getBackground();
1571        Color fg = getForeground();
1572        if (!getEnabled()) {
1573            bg = getDisplay().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);
1574            fg = getDisplay().getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW);
1575        }
1576        GC textGC = new GC(textBuffer, gc.getStyle());
1577        textGC.setForeground(fg);
1578        textGC.setBackground(bg);
1579        textGC.setFont(getFont());
1580        textGC.fillRectangle(0, 0, width, height);
1581        Rectangle repaintRegion = new Rectangle(x, y, width, height);
1582
1583        Paragraph[] paragraphs = model.getParagraphs();
1584        IHyperlinkSegment selectedLink = getSelectedLink();
1585        if (getDisplay().getFocusControl() != this)
1586            selectedLink = null;
1587        for (int i = 0; i < paragraphs.length; i++) {
1588            Paragraph p = paragraphs[i];
1589            p
1590                    .paint(textGC, repaintRegion, resourceTable, selectedLink,
1591                            selData);
1592        }
1593        textGC.dispose();
1594        gc.drawImage(textBuffer, x, y);
1595        textBuffer.dispose();
1596    }
1597
1598    private int getParagraphSpacing(int lineHeight) {
1599        return lineHeight / 2;
1600    }
1601
1602    private void paintFocusTransfer(IHyperlinkSegment oldLink,
1603            IHyperlinkSegment newLink) {
1604        GC gc = new GC(this);
1605        Color bg = getBackground();
1606        Color fg = getForeground();
1607        gc.setFont(getFont());
1608        if (oldLink != null) {
1609            gc.setBackground(bg);
1610            gc.setForeground(fg);
1611            oldLink.paintFocus(gc, bg, fg, false, null);
1612        }
1613        if (newLink != null) {
1614            // ensureVisible(newLink);
1615
gc.setBackground(bg);
1616            gc.setForeground(fg);
1617            newLink.paintFocus(gc, bg, fg, true, null);
1618        }
1619        gc.dispose();
1620    }
1621
1622    private void ensureVisible(IFocusSelectable segment) {
1623        if (mouseFocus) {
1624            mouseFocus = false;
1625            return;
1626        }
1627        if (segment == null)
1628            return;
1629        Rectangle bounds = segment.getBounds();
1630        ScrolledComposite scomp = FormUtil.getScrolledComposite(this);
1631        if (scomp == null)
1632            return;
1633        Point origin = FormUtil.getControlLocation(scomp, this);
1634        origin.x += bounds.x;
1635        origin.y += bounds.y;
1636        FormUtil.ensureVisible(scomp, origin, new Point(bounds.width,
1637                bounds.height));
1638    }
1639
1640    /**
1641     * Overrides the method by fully trusting the layout manager (computed width
1642     * or height may be larger than the provider width or height hints). Callers
1643     * should be prepared that the computed width is larger than the provided
1644     * wHint.
1645     *
1646     * @see org.eclipse.swt.widgets.Composite#computeSize(int, int, boolean)
1647     */

1648    public Point computeSize(int wHint, int hHint, boolean changed) {
1649        checkWidget();
1650        Point size;
1651        FormTextLayout layout = (FormTextLayout) getLayout();
1652        if (wHint == SWT.DEFAULT || hHint == SWT.DEFAULT) {
1653            size = layout.computeSize(this, wHint, hHint, changed);
1654        } else {
1655            size = new Point(wHint, hHint);
1656        }
1657        Rectangle trim = computeTrim(0, 0, size.x, size.y);
1658        if (DEBUG_TEXTSIZE)
1659            System.out.println("FormText Computed size: "+trim); //$NON-NLS-1$
1660
return new Point(trim.width, trim.height);
1661    }
1662
1663    private void disposeResourceTable(boolean disposeBoldFont) {
1664        if (disposeBoldFont) {
1665            Font boldFont = (Font) resourceTable
1666                    .get(FormTextModel.BOLD_FONT_ID);
1667            if (boldFont != null) {
1668                boldFont.dispose();
1669                resourceTable.remove(FormTextModel.BOLD_FONT_ID);
1670            }
1671        }
1672        ArrayList JavaDoc imagesToRemove = new ArrayList JavaDoc();
1673        for (Enumeration JavaDoc enm = resourceTable.keys(); enm.hasMoreElements();) {
1674            String JavaDoc key = (String JavaDoc) enm.nextElement();
1675            if (key.startsWith(ImageSegment.SEL_IMAGE_PREFIX)) {
1676                Object JavaDoc obj = resourceTable.get(key);
1677                if (obj instanceof Image) {
1678                    Image image = (Image) obj;
1679                    if (!image.isDisposed()) {
1680                        image.dispose();
1681                        imagesToRemove.add(key);
1682                    }
1683                }
1684            }
1685        }
1686        for (int i = 0; i < imagesToRemove.size(); i++) {
1687            resourceTable.remove(imagesToRemove.get(i));
1688        }
1689    }
1690
1691    /*
1692     * (non-Javadoc)
1693     *
1694     * @see org.eclipse.swt.widgets.Control#setEnabled(boolean)
1695     */

1696    public void setEnabled(boolean enabled) {
1697        super.setEnabled(enabled);
1698        redraw();
1699    }
1700
1701    /* (non-Javadoc)
1702     * @see org.eclipse.swt.widgets.Control#setFocus()
1703     */

1704    public boolean setFocus() {
1705        mouseFocus = true;
1706        FormUtil.setFocusScrollingEnabled(this, false);
1707        boolean result = super.setFocus();
1708        mouseFocus = false;
1709        FormUtil.setFocusScrollingEnabled(this, true);
1710        return result;
1711    }
1712}
1713
Popular Tags