comparison org.eclipse.ui.forms/src/org/eclipse/ui/forms/widgets/FormText.d @ 12:bc29606a740c

Added dwt-addons in original directory structure of eclipse.org
author Frank Benoit <benoit@tionex.de>
date Sat, 14 Mar 2009 18:23:29 +0100
parents
children dbfb303e8fb0
comparison
equal deleted inserted replaced
11:43904fec5dca 12:bc29606a740c
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 * Port to the D programming language:
11 * Frank Benoit <benoit@tionex.de>
12 *******************************************************************************/
13 module org.eclipse.ui.forms.widgets.FormText;
14
15 import org.eclipse.ui.forms.widgets.ILayoutExtension;
16 import org.eclipse.ui.forms.widgets.Form;
17
18 import org.eclipse.swt.SWT;
19 import org.eclipse.swt.SWTException;
20 import org.eclipse.swt.accessibility.ACC;
21 import org.eclipse.swt.accessibility.Accessible;
22 import org.eclipse.swt.accessibility.AccessibleAdapter;
23 import org.eclipse.swt.accessibility.AccessibleControlAdapter;
24 import org.eclipse.swt.accessibility.AccessibleControlEvent;
25 import org.eclipse.swt.accessibility.AccessibleEvent;
26 import org.eclipse.swt.custom.ScrolledComposite;
27 import org.eclipse.swt.dnd.Clipboard;
28 import org.eclipse.swt.dnd.TextTransfer;
29 import org.eclipse.swt.dnd.Transfer;
30 import org.eclipse.swt.events.DisposeEvent;
31 import org.eclipse.swt.events.DisposeListener;
32 import org.eclipse.swt.events.FocusEvent;
33 import org.eclipse.swt.events.FocusListener;
34 import org.eclipse.swt.events.MenuEvent;
35 import org.eclipse.swt.events.MenuListener;
36 import org.eclipse.swt.events.MouseEvent;
37 import org.eclipse.swt.events.MouseListener;
38 import org.eclipse.swt.events.MouseMoveListener;
39 import org.eclipse.swt.events.MouseTrackListener;
40 import org.eclipse.swt.events.PaintEvent;
41 import org.eclipse.swt.events.PaintListener;
42 import org.eclipse.swt.events.SelectionAdapter;
43 import org.eclipse.swt.events.SelectionEvent;
44 import org.eclipse.swt.events.SelectionListener;
45 import org.eclipse.swt.graphics.Color;
46 import org.eclipse.swt.graphics.Font;
47 import org.eclipse.swt.graphics.FontMetrics;
48 import org.eclipse.swt.graphics.GC;
49 import org.eclipse.swt.graphics.Image;
50 import org.eclipse.swt.graphics.Point;
51 import org.eclipse.swt.graphics.Rectangle;
52 import org.eclipse.swt.widgets.Canvas;
53 import org.eclipse.swt.widgets.Composite;
54 import org.eclipse.swt.widgets.Control;
55 import org.eclipse.swt.widgets.Event;
56 import org.eclipse.swt.widgets.Layout;
57 import org.eclipse.swt.widgets.Listener;
58 import org.eclipse.swt.widgets.Menu;
59 import org.eclipse.swt.widgets.MenuItem;
60 import org.eclipse.swt.widgets.TypedListener;
61 import org.eclipse.core.runtime.ListenerList;
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.FormFonts;
68 import org.eclipse.ui.internal.forms.widgets.FormTextModel;
69 import org.eclipse.ui.internal.forms.widgets.FormUtil;
70 import org.eclipse.ui.internal.forms.widgets.IFocusSelectable;
71 import org.eclipse.ui.internal.forms.widgets.IHyperlinkSegment;
72 import org.eclipse.ui.internal.forms.widgets.ImageSegment;
73 import org.eclipse.ui.internal.forms.widgets.Locator;
74 import org.eclipse.ui.internal.forms.widgets.Paragraph;
75 import org.eclipse.ui.internal.forms.widgets.ParagraphSegment;
76 import org.eclipse.ui.internal.forms.widgets.SelectionData;
77 import org.eclipse.ui.internal.forms.widgets.TextSegment;
78
79 import java.lang.all;
80 import java.util.Enumeration;
81 import java.util.ArrayList;
82 import java.util.Set;
83 import java.io.InputStream;
84 import tango.io.Stdout;
85
86 /**
87 * This class is a read-only text control that is capable of rendering wrapped
88 * text. Text can be rendered as-is or by parsing the formatting XML tags.
89 * Independently, words that start with http:// can be converted into hyperlinks
90 * on the fly.
91 * <p>
92 * When configured to use formatting XML, the control requires the root element
93 * <code>form</code> to be used. The following tags can be children of the
94 * <code>form</code> element:
95 * </p>
96 * <ul>
97 * <li><b>p </b>- for defining paragraphs. The following attributes are
98 * allowed:
99 * <ul>
100 * <li><b>vspace </b>- if set to 'false', no vertical space will be added
101 * (default is 'true')</li>
102 * </ul>
103 * </li>
104 * <li><b>li </b>- for defining list items. The following attributes are
105 * allowed:
106 * <ul>
107 * <li><b>vspace </b>- the same as with the <b>p </b> tag</li>
108 * <li><b>style </b>- could be 'bullet' (default), 'text' and 'image'</li>
109 * <li><b>value </b>- not used for 'bullet'. For text, it is the value of the
110 * text that is rendered as a bullet. For image, it is the href of the image to
111 * be rendered as a bullet.</li>
112 * <li><b>indent </b>- the number of pixels to indent the text in the list item
113 * </li>
114 * <li><b>bindent </b>- the number of pixels to indent the bullet itself</li>
115 * </ul>
116 * </li>
117 * </ul>
118 * <p>
119 * Text in paragraphs and list items will be wrapped according to the width of
120 * the control. The following tags can appear as children of either <b>p </b> or
121 * <b>li </b> elements:
122 * <ul>
123 * <li><b>img </b>- to render an image. Element accepts attribute 'href' that
124 * is a key to the <code>Image</code> set using 'setImage' method. Vertical
125 * position of image relative to surrounding text is optionally controlled by
126 * the attribute <b>align</b> that can have values <b>top</b>, <b>middle</b>
127 * and <b>bottom</b></li>
128 * <li><b>a </b>- to render a hyperlink. Element accepts attribute 'href' that
129 * will be provided to the hyperlink listeners via HyperlinkEvent object. The
130 * element also accepts 'nowrap' attribute (default is false). When set to
131 * 'true', the hyperlink will not be wrapped. Hyperlinks automatically created
132 * when 'http://' is encountered in text are not wrapped.</li>
133 * <li><b>b </b>- the enclosed text will use bold font.</li>
134 * <li><b>br </b>- forced line break (no attributes).</li>
135 * <li><b>span </b>- the enclosed text will have the color and font specified
136 * in the element attributes. Color is provided using 'color' attribute and is a
137 * key to the Color object set by 'setColor' method. Font is provided using
138 * 'font' attribute and is a key to the Font object set by 'setFont' method. As with
139 * hyperlinks, it is possible to block wrapping by setting 'nowrap' to true
140 * (false by default).
141 * </li>
142 * <li><b>control (new in 3.1)</b> - to place a control that is a child of the
143 * text control. Element accepts attribute 'href' that is a key to the Control
144 * object set using 'setControl' method. Optionally, attribute 'fill' can be set
145 * to <code>true</code> to make the control fill the entire width of the text.
146 * Form text is not responsible for creating or disposing controls, it only
147 * places them relative to the surrounding text. Similar to <b>img</b>,
148 * vertical position of the control can be set using the <b>align</b>
149 * attribute. In addition, <b>width</b> and <b>height</b> attributes can
150 * be used to force the dimensions of the control. If not used,
151 * the preferred control size will be used.
152 * </ul>
153 * <p>
154 * None of the elements can nest. For example, you cannot have <b>b </b> inside
155 * a <b>span </b>. This was done to keep everything simple and transparent.
156 * Since 3.1, an exception to this rule has been added to support nesting images
157 * and text inside the hyperlink tag (<b>a</b>). Image enclosed in the
158 * hyperlink tag acts as a hyperlink, can be clicked on and can accept and
159 * render selection focus. When both text and image is enclosed, selection and
160 * rendering will affect both as a single hyperlink.
161 * </p>
162 * <p>
163 * Since 3.1, it is possible to select text. Text selection can be
164 * programmatically accessed and also copied to clipboard. Non-textual objects
165 * (images, controls etc.) in the selection range are ignored.
166 * <p>
167 * Care should be taken when using this control. Form text is not an HTML
168 * browser and should not be treated as such. If you need complex formatting
169 * capabilities, use Browser widget. If you need editing capabilities and
170 * font/color styles of text segments is all you need, use StyleText widget.
171 * Finally, if all you need is to wrap text, use SWT Label widget and create it
172 * with SWT.WRAP style.
173 *
174 * @see FormToolkit
175 * @see TableWrapLayout
176 * @since 3.0
177 */
178 public class FormText : Canvas {
179 /**
180 * The object ID to be used when registering action to handle URL hyperlinks
181 * (those that should result in opening the web browser). Value is
182 * "urlHandler".
183 */
184 public static const String URL_HANDLER_ID = "urlHandler"; //$NON-NLS-1$
185
186 /**
187 * Value of the horizontal margin (default is 0).
188 */
189 public int marginWidth = 0;
190
191 /**
192 * Value of tue vertical margin (default is 1).
193 */
194 public int marginHeight = 1;
195
196 // private fields
197 private static const bool DEBUG_TEXT = false;//"true".equalsIgnoreCase(Platform.getDebugOption(FormUtil.DEBUG_TEXT));
198 private static const bool DEBUG_TEXTSIZE = false;//"true".equalsIgnoreCase(Platform.getDebugOption(FormUtil.DEBUG_TEXTSIZE));
199
200 private static const bool DEBUG_FOCUS = false;//"true".equalsIgnoreCase(Platform.getDebugOption(FormUtil.DEBUG_FOCUS));
201
202 private bool hasFocus;
203
204 private bool paragraphsSeparated = true;
205
206 private FormTextModel model;
207
208 private ListenerList listeners;
209
210 private Hashtable resourceTable;
211
212 private IHyperlinkSegment entered;
213
214 private IHyperlinkSegment armed;
215
216 private bool mouseFocus = false;
217
218 private bool controlFocusTransfer = false;
219
220 private bool inSelection = false;
221
222 private SelectionData selData;
223
224 private static const String INTERNAL_MENU = "__internal_menu__"; //$NON-NLS-1$
225
226 private static const String CONTROL_KEY = "__segment__"; //$NON-NLS-1$
227
228 private class FormTextLayout : Layout, ILayoutExtension {
229 public this() {
230 }
231
232 public int computeMaximumWidth(Composite parent, bool changed) {
233 return computeSize(parent, SWT.DEFAULT, SWT.DEFAULT, changed).x;
234 }
235
236 public int computeMinimumWidth(Composite parent, bool changed) {
237 return computeSize(parent, 5, SWT.DEFAULT, true).x;
238 }
239
240 /*
241 * @see Layout#computeSize(Composite, int, int, bool)
242 */
243 public Point computeSize(Composite composite, int wHint, int hHint,
244 bool changed) {
245 long start = 0;
246
247 if (DEBUG_TEXT)
248 start = System.currentTimeMillis();
249 int innerWidth = wHint;
250 if (innerWidth !is SWT.DEFAULT)
251 innerWidth -= marginWidth * 2;
252 Point textSize = computeTextSize(innerWidth);
253 int textWidth = textSize.x + 2 * marginWidth;
254 int textHeight = textSize.y + 2 * marginHeight;
255 Point result = new Point(textWidth, textHeight);
256 if (DEBUG_TEXT) {
257 long stop = System.currentTimeMillis();
258 Stdout.formatln("FormText computeSize: {}ms", (stop - start)); //$NON-NLS-1$
259 }
260 if (DEBUG_TEXTSIZE) {
261 Stdout.formatln("FormText ({}), computeSize: wHint={}, result={}", model.getAccessibleText(), wHint, result); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
262 }
263 return result;
264 }
265
266 private Point computeTextSize(int wHint) {
267 Paragraph[] paragraphs = model.getParagraphs();
268 GC gc = new GC(this.outer);
269 gc.setFont(getFont());
270 Locator loc = new Locator();
271 int width = wHint !is SWT.DEFAULT ? wHint : 0;
272 FontMetrics fm = gc.getFontMetrics();
273 int lineHeight = fm.getHeight();
274 bool selectableInTheLastRow = false;
275 for (int i = 0; i < paragraphs.length; i++) {
276 Paragraph p = paragraphs[i];
277 if (i > 0 && getParagraphsSeparated()
278 && p.getAddVerticalSpace())
279 loc.y += getParagraphSpacing(lineHeight);
280 loc.rowHeight = 0;
281 loc.indent = p.getIndent();
282 loc.x = p.getIndent();
283 ParagraphSegment[] segments = p.getSegments();
284 if (segments.length > 0) {
285 selectableInTheLastRow = false;
286 int pwidth = 0;
287 for (int j = 0; j < segments.length; j++) {
288 ParagraphSegment segment = segments[j];
289 segment.advanceLocator(gc, wHint, loc, resourceTable,
290 false);
291 if (wHint !is SWT.DEFAULT) {
292 width = Math.max(width, loc.width);
293 } else {
294 pwidth += loc.width;
295 }
296 if (null !is cast(IFocusSelectable)segment )
297 selectableInTheLastRow = true;
298 }
299 if (wHint is SWT.DEFAULT)
300 width = Math.max(width, pwidth);
301 loc.y += loc.rowHeight;
302 } else {
303 // empty new line
304 loc.y += lineHeight;
305 }
306 }
307 gc.dispose();
308 if (selectableInTheLastRow)
309 loc.y += 1;
310 return new Point(width, loc.y);
311 }
312
313 protected void layout(Composite composite, bool flushCache) {
314 long start = 0;
315
316 if (DEBUG_TEXT) {
317 start = System.currentTimeMillis();
318 }
319 selData = null;
320 Rectangle carea = composite.getClientArea();
321 if (DEBUG_TEXTSIZE) {
322 Stdout.formatln("FormText layout ({}), carea={}",model.getAccessibleText(),carea); //$NON-NLS-1$ //$NON-NLS-2$
323 }
324 GC gc = new GC(composite);
325 gc.setFont(getFont());
326 ensureBoldFontPresent(getFont());
327 gc.setForeground(getForeground());
328 gc.setBackground(getBackground());
329
330 Locator loc = new Locator();
331 loc.marginWidth = marginWidth;
332 loc.marginHeight = marginHeight;
333 loc.x = marginWidth;
334 loc.y = marginHeight;
335 FontMetrics fm = gc.getFontMetrics();
336 int lineHeight = fm.getHeight();
337
338 Paragraph[] paragraphs = model.getParagraphs();
339 IHyperlinkSegment selectedLink = getSelectedLink();
340 for (int i = 0; i < paragraphs.length; i++) {
341 Paragraph p = paragraphs[i];
342 if (i > 0 && paragraphsSeparated && p.getAddVerticalSpace())
343 loc.y += getParagraphSpacing(lineHeight);
344 loc.indent = p.getIndent();
345 loc.resetCaret();
346 loc.rowHeight = 0;
347 p.layout(gc, carea.width, loc, lineHeight, resourceTable,
348 selectedLink);
349 }
350 gc.dispose();
351 if (DEBUG_TEXT) {
352 long stop = System.currentTimeMillis();
353 Stdout.formatln("FormText.layout: {}ms", (stop - start)); //$NON-NLS-1$ //$NON-NLS-2$
354 }
355 }
356 }
357
358 /**
359 * Contructs a new form text widget in the provided parent and using the
360 * styles.
361 *
362 * @param parent
363 * form text parent control
364 * @param style
365 * the widget style
366 */
367 public this(Composite parent, int style) {
368 resourceTable = new Hashtable();
369 super(parent, SWT.NO_BACKGROUND | SWT.WRAP | style);
370 setLayout(new FormTextLayout());
371 model = new FormTextModel();
372 addDisposeListener(new class DisposeListener {
373 public void widgetDisposed(DisposeEvent e) {
374 model.dispose();
375 disposeResourceTable(true);
376 }
377 });
378 addPaintListener(new class PaintListener {
379 public void paintControl(PaintEvent e) {
380 paint(e);
381 }
382 });
383 addListener(SWT.KeyDown, new class Listener {
384 public void handleEvent(Event e) {
385 if (e.character is '\r') {
386 activateSelectedLink();
387 return;
388 }
389 }
390 });
391 addListener(SWT.Traverse, new class Listener {
392 public void handleEvent(Event e) {
393 if (DEBUG_FOCUS)
394 Stdout.formatln("Traversal: {}", e); //$NON-NLS-1$
395 switch (e.detail) {
396 case SWT.TRAVERSE_PAGE_NEXT:
397 case SWT.TRAVERSE_PAGE_PREVIOUS:
398 case SWT.TRAVERSE_ARROW_NEXT:
399 case SWT.TRAVERSE_ARROW_PREVIOUS:
400 e.doit = false;
401 return;
402 default:
403 }
404 if (!model.hasFocusSegments()) {
405 e.doit = true;
406 return;
407 }
408 if (e.detail is SWT.TRAVERSE_TAB_NEXT)
409 e.doit = advance(true);
410 else if (e.detail is SWT.TRAVERSE_TAB_PREVIOUS)
411 e.doit = advance(false);
412 else if (e.detail !is SWT.TRAVERSE_RETURN)
413 e.doit = true;
414 }
415 });
416 addFocusListener(new class FocusListener {
417 public void focusGained(FocusEvent e) {
418 if (!hasFocus) {
419 hasFocus = true;
420 if (DEBUG_FOCUS) {
421 Stdout.formatln("FormText: focus gained"); //$NON-NLS-1$
422 }
423 if (!mouseFocus && !controlFocusTransfer) {
424 handleFocusChange();
425 }
426 }
427 }
428
429 public void focusLost(FocusEvent e) {
430 if (DEBUG_FOCUS) {
431 Stdout.formatln("FormText: focus lost"); //$NON-NLS-1$
432 }
433 if (hasFocus) {
434 hasFocus = false;
435 if (!controlFocusTransfer)
436 handleFocusChange();
437 }
438 }
439 });
440 addMouseListener(new class MouseListener {
441 public void mouseDoubleClick(MouseEvent e) {
442 }
443
444 public void mouseDown(MouseEvent e) {
445 // select a link
446 handleMouseClick(e, true);
447 }
448
449 public void mouseUp(MouseEvent e) {
450 // activate a link
451 handleMouseClick(e, false);
452 }
453 });
454 addMouseTrackListener(new class MouseTrackListener {
455 public void mouseEnter(MouseEvent e) {
456 handleMouseMove(e);
457 }
458
459 public void mouseExit(MouseEvent e) {
460 if (entered !is null) {
461 exitLink(entered, e.stateMask);
462 paintLinkHover(entered, false);
463 entered = null;
464 setCursor(null);
465 }
466 }
467
468 public void mouseHover(MouseEvent e) {
469 handleMouseHover(e);
470 }
471 });
472 addMouseMoveListener(new class MouseMoveListener {
473 public void mouseMove(MouseEvent e) {
474 handleMouseMove(e);
475 }
476 });
477 initAccessible();
478 ensureBoldFontPresent(getFont());
479 createMenu();
480 // we will handle traversal of controls, if any
481 setTabList(cast(Control[])null);
482 }
483
484 /**
485 * Test for focus.
486 *
487 * @return <samp>true </samp> if the widget has focus.
488 */
489 public bool getFocus() {
490 return hasFocus;
491 }
492
493 /**
494 * Test if the widget is currently processing the text it is about to
495 * render.
496 *
497 * @return <samp>true </samp> if the widget is still loading the text,
498 * <samp>false </samp> otherwise.
499 * @deprecated not used any more - returns <code>false</code>
500 */
501 public bool isLoading() {
502 return false;
503 }
504
505 /**
506 * Returns the text that will be shown in the control while the real content
507 * is loading.
508 *
509 * @return loading text message
510 * @deprecated loading text is not used since 3.1
511 */
512 public String getLoadingText() {
513 return null;
514 }
515
516 /**
517 * Sets the text that will be shown in the control while the real content is
518 * loading. This is significant when content to render is loaded from the
519 * input stream that was created from a remote URL, and the time to load the
520 * entire content is nontrivial.
521 *
522 * @param loadingText
523 * loading text message
524 * @deprecated use setText(loadingText, false, false);
525 */
526 public void setLoadingText(String loadingText) {
527 setText(loadingText, false, false);
528 }
529
530 /**
531 * If paragraphs are separated, spacing will be added between them.
532 * Otherwise, new paragraphs will simply start on a new line with no
533 * spacing.
534 *
535 * @param value
536 * <samp>true </samp> if paragraphs are separated, </samp> false
537 * </samp> otherwise.
538 */
539 public void setParagraphsSeparated(bool value) {
540 paragraphsSeparated = value;
541 }
542
543 /**
544 * Tests if there is some inter-paragraph spacing.
545 *
546 * @return <samp>true </samp> if paragraphs are separated, <samp>false
547 * </samp> otherwise.
548 */
549 public bool getParagraphsSeparated() {
550 return paragraphsSeparated;
551 }
552
553 /**
554 * Registers the image referenced by the provided key.
555 * <p>
556 * For <samp>img </samp> tags, an object of a type <samp>Image </samp> must
557 * be registered using the key equivalent to the value of the <samp>href
558 * </samp> attribute used in the tag.
559 *
560 * @param key
561 * unique key that matches the value of the <samp>href </samp>
562 * attribute.
563 * @param image
564 * an object of a type <samp>Image </samp>.
565 */
566 public void setImage(String key, Image image) {
567 resourceTable.put("i." ~ key, image); //$NON-NLS-1$
568 }
569
570 /**
571 * Registers the color referenced by the provided key.
572 * <p>
573 * For <samp>span </samp> tags, an object of a type <samp>Color </samp> must
574 * be registered using the key equivalent to the value of the <samp>color
575 * </samp> attribute.
576 *
577 * @param key
578 * unique key that matches the value of the <samp>color </samp>
579 * attribute.
580 * @param color
581 * an object of the type <samp>Color </samp> or <samp>null</samp>
582 * if the key needs to be cleared.
583 */
584 public void setColor(String key, Color color) {
585 String fullKey = "c." ~ key; //$NON-NLS-1$
586 if (color is null)
587 resourceTable.remove(fullKey);
588 else
589 resourceTable.put(fullKey, color);
590 }
591
592 /**
593 * Registers the font referenced by the provided key.
594 * <p>
595 * For <samp>span </samp> tags, an object of a type <samp>Font </samp> must
596 * be registered using the key equivalent to the value of the <samp>font
597 * </samp> attribute.
598 *
599 * @param key
600 * unique key that matches the value of the <samp>font </samp>
601 * attribute.
602 * @param font
603 * an object of the type <samp>Font </samp> or <samp>null</samp>
604 * if the key needs to be cleared.
605 */
606 public void setFont(String key, Font font) {
607 String fullKey = "f." ~ key; //$NON-NLS-1$
608 if (font is null)
609 resourceTable.remove(fullKey);
610 else
611 resourceTable.put(fullKey, font);
612 model.clearCache(fullKey);
613 }
614
615 /**
616 * Registers the control referenced by the provided key.
617 * <p>
618 * For <samp>control</samp> tags, an object of a type <samp>Control</samp>
619 * must be registered using the key equivalent to the value of the
620 * <samp>control</samp> attribute.
621 *
622 * @param key
623 * unique key that matches the value of the <samp>control</samp>
624 * attribute.
625 * @param control
626 * an object of the type <samp>Control</samp> or <samp>null</samp>
627 * if the existing control at the specified key needs to be
628 * removed.
629 * @since 3.1
630 */
631 public void setControl(String key, Control control) {
632 String fullKey = "o." ~ key; //$NON-NLS-1$
633 if (control is null)
634 resourceTable.remove(fullKey);
635 else
636 resourceTable.put(fullKey, control);
637 }
638
639 /**
640 * Sets the font to use to render the default text (text that does not have
641 * special font property assigned). Bold font will be constructed from this
642 * font.
643 *
644 * @param font
645 * the default font to use
646 */
647 public void setFont(Font font) {
648 super.setFont(font);
649 model.clearCache(null);
650 Font boldFont = cast(Font) resourceTable.get(FormTextModel.BOLD_FONT_ID);
651 if (boldFont !is null) {
652 FormFonts.getInstance().markFinished(boldFont);
653 resourceTable.remove(FormTextModel.BOLD_FONT_ID);
654 }
655 ensureBoldFontPresent(getFont());
656 }
657
658 /**
659 * Sets the provided text. Text can be rendered as-is, or by parsing the
660 * formatting tags. Optionally, sections of text starting with http:// will
661 * be converted to hyperlinks.
662 *
663 * @param text
664 * the text to render
665 * @param parseTags
666 * if <samp>true </samp>, formatting tags will be parsed.
667 * Otherwise, text will be rendered as-is.
668 * @param expandURLs
669 * if <samp>true </samp>, URLs found in the untagged text will be
670 * converted into hyperlinks.
671 */
672 public void setText(String text, bool parseTags, bool expandURLs) {
673 disposeResourceTable(false);
674 entered = null;
675 if (parseTags)
676 model.parseTaggedText(text, expandURLs);
677 else
678 model.parseRegularText(text, expandURLs);
679 hookControlSegmentFocus();
680 layout();
681 redraw();
682 }
683
684 /**
685 * Sets the contents of the stream. Optionally, URLs in untagged text can be
686 * converted into hyperlinks. The caller is responsible for closing the
687 * stream.
688 *
689 * @param is
690 * stream to render
691 * @param expandURLs
692 * if <samp>true </samp>, URLs found in untagged text will be
693 * converted into hyperlinks.
694 */
695 public void setContents(InputStream is_, bool expandURLs) {
696 entered = null;
697 disposeResourceTable(false);
698 model.parseInputStream(is_, expandURLs);
699 hookControlSegmentFocus();
700 layout();
701 redraw();
702 }
703
704 private void hookControlSegmentFocus() {
705 Paragraph[] paragraphs = model.getParagraphs();
706 if (paragraphs is null)
707 return;
708 Listener listener = new class Listener {
709 public void handleEvent(Event e) {
710 switch (e.type) {
711 case SWT.FocusIn:
712 if (!controlFocusTransfer)
713 syncControlSegmentFocus(cast(Control) e.widget);
714 break;
715 case SWT.Traverse:
716 if (DEBUG_FOCUS)
717 Stdout.formatln("Control traversal: {}", e); //$NON-NLS-1$
718 switch (e.detail) {
719 case SWT.TRAVERSE_PAGE_NEXT:
720 case SWT.TRAVERSE_PAGE_PREVIOUS:
721 case SWT.TRAVERSE_ARROW_NEXT:
722 case SWT.TRAVERSE_ARROW_PREVIOUS:
723 e.doit = false;
724 return;
725 default:
726 }
727 Control c = cast(Control) e.widget;
728 ControlSegment segment = cast(ControlSegment) c
729 .getData(CONTROL_KEY);
730 if (e.detail is SWT.TRAVERSE_TAB_NEXT)
731 e.doit = advanceControl(c, segment, true);
732 else if (e.detail is SWT.TRAVERSE_TAB_PREVIOUS)
733 e.doit = advanceControl(c, segment, false);
734 if (!e.doit)
735 e.detail = SWT.TRAVERSE_NONE;
736 break;
737 default:
738 }
739 }
740 };
741 for (int i = 0; i < paragraphs.length; i++) {
742 Paragraph p = paragraphs[i];
743 ParagraphSegment[] segments = p.getSegments();
744 for (int j = 0; j < segments.length; j++) {
745 if (auto cs = cast(ControlSegment)segments[j] ) {
746 Control c = cs.getControl(resourceTable);
747 if (c !is null) {
748 if (c.getData(CONTROL_KEY) is null) {
749 // first time - hook
750 c.setData(CONTROL_KEY, cs);
751 attachTraverseListener(c, listener);
752 }
753 }
754 }
755 }
756 }
757 }
758
759 private void attachTraverseListener(Control c, Listener listener) {
760 if ( auto parent = cast(Composite) c ) {
761 Control[] children = parent.getChildren();
762 for (int i = 0; i < children.length; i++) {
763 attachTraverseListener(children[i], listener);
764 }
765 if (auto canv = cast(Canvas)c ) {
766 // If Canvas, the control iteself can accept
767 // traverse events and should be monitored
768 c.addListener(SWT.Traverse, listener);
769 c.addListener(SWT.FocusIn, listener);
770 }
771 } else {
772 c.addListener(SWT.Traverse, listener);
773 c.addListener(SWT.FocusIn, listener);
774 }
775 }
776
777 /**
778 * If we click on the control randomly, our internal book-keeping will be
779 * off. We need to update the model and mark the control segment and
780 * currently selected. Hyperlink that may have had focus must also be
781 * exited.
782 *
783 * @param control
784 * the control that got focus
785 */
786 private void syncControlSegmentFocus(Control control) {
787 ControlSegment cs = null;
788
789 while (control !is null) {
790 cs = cast(ControlSegment) control.getData(CONTROL_KEY);
791 if (cs !is null)
792 break;
793 control = control.getParent();
794 }
795 if (cs is null)
796 return;
797 IFocusSelectable current = model.getSelectedSegment();
798 // If the model and the control match, all is well
799 if (current is cs)
800 return;
801 IHyperlinkSegment oldLink = null;
802 if (current !is null && null !is cast(IHyperlinkSegment)current ) {
803 oldLink = cast(IHyperlinkSegment) current;
804 exitLink(oldLink, SWT.NULL);
805 }
806 if (DEBUG_FOCUS)
807 Stdout.formatln("Sync control: {}, oldLink={}", cs, oldLink); //$NON-NLS-1$ //$NON-NLS-2$
808 model.select(cs);
809 if (oldLink !is null)
810 paintFocusTransfer(oldLink, null);
811 // getAccessible().setFocus(model.getSelectedSegmentIndex());
812 }
813
814 private bool advanceControl(Control c, ControlSegment segment,
815 bool next) {
816 Composite parent = c.getParent();
817 if (parent is this) {
818 // segment-level control
819 IFocusSelectable nextSegment = model.getNextFocusSegment(next);
820 if (nextSegment !is null) {
821 controlFocusTransfer = true;
822 super.forceFocus();
823 controlFocusTransfer = false;
824 model.select(segment);
825 return advance(next);
826 }
827 // nowhere to go
828 return setFocusToNextSibling(this, next);
829 }
830 if (setFocusToNextSibling(c, next))
831 return true;
832 // still here - must go one level up
833 segment = cast(ControlSegment) parent.getData(CONTROL_KEY);
834 return advanceControl(parent, segment, next);
835 }
836
837 private bool setFocusToNextSibling(Control c, bool next) {
838 Composite parent = c.getParent();
839 Control[] children = parent.getTabList();
840 for (int i = 0; i < children.length; i++) {
841 Control child = children[i];
842 if (child is c) {
843 // here
844 if (next) {
845 for (int j = i + 1; j < children.length; j++) {
846 Control nc = children[j];
847 if (nc.setFocus())
848 return false;
849 }
850 } else {
851 for (int j = i - 1; j >= 0; j--) {
852 Control pc = children[j];
853 if (pc.setFocus())
854 return false;
855 }
856 }
857 }
858 }
859 return false;
860 }
861
862 /**
863 * Controls whether whitespace inside paragraph and list items is
864 * normalized. Note that the new value will not affect the current text in
865 * the control, only subsequent calls to <code>setText</code> or
866 * <code>setContents</code>.
867 * <p>
868 * If normalized:
869 * <ul>
870 * <li>all white space characters will be condensed into at most one when
871 * between words.</li>
872 * <li>new line characters will be ignored and replaced with one white
873 * space character</li>
874 * <li>white space characters after the opening tags and before the closing
875 * tags will be trimmed</li>
876 *
877 * @param value
878 * <code>true</code> if whitespace is normalized,
879 * <code>false</code> otherwise.
880 */
881 public void setWhitespaceNormalized(bool value) {
882 model.setWhitespaceNormalized(value);
883 }
884
885 /**
886 * Tests whether whitespace inside paragraph and list item is normalized.
887 *
888 * @see #setWhitespaceNormalized(bool)
889 * @return <code>true</code> if whitespace is normalized,
890 * <code>false</code> otherwise.
891 */
892 public bool isWhitespaceNormalized() {
893 return model.isWhitespaceNormalized();
894 }
895
896 /**
897 * Disposes the internal menu if created and sets the menu provided as a
898 * parameter.
899 *
900 * @param menu
901 * the menu to associate with this text control
902 */
903 public void setMenu(Menu menu) {
904 Menu currentMenu = super.getMenu();
905 if (currentMenu !is null && INTERNAL_MENU.equals(stringcast(currentMenu.getData()))) {
906 // internal menu set
907 if (menu !is null) {
908 currentMenu.dispose();
909 super.setMenu(menu);
910 }
911 } else
912 super.setMenu(menu);
913 }
914
915 private void createMenu() {
916 Menu menu = new Menu(this);
917 final MenuItem copyItem = new MenuItem(menu, SWT.PUSH);
918 copyItem.setText(Messages.FormText_copy);
919
920 SelectionListener listener = new class SelectionAdapter {
921 public void widgetSelected(SelectionEvent e) {
922 if (e.widget is copyItem) {
923 copy();
924 }
925 }
926 };
927 copyItem.addSelectionListener(listener);
928 menu.addMenuListener(new class MenuListener {
929 public void menuShown(MenuEvent e) {
930 copyItem.setEnabled(canCopy());
931 }
932
933 public void menuHidden(MenuEvent e) {
934 }
935 });
936 menu.setData(stringcast(INTERNAL_MENU));
937 super.setMenu(menu);
938 }
939
940 /**
941 * Returns the hyperlink settings that are in effect for this control.
942 *
943 * @return current hyperlinks settings
944 */
945 public HyperlinkSettings getHyperlinkSettings() {
946 return model.getHyperlinkSettings();
947 }
948
949 /**
950 * Sets the hyperlink settings to be used for this control. Settings will
951 * affect things like hyperlink color, rendering style, cursor etc.
952 *
953 * @param settings
954 * hyperlink settings for this control
955 */
956 public void setHyperlinkSettings(HyperlinkSettings settings) {
957 model.setHyperlinkSettings(settings);
958 }
959
960 /**
961 * Adds a listener that will handle hyperlink events.
962 *
963 * @param listener
964 * the listener to add
965 */
966 public void addHyperlinkListener(IHyperlinkListener listener) {
967 if (listeners is null)
968 listeners = new ListenerList();
969 listeners.add(cast(Object)listener);
970 }
971
972 /**
973 * Removes the hyperlink listener.
974 *
975 * @param listener
976 * the listener to remove
977 */
978 public void removeHyperlinkListener(IHyperlinkListener listener) {
979 if (listeners is null)
980 return;
981 listeners.remove(cast(Object)listener);
982 }
983
984 /**
985 * Adds a selection listener. A Selection event is sent by the widget when
986 * the selection has changed.
987 * <p>
988 * <code>widgetDefaultSelected</code> is not called for FormText.
989 * </p>
990 *
991 * @param listener
992 * the listener
993 * @exception SWTException
994 * <ul>
995 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been
996 * disposed</li>
997 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
998 * thread that created the receiver</li>
999 * </ul>
1000 * @exception IllegalArgumentException
1001 * <ul>
1002 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
1003 * </ul>
1004 * @since 3.1
1005 */
1006 public void addSelectionListener(SelectionListener listener) {
1007 checkWidget();
1008 if (listener is null) {
1009 SWT.error(SWT.ERROR_NULL_ARGUMENT);
1010 }
1011 TypedListener typedListener = new TypedListener(listener);
1012 addListener(SWT.Selection, typedListener);
1013 }
1014
1015 /**
1016 * Removes the specified selection listener.
1017 * <p>
1018 *
1019 * @param listener
1020 * the listener
1021 * @exception SWTException
1022 * <ul>
1023 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been
1024 * disposed</li>
1025 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
1026 * thread that created the receiver</li>
1027 * </ul>
1028 * @exception IllegalArgumentException
1029 * <ul>
1030 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
1031 * </ul>
1032 * @since 3.1
1033 */
1034 public void removeSelectionListener(SelectionListener listener) {
1035 checkWidget();
1036 if (listener is null) {
1037 SWT.error(SWT.ERROR_NULL_ARGUMENT);
1038 }
1039 removeListener(SWT.Selection, listener);
1040 }
1041
1042 /**
1043 * Returns the selected text.
1044 * <p>
1045 *
1046 * @return selected text, or an empty String if there is no selection.
1047 * @exception SWTException
1048 * <ul>
1049 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been
1050 * disposed</li>
1051 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
1052 * thread that created the receiver</li>
1053 * </ul>
1054 * @since 3.1
1055 */
1056
1057 public String getSelectionText() {
1058 checkWidget();
1059 if (selData !is null)
1060 return selData.getSelectionText();
1061 return ""; //$NON-NLS-1$
1062 }
1063
1064 /**
1065 * Tests if the text is selected and can be copied into the clipboard.
1066 *
1067 * @return <code>true</code> if the selected text can be copied into the
1068 * clipboard, <code>false</code> otherwise.
1069 * @since 3.1
1070 */
1071 public bool canCopy() {
1072 return selData !is null && selData.canCopy();
1073 }
1074
1075 /**
1076 * Copies the selected text into the clipboard. Does nothing if no text is
1077 * selected or the text cannot be copied for any other reason.
1078 *
1079 * @since 3.1
1080 */
1081
1082 public void copy() {
1083 if (!canCopy())
1084 return;
1085 Clipboard clipboard = new Clipboard(getDisplay());
1086 Object[] o = [ stringcast(getSelectionText()) ];
1087 Transfer[] t = [ TextTransfer.getInstance() ];
1088 clipboard.setContents(o, t);
1089 clipboard.dispose();
1090 }
1091
1092 /**
1093 * Returns the reference of the hyperlink that currently has keyboard focus,
1094 * or <code>null</code> if there are no hyperlinks in the receiver or no
1095 * hyperlink has focus at the moment.
1096 *
1097 * @return href of the selected hyperlink or <code>null</code> if none
1098 * selected.
1099 * @since 3.1
1100 */
1101 public Object getSelectedLinkHref() {
1102 IHyperlinkSegment link = getSelectedLink();
1103 return link !is null ? stringcast(link.getHref()) : null;
1104 }
1105
1106 /**
1107 * Returns the text of the hyperlink that currently has keyboard focus, or
1108 * <code>null</code> if there are no hyperlinks in the receiver or no
1109 * hyperlink has focus at the moment.
1110 *
1111 * @return text of the selected hyperlink or <code>null</code> if none
1112 * selected.
1113 * @since 3.1
1114 */
1115 public String getSelectedLinkText() {
1116 IHyperlinkSegment link = getSelectedLink();
1117 return link !is null ? link.getText() : null;
1118 }
1119
1120 private IHyperlinkSegment getSelectedLink() {
1121 IFocusSelectable segment = model.getSelectedSegment();
1122 if (segment !is null && null !is cast(IHyperlinkSegment)segment )
1123 return cast(IHyperlinkSegment) segment;
1124 return null;
1125 }
1126
1127 private void initAccessible() {
1128 Accessible accessible = getAccessible();
1129 accessible.addAccessibleListener(new class AccessibleAdapter {
1130 public void getName(AccessibleEvent e) {
1131 if (e.childID is ACC.CHILDID_SELF)
1132 e.result = model.getAccessibleText();
1133 else {
1134 int linkCount = model.getHyperlinkCount();
1135 if (e.childID >= 0 && e.childID < linkCount) {
1136 IHyperlinkSegment link = model.getHyperlink(e.childID);
1137 e.result = link.getText();
1138 }
1139 }
1140 }
1141
1142 public void getHelp(AccessibleEvent e) {
1143 e.result = getToolTipText();
1144 int linkCount = model.getHyperlinkCount();
1145 if (e.result is null && e.childID >= 0 && e.childID < linkCount) {
1146 IHyperlinkSegment link = model.getHyperlink(e.childID);
1147 e.result = link.getText();
1148 }
1149 }
1150 });
1151 accessible.addAccessibleControlListener(new class AccessibleControlAdapter {
1152 public void getChildAtPoint(AccessibleControlEvent e) {
1153 Point pt = toControl(new Point(e.x, e.y));
1154 IHyperlinkSegment link = model.findHyperlinkAt(pt.x, pt.y);
1155 if (link !is null)
1156 e.childID = model.indexOf(link);
1157 else
1158 e.childID = ACC.CHILDID_SELF;
1159 }
1160
1161 public void getLocation(AccessibleControlEvent e) {
1162 Rectangle location = null;
1163 if (e.childID !is ACC.CHILDID_SELF
1164 && e.childID !is ACC.CHILDID_NONE) {
1165 int index = e.childID;
1166 IHyperlinkSegment link = model.getHyperlink(index);
1167 if (link !is null) {
1168 location = link.getBounds();
1169 }
1170 }
1171 if (location is null) {
1172 location = getBounds();
1173 }
1174 Point pt = toDisplay(new Point(location.x, location.y));
1175 e.x = pt.x;
1176 e.y = pt.y;
1177 e.width = location.width;
1178 e.height = location.height;
1179 }
1180
1181 public void getFocus(AccessibleControlEvent e) {
1182 int childID = ACC.CHILDID_NONE;
1183
1184 if (model.hasFocusSegments()) {
1185 int selectedIndex = model.getSelectedSegmentIndex();
1186 if (selectedIndex !is -1) {
1187 childID = selectedIndex;
1188 }
1189 }
1190 e.childID = childID;
1191 }
1192
1193 public void getDefaultAction (AccessibleControlEvent e) {
1194 if (model.getHyperlinkCount() > 0) {
1195 e.result = SWT.getMessage ("SWT_Press"); //$NON-NLS-1$
1196 }
1197 }
1198
1199 public void getChildCount(AccessibleControlEvent e) {
1200 e.detail = model.getHyperlinkCount();
1201 }
1202
1203 public void getRole(AccessibleControlEvent e) {
1204 int role = 0;
1205 int childID = e.childID;
1206 int linkCount = model.getHyperlinkCount();
1207 if (childID is ACC.CHILDID_SELF) {
1208 if (linkCount > 0) {
1209 role = ACC.ROLE_LINK;
1210 } else {
1211 role = ACC.ROLE_TEXT;
1212 }
1213 } else if (childID >= 0 && childID < linkCount) {
1214 role = ACC.ROLE_LINK;
1215 }
1216 e.detail = role;
1217 }
1218
1219 public void getSelection(AccessibleControlEvent e) {
1220 int selectedIndex = model.getSelectedSegmentIndex();
1221 e.childID = (selectedIndex is -1) ? ACC.CHILDID_NONE
1222 : selectedIndex;
1223 }
1224
1225 public void getState(AccessibleControlEvent e) {
1226 int linkCount = model.getHyperlinkCount();
1227 int selectedIndex = model.getSelectedSegmentIndex();
1228 int state = 0;
1229 int childID = e.childID;
1230 if (childID is ACC.CHILDID_SELF) {
1231 state = ACC.STATE_NORMAL;
1232 } else if (childID >= 0 && childID < linkCount) {
1233 state = ACC.STATE_SELECTABLE;
1234 if (isFocusControl()) {
1235 state |= ACC.STATE_FOCUSABLE;
1236 }
1237 if (selectedIndex is childID) {
1238 state |= ACC.STATE_SELECTED;
1239 if (isFocusControl()) {
1240 state |= ACC.STATE_FOCUSED;
1241 }
1242 }
1243 }
1244 state |= ACC.STATE_READONLY;
1245 e.detail = state;
1246 }
1247
1248 public void getChildren(AccessibleControlEvent e) {
1249 int linkCount = model.getHyperlinkCount();
1250 Object[] children = new Object[linkCount];
1251 for (int i = 0; i < linkCount; i++) {
1252 children[i] = new Integer(i);
1253 }
1254 e.children = children;
1255 }
1256
1257 public void getValue(AccessibleControlEvent e) {
1258 // e.result = model.getAccessibleText();
1259 }
1260 });
1261 }
1262
1263 private void startSelection(MouseEvent e) {
1264 inSelection = true;
1265 selData = new SelectionData(e);
1266 redraw();
1267 Form form = FormUtil.getForm(this);
1268 if (form !is null)
1269 form.setSelectionText(this);
1270 }
1271
1272 private void endSelection(MouseEvent e) {
1273 inSelection = false;
1274 if (selData !is null) {
1275 if (!selData.isEnclosed())
1276 selData = null;
1277 else
1278 computeSelection();
1279 }
1280 notifySelectionChanged();
1281 }
1282
1283 private void computeSelection() {
1284 GC gc = new GC(this);
1285 Paragraph[] paragraphs = model.getParagraphs();
1286 IHyperlinkSegment selectedLink = getSelectedLink();
1287 if (getDisplay().getFocusControl() !is this)
1288 selectedLink = null;
1289 for (int i = 0; i < paragraphs.length; i++) {
1290 Paragraph p = paragraphs[i];
1291 if (i > 0)
1292 selData.markNewLine();
1293 p.computeSelection(gc, resourceTable, selectedLink, selData);
1294 }
1295 gc.dispose();
1296 }
1297
1298 void clearSelection() {
1299 selData = null;
1300 if (!isDisposed()) {
1301 redraw();
1302 notifySelectionChanged();
1303 }
1304 }
1305
1306 private void notifySelectionChanged() {
1307 Event event = new Event();
1308 event.widget = this;
1309 event.display = this.getDisplay();
1310 event.type = SWT.Selection;
1311 notifyListeners(SWT.Selection, event);
1312 getAccessible().selectionChanged();
1313 }
1314
1315 private void handleDrag(MouseEvent e) {
1316 if (selData !is null) {
1317 ScrolledComposite scomp = FormUtil.getScrolledComposite(this);
1318 if (scomp !is null) {
1319 FormUtil.ensureVisible(scomp, this, e);
1320 }
1321 selData.update(e);
1322 redraw();
1323 }
1324 }
1325
1326 private void handleMouseClick(MouseEvent e, bool down) {
1327 if (DEBUG_FOCUS)
1328 Stdout.formatln("FormText: mouse click({})", down ); //$NON-NLS-1$ //$NON-NLS-2$
1329 if (down) {
1330 // select a hyperlink
1331 mouseFocus = true;
1332 IHyperlinkSegment segmentUnder = model.findHyperlinkAt(e.x, e.y);
1333 if (segmentUnder !is null) {
1334 IHyperlinkSegment oldLink = getSelectedLink();
1335 if (getDisplay().getFocusControl() !is this) {
1336 setFocus();
1337 }
1338 model.selectLink(segmentUnder);
1339 enterLink(segmentUnder, e.stateMask);
1340 paintFocusTransfer(oldLink, segmentUnder);
1341 }
1342 if (e.button is 1) {
1343 startSelection(e);
1344 armed = segmentUnder;
1345 }
1346 else {
1347 }
1348 } else {
1349 if (e.button is 1) {
1350 endSelection(e);
1351 IHyperlinkSegment segmentUnder = model
1352 .findHyperlinkAt(e.x, e.y);
1353 if (segmentUnder !is null && armed is segmentUnder && selData is null) {
1354 activateLink(segmentUnder, e.stateMask);
1355 armed = null;
1356 }
1357 }
1358 mouseFocus = false;
1359 }
1360 }
1361
1362 private void handleMouseHover(MouseEvent e) {
1363 }
1364
1365 private void updateTooltipText(ParagraphSegment segment) {
1366 String tooltipText = null;
1367 if (segment !is null) {
1368 tooltipText = segment.getTooltipText();
1369 }
1370 String currentTooltipText = getToolTipText();
1371
1372 if ((currentTooltipText !is null && tooltipText is null)
1373 || (currentTooltipText is null && tooltipText !is null))
1374 setToolTipText(tooltipText);
1375 }
1376
1377 private void handleMouseMove(MouseEvent e) {
1378 if (inSelection) {
1379 handleDrag(e);
1380 return;
1381 }
1382 ParagraphSegment segmentUnder = model.findSegmentAt(e.x, e.y);
1383 updateTooltipText(segmentUnder);
1384 if (segmentUnder is null) {
1385 if (entered !is null) {
1386 exitLink(entered, e.stateMask);
1387 paintLinkHover(entered, false);
1388 entered = null;
1389 }
1390 setCursor(null);
1391 } else {
1392 if (auto linkUnder = cast(IHyperlinkSegment) segmentUnder ) {
1393 if (entered !is null && linkUnder !is entered) {
1394 // Special case: links are so close that there are 0 pixels between.
1395 // Must exit the link before entering the next one.
1396 exitLink(entered, e.stateMask);
1397 paintLinkHover(entered, false);
1398 entered = null;
1399 }
1400 if (entered is null) {
1401 entered = linkUnder;
1402 enterLink(linkUnder, e.stateMask);
1403 paintLinkHover(entered, true);
1404 setCursor(model.getHyperlinkSettings().getHyperlinkCursor());
1405 }
1406 } else {
1407 if (entered !is null) {
1408 exitLink(entered, e.stateMask);
1409 paintLinkHover(entered, false);
1410 entered = null;
1411 }
1412 if (null !is cast(TextSegment)segmentUnder )
1413 setCursor(model.getHyperlinkSettings().getTextCursor());
1414 else
1415 setCursor(null);
1416 }
1417 }
1418 }
1419
1420 private bool advance(bool next) {
1421 if (DEBUG_FOCUS)
1422 Stdout.formatln("Advance: next={}", next); //$NON-NLS-1$
1423 IFocusSelectable current = model.getSelectedSegment();
1424 if (current !is null && null !is cast(IHyperlinkSegment)current )
1425 exitLink(cast(IHyperlinkSegment) current, SWT.NULL);
1426 IFocusSelectable newSegment = null;
1427 bool valid = false;
1428 // get the next segment that can accept focus. Links
1429 // can always accept focus but controls may not
1430 while (!valid) {
1431 if (!model.traverseFocusSelectableObjects(next))
1432 break;
1433 newSegment = model.getSelectedSegment();
1434 if (newSegment is null)
1435 break;
1436 valid = setControlFocus(next, newSegment);
1437 }
1438 IHyperlinkSegment newLink = null !is cast(IHyperlinkSegment)newSegment ? cast(IHyperlinkSegment) newSegment
1439 : null;
1440 if (valid)
1441 enterLink(newLink, SWT.NULL);
1442 IHyperlinkSegment oldLink = null !is cast(IHyperlinkSegment)current ? cast(IHyperlinkSegment) current
1443 : null;
1444 if (oldLink !is null || newLink !is null)
1445 paintFocusTransfer(oldLink, newLink);
1446 if (newLink !is null)
1447 ensureVisible(newLink);
1448 if (newLink !is null)
1449 getAccessible().setFocus(model.getSelectedSegmentIndex());
1450 return !valid;
1451 }
1452
1453 private bool setControlFocus(bool next, IFocusSelectable selectable) {
1454 controlFocusTransfer = true;
1455 bool result = selectable.setFocus(resourceTable, next);
1456 controlFocusTransfer = false;
1457 return result;
1458 }
1459
1460 private void handleFocusChange() {
1461 if (DEBUG_FOCUS) {
1462 Stdout.formatln("Handle focus change: hasFocus={}, mouseFocus={}", hasFocus, //$NON-NLS-1$
1463 mouseFocus); //$NON-NLS-1$
1464 }
1465 if (hasFocus) {
1466 bool advance = true;
1467 if (!mouseFocus) {
1468 // if (model.restoreSavedLink() is false)
1469 bool valid = false;
1470 IFocusSelectable selectable = null;
1471 while (!valid) {
1472 if (!model.traverseFocusSelectableObjects(advance))
1473 break;
1474 selectable = model.getSelectedSegment();
1475 if (selectable is null)
1476 break;
1477 valid = setControlFocus(advance, selectable);
1478 }
1479 if (selectable is null)
1480 setFocusToNextSibling(this, true);
1481 else
1482 ensureVisible(selectable);
1483 if ( auto hls = cast(IHyperlinkSegment)selectable ) {
1484 enterLink(hls, SWT.NULL);
1485 paintFocusTransfer(null, hls);
1486 }
1487 }
1488 } else {
1489 paintFocusTransfer(getSelectedLink(), null);
1490 model.selectLink(null);
1491 }
1492 }
1493
1494 private void enterLink(IHyperlinkSegment link, int stateMask) {
1495 if (link is null || listeners is null)
1496 return;
1497 int size = listeners.size();
1498 HyperlinkEvent he = new HyperlinkEvent(this, stringcast(link.getHref()), link
1499 .getText(), stateMask);
1500 Object [] listenerList = listeners.getListeners();
1501 for (int i = 0; i < size; i++) {
1502 IHyperlinkListener listener = cast(IHyperlinkListener) listenerList[i];
1503 listener.linkEntered(he);
1504 }
1505 }
1506
1507 private void exitLink(IHyperlinkSegment link, int stateMask) {
1508 if (link is null || listeners is null)
1509 return;
1510 int size = listeners.size();
1511 HyperlinkEvent he = new HyperlinkEvent(this, stringcast(link.getHref()), link
1512 .getText(), stateMask);
1513 Object [] listenerList = listeners.getListeners();
1514 for (int i = 0; i < size; i++) {
1515 IHyperlinkListener listener = cast(IHyperlinkListener) listenerList[i];
1516 listener.linkExited(he);
1517 }
1518 }
1519
1520 private void paintLinkHover(IHyperlinkSegment link, bool hover) {
1521 GC gc = new GC(this);
1522 HyperlinkSettings settings = getHyperlinkSettings();
1523 Color newFg = hover ? settings.getActiveForeground() : settings
1524 .getForeground();
1525 if (newFg !is null)
1526 gc.setForeground(newFg);
1527 gc.setBackground(getBackground());
1528 gc.setFont(getFont());
1529 bool selected = (link is getSelectedLink());
1530 (cast(ParagraphSegment) link).paint(gc, hover, resourceTable, selected,
1531 selData, null);
1532 gc.dispose();
1533 }
1534
1535 private void activateSelectedLink() {
1536 IHyperlinkSegment link = getSelectedLink();
1537 if (link !is null)
1538 activateLink(link, SWT.NULL);
1539 }
1540
1541 private void activateLink(IHyperlinkSegment link, int stateMask) {
1542 setCursor(model.getHyperlinkSettings().getBusyCursor());
1543 if (listeners !is null) {
1544 int size = listeners.size();
1545 HyperlinkEvent e = new HyperlinkEvent(this, stringcast(link.getHref()), link
1546 .getText(), stateMask);
1547 Object [] listenerList = listeners.getListeners();
1548 for (int i = 0; i < size; i++) {
1549 IHyperlinkListener listener = cast(IHyperlinkListener) listenerList[i];
1550 listener.linkActivated(e);
1551 }
1552 }
1553 if (!isDisposed() && model.linkExists(link)) {
1554 setCursor(model.getHyperlinkSettings().getHyperlinkCursor());
1555 }
1556 }
1557
1558 private void ensureBoldFontPresent(Font regularFont) {
1559 Font boldFont = cast(Font) resourceTable.get(FormTextModel.BOLD_FONT_ID);
1560 if (boldFont !is null)
1561 return;
1562 boldFont = FormFonts.getInstance().getBoldFont(getDisplay(), regularFont);
1563 resourceTable.put(FormTextModel.BOLD_FONT_ID, boldFont);
1564 }
1565
1566 private void paint(PaintEvent e) {
1567 GC gc = e.gc;
1568 gc.setFont(getFont());
1569 ensureBoldFontPresent(getFont());
1570 gc.setForeground(getForeground());
1571 gc.setBackground(getBackground());
1572 repaint(gc, e.x, e.y, e.width, e.height);
1573 }
1574
1575 private void repaint(GC gc, int x, int y, int width, int height) {
1576 Image textBuffer = new Image(getDisplay(), width, height);
1577 Color bg = getBackground();
1578 Color fg = getForeground();
1579 if (!getEnabled()) {
1580 bg = getDisplay().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND);
1581 fg = getDisplay().getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW);
1582 }
1583 GC textGC = new GC(textBuffer, gc.getStyle());
1584 textGC.setForeground(fg);
1585 textGC.setBackground(bg);
1586 textGC.setFont(getFont());
1587 textGC.fillRectangle(0, 0, width, height);
1588 Rectangle repaintRegion = new Rectangle(x, y, width, height);
1589
1590 Paragraph[] paragraphs = model.getParagraphs();
1591 IHyperlinkSegment selectedLink = getSelectedLink();
1592 if (getDisplay().getFocusControl() !is this)
1593 selectedLink = null;
1594 for (int i = 0; i < paragraphs.length; i++) {
1595 Paragraph p = paragraphs[i];
1596 p
1597 .paint(textGC, repaintRegion, resourceTable, selectedLink,
1598 selData);
1599 }
1600 textGC.dispose();
1601 gc.drawImage(textBuffer, x, y);
1602 textBuffer.dispose();
1603 }
1604
1605 private int getParagraphSpacing(int lineHeight) {
1606 return lineHeight / 2;
1607 }
1608
1609 private void paintFocusTransfer(IHyperlinkSegment oldLink,
1610 IHyperlinkSegment newLink) {
1611 GC gc = new GC(this);
1612 Color bg = getBackground();
1613 Color fg = getForeground();
1614 gc.setFont(getFont());
1615 if (oldLink !is null) {
1616 gc.setBackground(bg);
1617 gc.setForeground(fg);
1618 oldLink.paintFocus(gc, bg, fg, false, null);
1619 }
1620 if (newLink !is null) {
1621 // ensureVisible(newLink);
1622 gc.setBackground(bg);
1623 gc.setForeground(fg);
1624 newLink.paintFocus(gc, bg, fg, true, null);
1625 }
1626 gc.dispose();
1627 }
1628
1629 private void ensureVisible(IFocusSelectable segment) {
1630 if (mouseFocus) {
1631 mouseFocus = false;
1632 return;
1633 }
1634 if (segment is null)
1635 return;
1636 Rectangle bounds = segment.getBounds();
1637 ScrolledComposite scomp = FormUtil.getScrolledComposite(this);
1638 if (scomp is null)
1639 return;
1640 Point origin = FormUtil.getControlLocation(scomp, this);
1641 origin.x += bounds.x;
1642 origin.y += bounds.y;
1643 FormUtil.ensureVisible(scomp, origin, new Point(bounds.width,
1644 bounds.height));
1645 }
1646
1647 /**
1648 * Overrides the method by fully trusting the layout manager (computed width
1649 * or height may be larger than the provider width or height hints). Callers
1650 * should be prepared that the computed width is larger than the provided
1651 * wHint.
1652 *
1653 * @see org.eclipse.swt.widgets.Composite#computeSize(int, int, bool)
1654 */
1655 public Point computeSize(int wHint, int hHint, bool changed) {
1656 checkWidget();
1657 Point size;
1658 FormTextLayout layout = cast(FormTextLayout) getLayout();
1659 if (wHint is SWT.DEFAULT || hHint is SWT.DEFAULT) {
1660 size = layout.computeSize(this, wHint, hHint, changed);
1661 } else {
1662 size = new Point(wHint, hHint);
1663 }
1664 Rectangle trim = computeTrim(0, 0, size.x, size.y);
1665 if (DEBUG_TEXTSIZE)
1666 Stdout.formatln("FormText Computed size: {}",trim); //$NON-NLS-1$
1667 return new Point(trim.width, trim.height);
1668 }
1669
1670 private void disposeResourceTable(bool disposeBoldFont) {
1671 if (disposeBoldFont) {
1672 Font boldFont = cast(Font) resourceTable
1673 .get(FormTextModel.BOLD_FONT_ID);
1674 if (boldFont !is null) {
1675 FormFonts.getInstance().markFinished(boldFont);
1676 resourceTable.remove(FormTextModel.BOLD_FONT_ID);
1677 }
1678 }
1679 ArrayList imagesToRemove = new ArrayList();
1680 for (Enumeration enm = resourceTable.keys(); enm.hasMoreElements();) {
1681 String key = stringcast( enm.nextElement());
1682 if (key.startsWith(ImageSegment.SEL_IMAGE_PREFIX)) {
1683 Object obj = resourceTable.get(key);
1684 if (auto image = cast(Image)obj ) {
1685 if (!image.isDisposed()) {
1686 image.dispose();
1687 imagesToRemove.add(key);
1688 }
1689 }
1690 }
1691 }
1692 for (int i = 0; i < imagesToRemove.size(); i++) {
1693 resourceTable.remove(imagesToRemove.get(i));
1694 }
1695 }
1696
1697 /*
1698 * (non-Javadoc)
1699 *
1700 * @see org.eclipse.swt.widgets.Control#setEnabled(bool)
1701 */
1702 public void setEnabled(bool enabled) {
1703 super.setEnabled(enabled);
1704 redraw();
1705 }
1706
1707 /* (non-Javadoc)
1708 * @see org.eclipse.swt.widgets.Control#setFocus()
1709 */
1710 public bool setFocus() {
1711 FormUtil.setFocusScrollingEnabled(this, false);
1712 bool result = super.setFocus();
1713 FormUtil.setFocusScrollingEnabled(this, true);
1714 return result;
1715 }
1716 }