Mercurial > projects > dwt2
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 } |