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