comparison dwt/custom/StyledText.d @ 0:380af2bdd8e5

Upload of whole dwt tree
author Jacob Carlborg <doob@me.com> <jacob.carlborg@gmail.com>
date Sat, 09 Aug 2008 17:00:02 +0200
parents
children 1a8b3cb347e0
comparison
equal deleted inserted replaced
-1:000000000000 0:380af2bdd8e5
1 /*******************************************************************************
2 * Copyright (c) 2000, 2007 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 * IBM Corporation - initial API and implementation
10 *******************************************************************************/
11 module dwt.custom;
12
13
14 import java.util.*;
15
16 import dwt.*;
17 import dwt.accessibility.*;
18 import dwt.dnd.*;
19 import dwt.events.*;
20 import dwt.graphics.*;
21 import dwt.internal.*;
22 import dwt.printing.*;
23 import dwt.widgets.*;
24
25 /**
26 * A StyledText is an editable user interface object that displays lines
27 * of text. The following style attributes can be defined for the text:
28 * <ul>
29 * <li>foreground color
30 * <li>background color
31 * <li>font style (bold, italic, bold-italic, regular)
32 * <li>underline
33 * <li>strikeout
34 * </ul>
35 * <p>
36 * In addition to text style attributes, the background color of a line may
37 * be specified.
38 * </p><p>
39 * There are two ways to use this widget when specifying text style information.
40 * You may use the API that is defined for StyledText or you may define your own
41 * LineStyleListener. If you define your own listener, you will be responsible
42 * for maintaining the text style information for the widget. IMPORTANT: You may
43 * not define your own listener and use the StyledText API. The following
44 * StyledText API is not supported if you have defined a LineStyleListener:
45 * <ul>
46 * <li>getStyleRangeAtOffset(int)
47 * <li>getStyleRanges()
48 * <li>replaceStyleRanges(int,int,StyleRange[])
49 * <li>setStyleRange(StyleRange)
50 * <li>setStyleRanges(StyleRange[])
51 * </ul>
52 * </p><p>
53 * There are two ways to use this widget when specifying line background colors.
54 * You may use the API that is defined for StyledText or you may define your own
55 * LineBackgroundListener. If you define your own listener, you will be responsible
56 * for maintaining the line background color information for the widget.
57 * IMPORTANT: You may not define your own listener and use the StyledText API.
58 * The following StyledText API is not supported if you have defined a
59 * LineBackgroundListener:
60 * <ul>
61 * <li>getLineBackground(int)
62 * <li>setLineBackground(int,int,Color)
63 * </ul>
64 * </p><p>
65 * The content implementation for this widget may also be user-defined. To do so,
66 * you must implement the StyledTextContent interface and use the StyledText API
67 * setContent(StyledTextContent) to initialize the widget.
68 * </p><p>
69 * <dl>
70 * <dt><b>Styles:</b><dd>FULL_SELECTION, MULTI, READ_ONLY, SINGLE, WRAP
71 * <dt><b>Events:</b><dd>ExtendedModify, LineGetBackground, LineGetSegments, LineGetStyle, Modify, Selection, Verify, VerifyKey
72 * </dl>
73 * </p><p>
74 * IMPORTANT: This class is <em>not</em> intended to be subclassed.
75 * </p>
76 */
77 public class StyledText : Canvas {
78 static final char TAB = '\t';
79 static final String PlatformLineDelimiter = System.getProperty("line.separator");
80 static final int BIDI_CARET_WIDTH = 3;
81 static final int DEFAULT_WIDTH = 64;
82 static final int DEFAULT_HEIGHT = 64;
83 static final int V_SCROLL_RATE = 50;
84 static final int H_SCROLL_RATE = 10;
85
86 static final int ExtendedModify = 3000;
87 static final int LineGetBackground = 3001;
88 static final int LineGetStyle = 3002;
89 static final int TextChanging = 3003;
90 static final int TextSet = 3004;
91 static final int VerifyKey = 3005;
92 static final int TextChanged = 3006;
93 static final int LineGetSegments = 3007;
94 static final int PaintObject = 3008;
95 static final int WordNext = 3009;
96 static final int WordPrevious = 3010;
97
98 static final int PREVIOUS_OFFSET_TRAILING = 0;
99 static final int OFFSET_LEADING = 1;
100
101 Color selectionBackground; // selection background color
102 Color selectionForeground; // selection foreground color
103 StyledTextContent content; // native content (default or user specified)
104 StyledTextRenderer renderer;
105 Listener listener;
106 TextChangeListener textChangeListener; // listener for TextChanging, TextChanged and TextSet events from StyledTextContent
107 int verticalScrollOffset = 0; // pixel based
108 int horizontalScrollOffset = 0; // pixel based
109 int topIndex = 0; // top visible line
110 int topIndexY;
111 int clientAreaHeight = 0; // the client area height. Needed to calculate content width for new visible lines during Resize callback
112 int clientAreaWidth = 0; // the client area width. Needed during Resize callback to determine if line wrap needs to be recalculated
113 int tabLength = 4; // number of characters in a tab
114 int leftMargin;
115 int topMargin;
116 int rightMargin;
117 int bottomMargin;
118 int columnX; // keep track of the horizontal caret position when changing lines/pages. Fixes bug 5935
119 int caretOffset = 0;
120 int caretAlignment;
121 Point selection = new Point(0, 0); // x and y are start and end caret offsets of selection
122 Point clipboardSelection; // x and y are start and end caret offsets of previous selection
123 int selectionAnchor; // position of selection anchor. 0 based offset from beginning of text
124 Point doubleClickSelection; // selection after last mouse double click
125 bool editable = true;
126 bool wordWrap = false;
127 bool doubleClickEnabled = true; // see getDoubleClickEnabled
128 bool overwrite = false; // insert/overwrite edit mode
129 int textLimit = -1; // limits the number of characters the user can type in the widget. Unlimited by default.
130 Hashtable keyActionMap = new Hashtable();
131 Color background = null; // workaround for bug 4791
132 Color foreground = null; //
133 Clipboard clipboard;
134 int clickCount;
135 int autoScrollDirection = DWT.NULL; // the direction of autoscrolling (up, down, right, left)
136 int autoScrollDistance = 0;
137 int lastTextChangeStart; // cache data of the
138 int lastTextChangeNewLineCount; // last text changing
139 int lastTextChangeNewCharCount; // event for use in the
140 int lastTextChangeReplaceLineCount; // text changed handler
141 int lastTextChangeReplaceCharCount;
142 int lastLineBottom; // the bottom pixel of the last line been replaced
143 bool isMirrored;
144 bool bidiColoring = false; // apply the BIDI algorithm on text segments of the same color
145 Image leftCaretBitmap = null;
146 Image rightCaretBitmap = null;
147 int caretDirection = DWT.NULL;
148 int caretWidth = 0;
149 Caret defaultCaret = null;
150 bool updateCaretDirection = true;
151 bool fixedLineHeight;
152 bool dragDetect = true;
153 IME ime;
154
155 int alignment;
156 bool justify;
157 int indent;
158 int lineSpacing;
159
160 final static bool IS_CARBON, IS_GTK, IS_MOTIF;
161 static {
162 String platform = DWT.getPlatform();
163 IS_CARBON = "carbon".opEquals(platform);
164 IS_GTK = "gtk".opEquals(platform);
165 IS_MOTIF = "motif".opEquals(platform);
166 }
167
168 /**
169 * The Printing class : printing of a range of text.
170 * An instance of <code>Printing</code> is returned in the
171 * StyledText#print(Printer) API. The run() method may be
172 * invoked from any thread.
173 */
174 static class Printing : Runnable {
175 final static int LEFT = 0; // left aligned header/footer segment
176 final static int CENTER = 1; // centered header/footer segment
177 final static int RIGHT = 2; // right aligned header/footer segment
178
179 Printer printer;
180 StyledTextRenderer printerRenderer;
181 StyledTextPrintOptions printOptions;
182 Rectangle clientArea;
183 FontData fontData;
184 Font printerFont;
185 Hashtable resources;
186 int tabLength;
187 GC gc; // printer GC
188 int pageWidth; // width of a printer page in pixels
189 int startPage; // first page to print
190 int endPage; // last page to print
191 int startLine; // first (wrapped) line to print
192 int endLine; // last (wrapped) line to print
193 bool singleLine; // widget single line mode
194 Point selection = null; // selected text
195 bool mirrored; // indicates the printing gc should be mirrored
196 int lineSpacing;
197 int printMargin;
198
199 /**
200 * Creates an instance of <code>Printing</code>.
201 * Copies the widget content and rendering data that needs
202 * to be requested from listeners.
203 * </p>
204 * @param parent StyledText widget to print.
205 * @param printer printer device to print on.
206 * @param printOptions print options
207 */
208 Printing(StyledText styledText, Printer printer, StyledTextPrintOptions printOptions) {
209 this.printer = printer;
210 this.printOptions = printOptions;
211 this.mirrored = (styledText.getStyle() & DWT.MIRRORED) !is 0;
212 singleLine = styledText.isSingleLine();
213 startPage = 1;
214 endPage = Integer.MAX_VALUE;
215 PrinterData data = printer.getPrinterData();
216 if (data.scope is PrinterData.PAGE_RANGE) {
217 startPage = data.startPage;
218 endPage = data.endPage;
219 if (endPage < startPage) {
220 int temp = endPage;
221 endPage = startPage;
222 startPage = temp;
223 }
224 } else if (data.scope is PrinterData.SELECTION) {
225 selection = styledText.getSelectionRange();
226 }
227 printerRenderer = new StyledTextRenderer(printer, null);
228 printerRenderer.setContent(copyContent(styledText.getContent()));
229 cacheLineData(styledText);
230 }
231 /**
232 * Caches all line data that needs to be requested from a listener.
233 * </p>
234 * @param printerContent <code>StyledTextContent</code> to request
235 * line data for.
236 */
237 void cacheLineData(StyledText styledText) {
238 StyledTextRenderer renderer = styledText.renderer;
239 renderer.copyInto(printerRenderer);
240 fontData = styledText.getFont().getFontData()[0];
241 tabLength = styledText.tabLength;
242 int lineCount = printerRenderer.lineCount;
243 if (styledText.isListening(LineGetBackground) || (styledText.isBidi() && styledText.isListening(LineGetSegments)) || styledText.isListening(LineGetStyle)) {
244 StyledTextContent content = printerRenderer.content;
245 for (int i = 0; i < lineCount; i++) {
246 String line = content.getLine(i);
247 int lineOffset = content.getOffsetAtLine(i);
248 StyledTextEvent event = styledText.getLineBackgroundData(lineOffset, line);
249 if (event !is null && event.lineBackground !is null) {
250 printerRenderer.setLineBackground(i, 1, event.lineBackground);
251 }
252 if (styledText.isBidi()) {
253 int[] segments = styledText.getBidiSegments(lineOffset, line);
254 printerRenderer.setLineSegments(i, 1, segments);
255 }
256 event = styledText.getLineStyleData(lineOffset, line);
257 if (event !is null) {
258 printerRenderer.setLineIndent(i, 1, event.indent);
259 printerRenderer.setLineAlignment(i, 1, event.alignment);
260 printerRenderer.setLineJustify(i, 1, event.justify);
261 printerRenderer.setLineBullet(i, 1, event.bullet);
262 StyleRange[] styles = event.styles;
263 if (styles !is null && styles.length > 0) {
264 printerRenderer.setStyleRanges(event.ranges, styles);
265 }
266 }
267 }
268 }
269 Point screenDPI = styledText.getDisplay().getDPI();
270 Point printerDPI = printer.getDPI();
271 resources = new Hashtable ();
272 for (int i = 0; i < lineCount; i++) {
273 Color color = printerRenderer.getLineBackground(i, null);
274 if (color !is null) {
275 if (printOptions.printLineBackground) {
276 Color printerColor = (Color)resources.get(color);
277 if (printerColor is null) {
278 printerColor = new Color (printer, color.getRGB());
279 resources.put(color, printerColor);
280 }
281 printerRenderer.setLineBackground(i, 1, printerColor);
282 } else {
283 printerRenderer.setLineBackground(i, 1, null);
284 }
285 }
286 int indent = printerRenderer.getLineIndent(i, 0);
287 if (indent !is 0) {
288 printerRenderer.setLineIndent(i, 1, indent * printerDPI.x / screenDPI.x);
289 }
290 }
291 StyleRange[] styles = printerRenderer.styles;
292 for (int i = 0; i < printerRenderer.styleCount; i++) {
293 StyleRange style = styles[i];
294 Font font = style.font;
295 if (style.font !is null) {
296 Font printerFont = (Font)resources.get(font);
297 if (printerFont is null) {
298 printerFont = new Font (printer, font.getFontData());
299 resources.put(font, printerFont);
300 }
301 style.font = printerFont;
302 }
303 Color color = style.foreground;
304 if (color !is null) {
305 Color printerColor = (Color)resources.get(color);
306 if (printOptions.printTextForeground) {
307 if (printerColor is null) {
308 printerColor = new Color (printer, color.getRGB());
309 resources.put(color, printerColor);
310 }
311 style.foreground = printerColor;
312 } else {
313 style.foreground = null;
314 }
315 }
316 color = style.background;
317 if (color !is null) {
318 Color printerColor = (Color)resources.get(color);
319 if (printOptions.printTextBackground) {
320 if (printerColor is null) {
321 printerColor = new Color (printer, color.getRGB());
322 resources.put(color, printerColor);
323 }
324 style.background = printerColor;
325 } else {
326 style.background = null;
327 }
328 }
329 if (!printOptions.printTextFontStyle) {
330 style.fontStyle = DWT.NORMAL;
331 }
332 style.rise = style.rise * printerDPI.y / screenDPI.y;
333 GlyphMetrics metrics = style.metrics;
334 if (metrics !is null) {
335 metrics.ascent = metrics.ascent * printerDPI.y / screenDPI.y;
336 metrics.descent = metrics.descent * printerDPI.y / screenDPI.y;
337 metrics.width = metrics.width * printerDPI.x / screenDPI.x;
338 }
339 }
340 lineSpacing = styledText.lineSpacing * printerDPI.y / screenDPI.y;
341 if (printOptions.printLineNumbers) {
342 printMargin = 3 * printerDPI.x / screenDPI.x;
343 }
344 }
345 /**
346 * Copies the text of the specified <code>StyledTextContent</code>.
347 * </p>
348 * @param original the <code>StyledTextContent</code> to copy.
349 */
350 StyledTextContent copyContent(StyledTextContent original) {
351 StyledTextContent printerContent = new DefaultContent();
352 int insertOffset = 0;
353 for (int i = 0; i < original.getLineCount(); i++) {
354 int insertEndOffset;
355 if (i < original.getLineCount() - 1) {
356 insertEndOffset = original.getOffsetAtLine(i + 1);
357 } else {
358 insertEndOffset = original.getCharCount();
359 }
360 printerContent.replaceTextRange(insertOffset, 0, original.getTextRange(insertOffset, insertEndOffset - insertOffset));
361 insertOffset = insertEndOffset;
362 }
363 return printerContent;
364 }
365 /**
366 * Disposes of the resources and the <code>PrintRenderer</code>.
367 */
368 void dispose() {
369 if (gc !is null) {
370 gc.dispose();
371 gc = null;
372 }
373 if (resources !is null) {
374 Enumeration enumeration = resources.elements();
375 while (enumeration.hasMoreElements()) {
376 Resource resource = (Resource) enumeration.nextElement();
377 resource.dispose();
378 }
379 resources = null;
380 }
381 if (printerFont !is null) {
382 printerFont.dispose();
383 printerFont = null;
384 }
385 if (printerRenderer !is null) {
386 printerRenderer.dispose();
387 printerRenderer = null;
388 }
389 }
390 void init() {
391 Rectangle trim = printer.computeTrim(0, 0, 0, 0);
392 Point dpi = printer.getDPI();
393
394 printerFont = new Font(printer, fontData.getName(), fontData.getHeight(), DWT.NORMAL);
395 clientArea = printer.getClientArea();
396 pageWidth = clientArea.width;
397 // one inch margin around text
398 clientArea.x = dpi.x + trim.x;
399 clientArea.y = dpi.y + trim.y;
400 clientArea.width -= (clientArea.x + trim.width);
401 clientArea.height -= (clientArea.y + trim.height);
402
403 int style = mirrored ? DWT.RIGHT_TO_LEFT : DWT.LEFT_TO_RIGHT;
404 gc = new GC(printer, style);
405 gc.setFont(printerFont);
406 printerRenderer.setFont(printerFont, tabLength);
407 int lineHeight = printerRenderer.getLineHeight();
408 if (printOptions.header !is null) {
409 clientArea.y += lineHeight * 2;
410 clientArea.height -= lineHeight * 2;
411 }
412 if (printOptions.footer !is null) {
413 clientArea.height -= lineHeight * 2;
414 }
415
416 // TODO not wrapped
417 StyledTextContent content = printerRenderer.content;
418 startLine = 0;
419 endLine = singleLine ? 0 : content.getLineCount() - 1;
420 PrinterData data = printer.getPrinterData();
421 if (data.scope is PrinterData.PAGE_RANGE) {
422 int pageSize = clientArea.height / lineHeight;//WRONG
423 startLine = (startPage - 1) * pageSize;
424 } else if (data.scope is PrinterData.SELECTION) {
425 startLine = content.getLineAtOffset(selection.x);
426 if (selection.y > 0) {
427 endLine = content.getLineAtOffset(selection.x + selection.y - 1);
428 } else {
429 endLine = startLine - 1;
430 }
431 }
432 }
433 /**
434 * Prints the lines in the specified page range.
435 */
436 void print() {
437 Color background = gc.getBackground();
438 Color foreground = gc.getForeground();
439 int paintY = clientArea.y;
440 int paintX = clientArea.x;
441 int width = clientArea.width;
442 int page = startPage;
443 int pageBottom = clientArea.y + clientArea.height;
444 int orientation = gc.getStyle() & (DWT.RIGHT_TO_LEFT | DWT.LEFT_TO_RIGHT);
445 TextLayout printLayout = null;
446 if (printOptions.printLineNumbers || printOptions.header !is null || printOptions.footer !is null) {
447 printLayout = new TextLayout(printer);
448 printLayout.setFont(printerFont);
449 }
450 if (printOptions.printLineNumbers) {
451 int numberingWidth = 0;
452 int count = endLine - startLine + 1;
453 String[] lineLabels = printOptions.lineLabels;
454 if (lineLabels !is null) {
455 for (int i = startLine; i < Math.min(count, lineLabels.length); i++) {
456 if (lineLabels[i] !is null) {
457 printLayout.setText(lineLabels[i]);
458 int lineWidth = printLayout.getBounds().width;
459 numberingWidth = Math.max(numberingWidth, lineWidth);
460 }
461 }
462 } else {
463 StringBuffer buffer = new StringBuffer("0");
464 while ((count /= 10) > 0) buffer.append("0");
465 printLayout.setText(buffer.toString());
466 numberingWidth = printLayout.getBounds().width;
467 }
468 numberingWidth += printMargin;
469 if (numberingWidth > width) numberingWidth = width;
470 paintX += numberingWidth;
471 width -= numberingWidth;
472 }
473 for (int i = startLine; i <= endLine && page <= endPage; i++) {
474 if (paintY is clientArea.y) {
475 printer.startPage();
476 printDecoration(page, true, printLayout);
477 }
478 TextLayout layout = printerRenderer.getTextLayout(i, orientation, width, lineSpacing);
479 Color lineBackground = printerRenderer.getLineBackground(i, background);
480 int paragraphBottom = paintY + layout.getBounds().height;
481 if (paragraphBottom <= pageBottom) {
482 //normal case, the whole paragraph fits in the current page
483 printLine(paintX, paintY, gc, foreground, lineBackground, layout, printLayout, i);
484 paintY = paragraphBottom;
485 } else {
486 int lineCount = layout.getLineCount();
487 while (paragraphBottom > pageBottom && lineCount > 0) {
488 lineCount--;
489 paragraphBottom -= layout.getLineBounds(lineCount).height + layout.getSpacing();
490 }
491 if (lineCount is 0) {
492 //the whole paragraph goes to the next page
493 printDecoration(page, false, printLayout);
494 printer.endPage();
495 page++;
496 if (page <= endPage) {
497 printer.startPage();
498 printDecoration(page, true, printLayout);
499 paintY = clientArea.y;
500 printLine(paintX, paintY, gc, foreground, lineBackground, layout, printLayout, i);
501 paintY += layout.getBounds().height;
502 }
503 } else {
504 //draw paragraph top in the current page and paragraph bottom in the next
505 int height = paragraphBottom - paintY;
506 gc.setClipping(clientArea.x, paintY, clientArea.width, height);
507 printLine(paintX, paintY, gc, foreground, lineBackground, layout, printLayout, i);
508 gc.setClipping((Rectangle)null);
509 printDecoration(page, false, printLayout);
510 printer.endPage();
511 page++;
512 if (page <= endPage) {
513 printer.startPage();
514 printDecoration(page, true, printLayout);
515 paintY = clientArea.y - height;
516 int layoutHeight = layout.getBounds().height;
517 gc.setClipping(clientArea.x, clientArea.y, clientArea.width, layoutHeight - height);
518 printLine(paintX, paintY, gc, foreground, lineBackground, layout, printLayout, i);
519 gc.setClipping((Rectangle)null);
520 paintY += layoutHeight;
521 }
522 }
523 }
524 printerRenderer.disposeTextLayout(layout);
525 }
526 if (page <= endPage && paintY > clientArea.y) {
527 // close partial page
528 printDecoration(page, false, printLayout);
529 printer.endPage();
530 }
531 if (printLayout !is null) printLayout.dispose();
532 }
533 /**
534 * Print header or footer decorations.
535 *
536 * @param page page number to print, if specified in the StyledTextPrintOptions header or footer.
537 * @param header true = print the header, false = print the footer
538 */
539 void printDecoration(int page, bool header, TextLayout layout) {
540 String text = header ? printOptions.header : printOptions.footer;
541 if (text is null) return;
542 int lastSegmentIndex = 0;
543 for (int i = 0; i < 3; i++) {
544 int segmentIndex = text.indexOf(StyledTextPrintOptions.SEPARATOR, lastSegmentIndex);
545 String segment;
546 if (segmentIndex is -1) {
547 segment = text.substring(lastSegmentIndex);
548 printDecorationSegment(segment, i, page, header, layout);
549 break;
550 } else {
551 segment = text.substring(lastSegmentIndex, segmentIndex);
552 printDecorationSegment(segment, i, page, header, layout);
553 lastSegmentIndex = segmentIndex + StyledTextPrintOptions.SEPARATOR.length();
554 }
555 }
556 }
557 /**
558 * Print one segment of a header or footer decoration.
559 * Headers and footers have three different segments.
560 * One each for left aligned, centered, and right aligned text.
561 *
562 * @param segment decoration segment to print
563 * @param alignment alignment of the segment. 0=left, 1=center, 2=right
564 * @param page page number to print, if specified in the decoration segment.
565 * @param header true = print the header, false = print the footer
566 */
567 void printDecorationSegment(String segment, int alignment, int page, bool header, TextLayout layout) {
568 int pageIndex = segment.indexOf(StyledTextPrintOptions.PAGE_TAG);
569 if (pageIndex !is -1) {
570 int pageTagLength = StyledTextPrintOptions.PAGE_TAG.length();
571 StringBuffer buffer = new StringBuffer(segment.substring (0, pageIndex));
572 buffer.append (page);
573 buffer.append (segment.substring(pageIndex + pageTagLength));
574 segment = buffer.toString();
575 }
576 if (segment.length() > 0) {
577 layout.setText(segment);
578 int segmentWidth = layout.getBounds().width;
579 int segmentHeight = printerRenderer.getLineHeight();
580 int drawX = 0, drawY;
581 if (alignment is LEFT) {
582 drawX = clientArea.x;
583 } else if (alignment is CENTER) {
584 drawX = (pageWidth - segmentWidth) / 2;
585 } else if (alignment is RIGHT) {
586 drawX = clientArea.x + clientArea.width - segmentWidth;
587 }
588 if (header) {
589 drawY = clientArea.y - segmentHeight * 2;
590 } else {
591 drawY = clientArea.y + clientArea.height + segmentHeight;
592 }
593 layout.draw(gc, drawX, drawY);
594 }
595 }
596 void printLine(int x, int y, GC gc, Color foreground, Color background, TextLayout layout, TextLayout printLayout, int index) {
597 if (background !is null) {
598 Rectangle rect = layout.getBounds();
599 gc.setBackground(background);
600 gc.fillRectangle(x, y, rect.width, rect.height);
601
602 // int lineCount = layout.getLineCount();
603 // for (int i = 0; i < lineCount; i++) {
604 // Rectangle rect = layout.getLineBounds(i);
605 // rect.x += paintX;
606 // rect.y += paintY + layout.getSpacing();
607 // rect.width = width;//layout bounds
608 // gc.fillRectangle(rect);
609 // }
610 }
611 if (printOptions.printLineNumbers) {
612 FontMetrics metrics = layout.getLineMetrics(0);
613 printLayout.setAscent(metrics.getAscent() + metrics.getLeading());
614 printLayout.setDescent(metrics.getDescent());
615 String[] lineLabels = printOptions.lineLabels;
616 if (lineLabels !is null) {
617 if (0 <= index && index < lineLabels.length && lineLabels[index] !is null) {
618 printLayout.setText(lineLabels[index]);
619 } else {
620 printLayout.setText("");
621 }
622 } else {
623 printLayout.setText(String.valueOf(index));
624 }
625 int paintX = x - printMargin - printLayout.getBounds().width;
626 printLayout.draw(gc, paintX, y);
627 printLayout.setAscent(-1);
628 printLayout.setDescent(-1);
629 }
630 gc.setForeground(foreground);
631 layout.draw(gc, x, y);
632 }
633 /**
634 * Starts a print job and prints the pages specified in the constructor.
635 */
636 public void run() {
637 String jobName = printOptions.jobName;
638 if (jobName is null) {
639 jobName = "Printing";
640 }
641 if (printer.startJob(jobName)) {
642 init();
643 print();
644 dispose();
645 printer.endJob();
646 }
647 }
648 }
649 /**
650 * The <code>RTFWriter</code> class is used to write widget content as
651 * rich text. The implementation complies with the RTF specification
652 * version 1.5.
653 * <p>
654 * toString() is guaranteed to return a valid RTF String only after
655 * close() has been called.
656 * </p><p>
657 * Whole and partial lines and line breaks can be written. Lines will be
658 * formatted using the styles queried from the LineStyleListener, if
659 * set, or those set directly in the widget. All styles are applied to
660 * the RTF stream like they are rendered by the widget. In addition, the
661 * widget font name and size is used for the whole text.
662 * </p>
663 */
664 class RTFWriter : TextWriter {
665 static final int DEFAULT_FOREGROUND = 0;
666 static final int DEFAULT_BACKGROUND = 1;
667 Vector colorTable, fontTable;
668 bool WriteUnicode;
669
670 /**
671 * Creates a RTF writer that writes content starting at offset "start"
672 * in the document. <code>start</code> and <code>length</code>can be set to specify partial
673 * lines.
674 *
675 * @param start start offset of content to write, 0 based from
676 * beginning of document
677 * @param length length of content to write
678 */
679 public RTFWriter(int start, int length) {
680 super(start, length);
681 colorTable = new Vector();
682 fontTable = new Vector();
683 colorTable.addElement(getForeground());
684 colorTable.addElement(getBackground());
685 fontTable.addElement(getFont());
686 setUnicode();
687 }
688 /**
689 * Closes the RTF writer. Once closed no more content can be written.
690 * <b>NOTE:</b> <code>toString()</code> does not return a valid RTF String until
691 * <code>close()</code> has been called.
692 */
693 public void close() {
694 if (!isClosed()) {
695 writeHeader();
696 write("\n}}\0");
697 super.close();
698 }
699 }
700 /**
701 * Returns the index of the specified color in the RTF color table.
702 *
703 * @param color the color
704 * @param defaultIndex return value if color is null
705 * @return the index of the specified color in the RTF color table
706 * or "defaultIndex" if "color" is null.
707 */
708 int getColorIndex(Color color, int defaultIndex) {
709 if (color is null) return defaultIndex;
710 int index = colorTable.indexOf(color);
711 if (index is -1) {
712 index = colorTable.size();
713 colorTable.addElement(color);
714 }
715 return index;
716 }
717 /**
718 * Returns the index of the specified color in the RTF color table.
719 *
720 * @param color the color
721 * @param defaultIndex return value if color is null
722 * @return the index of the specified color in the RTF color table
723 * or "defaultIndex" if "color" is null.
724 */
725 int getFontIndex(Font font) {
726 int index = fontTable.indexOf(font);
727 if (index is -1) {
728 index = fontTable.size();
729 fontTable.addElement(font);
730 }
731 return index;
732 }
733 /**
734 * Determines if Unicode RTF should be written.
735 * Don't write Unicode RTF on Windows 95/98/ME or NT.
736 */
737 void setUnicode() {
738 final String Win95 = "windows 95";
739 final String Win98 = "windows 98";
740 final String WinME = "windows me";
741 final String WinNT = "windows nt";
742 String osName = System.getProperty("os.name").toLowerCase();
743 String osVersion = System.getProperty("os.version");
744 int majorVersion = 0;
745
746 if (osName.startsWith(WinNT) && osVersion !is null) {
747 int majorIndex = osVersion.indexOf('.');
748 if (majorIndex !is -1) {
749 osVersion = osVersion.substring(0, majorIndex);
750 try {
751 majorVersion = Integer.parseInt(osVersion);
752 } catch (NumberFormatException exception) {
753 // ignore exception. version number remains unknown.
754 // will write without Unicode
755 }
756 }
757 }
758 WriteUnicode = !osName.startsWith(Win95) &&
759 !osName.startsWith(Win98) &&
760 !osName.startsWith(WinME) &&
761 (!osName.startsWith(WinNT) || majorVersion > 4);
762 }
763 /**
764 * Appends the specified segment of "String" to the RTF data.
765 * Copy from <code>start</code> up to, but excluding, <code>end</code>.
766 *
767 * @param String String to copy a segment from. Must not contain
768 * line breaks. Line breaks should be written using writeLineDelimiter()
769 * @param start start offset of segment. 0 based.
770 * @param end end offset of segment
771 */
772 void write(String String, int start, int end) {
773 for (int index = start; index < end; index++) {
774 char ch = String.charAt(index);
775 if (ch > 0xFF && WriteUnicode) {
776 // write the sub String from the last escaped character
777 // to the current one. Fixes bug 21698.
778 if (index > start) {
779 write(String.substring(start, index));
780 }
781 write("\\u");
782 write(Integer.toString((short) ch));
783 write(' '); // control word delimiter
784 start = index + 1;
785 } else if (ch is '}' || ch is '{' || ch is '\\') {
786 // write the sub String from the last escaped character
787 // to the current one. Fixes bug 21698.
788 if (index > start) {
789 write(String.substring(start, index));
790 }
791 write('\\');
792 write(ch);
793 start = index + 1;
794 }
795 }
796 // write from the last escaped character to the end.
797 // Fixes bug 21698.
798 if (start < end) {
799 write(String.substring(start, end));
800 }
801 }
802 /**
803 * Writes the RTF header including font table and color table.
804 */
805 void writeHeader() {
806 StringBuffer header = new StringBuffer();
807 FontData fontData = getFont().getFontData()[0];
808 header.append("{\\rtf1\\ansi");
809 // specify code page, necessary for copy to work in bidi
810 // systems that don't support Unicode RTF.
811 String cpg = System.getProperty("file.encoding").toLowerCase();
812 if (cpg.startsWith("cp") || cpg.startsWith("ms")) {
813 cpg = cpg.substring(2, cpg.length());
814 header.append("\\ansicpg");
815 header.append(cpg);
816 }
817 header.append("\\uc0\\deff0{\\fonttbl{\\f0\\fnil ");
818 header.append(fontData.getName());
819 header.append(";");
820 for (int i = 1; i < fontTable.size(); i++) {
821 header.append("\\f");
822 header.append(i);
823 header.append(" ");
824 FontData fd = ((Font)fontTable.elementAt(i)).getFontData()[0];
825 header.append(fd.getName());
826 header.append(";");
827 }
828 header.append("}}\n{\\colortbl");
829 for (int i = 0; i < colorTable.size(); i++) {
830 Color color = (Color) colorTable.elementAt(i);
831 header.append("\\red");
832 header.append(color.getRed());
833 header.append("\\green");
834 header.append(color.getGreen());
835 header.append("\\blue");
836 header.append(color.getBlue());
837 header.append(";");
838 }
839 // some RTF readers ignore the deff0 font tag. Explicitly
840 // set the font for the whole document to work around this.
841 header.append("}\n{\\f0\\fs");
842 // font size is specified in half points
843 header.append(fontData.getHeight() * 2);
844 header.append(" ");
845 write(header.toString(), 0);
846 }
847 /**
848 * Appends the specified line text to the RTF data. Lines will be formatted
849 * using the styles queried from the LineStyleListener, if set, or those set
850 * directly in the widget.
851 *
852 * @param line line text to write as RTF. Must not contain line breaks
853 * Line breaks should be written using writeLineDelimiter()
854 * @param lineOffset offset of the line. 0 based from the start of the
855 * widget document. Any text occurring before the start offset or after the
856 * end offset specified during object creation is ignored.
857 * @exception DWTException <ul>
858 * <li>ERROR_IO when the writer is closed.</li>
859 * </ul>
860 */
861 public void writeLine(String line, int lineOffset) {
862 if (isClosed()) {
863 DWT.error(DWT.ERROR_IO);
864 }
865 int lineIndex = content.getLineAtOffset(lineOffset);
866 int lineAlignment, lineIndent;
867 bool lineJustify;
868 int[] ranges;
869 StyleRange[] styles;
870 StyledTextEvent event = getLineStyleData(lineOffset, line);
871 if (event !is null) {
872 lineAlignment = event.alignment;
873 lineIndent = event.indent;
874 lineJustify = event.justify;
875 ranges = event.ranges;
876 styles = event.styles;
877 } else {
878 lineAlignment = renderer.getLineAlignment(lineIndex, alignment);
879 lineIndent = renderer.getLineIndent(lineIndex, indent);
880 lineJustify = renderer.getLineJustify(lineIndex, justify);
881 ranges = renderer.getRanges(lineOffset, line.length());
882 styles = renderer.getStyleRanges(lineOffset, line.length(), false);
883 }
884 if (styles is null) styles = new StyleRange[0];
885 Color lineBackground = renderer.getLineBackground(lineIndex, null);
886 event = getLineBackgroundData(lineOffset, line);
887 if (event !is null && event.lineBackground !is null) lineBackground = event.lineBackground;
888 writeStyledLine(line, lineOffset, ranges, styles, lineBackground, lineIndent, lineAlignment, lineJustify);
889 }
890 /**
891 * Appends the specified line delimiter to the RTF data.
892 *
893 * @param lineDelimiter line delimiter to write as RTF.
894 * @exception DWTException <ul>
895 * <li>ERROR_IO when the writer is closed.</li>
896 * </ul>
897 */
898 public void writeLineDelimiter(String lineDelimiter) {
899 if (isClosed()) {
900 DWT.error(DWT.ERROR_IO);
901 }
902 write(lineDelimiter, 0, lineDelimiter.length());
903 write("\\par ");
904 }
905 /**
906 * Appends the specified line text to the RTF data.
907 * <p>
908 * Use the colors and font styles specified in "styles" and "lineBackground".
909 * Formatting is written to reflect the text rendering by the text widget.
910 * Style background colors take precedence over the line background color.
911 * Background colors are written using the \highlight tag (vs. the \cb tag).
912 * </p>
913 *
914 * @param line line text to write as RTF. Must not contain line breaks
915 * Line breaks should be written using writeLineDelimiter()
916 * @param lineOffset offset of the line. 0 based from the start of the
917 * widget document. Any text occurring before the start offset or after the
918 * end offset specified during object creation is ignored.
919 * @param styles styles to use for formatting. Must not be null.
920 * @param lineBackground line background color to use for formatting.
921 * May be null.
922 */
923 void writeStyledLine(String line, int lineOffset, int ranges[], StyleRange[] styles, Color lineBackground, int indent, int alignment, bool justify) {
924 int lineLength = line.length();
925 int startOffset = getStart();
926 int writeOffset = startOffset - lineOffset;
927 if (writeOffset >= lineLength) return;
928 int lineIndex = Math.max(0, writeOffset);
929
930 write("\\fi");
931 write(indent);
932 switch (alignment) {
933 case DWT.LEFT: write("\\ql"); break;
934 case DWT.CENTER: write("\\qc"); break;
935 case DWT.RIGHT: write("\\qr"); break;
936 }
937 if (justify) write("\\qj");
938 write(" ");
939
940 if (lineBackground !is null) {
941 write("{\\highlight");
942 write(getColorIndex(lineBackground, DEFAULT_BACKGROUND));
943 write(" ");
944 }
945 int endOffset = startOffset + super.getCharCount();
946 int lineEndOffset = Math.min(lineLength, endOffset - lineOffset);
947 for (int i = 0; i < styles.length; i++) {
948 StyleRange style = styles[i];
949 int start, end;
950 if (ranges !is null) {
951 start = ranges[i << 1] - lineOffset;
952 end = start + ranges[(i << 1) + 1];
953 } else {
954 start = style.start - lineOffset;
955 end = start + style.length;
956 }
957 // skip over partial first line
958 if (end < writeOffset) {
959 continue;
960 }
961 // style starts beyond line end or RTF write end
962 if (start >= lineEndOffset) {
963 break;
964 }
965 // write any unstyled text
966 if (lineIndex < start) {
967 // copy to start of style
968 // style starting beyond end of write range or end of line
969 // is guarded against above.
970 write(line, lineIndex, start);
971 lineIndex = start;
972 }
973 // write styled text
974 write("{\\cf");
975 write(getColorIndex(style.foreground, DEFAULT_FOREGROUND));
976 int colorIndex = getColorIndex(style.background, DEFAULT_BACKGROUND);
977 if (colorIndex !is DEFAULT_BACKGROUND) {
978 write("\\highlight");
979 write(colorIndex);
980 }
981 Font font = style.font;
982 if (font !is null) {
983 int fontIndex = getFontIndex(font);
984 write("\\f");
985 write(fontIndex);
986 FontData fontData = font.getFontData()[0];
987 write("\\fs");
988 write(fontData.getHeight() * 2);
989 } else {
990 if ((style.fontStyle & DWT.BOLD) !is 0) {
991 write("\\b");
992 }
993 if ((style.fontStyle & DWT.ITALIC) !is 0) {
994 write("\\i");
995 }
996 }
997 if (style.underline) {
998 write("\\ul");
999 }
1000 if (style.strikeout) {
1001 write("\\strike");
1002 }
1003 write(" ");
1004 // copy to end of style or end of write range or end of line
1005 int copyEnd = Math.min(end, lineEndOffset);
1006 // guard against invalid styles and let style processing continue
1007 copyEnd = Math.max(copyEnd, lineIndex);
1008 write(line, lineIndex, copyEnd);
1009 if (font is null) {
1010 if ((style.fontStyle & DWT.BOLD) !is 0) {
1011 write("\\b0");
1012 }
1013 if ((style.fontStyle & DWT.ITALIC) !is 0) {
1014 write("\\i0");
1015 }
1016 }
1017 if (style.underline) {
1018 write("\\ul0");
1019 }
1020 if (style.strikeout) {
1021 write("\\strike0");
1022 }
1023 write("}");
1024 lineIndex = copyEnd;
1025 }
1026 // write unstyled text at the end of the line
1027 if (lineIndex < lineEndOffset) {
1028 write(line, lineIndex, lineEndOffset);
1029 }
1030 if (lineBackground !is null) write("}");
1031 }
1032 }
1033 /**
1034 * The <code>TextWriter</code> class is used to write widget content to
1035 * a String. Whole and partial lines and line breaks can be written. To write
1036 * partial lines, specify the start and length of the desired segment
1037 * during object creation.
1038 * <p>
1039 * </b>NOTE:</b> <code>toString()</code> is guaranteed to return a valid String only after close()
1040 * has been called.
1041 * </p>
1042 */
1043 class TextWriter {
1044 private StringBuffer buffer;
1045 private int startOffset; // offset of first character that will be written
1046 private int endOffset; // offset of last character that will be written.
1047 // 0 based from the beginning of the widget text.
1048 private bool isClosed = false;
1049
1050 /**
1051 * Creates a writer that writes content starting at offset "start"
1052 * in the document. <code>start</code> and <code>length</code> can be set to specify partial lines.
1053 *
1054 * @param start start offset of content to write, 0 based from beginning of document
1055 * @param length length of content to write
1056 */
1057 public TextWriter(int start, int length) {
1058 buffer = new StringBuffer(length);
1059 startOffset = start;
1060 endOffset = start + length;
1061 }
1062 /**
1063 * Closes the writer. Once closed no more content can be written.
1064 * <b>NOTE:</b> <code>toString()</code> is not guaranteed to return a valid String unless
1065 * the writer is closed.
1066 */
1067 public void close() {
1068 if (!isClosed) {
1069 isClosed = true;
1070 }
1071 }
1072 /**
1073 * Returns the number of characters to write.
1074 * @return the integer number of characters to write
1075 */
1076 public int getCharCount() {
1077 return endOffset - startOffset;
1078 }
1079 /**
1080 * Returns the offset where writing starts. 0 based from the start of
1081 * the widget text. Used to write partial lines.
1082 * @return the integer offset where writing starts
1083 */
1084 public int getStart() {
1085 return startOffset;
1086 }
1087 /**
1088 * Returns whether the writer is closed.
1089 * @return a bool specifying whether or not the writer is closed
1090 */
1091 public bool isClosed() {
1092 return isClosed;
1093 }
1094 /**
1095 * Returns the String. <code>close()</code> must be called before <code>toString()</code>
1096 * is guaranteed to return a valid String.
1097 *
1098 * @return the String
1099 */
1100 public String toString() {
1101 return buffer.toString();
1102 }
1103 /**
1104 * Appends the given String to the data.
1105 */
1106 void write(String String) {
1107 buffer.append(String);
1108 }
1109 /**
1110 * Inserts the given String to the data at the specified offset.
1111 * <p>
1112 * Do nothing if "offset" is < 0 or > getCharCount()
1113 * </p>
1114 *
1115 * @param String text to insert
1116 * @param offset offset in the existing data to insert "String" at.
1117 */
1118 void write(String String, int offset) {
1119 if (offset < 0 || offset > buffer.length()) {
1120 return;
1121 }
1122 buffer.insert(offset, String);
1123 }
1124 /**
1125 * Appends the given int to the data.
1126 */
1127 void write(int i) {
1128 buffer.append(i);
1129 }
1130 /**
1131 * Appends the given character to the data.
1132 */
1133 void write(char i) {
1134 buffer.append(i);
1135 }
1136 /**
1137 * Appends the specified line text to the data.
1138 *
1139 * @param line line text to write. Must not contain line breaks
1140 * Line breaks should be written using writeLineDelimiter()
1141 * @param lineOffset offset of the line. 0 based from the start of the
1142 * widget document. Any text occurring before the start offset or after the
1143 * end offset specified during object creation is ignored.
1144 * @exception DWTException <ul>
1145 * <li>ERROR_IO when the writer is closed.</li>
1146 * </ul>
1147 */
1148 public void writeLine(String line, int lineOffset) {
1149 if (isClosed) {
1150 DWT.error(DWT.ERROR_IO);
1151 }
1152 int writeOffset = startOffset - lineOffset;
1153 int lineLength = line.length();
1154 int lineIndex;
1155 if (writeOffset >= lineLength) {
1156 return; // whole line is outside write range
1157 } else if (writeOffset > 0) {
1158 lineIndex = writeOffset; // line starts before write start
1159 } else {
1160 lineIndex = 0;
1161 }
1162 int copyEnd = Math.min(lineLength, endOffset - lineOffset);
1163 if (lineIndex < copyEnd) {
1164 write(line.substring(lineIndex, copyEnd));
1165 }
1166 }
1167 /**
1168 * Appends the specified line delimiter to the data.
1169 *
1170 * @param lineDelimiter line delimiter to write
1171 * @exception DWTException <ul>
1172 * <li>ERROR_IO when the writer is closed.</li>
1173 * </ul>
1174 */
1175 public void writeLineDelimiter(String lineDelimiter) {
1176 if (isClosed) {
1177 DWT.error(DWT.ERROR_IO);
1178 }
1179 write(lineDelimiter);
1180 }
1181 }
1182
1183 /**
1184 * Constructs a new instance of this class given its parent
1185 * and a style value describing its behavior and appearance.
1186 * <p>
1187 * The style value is either one of the style constants defined in
1188 * class <code>DWT</code> which is applicable to instances of this
1189 * class, or must be built by <em>bitwise OR</em>'ing together
1190 * (that is, using the <code>int</code> "|" operator) two or more
1191 * of those <code>DWT</code> style constants. The class description
1192 * lists the style constants that are applicable to the class.
1193 * Style bits are also inherited from superclasses.
1194 * </p>
1195 *
1196 * @param parent a widget which will be the parent of the new instance (cannot be null)
1197 * @param style the style of widget to construct
1198 *
1199 * @exception IllegalArgumentException <ul>
1200 * <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
1201 * </ul>
1202 * @exception DWTException <ul>
1203 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
1204 * </ul>
1205 *
1206 * @see DWT#FULL_SELECTION
1207 * @see DWT#MULTI
1208 * @see DWT#READ_ONLY
1209 * @see DWT#SINGLE
1210 * @see DWT#WRAP
1211 * @see #getStyle
1212 */
1213 public StyledText(Composite parent, int style) {
1214 super(parent, checkStyle(style));
1215 // set the fg in the OS to ensure that these are the same as StyledText, necessary
1216 // for ensuring that the bg/fg the IME box uses is the same as what StyledText uses
1217 super.setForeground(getForeground());
1218 super.setDragDetect(false);
1219 Display display = getDisplay();
1220 isMirrored = (super.getStyle() & DWT.MIRRORED) !is 0;
1221 fixedLineHeight = true;
1222 if ((style & DWT.READ_ONLY) !is 0) {
1223 setEditable(false);
1224 }
1225 leftMargin = rightMargin = isBidiCaret() ? BIDI_CARET_WIDTH - 1: 0;
1226 if ((style & DWT.SINGLE) !is 0 && (style & DWT.BORDER) !is 0) {
1227 leftMargin = topMargin = rightMargin = bottomMargin = 2;
1228 }
1229 alignment = style & (DWT.LEFT | DWT.RIGHT | DWT.CENTER);
1230 if (alignment is 0) alignment = DWT.LEFT;
1231 clipboard = new Clipboard(display);
1232 installDefaultContent();
1233 renderer = new StyledTextRenderer(getDisplay(), this);
1234 renderer.setContent(content);
1235 renderer.setFont(getFont(), tabLength);
1236 ime = new IME(this, DWT.NONE);
1237 defaultCaret = new Caret(this, DWT.NONE);
1238 if ((style & DWT.WRAP) !is 0) {
1239 setWordWrap(true);
1240 }
1241 if (isBidiCaret()) {
1242 createCaretBitmaps();
1243 Runnable runnable = new Runnable() {
1244 public void run() {
1245 int direction = BidiUtil.getKeyboardLanguage() is BidiUtil.KEYBOARD_BIDI ? DWT.RIGHT : DWT.LEFT;
1246 if (direction is caretDirection) return;
1247 if (getCaret() !is defaultCaret) return;
1248 Point newCaretPos = getPointAtOffset(caretOffset);
1249 setCaretLocation(newCaretPos, direction);
1250 }
1251 };
1252 BidiUtil.addLanguageListener(this, runnable);
1253 }
1254 setCaret(defaultCaret);
1255 calculateScrollBars();
1256 createKeyBindings();
1257 setCursor(display.getSystemCursor(DWT.CURSOR_IBEAM));
1258 installListeners();
1259 initializeAccessible();
1260 setData("DEFAULT_DROP_TARGET_EFFECT", new StyledTextDropTargetEffect(this));
1261 }
1262 /**
1263 * Adds an extended modify listener. An ExtendedModify event is sent by the
1264 * widget when the widget text has changed.
1265 *
1266 * @param extendedModifyListener the listener
1267 * @exception DWTException <ul>
1268 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1269 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1270 * </ul>
1271 * @exception IllegalArgumentException <ul>
1272 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
1273 * </ul>
1274 */
1275 public void addExtendedModifyListener(ExtendedModifyListener extendedModifyListener) {
1276 checkWidget();
1277 if (extendedModifyListener is null) DWT.error(DWT.ERROR_NULL_ARGUMENT);
1278 StyledTextListener typedListener = new StyledTextListener(extendedModifyListener);
1279 addListener(ExtendedModify, typedListener);
1280 }
1281 /**
1282 * Adds a bidirectional segment listener.
1283 * <p>
1284 * A BidiSegmentEvent is sent
1285 * whenever a line of text is measured or rendered. The user can
1286 * specify text ranges in the line that should be treated as if they
1287 * had a different direction than the surrounding text.
1288 * This may be used when adjacent segments of right-to-left text should
1289 * not be reordered relative to each other.
1290 * E.g., Multiple Java String literals in a right-to-left language
1291 * should generally remain in logical order to each other, that is, the
1292 * way they are stored.
1293 * </p>
1294 *
1295 * @param listener the listener
1296 * @exception DWTException <ul>
1297 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1298 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1299 * </ul>
1300 * @exception IllegalArgumentException <ul>
1301 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
1302 * </ul>
1303 * @see BidiSegmentEvent
1304 * @since 2.0
1305 */
1306 public void addBidiSegmentListener(BidiSegmentListener listener) {
1307 checkWidget();
1308 if (listener is null) DWT.error(DWT.ERROR_NULL_ARGUMENT);
1309 addListener(LineGetSegments, new StyledTextListener(listener));
1310 }
1311 /**
1312 * Adds a line background listener. A LineGetBackground event is sent by the
1313 * widget to determine the background color for a line.
1314 *
1315 * @param listener the listener
1316 * @exception DWTException <ul>
1317 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1318 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1319 * </ul>
1320 * @exception IllegalArgumentException <ul>
1321 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
1322 * </ul>
1323 */
1324 public void addLineBackgroundListener(LineBackgroundListener listener) {
1325 checkWidget();
1326 if (listener is null) DWT.error(DWT.ERROR_NULL_ARGUMENT);
1327 if (!isListening(LineGetBackground)) {
1328 renderer.clearLineBackground(0, content.getLineCount());
1329 }
1330 addListener(LineGetBackground, new StyledTextListener(listener));
1331 }
1332 /**
1333 * Adds a line style listener. A LineGetStyle event is sent by the widget to
1334 * determine the styles for a line.
1335 *
1336 * @param listener the listener
1337 * @exception DWTException <ul>
1338 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1339 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1340 * </ul>
1341 * @exception IllegalArgumentException <ul>
1342 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
1343 * </ul>
1344 */
1345 public void addLineStyleListener(LineStyleListener listener) {
1346 checkWidget();
1347 if (listener is null) DWT.error(DWT.ERROR_NULL_ARGUMENT);
1348 if (!isListening(LineGetStyle)) {
1349 setStyleRanges(0, 0, null, null, true);
1350 renderer.clearLineStyle(0, content.getLineCount());
1351 }
1352 addListener(LineGetStyle, new StyledTextListener(listener));
1353 }
1354 /**
1355 * Adds a modify listener. A Modify event is sent by the widget when the widget text
1356 * has changed.
1357 *
1358 * @param modifyListener the listener
1359 * @exception DWTException <ul>
1360 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1361 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1362 * </ul>
1363 * @exception IllegalArgumentException <ul>
1364 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
1365 * </ul>
1366 */
1367 public void addModifyListener(ModifyListener modifyListener) {
1368 checkWidget();
1369 if (modifyListener is null) DWT.error(DWT.ERROR_NULL_ARGUMENT);
1370 addListener(DWT.Modify, new TypedListener(modifyListener));
1371 }
1372 /**
1373 * Adds a paint object listener. A paint object event is sent by the widget when an object
1374 * needs to be drawn.
1375 *
1376 * @param listener the listener
1377 * @exception DWTException <ul>
1378 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1379 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1380 * </ul>
1381 * @exception IllegalArgumentException <ul>
1382 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
1383 * </ul>
1384 *
1385 * @since 3.2
1386 *
1387 * @see PaintObjectListener
1388 * @see PaintObjectEvent
1389 */
1390 public void addPaintObjectListener(PaintObjectListener listener) {
1391 checkWidget();
1392 if (listener is null) DWT.error(DWT.ERROR_NULL_ARGUMENT);
1393 addListener(PaintObject, new StyledTextListener(listener));
1394 }
1395 /**
1396 * Adds a selection listener. A Selection event is sent by the widget when the
1397 * user changes the selection.
1398 * <p>
1399 * When <code>widgetSelected</code> is called, the event x and y fields contain
1400 * the start and end caret indices of the selection.
1401 * <code>widgetDefaultSelected</code> is not called for StyledTexts.
1402 * </p>
1403 *
1404 * @param listener the listener which should be notified when the user changes the receiver's selection
1405
1406 * @exception IllegalArgumentException <ul>
1407 * <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
1408 * </ul>
1409 * @exception DWTException <ul>
1410 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1411 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1412 * </ul>
1413 *
1414 * @see SelectionListener
1415 * @see #removeSelectionListener
1416 * @see SelectionEvent
1417 */
1418 public void addSelectionListener(SelectionListener listener) {
1419 checkWidget();
1420 if (listener is null) DWT.error(DWT.ERROR_NULL_ARGUMENT);
1421 addListener(DWT.Selection, new TypedListener(listener));
1422 }
1423 /**
1424 * Adds a verify key listener. A VerifyKey event is sent by the widget when a key
1425 * is pressed. The widget ignores the key press if the listener sets the doit field
1426 * of the event to false.
1427 *
1428 * @param listener the listener
1429 * @exception DWTException <ul>
1430 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1431 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1432 * </ul>
1433 * @exception IllegalArgumentException <ul>
1434 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
1435 * </ul>
1436 */
1437 public void addVerifyKeyListener(VerifyKeyListener listener) {
1438 checkWidget();
1439 if (listener is null) DWT.error(DWT.ERROR_NULL_ARGUMENT);
1440 addListener(VerifyKey, new StyledTextListener(listener));
1441 }
1442 /**
1443 * Adds a verify listener. A Verify event is sent by the widget when the widget text
1444 * is about to change. The listener can set the event text and the doit field to
1445 * change the text that is set in the widget or to force the widget to ignore the
1446 * text change.
1447 *
1448 * @param verifyListener the listener
1449 * @exception DWTException <ul>
1450 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1451 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1452 * </ul>
1453 * @exception IllegalArgumentException <ul>
1454 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
1455 * </ul>
1456 */
1457 public void addVerifyListener(VerifyListener verifyListener) {
1458 checkWidget();
1459 if (verifyListener is null) DWT.error(DWT.ERROR_NULL_ARGUMENT);
1460 addListener(DWT.Verify, new TypedListener(verifyListener));
1461 }
1462 /**
1463 * Adds a word movement listener. A movement event is sent when the boundary
1464 * of a word is needed. For example, this occurs during word next and word
1465 * previous actions.
1466 *
1467 * @param movementListener the listener
1468 * @exception DWTException <ul>
1469 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1470 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1471 * </ul>
1472 * @exception IllegalArgumentException <ul>
1473 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
1474 * </ul>
1475 *
1476 * @see MovementEvent
1477 * @see MovementListener
1478 * @see #removeWordMovementListener
1479 *
1480 * @since 3.3
1481 */
1482 public void addWordMovementListener(MovementListener movementListener) {
1483 checkWidget();
1484 if (listener is null) DWT.error(DWT.ERROR_NULL_ARGUMENT);
1485 addListener(WordNext, new StyledTextListener(movementListener));
1486 addListener(WordPrevious, new StyledTextListener(movementListener));
1487 }
1488 /**
1489 * Appends a String to the text at the end of the widget.
1490 *
1491 * @param String the String to be appended
1492 * @see #replaceTextRange(int,int,String)
1493 * @exception DWTException <ul>
1494 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1495 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1496 * </ul>
1497 * @exception IllegalArgumentException <ul>
1498 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
1499 * </ul>
1500 */
1501 public void append(String String) {
1502 checkWidget();
1503 if (String is null) {
1504 DWT.error(DWT.ERROR_NULL_ARGUMENT);
1505 }
1506 int lastChar = Math.max(getCharCount(), 0);
1507 replaceTextRange(lastChar, 0, String);
1508 }
1509 /**
1510 * Calculates the scroll bars
1511 */
1512 void calculateScrollBars() {
1513 ScrollBar horizontalBar = getHorizontalBar();
1514 ScrollBar verticalBar = getVerticalBar();
1515 setScrollBars(true);
1516 if (verticalBar !is null) {
1517 verticalBar.setIncrement(getVerticalIncrement());
1518 }
1519 if (horizontalBar !is null) {
1520 horizontalBar.setIncrement(getHorizontalIncrement());
1521 }
1522 }
1523 /**
1524 * Calculates the top index based on the current vertical scroll offset.
1525 * The top index is the index of the topmost fully visible line or the
1526 * topmost partially visible line if no line is fully visible.
1527 * The top index starts at 0.
1528 */
1529 void calculateTopIndex(int delta) {
1530 int oldTopIndex = topIndex;
1531 int oldTopIndexY = topIndexY;
1532 if (isFixedLineHeight()) {
1533 int verticalIncrement = getVerticalIncrement();
1534 if (verticalIncrement is 0) {
1535 return;
1536 }
1537 topIndex = Compatibility.ceil(getVerticalScrollOffset(), verticalIncrement);
1538 // Set top index to partially visible top line if no line is fully
1539 // visible but at least some of the widget client area is visible.
1540 // Fixes bug 15088.
1541 if (topIndex > 0) {
1542 if (clientAreaHeight > 0) {
1543 int bottomPixel = getVerticalScrollOffset() + clientAreaHeight;
1544 int fullLineTopPixel = topIndex * verticalIncrement;
1545 int fullLineVisibleHeight = bottomPixel - fullLineTopPixel;
1546 // set top index to partially visible line if no line fully fits in
1547 // client area or if space is available but not used (the latter should
1548 // never happen because we use claimBottomFreeSpace)
1549 if (fullLineVisibleHeight < verticalIncrement) {
1550 topIndex--;
1551 }
1552 } else if (topIndex >= content.getLineCount()) {
1553 topIndex = content.getLineCount() - 1;
1554 }
1555 }
1556 } else {
1557 if (delta >= 0) {
1558 delta -= topIndexY;
1559 int lineIndex = topIndex;
1560 int lineCount = content.getLineCount();
1561 while (lineIndex < lineCount) {
1562 if (delta <= 0) break;
1563 delta -= renderer.getLineHeight(lineIndex++);
1564 }
1565 if (lineIndex < lineCount && -delta + renderer.getLineHeight(lineIndex) <= clientAreaHeight - topMargin - bottomMargin) {
1566 topIndex = lineIndex;
1567 topIndexY = -delta;
1568 } else {
1569 topIndex = lineIndex - 1;
1570 topIndexY = -renderer.getLineHeight(topIndex) - delta;
1571 }
1572 } else {
1573 delta -= topIndexY;
1574 int lineIndex = topIndex;
1575 while (lineIndex > 0) {
1576 int lineHeight = renderer.getLineHeight(lineIndex - 1);
1577 if (delta + lineHeight > 0) break;
1578 delta += lineHeight;
1579 lineIndex--;
1580 }
1581 if (lineIndex is 0 || -delta + renderer.getLineHeight(lineIndex) <= clientAreaHeight - topMargin - bottomMargin) {
1582 topIndex = lineIndex;
1583 topIndexY = - delta;
1584 } else {
1585 topIndex = lineIndex - 1;
1586 topIndexY = - renderer.getLineHeight(topIndex) - delta;
1587 }
1588 }
1589 }
1590 if (topIndex !is oldTopIndex || oldTopIndexY !is topIndexY) {
1591 renderer.calculateClientArea();
1592 setScrollBars(false);
1593 }
1594 }
1595 /**
1596 * Hides the scroll bars if widget is created in single line mode.
1597 */
1598 static int checkStyle(int style) {
1599 if ((style & DWT.SINGLE) !is 0) {
1600 style &= ~(DWT.H_SCROLL | DWT.V_SCROLL | DWT.WRAP | DWT.MULTI);
1601 } else {
1602 style |= DWT.MULTI;
1603 if ((style & DWT.WRAP) !is 0) {
1604 style &= ~DWT.H_SCROLL;
1605 }
1606 }
1607 style |= DWT.NO_REDRAW_RESIZE | DWT.DOUBLE_BUFFERED | DWT.NO_BACKGROUND;
1608 return style;
1609 }
1610 /**
1611 * Scrolls down the text to use new space made available by a resize or by
1612 * deleted lines.
1613 */
1614 void claimBottomFreeSpace() {
1615 int clientAreaHeight = this.clientAreaHeight - topMargin - bottomMargin;
1616 if (isFixedLineHeight()) {
1617 int lineHeight = renderer.getLineHeight();
1618 int newVerticalOffset = Math.max(0, content.getLineCount() * lineHeight - clientAreaHeight);
1619 if (newVerticalOffset < getVerticalScrollOffset()) {
1620 scrollVertical(newVerticalOffset - getVerticalScrollOffset(), true);
1621 }
1622 } else {
1623 int bottomIndex = getPartialBottomIndex();
1624 int height = getLinePixel(bottomIndex + 1);
1625 if (clientAreaHeight > height) {
1626 scrollVertical(-getAvailableHeightAbove(clientAreaHeight - height), true);
1627 }
1628 }
1629 }
1630 /**
1631 * Scrolls text to the right to use new space made available by a resize.
1632 */
1633 void claimRightFreeSpace() {
1634 int newHorizontalOffset = Math.max(0, renderer.getWidth() - (clientAreaWidth - leftMargin - rightMargin));
1635 if (newHorizontalOffset < horizontalScrollOffset) {
1636 // item is no longer drawn past the right border of the client area
1637 // align the right end of the item with the right border of the
1638 // client area (window is scrolled right).
1639 scrollHorizontal(newHorizontalOffset - horizontalScrollOffset, true);
1640 }
1641 }
1642 /**
1643 * Removes the widget selection.
1644 *
1645 * @param sendEvent a Selection event is sent when set to true and when the selection is actually reset.
1646 */
1647 void clearSelection(bool sendEvent) {
1648 int selectionStart = selection.x;
1649 int selectionEnd = selection.y;
1650 resetSelection();
1651 // redraw old selection, if any
1652 if (selectionEnd - selectionStart > 0) {
1653 int length = content.getCharCount();
1654 // called internally to remove selection after text is removed
1655 // therefore make sure redraw range is valid.
1656 int redrawStart = Math.min(selectionStart, length);
1657 int redrawEnd = Math.min(selectionEnd, length);
1658 if (redrawEnd - redrawStart > 0) {
1659 internalRedrawRange(redrawStart, redrawEnd - redrawStart);
1660 }
1661 if (sendEvent) {
1662 sendSelectionEvent();
1663 }
1664 }
1665 }
1666 public Point computeSize (int wHint, int hHint, bool changed) {
1667 checkWidget();
1668 int lineCount = (getStyle() & DWT.SINGLE) !is 0 ? 1 : content.getLineCount();
1669 int width = 0;
1670 int height = 0;
1671 if (wHint is DWT.DEFAULT || hHint is DWT.DEFAULT) {
1672 Display display = getDisplay();
1673 int maxHeight = display.getClientArea().height;
1674 for (int lineIndex = 0; lineIndex < lineCount; lineIndex++) {
1675 TextLayout layout = renderer.getTextLayout(lineIndex);
1676 int wrapWidth = layout.getWidth();
1677 if (wordWrap) layout.setWidth(wHint is 0 ? 1 : wHint);
1678 Rectangle rect = layout.getBounds();
1679 height += rect.height;
1680 width = Math.max(width, rect.width);
1681 layout.setWidth(wrapWidth);
1682 renderer.disposeTextLayout(layout);
1683 if (isFixedLineHeight() && height > maxHeight) break;
1684 }
1685 if (isFixedLineHeight()) {
1686 height = lineCount * renderer.getLineHeight();
1687 }
1688 }
1689 // Use default values if no text is defined.
1690 if (width is 0) width = DEFAULT_WIDTH;
1691 if (height is 0) height = DEFAULT_HEIGHT;
1692 if (wHint !is DWT.DEFAULT) width = wHint;
1693 if (hHint !is DWT.DEFAULT) height = hHint;
1694 int wTrim = leftMargin + rightMargin + getCaretWidth();
1695 int hTrim = topMargin + bottomMargin;
1696 Rectangle rect = computeTrim(0, 0, width + wTrim, height + hTrim);
1697 return new Point (rect.width, rect.height);
1698 }
1699 /**
1700 * Copies the selected text to the <code>DND.CLIPBOARD</code> clipboard.
1701 * <p>
1702 * The text will be put on the clipboard in plain text format and RTF format.
1703 * The <code>DND.CLIPBOARD</code> clipboard is used for data that is
1704 * transferred by keyboard accelerator (such as Ctrl+C/Ctrl+V) or
1705 * by menu action.
1706 * </p>
1707 *
1708 * @exception DWTException <ul>
1709 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1710 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1711 * </ul>
1712 */
1713 public void copy() {
1714 checkWidget();
1715 copy(DND.CLIPBOARD);
1716 }
1717 /**
1718 * Copies the selected text to the specified clipboard. The text will be put in the
1719 * clipboard in plain text format and RTF format.
1720 * <p>
1721 * The clipboardType is one of the clipboard constants defined in class
1722 * <code>DND</code>. The <code>DND.CLIPBOARD</code> clipboard is
1723 * used for data that is transferred by keyboard accelerator (such as Ctrl+C/Ctrl+V)
1724 * or by menu action. The <code>DND.SELECTION_CLIPBOARD</code>
1725 * clipboard is used for data that is transferred by selecting text and pasting
1726 * with the middle mouse button.
1727 * </p>
1728 *
1729 * @param clipboardType indicates the type of clipboard
1730 *
1731 * @exception DWTException <ul>
1732 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1733 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1734 * </ul>
1735 *
1736 * @since 3.1
1737 */
1738 public void copy(int clipboardType) {
1739 checkWidget();
1740 if (clipboardType !is DND.CLIPBOARD && clipboardType !is DND.SELECTION_CLIPBOARD) return;
1741 int length = selection.y - selection.x;
1742 if (length > 0) {
1743 try {
1744 setClipboardContent(selection.x, length, clipboardType);
1745 } catch (DWTError error) {
1746 // Copy to clipboard failed. This happens when another application
1747 // is accessing the clipboard while we copy. Ignore the error.
1748 // Fixes 1GDQAVN
1749 // Rethrow all other errors. Fixes bug 17578.
1750 if (error.code !is DND.ERROR_CANNOT_SET_CLIPBOARD) {
1751 throw error;
1752 }
1753 }
1754 }
1755 }
1756 /**
1757 * Returns the alignment of the widget.
1758 *
1759 * @return the alignment
1760 *
1761 * @exception DWTException <ul>
1762 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1763 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1764 * </ul>
1765 *
1766 * @see #getLineAlignment(int)
1767 *
1768 * @since 3.2
1769 */
1770 public int getAlignment() {
1771 checkWidget();
1772 return alignment;
1773 }
1774 int getAvailableHeightAbove(int height) {
1775 int maxHeight = verticalScrollOffset;
1776 if (maxHeight is -1) {
1777 int lineIndex = topIndex - 1;
1778 maxHeight = -topIndexY;
1779 if (topIndexY > 0) {
1780 maxHeight += renderer.getLineHeight(lineIndex--);
1781 }
1782 while (height > maxHeight && lineIndex >= 0) {
1783 maxHeight += renderer.getLineHeight(lineIndex--);
1784 }
1785 }
1786 return Math.min(height, maxHeight);
1787 }
1788 int getAvailableHeightBellow(int height) {
1789 int partialBottomIndex = getPartialBottomIndex();
1790 int topY = getLinePixel(partialBottomIndex);
1791 int lineHeight = renderer.getLineHeight(partialBottomIndex);
1792 int availableHeight = 0;
1793 int clientAreaHeight = this.clientAreaHeight - topMargin - bottomMargin;
1794 if (topY + lineHeight > clientAreaHeight) {
1795 availableHeight = lineHeight - (clientAreaHeight - topY);
1796 }
1797 int lineIndex = partialBottomIndex + 1;
1798 int lineCount = content.getLineCount();
1799 while (height > availableHeight && lineIndex < lineCount) {
1800 availableHeight += renderer.getLineHeight(lineIndex++);
1801 }
1802 return Math.min(height, availableHeight);
1803 }
1804 /**
1805 * Returns a String that uses only the line delimiter specified by the
1806 * StyledTextContent implementation.
1807 * <p>
1808 * Returns only the first line if the widget has the DWT.SINGLE style.
1809 * </p>
1810 *
1811 * @param text the text that may have line delimiters that don't
1812 * match the model line delimiter. Possible line delimiters
1813 * are CR ('\r'), LF ('\n'), CR/LF ("\r\n")
1814 * @return the converted text that only uses the line delimiter
1815 * specified by the model. Returns only the first line if the widget
1816 * has the DWT.SINGLE style.
1817 */
1818 String getModelDelimitedText(String text) {
1819 int length = text.length();
1820 if (length is 0) {
1821 return text;
1822 }
1823 int crIndex = 0;
1824 int lfIndex = 0;
1825 int i = 0;
1826 StringBuffer convertedText = new StringBuffer(length);
1827 String delimiter = getLineDelimiter();
1828 while (i < length) {
1829 if (crIndex !is -1) {
1830 crIndex = text.indexOf(DWT.CR, i);
1831 }
1832 if (lfIndex !is -1) {
1833 lfIndex = text.indexOf(DWT.LF, i);
1834 }
1835 if (lfIndex is -1 && crIndex is -1) { // no more line breaks?
1836 break;
1837 } else if ((crIndex < lfIndex && crIndex !is -1) || lfIndex is -1) {
1838 convertedText.append(text.substring(i, crIndex));
1839 if (lfIndex is crIndex + 1) { // CR/LF combination?
1840 i = lfIndex + 1;
1841 } else {
1842 i = crIndex + 1;
1843 }
1844 } else { // LF occurs before CR!
1845 convertedText.append(text.substring(i, lfIndex));
1846 i = lfIndex + 1;
1847 }
1848 if (isSingleLine()) {
1849 break;
1850 }
1851 convertedText.append(delimiter);
1852 }
1853 // copy remaining text if any and if not in single line mode or no
1854 // text copied thus far (because there only is one line)
1855 if (i < length && (!isSingleLine() || convertedText.length() is 0)) {
1856 convertedText.append(text.substring(i));
1857 }
1858 return convertedText.toString();
1859 }
1860 bool checkDragDetect(Event event) {
1861 if (!isListening(DWT.DragDetect)) return false;
1862 if (IS_MOTIF) {
1863 if (event.button !is 2) return false;
1864 } else {
1865 if (event.button !is 1) return false;
1866 }
1867 if (selection.x is selection.y) return false;
1868 int offset = getOffsetAtPoint(event.x, event.y, null, true);
1869 if (selection.x <= offset && offset < selection.y) {
1870 return dragDetect(event);
1871 }
1872 return false;
1873 }
1874 /**
1875 * Creates default key bindings.
1876 */
1877 void createKeyBindings() {
1878 int nextKey = isMirrored() ? DWT.ARROW_LEFT : DWT.ARROW_RIGHT;
1879 int previousKey = isMirrored() ? DWT.ARROW_RIGHT : DWT.ARROW_LEFT;
1880
1881 // Navigation
1882 setKeyBinding(DWT.ARROW_UP, ST.LINE_UP);
1883 setKeyBinding(DWT.ARROW_DOWN, ST.LINE_DOWN);
1884 if (IS_CARBON) {
1885 setKeyBinding(previousKey | DWT.MOD1, ST.LINE_START);
1886 setKeyBinding(nextKey | DWT.MOD1, ST.LINE_END);
1887 setKeyBinding(DWT.HOME, ST.TEXT_START);
1888 setKeyBinding(DWT.END, ST.TEXT_END);
1889 setKeyBinding(DWT.ARROW_UP | DWT.MOD1, ST.TEXT_START);
1890 setKeyBinding(DWT.ARROW_DOWN | DWT.MOD1, ST.TEXT_END);
1891 setKeyBinding(nextKey | DWT.MOD3, ST.WORD_NEXT);
1892 setKeyBinding(previousKey | DWT.MOD3, ST.WORD_PREVIOUS);
1893 } else {
1894 setKeyBinding(DWT.HOME, ST.LINE_START);
1895 setKeyBinding(DWT.END, ST.LINE_END);
1896 setKeyBinding(DWT.HOME | DWT.MOD1, ST.TEXT_START);
1897 setKeyBinding(DWT.END | DWT.MOD1, ST.TEXT_END);
1898 setKeyBinding(nextKey | DWT.MOD1, ST.WORD_NEXT);
1899 setKeyBinding(previousKey | DWT.MOD1, ST.WORD_PREVIOUS);
1900 }
1901 setKeyBinding(DWT.PAGE_UP, ST.PAGE_UP);
1902 setKeyBinding(DWT.PAGE_DOWN, ST.PAGE_DOWN);
1903 setKeyBinding(DWT.PAGE_UP | DWT.MOD1, ST.WINDOW_START);
1904 setKeyBinding(DWT.PAGE_DOWN | DWT.MOD1, ST.WINDOW_END);
1905 setKeyBinding(nextKey, ST.COLUMN_NEXT);
1906 setKeyBinding(previousKey, ST.COLUMN_PREVIOUS);
1907
1908 // Selection
1909 setKeyBinding(DWT.ARROW_UP | DWT.MOD2, ST.SELECT_LINE_UP);
1910 setKeyBinding(DWT.ARROW_DOWN | DWT.MOD2, ST.SELECT_LINE_DOWN);
1911 if (IS_CARBON) {
1912 setKeyBinding(previousKey | DWT.MOD1 | DWT.MOD2, ST.SELECT_LINE_START);
1913 setKeyBinding(nextKey | DWT.MOD1 | DWT.MOD2, ST.SELECT_LINE_END);
1914 setKeyBinding(DWT.HOME | DWT.MOD2, ST.SELECT_TEXT_START);
1915 setKeyBinding(DWT.END | DWT.MOD2, ST.SELECT_TEXT_END);
1916 setKeyBinding(DWT.ARROW_UP | DWT.MOD1 | DWT.MOD2, ST.SELECT_TEXT_START);
1917 setKeyBinding(DWT.ARROW_DOWN | DWT.MOD1 | DWT.MOD2, ST.SELECT_TEXT_END);
1918 setKeyBinding(nextKey | DWT.MOD2 | DWT.MOD3, ST.SELECT_WORD_NEXT);
1919 setKeyBinding(previousKey | DWT.MOD2 | DWT.MOD3, ST.SELECT_WORD_PREVIOUS);
1920 } else {
1921 setKeyBinding(DWT.HOME | DWT.MOD2, ST.SELECT_LINE_START);
1922 setKeyBinding(DWT.END | DWT.MOD2, ST.SELECT_LINE_END);
1923 setKeyBinding(DWT.HOME | DWT.MOD1 | DWT.MOD2, ST.SELECT_TEXT_START);
1924 setKeyBinding(DWT.END | DWT.MOD1 | DWT.MOD2, ST.SELECT_TEXT_END);
1925 setKeyBinding(nextKey | DWT.MOD1 | DWT.MOD2, ST.SELECT_WORD_NEXT);
1926 setKeyBinding(previousKey | DWT.MOD1 | DWT.MOD2, ST.SELECT_WORD_PREVIOUS);
1927 }
1928 setKeyBinding(DWT.PAGE_UP | DWT.MOD2, ST.SELECT_PAGE_UP);
1929 setKeyBinding(DWT.PAGE_DOWN | DWT.MOD2, ST.SELECT_PAGE_DOWN);
1930 setKeyBinding(DWT.PAGE_UP | DWT.MOD1 | DWT.MOD2, ST.SELECT_WINDOW_START);
1931 setKeyBinding(DWT.PAGE_DOWN | DWT.MOD1 | DWT.MOD2, ST.SELECT_WINDOW_END);
1932 setKeyBinding(nextKey | DWT.MOD2, ST.SELECT_COLUMN_NEXT);
1933 setKeyBinding(previousKey | DWT.MOD2, ST.SELECT_COLUMN_PREVIOUS);
1934
1935 // Modification
1936 // Cut, Copy, Paste
1937 setKeyBinding('X' | DWT.MOD1, ST.CUT);
1938 setKeyBinding('C' | DWT.MOD1, ST.COPY);
1939 setKeyBinding('V' | DWT.MOD1, ST.PASTE);
1940 if (IS_CARBON) {
1941 setKeyBinding(DWT.DEL | DWT.MOD2, ST.DELETE_NEXT);
1942 setKeyBinding(DWT.BS | DWT.MOD3, ST.DELETE_WORD_PREVIOUS);
1943 setKeyBinding(DWT.DEL | DWT.MOD3, ST.DELETE_WORD_NEXT);
1944 } else {
1945 // Cut, Copy, Paste Wordstar style
1946 setKeyBinding(DWT.DEL | DWT.MOD2, ST.CUT);
1947 setKeyBinding(DWT.INSERT | DWT.MOD1, ST.COPY);
1948 setKeyBinding(DWT.INSERT | DWT.MOD2, ST.PASTE);
1949 }
1950 setKeyBinding(DWT.BS | DWT.MOD2, ST.DELETE_PREVIOUS);
1951 setKeyBinding(DWT.BS, ST.DELETE_PREVIOUS);
1952 setKeyBinding(DWT.DEL, ST.DELETE_NEXT);
1953 setKeyBinding(DWT.BS | DWT.MOD1, ST.DELETE_WORD_PREVIOUS);
1954 setKeyBinding(DWT.DEL | DWT.MOD1, ST.DELETE_WORD_NEXT);
1955
1956 // Miscellaneous
1957 setKeyBinding(DWT.INSERT, ST.TOGGLE_OVERWRITE);
1958 }
1959 /**
1960 * Create the bitmaps to use for the caret in bidi mode. This
1961 * method only needs to be called upon widget creation and when the
1962 * font changes (the caret bitmap height needs to match font height).
1963 */
1964 void createCaretBitmaps() {
1965 int caretWidth = BIDI_CARET_WIDTH;
1966 Display display = getDisplay();
1967 if (leftCaretBitmap !is null) {
1968 if (defaultCaret !is null && leftCaretBitmap.opEquals(defaultCaret.getImage())) {
1969 defaultCaret.setImage(null);
1970 }
1971 leftCaretBitmap.dispose();
1972 }
1973 int lineHeight = renderer.getLineHeight();
1974 leftCaretBitmap = new Image(display, caretWidth, lineHeight);
1975 GC gc = new GC (leftCaretBitmap);
1976 gc.setBackground(display.getSystemColor(DWT.COLOR_BLACK));
1977 gc.fillRectangle(0, 0, caretWidth, lineHeight);
1978 gc.setForeground(display.getSystemColor(DWT.COLOR_WHITE));
1979 gc.drawLine(0,0,0,lineHeight);
1980 gc.drawLine(0,0,caretWidth-1,0);
1981 gc.drawLine(0,1,1,1);
1982 gc.dispose();
1983
1984 if (rightCaretBitmap !is null) {
1985 if (defaultCaret !is null && rightCaretBitmap.opEquals(defaultCaret.getImage())) {
1986 defaultCaret.setImage(null);
1987 }
1988 rightCaretBitmap.dispose();
1989 }
1990 rightCaretBitmap = new Image(display, caretWidth, lineHeight);
1991 gc = new GC (rightCaretBitmap);
1992 gc.setBackground(display.getSystemColor(DWT.COLOR_BLACK));
1993 gc.fillRectangle(0, 0, caretWidth, lineHeight);
1994 gc.setForeground(display.getSystemColor(DWT.COLOR_WHITE));
1995 gc.drawLine(caretWidth-1,0,caretWidth-1,lineHeight);
1996 gc.drawLine(0,0,caretWidth-1,0);
1997 gc.drawLine(caretWidth-1,1,1,1);
1998 gc.dispose();
1999 }
2000 /**
2001 * Moves the selected text to the clipboard. The text will be put in the
2002 * clipboard in plain text format and RTF format.
2003 *
2004 * @exception DWTException <ul>
2005 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
2006 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
2007 * </ul>
2008 */
2009 public void cut(){
2010 checkWidget();
2011 int length = selection.y - selection.x;
2012 if (length > 0) {
2013 try {
2014 setClipboardContent(selection.x, length, DND.CLIPBOARD);
2015 } catch (DWTError error) {
2016 // Copy to clipboard failed. This happens when another application
2017 // is accessing the clipboard while we copy. Ignore the error.
2018 // Fixes 1GDQAVN
2019 // Rethrow all other errors. Fixes bug 17578.
2020 if (error.code !is DND.ERROR_CANNOT_SET_CLIPBOARD) {
2021 throw error;
2022 }
2023 // Abort cut operation if copy to clipboard fails.
2024 // Fixes bug 21030.
2025 return;
2026 }
2027 doDelete();
2028 }
2029 }
2030 /**
2031 * A mouse move event has occurred. See if we should start autoscrolling. If
2032 * the move position is outside of the client area, initiate autoscrolling.
2033 * Otherwise, we've moved back into the widget so end autoscrolling.
2034 */
2035 void doAutoScroll(Event event) {
2036 if (event.y > clientAreaHeight) {
2037 doAutoScroll(DWT.DOWN, event.y - clientAreaHeight);
2038 } else if (event.y < 0) {
2039 doAutoScroll(DWT.UP, -event.y);
2040 } else if (event.x < leftMargin && !wordWrap) {
2041 doAutoScroll(ST.COLUMN_PREVIOUS, leftMargin - event.x);
2042 } else if (event.x > clientAreaWidth - leftMargin - rightMargin && !wordWrap) {
2043 doAutoScroll(ST.COLUMN_NEXT, event.x - (clientAreaWidth - leftMargin - rightMargin));
2044 } else {
2045 endAutoScroll();
2046 }
2047 }
2048 /**
2049 * Initiates autoscrolling.
2050 *
2051 * @param direction DWT.UP, DWT.DOWN, DWT.COLUMN_NEXT, DWT.COLUMN_PREVIOUS
2052 */
2053 void doAutoScroll(int direction, int distance) {
2054 autoScrollDistance = distance;
2055 // If we're already autoscrolling in the given direction do nothing
2056 if (autoScrollDirection is direction) {
2057 return;
2058 }
2059
2060 Runnable timer = null;
2061 final Display display = getDisplay();
2062 // Set a timer that will simulate the user pressing and holding
2063 // down a cursor key (i.e., arrowUp, arrowDown).
2064 if (direction is DWT.UP) {
2065 timer = new Runnable() {
2066 public void run() {
2067 if (autoScrollDirection is DWT.UP) {
2068 doSelectionPageUp(autoScrollDistance);
2069 display.timerExec(V_SCROLL_RATE, this);
2070 }
2071 }
2072 };
2073 autoScrollDirection = direction;
2074 display.timerExec(V_SCROLL_RATE, timer);
2075 } else if (direction is DWT.DOWN) {
2076 timer = new Runnable() {
2077 public void run() {
2078 if (autoScrollDirection is DWT.DOWN) {
2079 doSelectionPageDown(autoScrollDistance);
2080 display.timerExec(V_SCROLL_RATE, this);
2081 }
2082 }
2083 };
2084 autoScrollDirection = direction;
2085 display.timerExec(V_SCROLL_RATE, timer);
2086 } else if (direction is ST.COLUMN_NEXT) {
2087 timer = new Runnable() {
2088 public void run() {
2089 if (autoScrollDirection is ST.COLUMN_NEXT) {
2090 doVisualNext();
2091 setMouseWordSelectionAnchor();
2092 doMouseSelection();
2093 display.timerExec(H_SCROLL_RATE, this);
2094 }
2095 }
2096 };
2097 autoScrollDirection = direction;
2098 display.timerExec(H_SCROLL_RATE, timer);
2099 } else if (direction is ST.COLUMN_PREVIOUS) {
2100 timer = new Runnable() {
2101 public void run() {
2102 if (autoScrollDirection is ST.COLUMN_PREVIOUS) {
2103 doVisualPrevious();
2104 setMouseWordSelectionAnchor();
2105 doMouseSelection();
2106 display.timerExec(H_SCROLL_RATE, this);
2107 }
2108 }
2109 };
2110 autoScrollDirection = direction;
2111 display.timerExec(H_SCROLL_RATE, timer);
2112 }
2113 }
2114 /**
2115 * Deletes the previous character. Delete the selected text if any.
2116 * Move the caret in front of the deleted text.
2117 */
2118 void doBackspace() {
2119 Event event = new Event();
2120 event.text = "";
2121 if (selection.x !is selection.y) {
2122 event.start = selection.x;
2123 event.end = selection.y;
2124 sendKeyEvent(event);
2125 } else if (caretOffset > 0) {
2126 int lineIndex = content.getLineAtOffset(caretOffset);
2127 int lineOffset = content.getOffsetAtLine(lineIndex);
2128 if (caretOffset is lineOffset) {
2129 lineOffset = content.getOffsetAtLine(lineIndex - 1);
2130 event.start = lineOffset + content.getLine(lineIndex - 1).length();
2131 event.end = caretOffset;
2132 } else {
2133 TextLayout layout = renderer.getTextLayout(lineIndex);
2134 int start = layout.getPreviousOffset(caretOffset - lineOffset, DWT.MOVEMENT_CHAR);
2135 renderer.disposeTextLayout(layout);
2136 event.start = start + lineOffset;
2137 event.end = caretOffset;
2138 }
2139 sendKeyEvent(event);
2140 }
2141 }
2142 /**
2143 * Replaces the selection with the character or insert the character at the
2144 * current caret position if no selection exists.
2145 * <p>
2146 * If a carriage return was typed replace it with the line break character
2147 * used by the widget on this platform.
2148 * </p>
2149 *
2150 * @param key the character typed by the user
2151 */
2152 void doContent(char key) {
2153 Event event = new Event();
2154 event.start = selection.x;
2155 event.end = selection.y;
2156 // replace a CR line break with the widget line break
2157 // CR does not make sense on Windows since most (all?) applications
2158 // don't recognize CR as a line break.
2159 if (key is DWT.CR || key is DWT.LF) {
2160 if (!isSingleLine()) {
2161 event.text = getLineDelimiter();
2162 }
2163 } else if (selection.x is selection.y && overwrite && key !is TAB) {
2164 // no selection and overwrite mode is on and the typed key is not a
2165 // tab character (tabs are always inserted without overwriting)?
2166 int lineIndex = content.getLineAtOffset(event.end);
2167 int lineOffset = content.getOffsetAtLine(lineIndex);
2168 String line = content.getLine(lineIndex);
2169 // replace character at caret offset if the caret is not at the
2170 // end of the line
2171 if (event.end < lineOffset + line.length()) {
2172 event.end++;
2173 }
2174 event.text = new String(new char[] {key});
2175 } else {
2176 event.text = new String(new char[] {key});
2177 }
2178 if (event.text !is null) {
2179 if (textLimit > 0 && content.getCharCount() - (event.end - event.start) >= textLimit) {
2180 return;
2181 }
2182 sendKeyEvent(event);
2183 }
2184 }
2185 /**
2186 * Moves the caret after the last character of the widget content.
2187 */
2188 void doContentEnd() {
2189 // place caret at end of first line if receiver is in single
2190 // line mode. fixes 4820.
2191 if (isSingleLine()) {
2192 doLineEnd();
2193 } else {
2194 int length = content.getCharCount();
2195 if (caretOffset < length) {
2196 caretOffset = length;
2197 showCaret();
2198 }
2199 }
2200 }
2201 /**
2202 * Moves the caret in front of the first character of the widget content.
2203 */
2204 void doContentStart() {
2205 if (caretOffset > 0) {
2206 caretOffset = 0;
2207 showCaret();
2208 }
2209 }
2210 /**
2211 * Moves the caret to the start of the selection if a selection exists.
2212 * Otherwise, if no selection exists move the cursor according to the
2213 * cursor selection rules.
2214 *
2215 * @see #doSelectionCursorPrevious
2216 */
2217 void doCursorPrevious() {
2218 if (selection.y - selection.x > 0) {
2219 caretOffset = selection.x;
2220 caretAlignment = OFFSET_LEADING;
2221 showCaret();
2222 } else {
2223 doSelectionCursorPrevious();
2224 }
2225 }
2226 /**
2227 * Moves the caret to the end of the selection if a selection exists.
2228 * Otherwise, if no selection exists move the cursor according to the
2229 * cursor selection rules.
2230 *
2231 * @see #doSelectionCursorNext
2232 */
2233 void doCursorNext() {
2234 if (selection.y - selection.x > 0) {
2235 caretOffset = selection.y;
2236 caretAlignment = PREVIOUS_OFFSET_TRAILING;
2237 showCaret();
2238 } else {
2239 doSelectionCursorNext();
2240 }
2241 }
2242 /**
2243 * Deletes the next character. Delete the selected text if any.
2244 */
2245 void doDelete() {
2246 Event event = new Event();
2247 event.text = "";
2248 if (selection.x !is selection.y) {
2249 event.start = selection.x;
2250 event.end = selection.y;
2251 sendKeyEvent(event);
2252 } else if (caretOffset < content.getCharCount()) {
2253 int line = content.getLineAtOffset(caretOffset);
2254 int lineOffset = content.getOffsetAtLine(line);
2255 int lineLength = content.getLine(line).length();
2256 if (caretOffset is lineOffset + lineLength) {
2257 event.start = caretOffset;
2258 event.end = content.getOffsetAtLine(line + 1);
2259 } else {
2260 event.start = caretOffset;
2261 event.end = getClusterNext(caretOffset, line);
2262 }
2263 sendKeyEvent(event);
2264 }
2265 }
2266 /**
2267 * Deletes the next word.
2268 */
2269 void doDeleteWordNext() {
2270 if (selection.x !is selection.y) {
2271 // if a selection exists, treat the as if
2272 // only the delete key was pressed
2273 doDelete();
2274 } else {
2275 Event event = new Event();
2276 event.text = "";
2277 event.start = caretOffset;
2278 event.end = getWordNext(caretOffset, DWT.MOVEMENT_WORD);
2279 sendKeyEvent(event);
2280 }
2281 }
2282 /**
2283 * Deletes the previous word.
2284 */
2285 void doDeleteWordPrevious() {
2286 if (selection.x !is selection.y) {
2287 // if a selection exists, treat as if
2288 // only the backspace key was pressed
2289 doBackspace();
2290 } else {
2291 Event event = new Event();
2292 event.text = "";
2293 event.start = getWordPrevious(caretOffset, DWT.MOVEMENT_WORD);
2294 event.end = caretOffset;
2295 sendKeyEvent(event);
2296 }
2297 }
2298 /**
2299 * Moves the caret one line down and to the same character offset relative
2300 * to the beginning of the line. Move the caret to the end of the new line
2301 * if the new line is shorter than the character offset.
2302 */
2303 void doLineDown(bool select) {
2304 int caretLine = getCaretLine();
2305 int lineCount = content.getLineCount();
2306 int y = 0;
2307 bool lastLine = false;
2308 if (wordWrap) {
2309 int lineOffset = content.getOffsetAtLine(caretLine);
2310 int offsetInLine = caretOffset - lineOffset;
2311 TextLayout layout = renderer.getTextLayout(caretLine);
2312 int lineIndex = getVisualLineIndex(layout, offsetInLine);
2313 int layoutLineCount = layout.getLineCount();
2314 if (lineIndex is layoutLineCount - 1) {
2315 lastLine = caretLine is lineCount - 1;
2316 caretLine++;
2317 } else {
2318 y = layout.getLineBounds(lineIndex + 1).y;
2319 }
2320 renderer.disposeTextLayout(layout);
2321 } else {
2322 lastLine = caretLine is lineCount - 1;
2323 caretLine++;
2324 }
2325 if (lastLine) {
2326 if (select) caretOffset = content.getCharCount();
2327 } else {
2328 caretOffset = getOffsetAtPoint(columnX, y, caretLine);
2329 }
2330 int oldColumnX = columnX;
2331 int oldHScrollOffset = horizontalScrollOffset;
2332 if (select) {
2333 setMouseWordSelectionAnchor();
2334 // select first and then scroll to reduce flash when key
2335 // repeat scrolls lots of lines
2336 doSelection(ST.COLUMN_NEXT);
2337 }
2338 showCaret();
2339 int hScrollChange = oldHScrollOffset - horizontalScrollOffset;
2340 columnX = oldColumnX + hScrollChange;
2341 }
2342 /**
2343 * Moves the caret to the end of the line.
2344 */
2345 void doLineEnd() {
2346 int caretLine = getCaretLine();
2347 int lineOffset = content.getOffsetAtLine(caretLine);
2348 int lineEndOffset;
2349 if (wordWrap) {
2350 TextLayout layout = renderer.getTextLayout(caretLine);
2351 int offsetInLine = caretOffset - lineOffset;
2352 int lineIndex = getVisualLineIndex(layout, offsetInLine);
2353 int[] offsets = layout.getLineOffsets();
2354 lineEndOffset = lineOffset + offsets[lineIndex + 1];
2355 renderer.disposeTextLayout(layout);
2356 } else {
2357 int lineLength = content.getLine(caretLine).length();
2358 lineEndOffset = lineOffset + lineLength;
2359 }
2360 if (caretOffset < lineEndOffset) {
2361 caretOffset = lineEndOffset;
2362 caretAlignment = PREVIOUS_OFFSET_TRAILING;
2363 showCaret();
2364 }
2365 }
2366 /**
2367 * Moves the caret to the beginning of the line.
2368 */
2369 void doLineStart() {
2370 int caretLine = getCaretLine();
2371 int lineOffset = content.getOffsetAtLine(caretLine);
2372 if (wordWrap) {
2373 TextLayout layout = renderer.getTextLayout(caretLine);
2374 int offsetInLine = caretOffset - lineOffset;
2375 int lineIndex = getVisualLineIndex(layout, offsetInLine);
2376 int[] offsets = layout.getLineOffsets();
2377 lineOffset += offsets[lineIndex];
2378 renderer.disposeTextLayout(layout);
2379 }
2380 if (caretOffset > lineOffset) {
2381 caretOffset = lineOffset;
2382 caretAlignment = OFFSET_LEADING;
2383 showCaret();
2384 }
2385 }
2386 /**
2387 * Moves the caret one line up and to the same character offset relative
2388 * to the beginning of the line. Move the caret to the end of the new line
2389 * if the new line is shorter than the character offset.
2390 */
2391 void doLineUp(bool select) {
2392 int caretLine = getCaretLine(), y = 0;
2393 bool firstLine = false;
2394 if (wordWrap) {
2395 int lineOffset = content.getOffsetAtLine(caretLine);
2396 int offsetInLine = caretOffset - lineOffset;
2397 TextLayout layout = renderer.getTextLayout(caretLine);
2398 int lineIndex = getVisualLineIndex(layout, offsetInLine);
2399 if (lineIndex is 0) {
2400 firstLine = caretLine is 0;
2401 if (!firstLine) {
2402 caretLine--;
2403 y = renderer.getLineHeight(caretLine) - 1;
2404 }
2405 } else {
2406 y = layout.getLineBounds(lineIndex - 1).y;
2407 }
2408 renderer.disposeTextLayout(layout);
2409 } else {
2410 firstLine = caretLine is 0;
2411 caretLine--;
2412 }
2413 if (firstLine) {
2414 if (select) caretOffset = 0;
2415 } else {
2416 caretOffset = getOffsetAtPoint(columnX, y, caretLine);
2417 }
2418 int oldColumnX = columnX;
2419 int oldHScrollOffset = horizontalScrollOffset;
2420 if (select) setMouseWordSelectionAnchor();
2421 showCaret();
2422 if (select) doSelection(ST.COLUMN_PREVIOUS);
2423 int hScrollChange = oldHScrollOffset - horizontalScrollOffset;
2424 columnX = oldColumnX + hScrollChange;
2425 }
2426 /**
2427 * Moves the caret to the specified location.
2428 *
2429 * @param x x location of the new caret position
2430 * @param y y location of the new caret position
2431 * @param select the location change is a selection operation.
2432 * include the line delimiter in the selection
2433 */
2434 void doMouseLocationChange(int x, int y, bool select) {
2435 int line = getLineIndex(y);
2436
2437 updateCaretDirection = true;
2438 // allow caret to be placed below first line only if receiver is
2439 // not in single line mode. fixes 4820.
2440 if (line < 0 || (isSingleLine() && line > 0)) {
2441 return;
2442 }
2443 int oldCaretAlignment = caretAlignment;
2444 int newCaretOffset = getOffsetAtPoint(x, y);
2445
2446 if (doubleClickEnabled && clickCount > 1) {
2447 newCaretOffset = doMouseWordSelect(x, newCaretOffset, line);
2448 }
2449
2450 int newCaretLine = content.getLineAtOffset(newCaretOffset);
2451
2452 // Is the mouse within the left client area border or on
2453 // a different line? If not the autoscroll selection
2454 // could be incorrectly reset. Fixes 1GKM3XS
2455 if (0 <= y && y < clientAreaHeight &&
2456 (0 <= x && x < clientAreaWidth || wordWrap ||
2457 newCaretLine !is content.getLineAtOffset(caretOffset))) {
2458 if (newCaretOffset !is caretOffset || caretAlignment !is oldCaretAlignment) {
2459 caretOffset = newCaretOffset;
2460 if (select) doMouseSelection();
2461 showCaret();
2462 }
2463 }
2464 if (!select) {
2465 caretOffset = newCaretOffset;
2466 clearSelection(true);
2467 }
2468 }
2469 /**
2470 * Updates the selection based on the caret position
2471 */
2472 void doMouseSelection() {
2473 if (caretOffset <= selection.x ||
2474 (caretOffset > selection.x &&
2475 caretOffset < selection.y && selectionAnchor is selection.x)) {
2476 doSelection(ST.COLUMN_PREVIOUS);
2477 } else {
2478 doSelection(ST.COLUMN_NEXT);
2479 }
2480 }
2481 /**
2482 * Returns the offset of the word at the specified offset.
2483 * If the current selection : from high index to low index
2484 * (i.e., right to left, or caret is at left border of selection on
2485 * non-bidi platforms) the start offset of the word preceding the
2486 * selection is returned. If the current selection : from
2487 * low index to high index the end offset of the word following
2488 * the selection is returned.
2489 *
2490 * @param x mouse x location
2491 * @param newCaretOffset caret offset of the mouse cursor location
2492 * @param line line index of the mouse cursor location
2493 */
2494 int doMouseWordSelect(int x, int newCaretOffset, int line) {
2495 // flip selection anchor based on word selection direction from
2496 // base double click. Always do this here (and don't rely on doAutoScroll)
2497 // because auto scroll only does not cover all possible mouse selections
2498 // (e.g., mouse x < 0 && mouse y > caret line y)
2499 if (newCaretOffset < selectionAnchor && selectionAnchor is selection.x) {
2500 selectionAnchor = doubleClickSelection.y;
2501 } else if (newCaretOffset > selectionAnchor && selectionAnchor is selection.y) {
2502 selectionAnchor = doubleClickSelection.x;
2503 }
2504 if (0 <= x && x < clientAreaWidth) {
2505 bool wordSelect = (clickCount & 1) is 0;
2506 if (caretOffset is selection.x) {
2507 if (wordSelect) {
2508 newCaretOffset = getWordPrevious(newCaretOffset, DWT.MOVEMENT_WORD_START);
2509 } else {
2510 newCaretOffset = content.getOffsetAtLine(line);
2511 }
2512 } else {
2513 if (wordSelect) {
2514 newCaretOffset = getWordNext(newCaretOffset, DWT.MOVEMENT_WORD_END);
2515 } else {
2516 int lineEnd = content.getCharCount();
2517 if (line + 1 < content.getLineCount()) {
2518 lineEnd = content.getOffsetAtLine(line + 1);
2519 }
2520 newCaretOffset = lineEnd;
2521 }
2522 }
2523 }
2524 return newCaretOffset;
2525 }
2526 /**
2527 * Scrolls one page down so that the last line (truncated or whole)
2528 * of the current page becomes the fully visible top line.
2529 * <p>
2530 * The caret is scrolled the same number of lines so that its location
2531 * relative to the top line remains the same. The exception is the end
2532 * of the text where a full page scroll is not possible. In this case
2533 * the caret is moved after the last character.
2534 * </p>
2535 *
2536 * @param select whether or not to select the page
2537 */
2538 void doPageDown(bool select, int height) {
2539 if (isSingleLine()) return;
2540 int oldColumnX = columnX;
2541 int oldHScrollOffset = horizontalScrollOffset;
2542 if (isFixedLineHeight()) {
2543 int lineCount = content.getLineCount();
2544 int caretLine = getCaretLine();
2545 if (caretLine < lineCount - 1) {
2546 int lineHeight = renderer.getLineHeight();
2547 int lines = (height is -1 ? clientAreaHeight : height) / lineHeight;
2548 int scrollLines = Math.min(lineCount - caretLine - 1, lines);
2549 // ensure that scrollLines never gets negative and at least one
2550 // line is scrolled. fixes bug 5602.
2551 scrollLines = Math.max(1, scrollLines);
2552 caretOffset = getOffsetAtPoint(columnX, getLinePixel(caretLine + scrollLines));
2553 if (select) {
2554 doSelection(ST.COLUMN_NEXT);
2555 }
2556 // scroll one page down or to the bottom
2557 int verticalMaximum = lineCount * getVerticalIncrement();
2558 int pageSize = clientAreaHeight;
2559 int verticalScrollOffset = getVerticalScrollOffset();
2560 int scrollOffset = verticalScrollOffset + scrollLines * getVerticalIncrement();
2561 if (scrollOffset + pageSize > verticalMaximum) {
2562 scrollOffset = verticalMaximum - pageSize;
2563 }
2564 if (scrollOffset > verticalScrollOffset) {
2565 scrollVertical(scrollOffset - verticalScrollOffset, true);
2566 }
2567 }
2568 } else {
2569 int lineCount = content.getLineCount();
2570 int caretLine = getCaretLine();
2571 int lineIndex, lineHeight;
2572 if (height is -1) {
2573 lineIndex = getPartialBottomIndex();
2574 int topY = getLinePixel(lineIndex);
2575 lineHeight = renderer.getLineHeight(lineIndex);
2576 height = topY;
2577 if (topY + lineHeight <= clientAreaHeight) {
2578 height += lineHeight;
2579 } else {
2580 if (wordWrap) {
2581 TextLayout layout = renderer.getTextLayout(lineIndex);
2582 int y = clientAreaHeight - topY;
2583 for (int i = 0; i < layout.getLineCount(); i++) {
2584 Rectangle bounds = layout.getLineBounds(i);
2585 if (bounds.contains(bounds.x, y)) {
2586 height += bounds.y;
2587 break;
2588 }
2589 }
2590 renderer.disposeTextLayout(layout);
2591 }
2592 }
2593 } else {
2594 lineIndex = getLineIndex(height);
2595 int topLineY = getLinePixel(lineIndex);
2596 if (wordWrap) {
2597 TextLayout layout = renderer.getTextLayout(lineIndex);
2598 int y = height - topLineY;
2599 for (int i = 0; i < layout.getLineCount(); i++) {
2600 Rectangle bounds = layout.getLineBounds(i);
2601 if (bounds.contains(bounds.x, y)) {
2602 height = topLineY + bounds.y + bounds.height;
2603 break;
2604 }
2605 }
2606 renderer.disposeTextLayout(layout);
2607 } else {
2608 height = topLineY + renderer.getLineHeight(lineIndex);
2609 }
2610 }
2611 int caretHeight = height;
2612 if (wordWrap) {
2613 TextLayout layout = renderer.getTextLayout(caretLine);
2614 int offsetInLine = caretOffset - content.getOffsetAtLine(caretLine);
2615 lineIndex = getVisualLineIndex(layout, offsetInLine);
2616 caretHeight += layout.getLineBounds(lineIndex).y;
2617 renderer.disposeTextLayout(layout);
2618 }
2619 lineIndex = caretLine;
2620 lineHeight = renderer.getLineHeight(lineIndex);
2621 while (caretHeight - lineHeight >= 0 && lineIndex < lineCount - 1) {
2622 caretHeight -= lineHeight;
2623 lineHeight = renderer.getLineHeight(++lineIndex);
2624 }
2625 caretOffset = getOffsetAtPoint(columnX, caretHeight, lineIndex);
2626 if (select) doSelection(ST.COLUMN_NEXT);
2627 height = getAvailableHeightBellow(height);
2628 scrollVertical(height, true);
2629 if (height is 0) setCaretLocation();
2630 }
2631 showCaret();
2632 int hScrollChange = oldHScrollOffset - horizontalScrollOffset;
2633 columnX = oldColumnX + hScrollChange;
2634 }
2635 /**
2636 * Moves the cursor to the end of the last fully visible line.
2637 */
2638 void doPageEnd() {
2639 // go to end of line if in single line mode. fixes 5673
2640 if (isSingleLine()) {
2641 doLineEnd();
2642 } else {
2643 int bottomOffset;
2644 if (wordWrap) {
2645 int lineIndex = getPartialBottomIndex();
2646 TextLayout layout = renderer.getTextLayout(lineIndex);
2647 int y = (clientAreaHeight - bottomMargin) - getLinePixel(lineIndex);
2648 int index = layout.getLineCount() - 1;
2649 while (index >= 0) {
2650 Rectangle bounds = layout.getLineBounds(index);
2651 if (y >= bounds.y + bounds.height) break;
2652 index--;
2653 }
2654 if (index is -1 && lineIndex > 0) {
2655 bottomOffset = content.getOffsetAtLine(lineIndex - 1) + content.getLine(lineIndex - 1).length();
2656 } else {
2657 bottomOffset = content.getOffsetAtLine(lineIndex) + Math.max(0, layout.getLineOffsets()[index + 1] - 1);
2658 }
2659 renderer.disposeTextLayout(layout);
2660 } else {
2661 int lineIndex = getBottomIndex();
2662 bottomOffset = content.getOffsetAtLine(lineIndex) + content.getLine(lineIndex).length();
2663 }
2664 if (caretOffset < bottomOffset) {
2665 caretOffset = bottomOffset;
2666 caretAlignment = OFFSET_LEADING;
2667 showCaret();
2668 }
2669 }
2670 }
2671 /**
2672 * Moves the cursor to the beginning of the first fully visible line.
2673 */
2674 void doPageStart() {
2675 int topOffset;
2676 if (wordWrap) {
2677 int y, lineIndex;
2678 if (topIndexY > 0) {
2679 lineIndex = topIndex - 1;
2680 y = renderer.getLineHeight(lineIndex) - topIndexY;
2681 } else {
2682 lineIndex = topIndex;
2683 y = -topIndexY;
2684 }
2685 TextLayout layout = renderer.getTextLayout(lineIndex);
2686 int index = 0;
2687 int lineCount = layout.getLineCount();
2688 while (index < lineCount) {
2689 Rectangle bounds = layout.getLineBounds(index);
2690 if (y <= bounds.y) break;
2691 index++;
2692 }
2693 if (index is lineCount) {
2694 topOffset = content.getOffsetAtLine(lineIndex + 1);
2695 } else {
2696 topOffset = content.getOffsetAtLine(lineIndex) + layout.getLineOffsets()[index];
2697 }
2698 renderer.disposeTextLayout(layout);
2699 } else {
2700 topOffset = content.getOffsetAtLine(topIndex);
2701 }
2702 if (caretOffset > topOffset) {
2703 caretOffset = topOffset;
2704 caretAlignment = OFFSET_LEADING;
2705 showCaret();
2706 }
2707 }
2708 /**
2709 * Scrolls one page up so that the first line (truncated or whole)
2710 * of the current page becomes the fully visible last line.
2711 * The caret is scrolled the same number of lines so that its location
2712 * relative to the top line remains the same. The exception is the beginning
2713 * of the text where a full page scroll is not possible. In this case the
2714 * caret is moved in front of the first character.
2715 */
2716 void doPageUp(bool select, int height) {
2717 if (isSingleLine()) return;
2718 int oldHScrollOffset = horizontalScrollOffset;
2719 int oldColumnX = columnX;
2720 if (isFixedLineHeight()) {
2721 int caretLine = getCaretLine();
2722 if (caretLine > 0) {
2723 int lineHeight = renderer.getLineHeight();
2724 int lines = (height is -1 ? clientAreaHeight : height) / lineHeight;
2725 int scrollLines = Math.max(1, Math.min(caretLine, lines));
2726 caretLine -= scrollLines;
2727 caretOffset = getOffsetAtPoint(columnX, getLinePixel(caretLine));
2728 if (select) {
2729 doSelection(ST.COLUMN_PREVIOUS);
2730 }
2731 int verticalScrollOffset = getVerticalScrollOffset();
2732 int scrollOffset = Math.max(0, verticalScrollOffset - scrollLines * getVerticalIncrement());
2733 if (scrollOffset < verticalScrollOffset) {
2734 scrollVertical(scrollOffset - verticalScrollOffset, true);
2735 }
2736 }
2737 } else {
2738 int caretLine = getCaretLine();
2739 int lineHeight, lineIndex;
2740 if (height is -1) {
2741 if (topIndexY is 0) {
2742 height = clientAreaHeight;
2743 } else {
2744 int y;
2745 if (topIndex > 0) {
2746 lineIndex = topIndex - 1;
2747 lineHeight = renderer.getLineHeight(lineIndex);
2748 height = clientAreaHeight - topIndexY;
2749 y = lineHeight - topIndexY;
2750 } else {
2751 lineIndex = topIndex;
2752 lineHeight = renderer.getLineHeight(lineIndex);
2753 height = clientAreaHeight - (lineHeight + topIndexY);
2754 y = -topIndexY;
2755 }
2756 if (wordWrap) {
2757 TextLayout layout = renderer.getTextLayout(lineIndex);
2758 for (int i = 0; i < layout.getLineCount(); i++) {
2759 Rectangle bounds = layout.getLineBounds(i);
2760 if (bounds.contains(bounds.x, y)) {
2761 height += lineHeight - (bounds.y + bounds.height);
2762 break;
2763 }
2764 }
2765 renderer.disposeTextLayout(layout);
2766 }
2767 }
2768 } else {
2769 lineIndex = getLineIndex(clientAreaHeight - height);
2770 int topLineY = getLinePixel(lineIndex);
2771 if (wordWrap) {
2772 TextLayout layout = renderer.getTextLayout(lineIndex);
2773 int y = topLineY;
2774 for (int i = 0; i < layout.getLineCount(); i++) {
2775 Rectangle bounds = layout.getLineBounds(i);
2776 if (bounds.contains(bounds.x, y)) {
2777 height = clientAreaHeight - (topLineY + bounds.y);
2778 break;
2779 }
2780 }
2781 renderer.disposeTextLayout(layout);
2782 } else {
2783 height = clientAreaHeight - topLineY;
2784 }
2785 }
2786 int caretHeight = height;
2787 if (wordWrap) {
2788 TextLayout layout = renderer.getTextLayout(caretLine);
2789 int offsetInLine = caretOffset - content.getOffsetAtLine(caretLine);
2790 lineIndex = getVisualLineIndex(layout, offsetInLine);
2791 caretHeight += layout.getBounds().height - layout.getLineBounds(lineIndex).y;
2792 renderer.disposeTextLayout(layout);
2793 }
2794 lineIndex = caretLine;
2795 lineHeight = renderer.getLineHeight(lineIndex);
2796 while (caretHeight - lineHeight >= 0 && lineIndex > 0) {
2797 caretHeight -= lineHeight;
2798 lineHeight = renderer.getLineHeight(--lineIndex);
2799 }
2800 lineHeight = renderer.getLineHeight(lineIndex);
2801 caretOffset = getOffsetAtPoint(columnX, lineHeight - caretHeight, lineIndex);
2802 if (select) doSelection(ST.COLUMN_PREVIOUS);
2803 height = getAvailableHeightAbove(height);
2804 scrollVertical(-height, true);
2805 if (height is 0) setCaretLocation();
2806 }
2807 showCaret();
2808 int hScrollChange = oldHScrollOffset - horizontalScrollOffset;
2809 columnX = oldColumnX + hScrollChange;
2810 }
2811 /**
2812 * Updates the selection to extend to the current caret position.
2813 */
2814 void doSelection(int direction) {
2815 int redrawStart = -1;
2816 int redrawEnd = -1;
2817 if (selectionAnchor is -1) {
2818 selectionAnchor = selection.x;
2819 }
2820 if (direction is ST.COLUMN_PREVIOUS) {
2821 if (caretOffset < selection.x) {
2822 // grow selection
2823 redrawEnd = selection.x;
2824 redrawStart = selection.x = caretOffset;
2825 // check if selection has reversed direction
2826 if (selection.y !is selectionAnchor) {
2827 redrawEnd = selection.y;
2828 selection.y = selectionAnchor;
2829 }
2830 // test whether selection actually changed. Fixes 1G71EO1
2831 } else if (selectionAnchor is selection.x && caretOffset < selection.y) {
2832 // caret moved towards selection anchor (left side of selection).
2833 // shrink selection
2834 redrawEnd = selection.y;
2835 redrawStart = selection.y = caretOffset;
2836 }
2837 } else {
2838 if (caretOffset > selection.y) {
2839 // grow selection
2840 redrawStart = selection.y;
2841 redrawEnd = selection.y = caretOffset;
2842 // check if selection has reversed direction
2843 if (selection.x !is selectionAnchor) {
2844 redrawStart = selection.x;
2845 selection.x = selectionAnchor;
2846 }
2847 // test whether selection actually changed. Fixes 1G71EO1
2848 } else if (selectionAnchor is selection.y && caretOffset > selection.x) {
2849 // caret moved towards selection anchor (right side of selection).
2850 // shrink selection
2851 redrawStart = selection.x;
2852 redrawEnd = selection.x = caretOffset;
2853 }
2854 }
2855 if (redrawStart !is -1 && redrawEnd !is -1) {
2856 internalRedrawRange(redrawStart, redrawEnd - redrawStart);
2857 sendSelectionEvent();
2858 }
2859 }
2860 /**
2861 * Moves the caret to the next character or to the beginning of the
2862 * next line if the cursor is at the end of a line.
2863 */
2864 void doSelectionCursorNext() {
2865 int caretLine = getCaretLine();
2866 int lineOffset = content.getOffsetAtLine(caretLine);
2867 int offsetInLine = caretOffset - lineOffset;
2868 if (offsetInLine < content.getLine(caretLine).length()) {
2869 TextLayout layout = renderer.getTextLayout(caretLine);
2870 offsetInLine = layout.getNextOffset(offsetInLine, DWT.MOVEMENT_CLUSTER);
2871 int lineStart = layout.getLineOffsets()[layout.getLineIndex(offsetInLine)];
2872 renderer.disposeTextLayout(layout);
2873 caretOffset = offsetInLine + lineOffset;
2874 caretAlignment = offsetInLine is lineStart ? OFFSET_LEADING : PREVIOUS_OFFSET_TRAILING;
2875 showCaret();
2876 } else if (caretLine < content.getLineCount() - 1 && !isSingleLine()) {
2877 caretLine++;
2878 caretOffset = content.getOffsetAtLine(caretLine);
2879 caretAlignment = PREVIOUS_OFFSET_TRAILING;
2880 showCaret();
2881 }
2882 }
2883 /**
2884 * Moves the caret to the previous character or to the end of the previous
2885 * line if the cursor is at the beginning of a line.
2886 */
2887 void doSelectionCursorPrevious() {
2888 int caretLine = getCaretLine();
2889 int lineOffset = content.getOffsetAtLine(caretLine);
2890 int offsetInLine = caretOffset - lineOffset;
2891 caretAlignment = OFFSET_LEADING;
2892 if (offsetInLine > 0) {
2893 caretOffset = getClusterPrevious(caretOffset, caretLine);
2894 showCaret();
2895 } else if (caretLine > 0) {
2896 caretLine--;
2897 lineOffset = content.getOffsetAtLine(caretLine);
2898 caretOffset = lineOffset + content.getLine(caretLine).length();
2899 showCaret();
2900 }
2901 }
2902 /**
2903 * Moves the caret one line down and to the same character offset relative
2904 * to the beginning of the line. Moves the caret to the end of the new line
2905 * if the new line is shorter than the character offset.
2906 * Moves the caret to the end of the text if the caret already is on the
2907 * last line.
2908 * Adjusts the selection according to the caret change. This can either add
2909 * to or subtract from the old selection, depending on the previous selection
2910 * direction.
2911 */
2912 void doSelectionLineDown() {
2913 int oldColumnX = columnX = getPointAtOffset(caretOffset).x;
2914 doLineDown(true);
2915 columnX = oldColumnX;
2916 }
2917 /**
2918 * Moves the caret one line up and to the same character offset relative
2919 * to the beginning of the line. Moves the caret to the end of the new line
2920 * if the new line is shorter than the character offset.
2921 * Moves the caret to the beginning of the document if it is already on the
2922 * first line.
2923 * Adjusts the selection according to the caret change. This can either add
2924 * to or subtract from the old selection, depending on the previous selection
2925 * direction.
2926 */
2927 void doSelectionLineUp() {
2928 int oldColumnX = columnX = getPointAtOffset(caretOffset).x;
2929 doLineUp(true);
2930 columnX = oldColumnX;
2931 }
2932 /**
2933 * Scrolls one page down so that the last line (truncated or whole)
2934 * of the current page becomes the fully visible top line.
2935 * <p>
2936 * The caret is scrolled the same number of lines so that its location
2937 * relative to the top line remains the same. The exception is the end
2938 * of the text where a full page scroll is not possible. In this case
2939 * the caret is moved after the last character.
2940 * <p></p>
2941 * Adjusts the selection according to the caret change. This can either add
2942 * to or subtract from the old selection, depending on the previous selection
2943 * direction.
2944 * </p>
2945 */
2946 void doSelectionPageDown(int pixels) {
2947 int oldColumnX = columnX = getPointAtOffset(caretOffset).x;
2948 doPageDown(true, pixels);
2949 columnX = oldColumnX;
2950 }
2951 /**
2952 * Scrolls one page up so that the first line (truncated or whole)
2953 * of the current page becomes the fully visible last line.
2954 * <p>
2955 * The caret is scrolled the same number of lines so that its location
2956 * relative to the top line remains the same. The exception is the beginning
2957 * of the text where a full page scroll is not possible. In this case the
2958 * caret is moved in front of the first character.
2959 * </p><p>
2960 * Adjusts the selection according to the caret change. This can either add
2961 * to or subtract from the old selection, depending on the previous selection
2962 * direction.
2963 * </p>
2964 */
2965 void doSelectionPageUp(int pixels) {
2966 int oldColumnX = columnX = getPointAtOffset(caretOffset).x;
2967 doPageUp(true, pixels);
2968 columnX = oldColumnX;
2969 }
2970 /**
2971 * Moves the caret to the end of the next word .
2972 */
2973 void doSelectionWordNext() {
2974 int newCaretOffset = getWordNext(caretOffset, DWT.MOVEMENT_WORD);
2975 // Force symmetrical movement for word next and previous. Fixes 14536
2976 caretAlignment = OFFSET_LEADING;
2977 // don't change caret position if in single line mode and the cursor
2978 // would be on a different line. fixes 5673
2979 if (!isSingleLine() ||
2980 content.getLineAtOffset(caretOffset) is content.getLineAtOffset(newCaretOffset)) {
2981 caretOffset = newCaretOffset;
2982 showCaret();
2983 }
2984 }
2985 /**
2986 * Moves the caret to the start of the previous word.
2987 */
2988 void doSelectionWordPrevious() {
2989 caretAlignment = OFFSET_LEADING;
2990 caretOffset = getWordPrevious(caretOffset, DWT.MOVEMENT_WORD);
2991 int caretLine = content.getLineAtOffset(caretOffset);
2992 // word previous always comes from bottom line. when
2993 // wrapping lines, stay on bottom line when on line boundary
2994 if (wordWrap && caretLine < content.getLineCount() - 1 &&
2995 caretOffset is content.getOffsetAtLine(caretLine + 1)) {
2996 caretLine++;
2997 }
2998 showCaret();
2999 }
3000 /**
3001 * Moves the caret one character to the left. Do not go to the previous line.
3002 * When in a bidi locale and at a R2L character the caret is moved to the
3003 * beginning of the R2L segment (visually right) and then one character to the
3004 * left (visually left because it's now in a L2R segment).
3005 */
3006 void doVisualPrevious() {
3007 caretOffset = getClusterPrevious(caretOffset, getCaretLine());
3008 showCaret();
3009 }
3010 /**
3011 * Moves the caret one character to the right. Do not go to the next line.
3012 * When in a bidi locale and at a R2L character the caret is moved to the
3013 * end of the R2L segment (visually left) and then one character to the
3014 * right (visually right because it's now in a L2R segment).
3015 */
3016 void doVisualNext() {
3017 caretOffset = getClusterNext(caretOffset, getCaretLine());
3018 showCaret();
3019 }
3020 /**
3021 * Moves the caret to the end of the next word.
3022 * If a selection exists, move the caret to the end of the selection
3023 * and remove the selection.
3024 */
3025 void doWordNext() {
3026 if (selection.y - selection.x > 0) {
3027 caretOffset = selection.y;
3028 showCaret();
3029 } else {
3030 doSelectionWordNext();
3031 }
3032 }
3033 /**
3034 * Moves the caret to the start of the previous word.
3035 * If a selection exists, move the caret to the start of the selection
3036 * and remove the selection.
3037 */
3038 void doWordPrevious() {
3039 if (selection.y - selection.x > 0) {
3040 caretOffset = selection.x;
3041 showCaret();
3042 } else {
3043 doSelectionWordPrevious();
3044 }
3045 }
3046 /**
3047 * Ends the autoscroll process.
3048 */
3049 void endAutoScroll() {
3050 autoScrollDirection = DWT.NULL;
3051 }
3052 public Color getBackground() {
3053 checkWidget();
3054 if (background is null) {
3055 return getDisplay().getSystemColor(DWT.COLOR_LIST_BACKGROUND);
3056 }
3057 return background;
3058 }
3059 /**
3060 * Returns the baseline, in pixels.
3061 *
3062 * Note: this API should not be used if a StyleRange attribute causes lines to
3063 * have different heights (i.e. different fonts, rise, etc).
3064 *
3065 * @return baseline the baseline
3066 * @exception DWTException <ul>
3067 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3068 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3069 * </ul>
3070 * @since 3.0
3071 *
3072 * @see #getBaseline(int)
3073 */
3074 public int getBaseline() {
3075 checkWidget();
3076 return renderer.getBaseline();
3077 }
3078 /**
3079 * Returns the baseline at the given offset, in pixels.
3080 *
3081 * @param offset the offset
3082 *
3083 * @return baseline the baseline
3084 *
3085 * @exception DWTException <ul>
3086 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3087 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3088 * </ul>
3089 * @exception IllegalArgumentException <ul>
3090 * <li>ERROR_INVALID_RANGE when the offset is outside the valid range (< 0 or > getCharCount())</li>
3091 * </ul>
3092 *
3093 * @since 3.2
3094 */
3095 public int getBaseline(int offset) {
3096 checkWidget();
3097 if (!(0 <= offset && offset <= content.getCharCount())) {
3098 DWT.error(DWT.ERROR_INVALID_RANGE);
3099 }
3100 if (isFixedLineHeight()) {
3101 return renderer.getBaseline();
3102 }
3103 int lineIndex = content.getLineAtOffset(offset);
3104 int lineOffset = content.getOffsetAtLine(lineIndex);
3105 TextLayout layout = renderer.getTextLayout(lineIndex);
3106 int lineInParagraph = layout.getLineIndex(Math.min(offset - lineOffset, layout.getText().length()));
3107 FontMetrics metrics = layout.getLineMetrics(lineInParagraph);
3108 renderer.disposeTextLayout(layout);
3109 return metrics.getAscent() + metrics.getLeading();
3110 }
3111 /**
3112 * Gets the BIDI coloring mode. When true the BIDI text display
3113 * algorithm is applied to segments of text that are the same
3114 * color.
3115 *
3116 * @return the current coloring mode
3117 * @exception DWTException <ul>
3118 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3119 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3120 * </ul>
3121 *
3122 * @deprecated use BidiSegmentListener instead.
3123 */
3124 public bool getBidiColoring() {
3125 checkWidget();
3126 return bidiColoring;
3127 }
3128 /**
3129 * Returns the index of the last fully visible line.
3130 *
3131 * @return index of the last fully visible line.
3132 */
3133 int getBottomIndex() {
3134 int bottomIndex;
3135 if (isFixedLineHeight()) {
3136 int lineCount = 1;
3137 int lineHeight = renderer.getLineHeight();
3138 if (lineHeight !is 0) {
3139 // calculate the number of lines that are fully visible
3140 int partialTopLineHeight = topIndex * lineHeight - getVerticalScrollOffset();
3141 lineCount = (clientAreaHeight - partialTopLineHeight) / lineHeight;
3142 }
3143 bottomIndex = Math.min(content.getLineCount() - 1, topIndex + Math.max(0, lineCount - 1));
3144 } else {
3145 int clientAreaHeight = this.clientAreaHeight - bottomMargin;
3146 bottomIndex = getLineIndex(clientAreaHeight);
3147 if (bottomIndex > 0) {
3148 int linePixel = getLinePixel(bottomIndex);
3149 int lineHeight = renderer.getLineHeight(bottomIndex);
3150 if (linePixel + lineHeight > clientAreaHeight) {
3151 if (getLinePixel(bottomIndex - 1) >= topMargin) {
3152 bottomIndex--;
3153 }
3154 }
3155 }
3156 }
3157 return bottomIndex;
3158 }
3159 Rectangle getBoundsAtOffset(int offset) {
3160 int lineIndex = content.getLineAtOffset(offset);
3161 int lineOffset = content.getOffsetAtLine(lineIndex);
3162 String line = content.getLine(lineIndex);
3163 Rectangle bounds;
3164 if (line.length() !is 0) {
3165 int offsetInLine = offset - lineOffset;
3166 TextLayout layout = renderer.getTextLayout(lineIndex);
3167 bounds = layout.getBounds(offsetInLine, offsetInLine);
3168 renderer.disposeTextLayout(layout);
3169 } else {
3170 bounds = new Rectangle (0, 0, 0, renderer.getLineHeight());
3171 }
3172 if (offset is caretOffset) {
3173 int lineEnd = lineOffset + line.length();
3174 if (offset is lineEnd && caretAlignment is PREVIOUS_OFFSET_TRAILING) {
3175 bounds.width += getCaretWidth();
3176 }
3177 }
3178 bounds.x += leftMargin - horizontalScrollOffset;
3179 bounds.y += getLinePixel(lineIndex);
3180 return bounds;
3181 }
3182 /**
3183 * Returns the caret position relative to the start of the text.
3184 *
3185 * @return the caret position relative to the start of the text.
3186 * @exception DWTException <ul>
3187 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3188 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3189 * </ul>
3190 */
3191 public int getCaretOffset() {
3192 checkWidget();
3193 return caretOffset;
3194 }
3195 /**
3196 * Returns the caret width.
3197 *
3198 * @return the caret width, 0 if caret is null.
3199 */
3200 int getCaretWidth() {
3201 Caret caret = getCaret();
3202 if (caret is null) return 0;
3203 return caret.getSize().x;
3204 }
3205 Object getClipboardContent(int clipboardType) {
3206 TextTransfer plainTextTransfer = TextTransfer.getInstance();
3207 return clipboard.getContents(plainTextTransfer, clipboardType);
3208 }
3209 int getClusterNext(int offset, int lineIndex) {
3210 int lineOffset = content.getOffsetAtLine(lineIndex);
3211 TextLayout layout = renderer.getTextLayout(lineIndex);
3212 offset -= lineOffset;
3213 offset = layout.getNextOffset(offset, DWT.MOVEMENT_CLUSTER);
3214 offset += lineOffset;
3215 renderer.disposeTextLayout(layout);
3216 return offset;
3217 }
3218 int getClusterPrevious(int offset, int lineIndex) {
3219 int lineOffset = content.getOffsetAtLine(lineIndex);
3220 TextLayout layout = renderer.getTextLayout(lineIndex);
3221 offset -= lineOffset;
3222 offset = layout.getPreviousOffset(offset, DWT.MOVEMENT_CLUSTER);
3223 offset += lineOffset;
3224 renderer.disposeTextLayout(layout);
3225 return offset;
3226 }
3227 /**
3228 * Returns the content implementation that is used for text storage.
3229 *
3230 * @return content the user defined content implementation that is used for
3231 * text storage or the default content implementation if no user defined
3232 * content implementation has been set.
3233 * @exception DWTException <ul>
3234 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3235 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3236 * </ul>
3237 */
3238 public StyledTextContent getContent() {
3239 checkWidget();
3240 return content;
3241 }
3242 public bool getDragDetect () {
3243 checkWidget ();
3244 return dragDetect;
3245 }
3246 /**
3247 * Returns whether the widget : double click mouse behavior.
3248 *
3249 * @return true if double clicking a word selects the word, false if double clicks
3250 * have the same effect as regular mouse clicks
3251 * @exception DWTException <ul>
3252 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3253 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3254 * </ul>
3255 */
3256 public bool getDoubleClickEnabled() {
3257 checkWidget();
3258 return doubleClickEnabled;
3259 }
3260 /**
3261 * Returns whether the widget content can be edited.
3262 *
3263 * @return true if content can be edited, false otherwise
3264 * @exception DWTException <ul>
3265 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3266 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3267 * </ul>
3268 */
3269 public bool getEditable() {
3270 checkWidget();
3271 return editable;
3272 }
3273 public Color getForeground() {
3274 checkWidget();
3275 if (foreground is null) {
3276 return getDisplay().getSystemColor(DWT.COLOR_LIST_FOREGROUND);
3277 }
3278 return foreground;
3279 }
3280 /**
3281 * Returns the horizontal scroll increment.
3282 *
3283 * @return horizontal scroll increment.
3284 */
3285 int getHorizontalIncrement() {
3286 return renderer.averageCharWidth;
3287 }
3288 /**
3289 * Returns the horizontal scroll offset relative to the start of the line.
3290 *
3291 * @return horizontal scroll offset relative to the start of the line,
3292 * measured in character increments starting at 0, if > 0 the content is scrolled
3293 * @exception DWTException <ul>
3294 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3295 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3296 * </ul>
3297 */
3298 public int getHorizontalIndex() {
3299 checkWidget();
3300 return horizontalScrollOffset / getHorizontalIncrement();
3301 }
3302 /**
3303 * Returns the horizontal scroll offset relative to the start of the line.
3304 *
3305 * @return the horizontal scroll offset relative to the start of the line,
3306 * measured in pixel starting at 0, if > 0 the content is scrolled.
3307 * @exception DWTException <ul>
3308 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3309 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3310 * </ul>
3311 */
3312 public int getHorizontalPixel() {
3313 checkWidget();
3314 return horizontalScrollOffset;
3315 }
3316 /**
3317 * Returns the line indentation of the widget.
3318 *
3319 * @return the line indentation
3320 *
3321 * @exception DWTException <ul>
3322 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3323 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3324 * </ul>
3325 *
3326 * @see #getLineIndent(int)
3327 *
3328 * @since 3.2
3329 */
3330 public int getIndent() {
3331 checkWidget();
3332 return indent;
3333 }
3334 /**
3335 * Returns whether the widget justifies lines.
3336 *
3337 * @return whether lines are justified
3338 *
3339 * @exception DWTException <ul>
3340 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3341 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3342 * </ul>
3343 *
3344 * @see #getLineJustify(int)
3345 *
3346 * @since 3.2
3347 */
3348 public bool getJustify() {
3349 checkWidget();
3350 return justify;
3351 }
3352 /**
3353 * Returns the action assigned to the key.
3354 * Returns DWT.NULL if there is no action associated with the key.
3355 *
3356 * @param key a key code defined in DWT.java or a character.
3357 * Optionally ORd with a state mask. Preferred state masks are one or more of
3358 * DWT.MOD1, DWT.MOD2, DWT.MOD3, since these masks account for modifier platform
3359 * differences. However, there may be cases where using the specific state masks
3360 * (i.e., DWT.CTRL, DWT.SHIFT, DWT.ALT, DWT.COMMAND) makes sense.
3361 * @return one of the predefined actions defined in ST.java or DWT.NULL
3362 * if there is no action associated with the key.
3363 * @exception DWTException <ul>
3364 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3365 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3366 * </ul>
3367 */
3368 public int getKeyBinding(int key) {
3369 checkWidget();
3370 Integer action = (Integer) keyActionMap.get(new Integer(key));
3371 return action is null ? DWT.NULL : action.intValue();
3372 }
3373 /**
3374 * Gets the number of characters.
3375 *
3376 * @return number of characters in the widget
3377 * @exception DWTException <ul>
3378 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3379 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3380 * </ul>
3381 */
3382 public int getCharCount() {
3383 checkWidget();
3384 return content.getCharCount();
3385 }
3386 /**
3387 * Returns the line at the given line index without delimiters.
3388 * Index 0 is the first line of the content. When there are not
3389 * any lines, getLine(0) is a valid call that answers an empty String.
3390 * <p>
3391 *
3392 * @param lineIndex index of the line to return.
3393 * @return the line text without delimiters
3394 *
3395 * @exception DWTException <ul>
3396 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3397 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3398 * </ul>
3399 * @exception IllegalArgumentException <ul>
3400 * <li>ERROR_INVALID_RANGE when the line index is outside the valid range (< 0 or >= getLineCount())</li>
3401 * </ul>
3402 * @since 3.4
3403 */
3404 public String getLine(int lineIndex) {
3405 checkWidget();
3406 if (lineIndex < 0 ||
3407 (lineIndex > 0 && lineIndex >= content.getLineCount())) {
3408 DWT.error(DWT.ERROR_INVALID_RANGE);
3409 }
3410 return content.getLine(lineIndex);
3411 }
3412 /**
3413 * Returns the alignment of the line at the given index.
3414 *
3415 * @param index the index of the line
3416 *
3417 * @return the line alignment
3418 *
3419 * @exception DWTException <ul>
3420 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3421 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3422 * </ul>
3423 * @exception IllegalArgumentException <ul>
3424 * <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
3425 * </ul>
3426 *
3427 * @see #getAlignment()
3428 *
3429 * @since 3.2
3430 */
3431 public int getLineAlignment(int index) {
3432 checkWidget();
3433 if (index < 0 || index > content.getLineCount()) {
3434 DWT.error(DWT.ERROR_INVALID_ARGUMENT);
3435 }
3436 return renderer.getLineAlignment(index, alignment);
3437 }
3438 /**
3439 * Returns the line at the specified offset in the text
3440 * where 0 &lt; offset &lt; getCharCount() so that getLineAtOffset(getCharCount())
3441 * returns the line of the insert location.
3442 *
3443 * @param offset offset relative to the start of the content.
3444 * 0 <= offset <= getCharCount()
3445 * @return line at the specified offset in the text
3446 * @exception DWTException <ul>
3447 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3448 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3449 * </ul>
3450 * @exception IllegalArgumentException <ul>
3451 * <li>ERROR_INVALID_RANGE when the offset is outside the valid range (< 0 or > getCharCount())</li>
3452 * </ul>
3453 */
3454 public int getLineAtOffset(int offset) {
3455 checkWidget();
3456 if (offset < 0 || offset > getCharCount()) {
3457 DWT.error(DWT.ERROR_INVALID_RANGE);
3458 }
3459 return content.getLineAtOffset(offset);
3460 }
3461 /**
3462 * Returns the background color of the line at the given index.
3463 * Returns null if a LineBackgroundListener has been set or if no background
3464 * color has been specified for the line. Should not be called if a
3465 * LineBackgroundListener has been set since the listener maintains the
3466 * line background colors.
3467 *
3468 * @param index the index of the line
3469 * @return the background color of the line at the given index.
3470 *
3471 * @exception DWTException <ul>
3472 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3473 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3474 * </ul>
3475 * @exception IllegalArgumentException <ul>
3476 * <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
3477 * </ul>
3478 */
3479 public Color getLineBackground(int index) {
3480 checkWidget();
3481 if (index < 0 || index > content.getLineCount()) {
3482 DWT.error(DWT.ERROR_INVALID_ARGUMENT);
3483 }
3484 return isListening(LineGetBackground) ? null : renderer.getLineBackground(index, null);
3485 }
3486 /**
3487 * Returns the bullet of the line at the given index.
3488 *
3489 * @param index the index of the line
3490 *
3491 * @return the line bullet
3492 *
3493 * @exception DWTException <ul>
3494 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3495 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3496 * </ul>
3497 * @exception IllegalArgumentException <ul>
3498 * <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
3499 * </ul>
3500 *
3501 * @since 3.2
3502 */
3503 public Bullet getLineBullet(int index) {
3504 checkWidget();
3505 if (index < 0 || index > content.getLineCount()) {
3506 DWT.error(DWT.ERROR_INVALID_ARGUMENT);
3507 }
3508 return isListening(LineGetStyle) ? null : renderer.getLineBullet(index, null);
3509 }
3510 /**
3511 * Returns the line background data for the given line or null if
3512 * there is none.
3513 *
3514 * @param lineOffset offset of the line start relative to the start
3515 * of the content.
3516 * @param line line to get line background data for
3517 * @return line background data for the given line.
3518 */
3519 StyledTextEvent getLineBackgroundData(int lineOffset, String line) {
3520 return sendLineEvent(LineGetBackground, lineOffset, line);
3521 }
3522 /**
3523 * Gets the number of text lines.
3524 *
3525 * @return the number of lines in the widget
3526 * @exception DWTException <ul>
3527 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3528 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3529 * </ul>
3530 */
3531 public int getLineCount() {
3532 checkWidget();
3533 return content.getLineCount();
3534 }
3535 /**
3536 * Returns the number of lines that can be completely displayed in the
3537 * widget client area.
3538 *
3539 * @return number of lines that can be completely displayed in the widget
3540 * client area.
3541 */
3542 int getLineCountWhole() {
3543 if (isFixedLineHeight()) {
3544 int lineHeight = renderer.getLineHeight();
3545 return lineHeight !is 0 ? clientAreaHeight / lineHeight : 1;
3546 }
3547 return getBottomIndex() - topIndex + 1;
3548 }
3549 /**
3550 * Returns the line delimiter used for entering new lines by key down
3551 * or paste operation.
3552 *
3553 * @return line delimiter used for entering new lines by key down
3554 * or paste operation.
3555 * @exception DWTException <ul>
3556 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3557 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3558 * </ul>
3559 */
3560 public String getLineDelimiter() {
3561 checkWidget();
3562 return content.getLineDelimiter();
3563 }
3564 /**
3565 * Returns the line height.
3566 * <p>
3567 * Note: this API should not be used if a StyleRange attribute causes lines to
3568 * have different heights (i.e. different fonts, rise, etc).
3569 * </p>
3570 *
3571 * @return line height in pixel.
3572 * @exception DWTException <ul>
3573 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3574 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3575 * </ul>
3576 * @see #getLineHeight(int)
3577 */
3578 public int getLineHeight() {
3579 checkWidget();
3580 return renderer.getLineHeight();
3581 }
3582 /**
3583 * Returns the line height at the given offset.
3584 *
3585 * @param offset the offset
3586 *
3587 * @return line height in pixels
3588 *
3589 * @exception DWTException <ul>
3590 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3591 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3592 * </ul>
3593 * @exception IllegalArgumentException <ul>
3594 * <li>ERROR_INVALID_RANGE when the offset is outside the valid range (< 0 or > getCharCount())</li>
3595 * </ul>
3596 *
3597 * @since 3.2
3598 */
3599 public int getLineHeight(int offset) {
3600 checkWidget();
3601 if (!(0 <= offset && offset <= content.getCharCount())) {
3602 DWT.error(DWT.ERROR_INVALID_RANGE);
3603 }
3604 if (isFixedLineHeight()) {
3605 return renderer.getLineHeight();
3606 }
3607 int lineIndex = content.getLineAtOffset(offset);
3608 int lineOffset = content.getOffsetAtLine(lineIndex);
3609 TextLayout layout = renderer.getTextLayout(lineIndex);
3610 int lineInParagraph = layout.getLineIndex(Math.min(offset - lineOffset, layout.getText().length()));
3611 int height = layout.getLineBounds(lineInParagraph).height;
3612 renderer.disposeTextLayout(layout);
3613 return height;
3614 }
3615 /**
3616 * Returns the indentation of the line at the given index.
3617 *
3618 * @param index the index of the line
3619 *
3620 * @return the line indentation
3621 *
3622 * @exception DWTException <ul>
3623 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3624 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3625 * </ul>
3626 * @exception IllegalArgumentException <ul>
3627 * <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
3628 * </ul>
3629 *
3630 * @see #getIndent()
3631 *
3632 * @since 3.2
3633 */
3634 public int getLineIndent(int index) {
3635 checkWidget();
3636 if (index < 0 || index > content.getLineCount()) {
3637 DWT.error(DWT.ERROR_INVALID_ARGUMENT);
3638 }
3639 return isListening(LineGetStyle) ? 0 : renderer.getLineIndent(index, indent);
3640 }
3641 /**
3642 * Returns whether the line at the given index is justified.
3643 *
3644 * @param index the index of the line
3645 *
3646 * @return whether the line is justified
3647 *
3648 * @exception DWTException <ul>
3649 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3650 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3651 * </ul>
3652 * @exception IllegalArgumentException <ul>
3653 * <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
3654 * </ul>
3655 *
3656 * @see #getJustify()
3657 *
3658 * @since 3.2
3659 */
3660 public bool getLineJustify(int index) {
3661 checkWidget();
3662 if (index < 0 || index > content.getLineCount()) {
3663 DWT.error(DWT.ERROR_INVALID_ARGUMENT);
3664 }
3665 return isListening(LineGetStyle) ? false : renderer.getLineJustify(index, justify);
3666 }
3667 /**
3668 * Returns the line spacing of the widget.
3669 *
3670 * @return the line spacing
3671 *
3672 * @exception DWTException <ul>
3673 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3674 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3675 * </ul>
3676 *
3677 * @since 3.2
3678 */
3679 public int getLineSpacing() {
3680 checkWidget();
3681 return lineSpacing;
3682 }
3683 /**
3684 * Returns the line style data for the given line or null if there is
3685 * none.
3686 * <p>
3687 * If there is a LineStyleListener but it does not set any styles,
3688 * the StyledTextEvent.styles field will be initialized to an empty
3689 * array.
3690 * </p>
3691 *
3692 * @param lineOffset offset of the line start relative to the start of
3693 * the content.
3694 * @param line line to get line styles for
3695 * @return line style data for the given line. Styles may start before
3696 * line start and end after line end
3697 */
3698 StyledTextEvent getLineStyleData(int lineOffset, String line) {
3699 return sendLineEvent(LineGetStyle, lineOffset, line);
3700 }
3701 /**
3702 * Returns the top pixel, relative to the client area, of a given line.
3703 * Clamps out of ranges index.
3704 *
3705 * @param lineIndex the line index, the max value is lineCount. If
3706 * lineIndex is lineCount it returns the bottom pixel of the last line.
3707 * It means this function can be used to retrieve the bottom pixel of any line.
3708 *
3709 * @since 3.2
3710 */
3711 public int getLinePixel(int lineIndex) {
3712 checkWidget();
3713 int lineCount = content.getLineCount();
3714 lineIndex = Math.max(0, Math.min(lineCount, lineIndex));
3715 if (isFixedLineHeight()) {
3716 int lineHeight = renderer.getLineHeight();
3717 return lineIndex * lineHeight - getVerticalScrollOffset() + topMargin;
3718 }
3719 if (lineIndex is topIndex) return topIndexY + topMargin;
3720 int height = topIndexY;
3721 if (lineIndex > topIndex) {
3722 for (int i = topIndex; i < lineIndex; i++) {
3723 height += renderer.getLineHeight(i);
3724 }
3725 } else {
3726 for (int i = topIndex - 1; i >= lineIndex; i--) {
3727 height -= renderer.getLineHeight(i);
3728 }
3729 }
3730 return height + topMargin;
3731 }
3732 /**
3733 * Returns the line index for a y, relative to the client area.
3734 * The line index returned is always in the range 0..lineCount - 1.
3735 *
3736 * @since 3.2
3737 */
3738 public int getLineIndex(int y) {
3739 checkWidget();
3740 y -= topMargin;
3741 if (isFixedLineHeight()) {
3742 int lineHeight = renderer.getLineHeight();
3743 int lineIndex = (y + getVerticalScrollOffset()) / lineHeight;
3744 int lineCount = content.getLineCount();
3745 lineIndex = Math.max(0, Math.min(lineCount - 1, lineIndex));
3746 return lineIndex;
3747 }
3748 if (y is topIndexY) return topIndex;
3749 int line = topIndex;
3750 if (y < topIndexY) {
3751 while (y < topIndexY && line > 0) {
3752 y += renderer.getLineHeight(--line);
3753 }
3754 } else {
3755 int lineCount = content.getLineCount();
3756 int lineHeight = renderer.getLineHeight(line);
3757 while (y - lineHeight >= topIndexY && line < lineCount - 1) {
3758 y -= lineHeight;
3759 lineHeight = renderer.getLineHeight(++line);
3760 }
3761 }
3762 return line;
3763 }
3764 /**
3765 * Returns the x, y location of the upper left corner of the character
3766 * bounding box at the specified offset in the text. The point is
3767 * relative to the upper left corner of the widget client area.
3768 *
3769 * @param offset offset relative to the start of the content.
3770 * 0 <= offset <= getCharCount()
3771 * @return x, y location of the upper left corner of the character
3772 * bounding box at the specified offset in the text.
3773 * @exception DWTException <ul>
3774 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3775 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3776 * </ul>
3777 * @exception IllegalArgumentException <ul>
3778 * <li>ERROR_INVALID_RANGE when the offset is outside the valid range (< 0 or > getCharCount())</li>
3779 * </ul>
3780 */
3781 public Point getLocationAtOffset(int offset) {
3782 checkWidget();
3783 if (offset < 0 || offset > getCharCount()) {
3784 DWT.error(DWT.ERROR_INVALID_RANGE);
3785 }
3786 return getPointAtOffset(offset);
3787 }
3788 /**
3789 * Returns the character offset of the first character of the given line.
3790 *
3791 * @param lineIndex index of the line, 0 based relative to the first
3792 * line in the content. 0 <= lineIndex < getLineCount(), except
3793 * lineIndex may always be 0
3794 * @return offset offset of the first character of the line, relative to
3795 * the beginning of the document. The first character of the document is
3796 * at offset 0.
3797 * When there are not any lines, getOffsetAtLine(0) is a valid call that
3798 * answers 0.
3799 * @exception DWTException <ul>
3800 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3801 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3802 * </ul>
3803 * @exception IllegalArgumentException <ul>
3804 * <li>ERROR_INVALID_RANGE when the line index is outside the valid range (< 0 or >= getLineCount())</li>
3805 * </ul>
3806 * @since 2.0
3807 */
3808 public int getOffsetAtLine(int lineIndex) {
3809 checkWidget();
3810 if (lineIndex < 0 ||
3811 (lineIndex > 0 && lineIndex >= content.getLineCount())) {
3812 DWT.error(DWT.ERROR_INVALID_RANGE);
3813 }
3814 return content.getOffsetAtLine(lineIndex);
3815 }
3816 /**
3817 * Returns the offset of the character at the given location relative
3818 * to the first character in the document.
3819 * <p>
3820 * The return value reflects the character offset that the caret will
3821 * be placed at if a mouse click occurred at the specified location.
3822 * If the x coordinate of the location is beyond the center of a character
3823 * the returned offset will be behind the character.
3824 * </p>
3825 *
3826 * @param point the origin of character bounding box relative to
3827 * the origin of the widget client area.
3828 * @return offset of the character at the given location relative
3829 * to the first character in the document.
3830 * @exception DWTException <ul>
3831 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3832 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3833 * </ul>
3834 * @exception IllegalArgumentException <ul>
3835 * <li>ERROR_NULL_ARGUMENT when point is null</li>
3836 * <li>ERROR_INVALID_ARGUMENT when there is no character at the specified location</li>
3837 * </ul>
3838 */
3839 public int getOffsetAtLocation(Point point) {
3840 checkWidget();
3841 if (point is null) {
3842 DWT.error(DWT.ERROR_NULL_ARGUMENT);
3843 }
3844 int[] trailing = new int[1];
3845 int offset = getOffsetAtPoint(point.x, point.y, trailing, true);
3846 if (offset is -1) {
3847 DWT.error(DWT.ERROR_INVALID_ARGUMENT);
3848 }
3849 return offset + trailing[0];
3850 }
3851 int getOffsetAtPoint(int x, int y) {
3852 int lineIndex = getLineIndex(y);
3853 y -= getLinePixel(lineIndex);
3854 return getOffsetAtPoint(x, y, lineIndex);
3855 }
3856 /**
3857 * Returns the offset at the specified x location in the specified line.
3858 *
3859 * @param x x location of the mouse location
3860 * @param line line the mouse location is in
3861 * @return the offset at the specified x location in the specified line,
3862 * relative to the beginning of the document
3863 */
3864 int getOffsetAtPoint(int x, int y, int lineIndex) {
3865 TextLayout layout = renderer.getTextLayout(lineIndex);
3866 x += horizontalScrollOffset - leftMargin;
3867 int[] trailing = new int[1];
3868 int offsetInLine = layout.getOffset(x, y, trailing);
3869 caretAlignment = OFFSET_LEADING;
3870 if (trailing[0] !is 0) {
3871 int lineInParagraph = layout.getLineIndex(offsetInLine + trailing[0]);
3872 int lineStart = layout.getLineOffsets()[lineInParagraph];
3873 if (offsetInLine + trailing[0] is lineStart) {
3874 offsetInLine += trailing[0];
3875 caretAlignment = PREVIOUS_OFFSET_TRAILING;
3876 } else {
3877 String line = content.getLine(lineIndex);
3878 int level;
3879 int offset = offsetInLine;
3880 while (offset > 0 && Character.isDigit(line.charAt(offset))) offset--;
3881 if (offset is 0 && Character.isDigit(line.charAt(offset))) {
3882 level = isMirrored() ? 1 : 0;
3883 } else {
3884 level = layout.getLevel(offset) & 0x1;
3885 }
3886 offsetInLine += trailing[0];
3887 int trailingLevel = layout.getLevel(offsetInLine) & 0x1;
3888 if ((level ^ trailingLevel) !is 0) {
3889 caretAlignment = PREVIOUS_OFFSET_TRAILING;
3890 } else {
3891 caretAlignment = OFFSET_LEADING;
3892 }
3893 }
3894 }
3895 renderer.disposeTextLayout(layout);
3896 return offsetInLine + content.getOffsetAtLine(lineIndex);
3897 }
3898 int getOffsetAtPoint(int x, int y, int[] trailing, bool inTextOnly) {
3899 if (inTextOnly && y + getVerticalScrollOffset() < 0 || x + horizontalScrollOffset < 0) {
3900 return -1;
3901 }
3902 int bottomIndex = getPartialBottomIndex();
3903 int height = getLinePixel(bottomIndex + 1);
3904 if (inTextOnly && y > height) {
3905 return -1;
3906 }
3907 int lineIndex = getLineIndex(y);
3908 int lineOffset = content.getOffsetAtLine(lineIndex);
3909 TextLayout layout = renderer.getTextLayout(lineIndex);
3910 x += horizontalScrollOffset - leftMargin ;
3911 y -= getLinePixel(lineIndex);
3912 int offset = layout.getOffset(x, y, trailing);
3913 Rectangle rect = layout.getLineBounds(layout.getLineIndex(offset));
3914 renderer.disposeTextLayout(layout);
3915 if (inTextOnly && !(rect.x <= x && x <= rect.x + rect.width)) {
3916 return -1;
3917 }
3918 return offset + lineOffset;
3919 }
3920 /**
3921 * Returns the orientation of the receiver.
3922 *
3923 * @return the orientation style
3924 *
3925 * @exception DWTException <ul>
3926 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3927 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3928 * </ul>
3929 *
3930 * @since 2.1.2
3931 */
3932 public int getOrientation () {
3933 checkWidget();
3934 return isMirrored() ? DWT.RIGHT_TO_LEFT : DWT.LEFT_TO_RIGHT;
3935 }
3936 /**
3937 * Returns the index of the last partially visible line.
3938 *
3939 * @return index of the last partially visible line.
3940 */
3941 int getPartialBottomIndex() {
3942 if (isFixedLineHeight()) {
3943 int lineHeight = renderer.getLineHeight();
3944 int partialLineCount = Compatibility.ceil(clientAreaHeight, lineHeight);
3945 return Math.max(0, Math.min(content.getLineCount(), topIndex + partialLineCount) - 1);
3946 }
3947 return getLineIndex(clientAreaHeight - bottomMargin);
3948 }
3949 /**
3950 * Returns the index of the first partially visible line.
3951 *
3952 * @return index of the first partially visible line.
3953 */
3954 int getPartialTopIndex() {
3955 if (isFixedLineHeight()) {
3956 int lineHeight = renderer.getLineHeight();
3957 return getVerticalScrollOffset() / lineHeight;
3958 }
3959 return topIndexY <= 0 ? topIndex : topIndex - 1;
3960 }
3961 /**
3962 * Returns the content in the specified range using the platform line
3963 * delimiter to separate lines.
3964 *
3965 * @param writer the TextWriter to write line text into
3966 * @return the content in the specified range using the platform line
3967 * delimiter to separate lines as written by the specified TextWriter.
3968 */
3969 String getPlatformDelimitedText(TextWriter writer) {
3970 int end = writer.getStart() + writer.getCharCount();
3971 int startLine = content.getLineAtOffset(writer.getStart());
3972 int endLine = content.getLineAtOffset(end);
3973 String endLineText = content.getLine(endLine);
3974 int endLineOffset = content.getOffsetAtLine(endLine);
3975
3976 for (int i = startLine; i <= endLine; i++) {
3977 writer.writeLine(content.getLine(i), content.getOffsetAtLine(i));
3978 if (i < endLine) {
3979 writer.writeLineDelimiter(PlatformLineDelimiter);
3980 }
3981 }
3982 if (end > endLineOffset + endLineText.length()) {
3983 writer.writeLineDelimiter(PlatformLineDelimiter);
3984 }
3985 writer.close();
3986 return writer.toString();
3987 }
3988 /**
3989 * Returns all the ranges of text that have an associated StyleRange.
3990 * Returns an empty array if a LineStyleListener has been set.
3991 * Should not be called if a LineStyleListener has been set since the
3992 * listener maintains the styles.
3993 * <p>
3994 * The ranges array contains start and length pairs. Each pair refers to
3995 * the corresponding style in the styles array. For example, the pair
3996 * that starts at ranges[n] with length ranges[n+1] uses the style
3997 * at styles[n/2] returned by <code>getStyleRanges(int, int, bool)</code>.
3998 * </p>
3999 *
4000 * @return the ranges or an empty array if a LineStyleListener has been set.
4001 *
4002 * @exception DWTException <ul>
4003 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4004 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4005 * </ul>
4006 *
4007 * @since 3.2
4008 *
4009 * @see #getStyleRanges(bool)
4010 */
4011 public int[] getRanges() {
4012 checkWidget();
4013 if (!isListening(LineGetStyle)) {
4014 int[] ranges = renderer.getRanges(0, content.getCharCount());
4015 if (ranges !is null) return ranges;
4016 }
4017 return new int[0];
4018 }
4019 /**
4020 * Returns the ranges of text that have an associated StyleRange.
4021 * Returns an empty array if a LineStyleListener has been set.
4022 * Should not be called if a LineStyleListener has been set since the
4023 * listener maintains the styles.
4024 * <p>
4025 * The ranges array contains start and length pairs. Each pair refers to
4026 * the corresponding style in the styles array. For example, the pair
4027 * that starts at ranges[n] with length ranges[n+1] uses the style
4028 * at styles[n/2] returned by <code>getStyleRanges(int, int, bool)</code>.
4029 * </p>
4030 *
4031 * @param start the start offset of the style ranges to return
4032 * @param length the number of style ranges to return
4033 *
4034 * @return the ranges or an empty array if a LineStyleListener has been set.
4035 *
4036 * @exception DWTException <ul>
4037 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4038 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4039 * </ul>
4040 * @exception IllegalArgumentException <ul>
4041 * <li>ERROR_INVALID_RANGE if start or length are outside the widget content</li>
4042 * </ul>
4043 *
4044 * @since 3.2
4045 *
4046 * @see #getStyleRanges(int, int, bool)
4047 */
4048 public int[] getRanges(int start, int length) {
4049 checkWidget();
4050 int contentLength = getCharCount();
4051 int end = start + length;
4052 if (start > end || start < 0 || end > contentLength) {
4053 DWT.error(DWT.ERROR_INVALID_RANGE);
4054 }
4055 if (!isListening(LineGetStyle)) {
4056 int[] ranges = renderer.getRanges(start, length);
4057 if (ranges !is null) return ranges;
4058 }
4059 return new int[0];
4060 }
4061 /**
4062 * Returns the selection.
4063 * <p>
4064 * Text selections are specified in terms of caret positions. In a text
4065 * widget that contains N characters, there are N+1 caret positions,
4066 * ranging from 0..N
4067 * </p>
4068 *
4069 * @return start and end of the selection, x is the offset of the first
4070 * selected character, y is the offset after the last selected character.
4071 * The selection values returned are visual (i.e., x will always always be
4072 * <= y). To determine if a selection is right-to-left (RtoL) vs. left-to-right
4073 * (LtoR), compare the caretOffset to the start and end of the selection
4074 * (e.g., caretOffset is start of selection implies that the selection is RtoL).
4075 * @see #getSelectionRange
4076 * @exception DWTException <ul>
4077 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4078 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4079 * </ul>
4080 */
4081 public Point getSelection() {
4082 checkWidget();
4083 return new Point(selection.x, selection.y);
4084 }
4085 /**
4086 * Returns the selection.
4087 *
4088 * @return start and length of the selection, x is the offset of the
4089 * first selected character, relative to the first character of the
4090 * widget content. y is the length of the selection.
4091 * The selection values returned are visual (i.e., length will always always be
4092 * positive). To determine if a selection is right-to-left (RtoL) vs. left-to-right
4093 * (LtoR), compare the caretOffset to the start and end of the selection
4094 * (e.g., caretOffset is start of selection implies that the selection is RtoL).
4095 * @exception DWTException <ul>
4096 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4097 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4098 * </ul>
4099 */
4100 public Point getSelectionRange() {
4101 checkWidget();
4102 return new Point(selection.x, selection.y - selection.x);
4103 }
4104 /**
4105 * Returns the receiver's selection background color.
4106 *
4107 * @return the selection background color
4108 *
4109 * @exception DWTException <ul>
4110 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4111 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4112 * </ul>
4113 * @since 2.1
4114 */
4115 public Color getSelectionBackground() {
4116 checkWidget();
4117 if (selectionBackground is null) {
4118 return getDisplay().getSystemColor(DWT.COLOR_LIST_SELECTION);
4119 }
4120 return selectionBackground;
4121 }
4122 /**
4123 * Gets the number of selected characters.
4124 *
4125 * @return the number of selected characters.
4126 * @exception DWTException <ul>
4127 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4128 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4129 * </ul>
4130 */
4131 public int getSelectionCount() {
4132 checkWidget();
4133 return getSelectionRange().y;
4134 }
4135 /**
4136 * Returns the receiver's selection foreground color.
4137 *
4138 * @return the selection foreground color
4139 *
4140 * @exception DWTException <ul>
4141 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4142 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4143 * </ul>
4144 * @since 2.1
4145 */
4146 public Color getSelectionForeground() {
4147 checkWidget();
4148 if (selectionForeground is null) {
4149 return getDisplay().getSystemColor(DWT.COLOR_LIST_SELECTION_TEXT);
4150 }
4151 return selectionForeground;
4152 }
4153 /**
4154 * Returns the selected text.
4155 *
4156 * @return selected text, or an empty String if there is no selection.
4157 * @exception DWTException <ul>
4158 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4159 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4160 * </ul>
4161 */
4162 public String getSelectionText() {
4163 checkWidget();
4164 return content.getTextRange(selection.x, selection.y - selection.x);
4165 }
4166 public int getStyle() {
4167 int style = super.getStyle();
4168 style &= ~(DWT.LEFT_TO_RIGHT | DWT.RIGHT_TO_LEFT | DWT.MIRRORED);
4169 if (isMirrored()) {
4170 style |= DWT.RIGHT_TO_LEFT | DWT.MIRRORED;
4171 } else {
4172 style |= DWT.LEFT_TO_RIGHT;
4173 }
4174 return style;
4175 }
4176
4177 /**
4178 * Returns the text segments that should be treated as if they
4179 * had a different direction than the surrounding text.
4180 *
4181 * @param lineOffset offset of the first character in the line.
4182 * 0 based from the beginning of the document.
4183 * @param line text of the line to specify bidi segments for
4184 * @return text segments that should be treated as if they had a
4185 * different direction than the surrounding text. Only the start
4186 * index of a segment is specified, relative to the start of the
4187 * line. Always starts with 0 and ends with the line length.
4188 * @exception IllegalArgumentException <ul>
4189 * <li>ERROR_INVALID_ARGUMENT - if the segment indices returned
4190 * by the listener do not start with 0, are not in ascending order,
4191 * exceed the line length or have duplicates</li>
4192 * </ul>
4193 */
4194 int [] getBidiSegments(int lineOffset, String line) {
4195 if (!isBidi()) return null;
4196 if (!isListening(LineGetSegments)) {
4197 return getBidiSegmentsCompatibility(line, lineOffset);
4198 }
4199 StyledTextEvent event = sendLineEvent(LineGetSegments, lineOffset, line);
4200 int lineLength = line.length();
4201 int[] segments;
4202 if (event is null || event.segments is null || event.segments.length is 0) {
4203 segments = new int[] {0, lineLength};
4204 } else {
4205 int segmentCount = event.segments.length;
4206
4207 // test segment index consistency
4208 if (event.segments[0] !is 0) {
4209 DWT.error(DWT.ERROR_INVALID_ARGUMENT);
4210 }
4211 for (int i = 1; i < segmentCount; i++) {
4212 if (event.segments[i] <= event.segments[i - 1] || event.segments[i] > lineLength) {
4213 DWT.error(DWT.ERROR_INVALID_ARGUMENT);
4214 }
4215 }
4216 // ensure that last segment index is line end offset
4217 if (event.segments[segmentCount - 1] !is lineLength) {
4218 segments = new int[segmentCount + 1];
4219 System.arraycopy(event.segments, 0, segments, 0, segmentCount);
4220 segments[segmentCount] = lineLength;
4221 } else {
4222 segments = event.segments;
4223 }
4224 }
4225 return segments;
4226 }
4227 /**
4228 * @see #getBidiSegments
4229 * Supports deprecated setBidiColoring API. Remove when API is removed.
4230 */
4231 int [] getBidiSegmentsCompatibility(String line, int lineOffset) {
4232 int lineLength = line.length();
4233 if (!bidiColoring) {
4234 return new int[] {0, lineLength};
4235 }
4236 StyleRange [] styles = null;
4237 StyledTextEvent event = getLineStyleData(lineOffset, line);
4238 if (event !is null) {
4239 styles = event.styles;
4240 } else {
4241 styles = renderer.getStyleRanges(lineOffset, lineLength, true);
4242 }
4243 if (styles is null || styles.length is 0) {
4244 return new int[] {0, lineLength};
4245 }
4246 int k=0, count = 1;
4247 while (k < styles.length && styles[k].start is 0 && styles[k].length is lineLength) {
4248 k++;
4249 }
4250 int[] offsets = new int[(styles.length - k) * 2 + 2];
4251 for (int i = k; i < styles.length; i++) {
4252 StyleRange style = styles[i];
4253 int styleLineStart = Math.max(style.start - lineOffset, 0);
4254 int styleLineEnd = Math.max(style.start + style.length - lineOffset, styleLineStart);
4255 styleLineEnd = Math.min (styleLineEnd, line.length ());
4256 if (i > 0 && count > 1 &&
4257 ((styleLineStart >= offsets[count-2] && styleLineStart <= offsets[count-1]) ||
4258 (styleLineEnd >= offsets[count-2] && styleLineEnd <= offsets[count-1])) &&
4259 style.similarTo(styles[i-1])) {
4260 offsets[count-2] = Math.min(offsets[count-2], styleLineStart);
4261 offsets[count-1] = Math.max(offsets[count-1], styleLineEnd);
4262 } else {
4263 if (styleLineStart > offsets[count - 1]) {
4264 offsets[count] = styleLineStart;
4265 count++;
4266 }
4267 offsets[count] = styleLineEnd;
4268 count++;
4269 }
4270 }
4271 // add offset for last non-colored segment in line, if any
4272 if (lineLength > offsets[count-1]) {
4273 offsets [count] = lineLength;
4274 count++;
4275 }
4276 if (count is offsets.length) {
4277 return offsets;
4278 }
4279 int [] result = new int [count];
4280 System.arraycopy (offsets, 0, result, 0, count);
4281 return result;
4282 }
4283 /**
4284 * Returns the style range at the given offset.
4285 * <p>
4286 * Returns null if a LineStyleListener has been set or if a style is not set
4287 * for the offset.
4288 * Should not be called if a LineStyleListener has been set since the
4289 * listener maintains the styles.
4290 * </p>
4291 *
4292 * @param offset the offset to return the style for.
4293 * 0 <= offset < getCharCount() must be true.
4294 * @return a StyleRange with start is offset and length is 1, indicating
4295 * the style at the given offset. null if a LineStyleListener has been set
4296 * or if a style is not set for the given offset.
4297 * @exception DWTException <ul>
4298 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4299 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4300 * </ul>
4301 * @exception IllegalArgumentException <ul>
4302 * <li>ERROR_INVALID_ARGUMENT when the offset is invalid</li>
4303 * </ul>
4304 */
4305 public StyleRange getStyleRangeAtOffset(int offset) {
4306 checkWidget();
4307 if (offset < 0 || offset >= getCharCount()) {
4308 DWT.error(DWT.ERROR_INVALID_ARGUMENT);
4309 }
4310 if (!isListening(LineGetStyle)) {
4311 StyleRange[] ranges = renderer.getStyleRanges(offset, 1, true);
4312 if (ranges !is null) return ranges[0];
4313 }
4314 return null;
4315 }
4316 /**
4317 * Returns the styles.
4318 * <p>
4319 * Returns an empty array if a LineStyleListener has been set.
4320 * Should not be called if a LineStyleListener has been set since the
4321 * listener maintains the styles.
4322 * <p></p>
4323 * Note: Because a StyleRange includes the start and length, the
4324 * same instance cannot occur multiple times in the array of styles.
4325 * If the same style attributes, such as font and color, occur in
4326 * multiple StyleRanges, <code>getStyleRanges(bool)</code>
4327 * can be used to get the styles without the ranges.
4328 * </p>
4329 *
4330 * @return the styles or an empty array if a LineStyleListener has been set.
4331 *
4332 * @exception DWTException <ul>
4333 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4334 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4335 * </ul>
4336 *
4337 * @see #getStyleRanges(bool)
4338 */
4339 public StyleRange[] getStyleRanges() {
4340 checkWidget();
4341 return getStyleRanges(0, content.getCharCount(), true);
4342 }
4343 /**
4344 * Returns the styles.
4345 * <p>
4346 * Returns an empty array if a LineStyleListener has been set.
4347 * Should not be called if a LineStyleListener has been set since the
4348 * listener maintains the styles.
4349 * </p><p>
4350 * Note: When <code>includeRanges</code> is true, the start and length
4351 * fields of each StyleRange will be valid, however the StyleRange
4352 * objects may need to be cloned. When <code>includeRanges</code> is
4353 * false, <code>getRanges(int, int)</code> can be used to get the
4354 * associated ranges.
4355 * </p>
4356 *
4357 * @param includeRanges whether the start and length field of the StyleRanges should be set.
4358 *
4359 * @return the styles or an empty array if a LineStyleListener has been set.
4360 *
4361 * @exception DWTException <ul>
4362 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4363 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4364 * </ul>
4365 *
4366 * @since 3.2
4367 *
4368 * @see #getRanges(int, int)
4369 * @see #setStyleRanges(int[], StyleRange[])
4370 */
4371 public StyleRange[] getStyleRanges(bool includeRanges) {
4372 checkWidget();
4373 return getStyleRanges(0, content.getCharCount(), includeRanges);
4374 }
4375 /**
4376 * Returns the styles for the given text range.
4377 * <p>
4378 * Returns an empty array if a LineStyleListener has been set.
4379 * Should not be called if a LineStyleListener has been set since the
4380 * listener maintains the styles.
4381 * </p><p>
4382 * Note: Because the StyleRange includes the start and length, the
4383 * same instance cannot occur multiple times in the array of styles.
4384 * If the same style attributes, such as font and color, occur in
4385 * multiple StyleRanges, <code>getStyleRanges(int, int, bool)</code>
4386 * can be used to get the styles without the ranges.
4387 * </p>
4388 * @param start the start offset of the style ranges to return
4389 * @param length the number of style ranges to return
4390 *
4391 * @return the styles or an empty array if a LineStyleListener has
4392 * been set. The returned styles will reflect the given range. The first
4393 * returned <code>StyleRange</code> will have a starting offset >= start
4394 * and the last returned <code>StyleRange</code> will have an ending
4395 * offset <= start + length - 1
4396 *
4397 * @exception DWTException <ul>
4398 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4399 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4400 * </ul>
4401 * @exception IllegalArgumentException <ul>
4402 * <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li>
4403 * </ul>
4404 *
4405 * @see #getStyleRanges(int, int, bool)
4406 *
4407 * @since 3.0
4408 */
4409 public StyleRange[] getStyleRanges(int start, int length) {
4410 checkWidget();
4411 return getStyleRanges(start, length, true);
4412 }
4413 /**
4414 * Returns the styles for the given text range.
4415 * <p>
4416 * Returns an empty array if a LineStyleListener has been set.
4417 * Should not be called if a LineStyleListener has been set since the
4418 * listener maintains the styles.
4419 * </p><p>
4420 * Note: When <code>includeRanges</code> is true, the start and length
4421 * fields of each StyleRange will be valid, however the StyleRange
4422 * objects may need to be cloned. When <code>includeRanges</code> is
4423 * false, <code>getRanges(int, int)</code> can be used to get the
4424 * associated ranges.
4425 * </p>
4426 *
4427 * @param start the start offset of the style ranges to return
4428 * @param length the number of style ranges to return
4429 * @param includeRanges whether the start and length field of the StyleRanges should be set.
4430 *
4431 * @return the styles or an empty array if a LineStyleListener has
4432 * been set. The returned styles will reflect the given range. The first
4433 * returned <code>StyleRange</code> will have a starting offset >= start
4434 * and the last returned <code>StyleRange</code> will have an ending
4435 * offset <= start + length - 1
4436 *
4437 * @exception DWTException <ul>
4438 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4439 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4440 * </ul>
4441 * @exception IllegalArgumentException <ul>
4442 * <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li>
4443 * </ul>
4444 *
4445 * @since 3.2
4446 *
4447 * @see #getRanges(int, int)
4448 * @see #setStyleRanges(int[], StyleRange[])
4449 */
4450 public StyleRange[] getStyleRanges(int start, int length, bool includeRanges) {
4451 checkWidget();
4452 int contentLength = getCharCount();
4453 int end = start + length;
4454 if (start > end || start < 0 || end > contentLength) {
4455 DWT.error(DWT.ERROR_INVALID_RANGE);
4456 }
4457 if (!isListening(LineGetStyle)) {
4458 StyleRange[] ranges = renderer.getStyleRanges(start, length, includeRanges);
4459 if (ranges !is null) return ranges;
4460 }
4461 return new StyleRange[0];
4462 }
4463 /**
4464 * Returns the tab width measured in characters.
4465 *
4466 * @return tab width measured in characters
4467 * @exception DWTException <ul>
4468 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4469 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4470 * </ul>
4471 */
4472 public int getTabs() {
4473 checkWidget();
4474 return tabLength;
4475 }
4476 /**
4477 * Returns a copy of the widget content.
4478 *
4479 * @return copy of the widget content
4480 * @exception DWTException <ul>
4481 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4482 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4483 * </ul>
4484 */
4485 public String getText() {
4486 checkWidget();
4487 return content.getTextRange(0, getCharCount());
4488 }
4489 /**
4490 * Returns the widget content between the two offsets.
4491 *
4492 * @param start offset of the first character in the returned String
4493 * @param end offset of the last character in the returned String
4494 * @return widget content starting at start and ending at end
4495 * @see #getTextRange(int,int)
4496 * @exception DWTException <ul>
4497 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4498 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4499 * </ul>
4500 * @exception IllegalArgumentException <ul>
4501 * <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li>
4502 * </ul>
4503 */
4504 public String getText(int start, int end) {
4505 checkWidget();
4506 int contentLength = getCharCount();
4507 if (start < 0 || start >= contentLength || end < 0 || end >= contentLength || start > end) {
4508 DWT.error(DWT.ERROR_INVALID_RANGE);
4509 }
4510 return content.getTextRange(start, end - start + 1);
4511 }
4512 /**
4513 * Returns the smallest bounding rectangle that includes the characters between two offsets.
4514 *
4515 * @param start offset of the first character included in the bounding box
4516 * @param end offset of the last character included in the bounding box
4517 * @return bounding box of the text between start and end
4518 * @exception DWTException <ul>
4519 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4520 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4521 * </ul>
4522 * @exception IllegalArgumentException <ul>
4523 * <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li>
4524 * </ul>
4525 * @since 3.1
4526 */
4527 public Rectangle getTextBounds(int start, int end) {
4528 checkWidget();
4529 int contentLength = getCharCount();
4530 if (start < 0 || start >= contentLength || end < 0 || end >= contentLength || start > end) {
4531 DWT.error(DWT.ERROR_INVALID_RANGE);
4532 }
4533 int lineStart = content.getLineAtOffset(start);
4534 int lineEnd = content.getLineAtOffset(end);
4535 Rectangle rect;
4536 int y = getLinePixel(lineStart);
4537 int height = 0;
4538 int left = 0x7fffffff, right = 0;
4539 for (int i = lineStart; i <= lineEnd; i++) {
4540 int lineOffset = content.getOffsetAtLine(i);
4541 TextLayout layout = renderer.getTextLayout(i);
4542 int length = layout.getText().length();
4543 if (length > 0) {
4544 if (i is lineStart) {
4545 if (i is lineEnd) {
4546 rect = layout.getBounds(start - lineOffset, end - lineOffset);
4547 } else {
4548 rect = layout.getBounds(start - lineOffset, length);
4549 }
4550 y += rect.y;
4551 } else if (i is lineEnd) {
4552 rect = layout.getBounds(0, end - lineOffset);
4553 } else {
4554 rect = layout.getBounds();
4555 }
4556 left = Math.min(left, rect.x);
4557 right = Math.max(right, rect.x + rect.width);
4558 height += rect.height;
4559 } else {
4560 height += renderer.getLineHeight();
4561 }
4562 renderer.disposeTextLayout(layout);
4563 }
4564 rect = new Rectangle (left, y, right-left, height);
4565 rect.x += leftMargin - horizontalScrollOffset;
4566 return rect;
4567 }
4568 /**
4569 * Returns the widget content starting at start for length characters.
4570 *
4571 * @param start offset of the first character in the returned String
4572 * @param length number of characters to return
4573 * @return widget content starting at start and extending length characters.
4574 * @exception DWTException <ul>
4575 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4576 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4577 * </ul>
4578 * @exception IllegalArgumentException <ul>
4579 * <li>ERROR_INVALID_RANGE when start and/or length are outside the widget content</li>
4580 * </ul>
4581 */
4582 public String getTextRange(int start, int length) {
4583 checkWidget();
4584 int contentLength = getCharCount();
4585 int end = start + length;
4586 if (start > end || start < 0 || end > contentLength) {
4587 DWT.error(DWT.ERROR_INVALID_RANGE);
4588 }
4589 return content.getTextRange(start, length);
4590 }
4591 /**
4592 * Returns the maximum number of characters that the receiver is capable of holding.
4593 *
4594 * @return the text limit
4595 *
4596 * @exception DWTException <ul>
4597 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4598 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4599 * </ul>
4600 */
4601 public int getTextLimit() {
4602 checkWidget();
4603 return textLimit;
4604 }
4605 /**
4606 * Gets the top index.
4607 * <p>
4608 * The top index is the index of the fully visible line that is currently
4609 * at the top of the widget or the topmost partially visible line if no line is fully visible.
4610 * The top index changes when the widget is scrolled. Indexing is zero based.
4611 * </p>
4612 *
4613 * @return the index of the top line
4614 * @exception DWTException <ul>
4615 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4616 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4617 * </ul>
4618 */
4619 public int getTopIndex() {
4620 checkWidget();
4621 return topIndex;
4622 }
4623 /**
4624 * Gets the top pixel.
4625 * <p>
4626 * The top pixel is the pixel position of the line that is
4627 * currently at the top of the widget. The text widget can be scrolled by pixels
4628 * by dragging the scroll thumb so that a partial line may be displayed at the top
4629 * the widget. The top pixel changes when the widget is scrolled. The top pixel
4630 * does not include the widget trimming.
4631 * </p>
4632 *
4633 * @return pixel position of the top line
4634 * @exception DWTException <ul>
4635 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4636 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4637 * </ul>
4638 */
4639 public int getTopPixel() {
4640 checkWidget();
4641 return getVerticalScrollOffset();
4642 }
4643 /**
4644 * Returns the vertical scroll increment.
4645 *
4646 * @return vertical scroll increment.
4647 */
4648 int getVerticalIncrement() {
4649 return renderer.getLineHeight();
4650 }
4651 int getVerticalScrollOffset() {
4652 if (verticalScrollOffset is -1) {
4653 renderer.calculate(0, topIndex);
4654 int height = 0;
4655 for (int i = 0; i < topIndex; i++) {
4656 height += renderer.getLineHeight(i);
4657 }
4658 height -= topIndexY;
4659 verticalScrollOffset = height;
4660 }
4661 return verticalScrollOffset;
4662 }
4663 int getVisualLineIndex(TextLayout layout, int offsetInLine) {
4664 int lineIndex = layout.getLineIndex(offsetInLine);
4665 int[] offsets = layout.getLineOffsets();
4666 if (lineIndex !is 0 && offsetInLine is offsets[lineIndex]) {
4667 int lineY = layout.getLineBounds(lineIndex).y;
4668 int caretY = getCaret().getLocation().y - topMargin - getLinePixel(getCaretLine());
4669 if (lineY > caretY) lineIndex--;
4670 }
4671 return lineIndex;
4672 }
4673 int getCaretDirection() {
4674 if (!isBidiCaret()) return DWT.DEFAULT;
4675 if (ime.getCompositionOffset() !is -1) return DWT.DEFAULT;
4676 if (!updateCaretDirection && caretDirection !is DWT.NULL) return caretDirection;
4677 updateCaretDirection = false;
4678 int caretLine = getCaretLine();
4679 int lineOffset = content.getOffsetAtLine(caretLine);
4680 String line = content.getLine(caretLine);
4681 int offset = caretOffset - lineOffset;
4682 int lineLength = line.length();
4683 if (lineLength is 0) return isMirrored() ? DWT.RIGHT : DWT.LEFT;
4684 if (caretAlignment is PREVIOUS_OFFSET_TRAILING && offset > 0) offset--;
4685 if (offset is lineLength && offset > 0) offset--;
4686 while (offset > 0 && Character.isDigit(line.charAt(offset))) offset--;
4687 if (offset is 0 && Character.isDigit(line.charAt(offset))) {
4688 return isMirrored() ? DWT.RIGHT : DWT.LEFT;
4689 }
4690 TextLayout layout = renderer.getTextLayout(caretLine);
4691 int level = layout.getLevel(offset);
4692 renderer.disposeTextLayout(layout);
4693 return ((level & 1) !is 0) ? DWT.RIGHT : DWT.LEFT;
4694 }
4695 /*
4696 * Returns the index of the line the caret is on.
4697 */
4698 int getCaretLine() {
4699 return content.getLineAtOffset(caretOffset);
4700 }
4701 int getWrapWidth () {
4702 if (wordWrap && !isSingleLine()) {
4703 int width = clientAreaWidth - leftMargin - rightMargin - getCaretWidth();
4704 return width > 0 ? width : 1;
4705 }
4706 return -1;
4707 }
4708 int getWordNext (int offset, int movement) {
4709 int newOffset, lineOffset;
4710 String lineText;
4711 if (offset >= getCharCount()) {
4712 newOffset = offset;
4713 int lineIndex = content.getLineCount() - 1;
4714 lineOffset = content.getOffsetAtLine(lineIndex);
4715 lineText = content.getLine(lineIndex);
4716 } else {
4717 int lineIndex = content.getLineAtOffset(offset);
4718 lineOffset = content.getOffsetAtLine(lineIndex);
4719 lineText = content.getLine(lineIndex);
4720 int lineLength = lineText.length();
4721 if (offset is lineOffset + lineLength) {
4722 newOffset = content.getOffsetAtLine(lineIndex + 1);
4723 } else {
4724 TextLayout layout = renderer.getTextLayout(lineIndex);
4725 newOffset = lineOffset + layout.getNextOffset(offset - lineOffset, movement);
4726 renderer.disposeTextLayout(layout);
4727 }
4728 }
4729 return sendWordBoundaryEvent(WordNext, movement, offset, newOffset, lineText, lineOffset);
4730 }
4731 int getWordPrevious(int offset, int movement) {
4732 int newOffset, lineOffset;
4733 String lineText;
4734 if (offset <= 0) {
4735 newOffset = 0;
4736 int lineIndex = content.getLineAtOffset(newOffset);
4737 lineOffset = content.getOffsetAtLine(lineIndex);
4738 lineText = content.getLine(lineIndex);
4739 } else {
4740 int lineIndex = content.getLineAtOffset(offset);
4741 lineOffset = content.getOffsetAtLine(lineIndex);
4742 lineText = content.getLine(lineIndex);
4743 if (offset is lineOffset) {
4744 String nextLineText = content.getLine(lineIndex - 1);
4745 int nextLineOffset = content.getOffsetAtLine(lineIndex - 1);
4746 newOffset = nextLineOffset + nextLineText.length();
4747 } else {
4748 TextLayout layout = renderer.getTextLayout(lineIndex);
4749 newOffset = lineOffset + layout.getPreviousOffset(offset - lineOffset, movement);
4750 renderer.disposeTextLayout(layout);
4751 }
4752 }
4753 return sendWordBoundaryEvent(WordPrevious, movement, offset, newOffset, lineText, lineOffset);
4754 }
4755 /**
4756 * Returns whether the widget wraps lines.
4757 *
4758 * @return true if widget wraps lines, false otherwise
4759 * @since 2.0
4760 */
4761 public bool getWordWrap() {
4762 checkWidget();
4763 return wordWrap;
4764 }
4765 /**
4766 * Returns the location of the given offset.
4767 * <p>
4768 * <b>NOTE:</b> Does not return correct values for true italic fonts (vs. slanted fonts).
4769 * </p>
4770 *
4771 * @return location of the character at the given offset in the line.
4772 */
4773 Point getPointAtOffset(int offset) {
4774 int lineIndex = content.getLineAtOffset(offset);
4775 String line = content.getLine(lineIndex);
4776 int lineOffset = content.getOffsetAtLine(lineIndex);
4777 int offsetInLine = offset - lineOffset;
4778 int lineLength = line.length();
4779 if (lineIndex < content.getLineCount() - 1) {
4780 int endLineOffset = content.getOffsetAtLine(lineIndex + 1) - 1;
4781 if (lineLength < offsetInLine && offsetInLine <= endLineOffset) {
4782 offsetInLine = lineLength;
4783 }
4784 }
4785 Point point;
4786 TextLayout layout = renderer.getTextLayout(lineIndex);
4787 if (lineLength !is 0 && offsetInLine <= lineLength) {
4788 if (offsetInLine is lineLength) {
4789 point = layout.getLocation(offsetInLine - 1, true);
4790 } else {
4791 switch (caretAlignment) {
4792 case OFFSET_LEADING:
4793 point = layout.getLocation(offsetInLine, false);
4794 break;
4795 case PREVIOUS_OFFSET_TRAILING:
4796 default:
4797 if (offsetInLine is 0) {
4798 point = layout.getLocation(offsetInLine, false);
4799 } else {
4800 point = layout.getLocation(offsetInLine - 1, true);
4801 }
4802 break;
4803 }
4804 }
4805 } else {
4806 point = new Point(layout.getIndent(), 0);
4807 }
4808 renderer.disposeTextLayout(layout);
4809 point.x += leftMargin - horizontalScrollOffset;
4810 point.y += getLinePixel(lineIndex);
4811 return point;
4812 }
4813 /**
4814 * Inserts a String. The old selection is replaced with the new text.
4815 *
4816 * @param String the String
4817 * @see #replaceTextRange(int,int,String)
4818 * @exception DWTException <ul>
4819 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4820 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4821 * </ul>
4822 * @exception IllegalArgumentException <ul>
4823 * <li>ERROR_NULL_ARGUMENT when String is null</li>
4824 * </ul>
4825 */
4826 public void insert(String String) {
4827 checkWidget();
4828 if (String is null) {
4829 DWT.error(DWT.ERROR_NULL_ARGUMENT);
4830 }
4831 Point sel = getSelectionRange();
4832 replaceTextRange(sel.x, sel.y, String);
4833 }
4834 /**
4835 * Creates content change listeners and set the default content model.
4836 */
4837 void installDefaultContent() {
4838 textChangeListener = new TextChangeListener() {
4839 public void textChanging(TextChangingEvent event) {
4840 handleTextChanging(event);
4841 }
4842 public void textChanged(TextChangedEvent event) {
4843 handleTextChanged(event);
4844 }
4845 public void textSet(TextChangedEvent event) {
4846 handleTextSet(event);
4847 }
4848 };
4849 content = new DefaultContent();
4850 content.addTextChangeListener(textChangeListener);
4851 }
4852 /**
4853 * Adds event listeners
4854 */
4855 void installListeners() {
4856 ScrollBar verticalBar = getVerticalBar();
4857 ScrollBar horizontalBar = getHorizontalBar();
4858
4859 listener = new Listener() {
4860 public void handleEvent(Event event) {
4861 switch (event.type) {
4862 case DWT.Dispose: handleDispose(event); break;
4863 case DWT.KeyDown: handleKeyDown(event); break;
4864 case DWT.KeyUp: handleKeyUp(event); break;
4865 case DWT.MouseDown: handleMouseDown(event); break;
4866 case DWT.MouseUp: handleMouseUp(event); break;
4867 case DWT.MouseMove: handleMouseMove(event); break;
4868 case DWT.Paint: handlePaint(event); break;
4869 case DWT.Resize: handleResize(event); break;
4870 case DWT.Traverse: handleTraverse(event); break;
4871 }
4872 }
4873 };
4874 addListener(DWT.Dispose, listener);
4875 addListener(DWT.KeyDown, listener);
4876 addListener(DWT.KeyUp, listener);
4877 addListener(DWT.MouseDown, listener);
4878 addListener(DWT.MouseUp, listener);
4879 addListener(DWT.MouseMove, listener);
4880 addListener(DWT.Paint, listener);
4881 addListener(DWT.Resize, listener);
4882 addListener(DWT.Traverse, listener);
4883 ime.addListener(DWT.ImeComposition, new Listener() {
4884 public void handleEvent(Event event) {
4885 switch (event.detail) {
4886 case DWT.COMPOSITION_SELECTION: handleCompositionSelection(event); break;
4887 case DWT.COMPOSITION_CHANGED: handleCompositionChanged(event); break;
4888 case DWT.COMPOSITION_OFFSET: handleCompositionOffset(event); break;
4889 }
4890 }
4891 });
4892 if (verticalBar !is null) {
4893 verticalBar.addListener(DWT.Selection, new Listener() {
4894 public void handleEvent(Event event) {
4895 handleVerticalScroll(event);
4896 }
4897 });
4898 }
4899 if (horizontalBar !is null) {
4900 horizontalBar.addListener(DWT.Selection, new Listener() {
4901 public void handleEvent(Event event) {
4902 handleHorizontalScroll(event);
4903 }
4904 });
4905 }
4906 }
4907 void internalRedrawRange(int start, int length) {
4908 if (length <= 0) return;
4909 int end = start + length;
4910 int startLine = content.getLineAtOffset(start);
4911 int endLine = content.getLineAtOffset(end);
4912 int partialBottomIndex = getPartialBottomIndex();
4913 int partialTopIndex = getPartialTopIndex();
4914 if (startLine > partialBottomIndex || endLine < partialTopIndex) {
4915 return;
4916 }
4917 if (partialTopIndex > startLine) {
4918 startLine = partialTopIndex;
4919 start = 0;
4920 } else {
4921 start -= content.getOffsetAtLine(startLine);
4922 }
4923 if (partialBottomIndex < endLine) {
4924 endLine = partialBottomIndex + 1;
4925 end = 0;
4926 } else {
4927 end -= content.getOffsetAtLine(endLine);
4928 }
4929
4930 TextLayout layout = renderer.getTextLayout(startLine);
4931 int lineX = leftMargin - horizontalScrollOffset, startLineY = getLinePixel(startLine);
4932 int[] offsets = layout.getLineOffsets();
4933 int startIndex = layout.getLineIndex(Math.min(start, layout.getText().length()));
4934
4935 /* Redraw end of line before start line if wrapped and start offset is first char */
4936 if (wordWrap && startIndex > 0 && offsets[startIndex] is start) {
4937 Rectangle rect = layout.getLineBounds(startIndex - 1);
4938 rect.x = rect.width;
4939 rect.width = clientAreaWidth - rightMargin - rect.x;
4940 rect.x += lineX;
4941 rect.y += startLineY;
4942 super.redraw(rect.x, rect.y, rect.width, rect.height, false);
4943 }
4944
4945 if (startLine is endLine) {
4946 int endIndex = layout.getLineIndex(Math.min(end, layout.getText().length()));
4947 if (startIndex is endIndex) {
4948 /* Redraw rect between start and end offset if start and end offsets are in same wrapped line */
4949 Rectangle rect = layout.getBounds(start, end - 1);
4950 rect.x += lineX;
4951 rect.y += startLineY;
4952 super.redraw(rect.x, rect.y, rect.width, rect.height, false);
4953 renderer.disposeTextLayout(layout);
4954 return;
4955 }
4956 }
4957
4958 /* Redraw start line from the start offset to the end of client area */
4959 Rectangle startRect = layout.getBounds(start, offsets[startIndex + 1] - 1);
4960 if (startRect.height is 0) {
4961 Rectangle bounds = layout.getLineBounds(startIndex);
4962 startRect.x = bounds.width;
4963 startRect.y = bounds.y;
4964 startRect.height = bounds.height;
4965 }
4966 startRect.x += lineX;
4967 startRect.y += startLineY;
4968 startRect.width = clientAreaWidth - rightMargin - startRect.x;
4969 super.redraw(startRect.x, startRect.y, startRect.width, startRect.height, false);
4970
4971 /* Redraw end line from the beginning of the line to the end offset */
4972 if (startLine !is endLine) {
4973 renderer.disposeTextLayout(layout);
4974 layout = renderer.getTextLayout(endLine);
4975 offsets = layout.getLineOffsets();
4976 }
4977 int endIndex = layout.getLineIndex(Math.min(end, layout.getText().length()));
4978 Rectangle endRect = layout.getBounds(offsets[endIndex], end - 1);
4979 if (endRect.height is 0) {
4980 Rectangle bounds = layout.getLineBounds(endIndex);
4981 endRect.y = bounds.y;
4982 endRect.height = bounds.height;
4983 }
4984 endRect.x += lineX;
4985 endRect.y += getLinePixel(endLine);
4986 super.redraw(endRect.x, endRect.y, endRect.width, endRect.height, false);
4987 renderer.disposeTextLayout(layout);
4988
4989 /* Redraw all lines in between start and end line */
4990 int y = startRect.y + startRect.height;
4991 if (endRect.y > y) {
4992 super.redraw(leftMargin, y, clientAreaWidth - rightMargin - leftMargin, endRect.y - y, false);
4993 }
4994 }
4995 void handleCompositionOffset (Event event) {
4996 int[] trailing = new int [1];
4997 event.index = getOffsetAtPoint(event.x, event.y, trailing, true);
4998 event.count = trailing[0];
4999 }
5000 void handleCompositionSelection (Event event) {
5001 event.start = selection.x;
5002 event.end = selection.y;
5003 event.text = getSelectionText();
5004 }
5005 void handleCompositionChanged(Event event) {
5006 String text = event.text;
5007 int start = event.start;
5008 int end = event.end;
5009 int length = text.length();
5010 if (length is ime.getCommitCount()) {
5011 content.replaceTextRange(start, end - start, "");
5012 caretOffset = start;
5013 caretWidth = 0;
5014 caretDirection = DWT.NULL;
5015 } else {
5016 content.replaceTextRange(start, end - start, text);
5017 caretOffset = ime.getCaretOffset();
5018 if (ime.getWideCaret()) {
5019 int lineIndex = getCaretLine();
5020 int lineOffset = content.getOffsetAtLine(lineIndex);
5021 TextLayout layout = renderer.getTextLayout(lineIndex);
5022 caretWidth = layout.getBounds(start - lineOffset, start + length - 1 - lineOffset).width;
5023 renderer.disposeTextLayout(layout);
5024 }
5025 }
5026 showCaret();
5027 }
5028 /**
5029 * Frees resources.
5030 */
5031 void handleDispose(Event event) {
5032 removeListener(DWT.Dispose, listener);
5033 notifyListeners(DWT.Dispose, event);
5034 event.type = DWT.None;
5035
5036 clipboard.dispose();
5037 if (renderer !is null) {
5038 renderer.dispose();
5039 renderer = null;
5040 }
5041 if (content !is null) {
5042 content.removeTextChangeListener(textChangeListener);
5043 content = null;
5044 }
5045 if (defaultCaret !is null) {
5046 defaultCaret.dispose();
5047 defaultCaret = null;
5048 }
5049 if (leftCaretBitmap !is null) {
5050 leftCaretBitmap.dispose();
5051 leftCaretBitmap = null;
5052 }
5053 if (rightCaretBitmap !is null) {
5054 rightCaretBitmap.dispose();
5055 rightCaretBitmap = null;
5056 }
5057 if (isBidiCaret()) {
5058 BidiUtil.removeLanguageListener(this);
5059 }
5060 selectionBackground = null;
5061 selectionForeground = null;
5062 textChangeListener = null;
5063 selection = null;
5064 doubleClickSelection = null;
5065 keyActionMap = null;
5066 background = null;
5067 foreground = null;
5068 clipboard = null;
5069 }
5070 /**
5071 * Scrolls the widget horizontally.
5072 */
5073 void handleHorizontalScroll(Event event) {
5074 int scrollPixel = getHorizontalBar().getSelection() - horizontalScrollOffset;
5075 scrollHorizontal(scrollPixel, false);
5076 }
5077 /**
5078 * If an action has been registered for the key stroke execute the action.
5079 * Otherwise, if a character has been entered treat it as new content.
5080 *
5081 * @param event keyboard event
5082 */
5083 void handleKey(Event event) {
5084 int action;
5085 caretAlignment = PREVIOUS_OFFSET_TRAILING;
5086 if (event.keyCode !is 0) {
5087 // special key pressed (e.g., F1)
5088 action = getKeyBinding(event.keyCode | event.stateMask);
5089 } else {
5090 // character key pressed
5091 action = getKeyBinding(event.character | event.stateMask);
5092 if (action is DWT.NULL) {
5093 // see if we have a control character
5094 if ((event.stateMask & DWT.CTRL) !is 0 && (event.character >= 0) && event.character <= 31) {
5095 // get the character from the CTRL+char sequence, the control
5096 // key subtracts 64 from the value of the key that it modifies
5097 int c = event.character + 64;
5098 action = getKeyBinding(c | event.stateMask);
5099 }
5100 }
5101 }
5102 if (action is DWT.NULL) {
5103 bool ignore = false;
5104
5105 if (IS_CARBON) {
5106 // Ignore accelerator key combinations (we do not want to
5107 // insert a character in the text in this instance). Do not
5108 // ignore COMMAND+ALT combinations since that key sequence
5109 // produces characters on the mac.
5110 ignore = (event.stateMask ^ DWT.COMMAND) is 0 ||
5111 (event.stateMask ^ (DWT.COMMAND | DWT.SHIFT)) is 0;
5112 } else if (IS_MOTIF) {
5113 // Ignore accelerator key combinations (we do not want to
5114 // insert a character in the text in this instance). Do not
5115 // ignore ALT combinations since this key sequence
5116 // produces characters on motif.
5117 ignore = (event.stateMask ^ DWT.CTRL) is 0 ||
5118 (event.stateMask ^ (DWT.CTRL | DWT.SHIFT)) is 0;
5119 } else {
5120 // Ignore accelerator key combinations (we do not want to
5121 // insert a character in the text in this instance). Don't
5122 // ignore CTRL+ALT combinations since that is the Alt Gr
5123 // key on some keyboards. See bug 20953.
5124 ignore = (event.stateMask ^ DWT.ALT) is 0 ||
5125 (event.stateMask ^ DWT.CTRL) is 0 ||
5126 (event.stateMask ^ (DWT.ALT | DWT.SHIFT)) is 0 ||
5127 (event.stateMask ^ (DWT.CTRL | DWT.SHIFT)) is 0;
5128 }
5129 // -ignore anything below SPACE except for line delimiter keys and tab.
5130 // -ignore DEL
5131 if (!ignore && event.character > 31 && event.character !is DWT.DEL ||
5132 event.character is DWT.CR || event.character is DWT.LF ||
5133 event.character is TAB) {
5134 doContent(event.character);
5135 update();
5136 }
5137 } else {
5138 invokeAction(action);
5139 }
5140 }
5141 /**
5142 * If a VerifyKey listener exists, verify that the key that was entered
5143 * should be processed.
5144 *
5145 * @param event keyboard event
5146 */
5147 void handleKeyDown(Event event) {
5148 if (clipboardSelection is null) {
5149 clipboardSelection = new Point(selection.x, selection.y);
5150 }
5151
5152 Event verifyEvent = new Event();
5153 verifyEvent.character = event.character;
5154 verifyEvent.keyCode = event.keyCode;
5155 verifyEvent.stateMask = event.stateMask;
5156 verifyEvent.doit = true;
5157 notifyListeners(VerifyKey, verifyEvent);
5158 if (verifyEvent.doit) {
5159 handleKey(event);
5160 }
5161 }
5162 /**
5163 * Update the Selection Clipboard.
5164 *
5165 * @param event keyboard event
5166 */
5167 void handleKeyUp(Event event) {
5168 if (clipboardSelection !is null) {
5169 if (clipboardSelection.x !is selection.x || clipboardSelection.y !is selection.y) {
5170 try {
5171 if (selection.y - selection.x > 0) {
5172 setClipboardContent(selection.x, selection.y - selection.x, DND.SELECTION_CLIPBOARD);
5173 }
5174 } catch (DWTError error) {
5175 // Copy to clipboard failed. This happens when another application
5176 // is accessing the clipboard while we copy. Ignore the error.
5177 // Fixes 1GDQAVN
5178 // Rethrow all other errors. Fixes bug 17578.
5179 if (error.code !is DND.ERROR_CANNOT_SET_CLIPBOARD) {
5180 throw error;
5181 }
5182 }
5183 }
5184 }
5185 clipboardSelection = null;
5186 }
5187 /**
5188 * Updates the caret location and selection if mouse button 1 has been
5189 * pressed.
5190 */
5191 void handleMouseDown(Event event) {
5192 //force focus (object support)
5193 forceFocus();
5194
5195 //drag detect
5196 if (dragDetect && checkDragDetect(event)) return;
5197
5198 //paste clipboard selection
5199 if (event.button is 2) {
5200 String text = (String)getClipboardContent(DND.SELECTION_CLIPBOARD);
5201 if (text !is null && text.length() > 0) {
5202 // position cursor
5203 doMouseLocationChange(event.x, event.y, false);
5204 // insert text
5205 Event e = new Event();
5206 e.start = selection.x;
5207 e.end = selection.y;
5208 e.text = getModelDelimitedText(text);
5209 sendKeyEvent(e);
5210 }
5211 }
5212
5213 //set selection
5214 if ((event.button !is 1) || (IS_CARBON && (event.stateMask & DWT.MOD4) !is 0)) {
5215 return;
5216 }
5217 clickCount = event.count;
5218 if (clickCount is 1) {
5219 bool select = (event.stateMask & DWT.MOD2) !is 0;
5220 doMouseLocationChange(event.x, event.y, select);
5221 } else {
5222 if (doubleClickEnabled) {
5223 clearSelection(false);
5224 int offset = getOffsetAtPoint(event.x, event.y);
5225 int lineIndex = content.getLineAtOffset(offset);
5226 int lineOffset = content.getOffsetAtLine(lineIndex);
5227 int lineEnd = content.getCharCount();
5228 if (lineIndex + 1 < content.getLineCount()) {
5229 lineEnd = content.getOffsetAtLine(lineIndex + 1);
5230 }
5231 int start, end;
5232 if ((clickCount & 1) is 0) {
5233 start = Math.max(0, getWordPrevious(offset, DWT.MOVEMENT_WORD_START));
5234 end = Math.min(content.getCharCount(), getWordNext(start, DWT.MOVEMENT_WORD_END));
5235 } else {
5236 start = lineOffset;
5237 end = lineEnd;
5238 }
5239 caretOffset = start;
5240 resetSelection();
5241 caretOffset = end;
5242 showCaret();
5243 doMouseSelection();
5244 doubleClickSelection = new Point(selection.x, selection.y);
5245 }
5246 }
5247 }
5248 /**
5249 * Updates the caret location and selection if mouse button 1 is pressed
5250 * during the mouse move.
5251 */
5252 void handleMouseMove(Event event) {
5253 if (clickCount is 0) return;
5254 doMouseLocationChange(event.x, event.y, true);
5255 update();
5256 doAutoScroll(event);
5257 }
5258 /**
5259 * Autoscrolling ends when the mouse button is released.
5260 */
5261 void handleMouseUp(Event event) {
5262 clickCount = 0;
5263 endAutoScroll();
5264 if (event.button is 1) {
5265 try {
5266 if (selection.y - selection.x > 0) {
5267 setClipboardContent(selection.x, selection.y - selection.x, DND.SELECTION_CLIPBOARD);
5268 }
5269 } catch (DWTError error) {
5270 // Copy to clipboard failed. This happens when another application
5271 // is accessing the clipboard while we copy. Ignore the error.
5272 // Fixes 1GDQAVN
5273 // Rethrow all other errors. Fixes bug 17578.
5274 if (error.code !is DND.ERROR_CANNOT_SET_CLIPBOARD) {
5275 throw error;
5276 }
5277 }
5278 }
5279 }
5280 /**
5281 * Renders the invalidated area specified in the paint event.
5282 *
5283 * @param event paint event
5284 */
5285 void handlePaint(Event event) {
5286 if (event.width is 0 || event.height is 0) return;
5287 if (clientAreaWidth is 0 || clientAreaHeight is 0) return;
5288
5289 int startLine = getLineIndex(event.y);
5290 int y = getLinePixel(startLine);
5291 int endY = event.y + event.height;
5292 GC gc = event.gc;
5293 Color background = getBackground();
5294 Color foreground = getForeground();
5295 if (endY > 0) {
5296 int lineCount = isSingleLine() ? 1 : content.getLineCount();
5297 int x = leftMargin - horizontalScrollOffset;
5298 for (int i = startLine; y < endY && i < lineCount; i++) {
5299 y += renderer.drawLine(i, x, y, gc, background, foreground);
5300 }
5301 if (y < endY) {
5302 gc.setBackground(background);
5303 drawBackground(gc, 0, y, clientAreaWidth, endY - y);
5304 }
5305 }
5306 // fill the margin background
5307 gc.setBackground(background);
5308 if (topMargin > 0) {
5309 drawBackground(gc, 0, 0, clientAreaWidth, topMargin);
5310 }
5311 if (bottomMargin > 0) {
5312 drawBackground(gc, 0, clientAreaHeight - bottomMargin, clientAreaWidth, bottomMargin);
5313 }
5314 if (leftMargin > 0) {
5315 drawBackground(gc, 0, 0, leftMargin, clientAreaHeight);
5316 }
5317 if (rightMargin > 0) {
5318 drawBackground(gc, clientAreaWidth - rightMargin, 0, rightMargin, clientAreaHeight);
5319 }
5320 }
5321 /**
5322 * Recalculates the scroll bars. Rewraps all lines when in word
5323 * wrap mode.
5324 *
5325 * @param event resize event
5326 */
5327 void handleResize(Event event) {
5328 int oldHeight = clientAreaHeight;
5329 int oldWidth = clientAreaWidth;
5330 Rectangle clientArea = getClientArea();
5331 clientAreaHeight = clientArea.height;
5332 clientAreaWidth = clientArea.width;
5333 /* Redraw the old or new right/bottom margin if needed */
5334 if (oldWidth !is clientAreaWidth) {
5335 if (rightMargin > 0) {
5336 int x = (oldWidth < clientAreaWidth ? oldWidth : clientAreaWidth) - rightMargin;
5337 super.redraw(x, 0, rightMargin, oldHeight, false);
5338 }
5339 }
5340 if (oldHeight !is clientAreaHeight) {
5341 if (bottomMargin > 0) {
5342 int y = (oldHeight < clientAreaHeight ? oldHeight : clientAreaHeight) - bottomMargin;
5343 super.redraw(0, y, oldWidth, bottomMargin, false);
5344 }
5345 }
5346 if (wordWrap) {
5347 if (oldWidth !is clientAreaWidth) {
5348 renderer.reset(0, content.getLineCount());
5349 verticalScrollOffset = -1;
5350 renderer.calculateIdle();
5351 super.redraw();
5352 }
5353 if (oldHeight !is clientAreaHeight) {
5354 if (oldHeight is 0) topIndexY = 0;
5355 setScrollBars(true);
5356 }
5357 setCaretLocation();
5358 } else {
5359 renderer.calculateClientArea();
5360 setScrollBars(true);
5361 claimRightFreeSpace();
5362 // StyledText allows any value for horizontalScrollOffset when clientArea is zero
5363 // in setHorizontalPixel() and setHorisontalOffset(). Fixes bug 168429.
5364 if (clientAreaWidth !is 0) {
5365 ScrollBar horizontalBar = getHorizontalBar();
5366 if (horizontalBar !is null && horizontalBar.getVisible()) {
5367 if (horizontalScrollOffset !is horizontalBar.getSelection()) {
5368 horizontalBar.setSelection(horizontalScrollOffset);
5369 horizontalScrollOffset = horizontalBar.getSelection();
5370 }
5371 }
5372 }
5373 }
5374 claimBottomFreeSpace();
5375 //TODO FIX TOP INDEX DURING RESIZE
5376 // if (oldHeight !is clientAreaHeight || wordWrap) {
5377 // calculateTopIndex(0);
5378 // }
5379 }
5380 /**
5381 * Updates the caret position and selection and the scroll bars to reflect
5382 * the content change.
5383 */
5384 void handleTextChanged(TextChangedEvent event) {
5385 int firstLine = content.getLineAtOffset(lastTextChangeStart);
5386 resetCache(firstLine, 0);
5387 if (!isFixedLineHeight() && topIndex > firstLine) {
5388 topIndex = firstLine;
5389 topIndexY = 0;
5390 super.redraw();
5391 } else {
5392 int lastLine = firstLine + lastTextChangeNewLineCount;
5393 int firstLineTop = getLinePixel(firstLine);
5394 int newLastLineBottom = getLinePixel(lastLine + 1);
5395 if (lastLineBottom !is newLastLineBottom) {
5396 super.redraw();
5397 if (wordWrap) setCaretLocation();
5398 } else {
5399 super.redraw(0, firstLineTop, clientAreaWidth, newLastLineBottom - firstLineTop, false);
5400 redrawLinesBullet(renderer.redrawLines);
5401 }
5402 }
5403 renderer.redrawLines = null;
5404 // update selection/caret location after styles have been changed.
5405 // otherwise any text measuring could be incorrect
5406 //
5407 // also, this needs to be done after all scrolling. Otherwise,
5408 // selection redraw would be flushed during scroll which is wrong.
5409 // in some cases new text would be drawn in scroll source area even
5410 // though the intent is to scroll it.
5411 updateSelection(lastTextChangeStart, lastTextChangeReplaceCharCount, lastTextChangeNewCharCount);
5412 if (lastTextChangeReplaceLineCount > 0 || wordWrap) {
5413 claimBottomFreeSpace();
5414 }
5415 if (lastTextChangeReplaceCharCount > 0) {
5416 claimRightFreeSpace();
5417 }
5418 }
5419 /**
5420 * Updates the screen to reflect a pending content change.
5421 *
5422 * @param event .start the start offset of the change
5423 * @param event .newText text that is going to be inserted or empty String
5424 * if no text will be inserted
5425 * @param event .replaceCharCount length of text that is going to be replaced
5426 * @param event .newCharCount length of text that is going to be inserted
5427 * @param event .replaceLineCount number of lines that are going to be replaced
5428 * @param event .newLineCount number of new lines that are going to be inserted
5429 */
5430 void handleTextChanging(TextChangingEvent event) {
5431 if (event.replaceCharCount < 0) {
5432 event.start += event.replaceCharCount;
5433 event.replaceCharCount *= -1;
5434 }
5435 lastTextChangeStart = event.start;
5436 lastTextChangeNewLineCount = event.newLineCount;
5437 lastTextChangeNewCharCount = event.newCharCount;
5438 lastTextChangeReplaceLineCount = event.replaceLineCount;
5439 lastTextChangeReplaceCharCount = event.replaceCharCount;
5440 int lineIndex = content.getLineAtOffset(event.start);
5441 int srcY = getLinePixel(lineIndex + event.replaceLineCount + 1);
5442 int destY = getLinePixel(lineIndex + 1) + event.newLineCount * renderer.getLineHeight();
5443 lastLineBottom = destY;
5444 if (srcY < 0 && destY < 0) {
5445 lastLineBottom += srcY - destY;
5446 verticalScrollOffset += destY - srcY;
5447 calculateTopIndex(destY - srcY);
5448 setScrollBars(true);
5449 } else {
5450 scrollText(srcY, destY);
5451 }
5452
5453 renderer.textChanging(event);
5454
5455 // Update the caret offset if it is greater than the length of the content.
5456 // This is necessary since style range API may be called between the
5457 // handleTextChanging and handleTextChanged events and this API sets the
5458 // caretOffset.
5459 int newEndOfText = content.getCharCount() - event.replaceCharCount + event.newCharCount;
5460 if (caretOffset > newEndOfText) caretOffset = newEndOfText;
5461 }
5462 /**
5463 * Called when the widget content is set programmatically, overwriting
5464 * the old content. Resets the caret position, selection and scroll offsets.
5465 * Recalculates the content width and scroll bars. Redraws the widget.
5466 *
5467 * @param event text change event.
5468 */
5469 void handleTextSet(TextChangedEvent event) {
5470 reset();
5471 }
5472 /**
5473 * Called when a traversal key is pressed.
5474 * Allow tab next traversal to occur when the widget is in single
5475 * line mode or in multi line and non-editable mode .
5476 * When in editable multi line mode we want to prevent the tab
5477 * traversal and receive the tab key event instead.
5478 *
5479 * @param event the event
5480 */
5481 void handleTraverse(Event event) {
5482 switch (event.detail) {
5483 case DWT.TRAVERSE_ESCAPE:
5484 case DWT.TRAVERSE_PAGE_NEXT:
5485 case DWT.TRAVERSE_PAGE_PREVIOUS:
5486 event.doit = true;
5487 break;
5488 case DWT.TRAVERSE_RETURN:
5489 case DWT.TRAVERSE_TAB_NEXT:
5490 case DWT.TRAVERSE_TAB_PREVIOUS:
5491 if ((getStyle() & DWT.SINGLE) !is 0) {
5492 event.doit = true;
5493 } else {
5494 if (!editable || (event.stateMask & DWT.MODIFIER_MASK) !is 0) {
5495 event.doit = true;
5496 }
5497 }
5498 break;
5499 }
5500 }
5501 /**
5502 * Scrolls the widget vertically.
5503 */
5504 void handleVerticalScroll(Event event) {
5505 int scrollPixel = getVerticalBar().getSelection() - getVerticalScrollOffset();
5506 scrollVertical(scrollPixel, false);
5507 }
5508 /**
5509 * Add accessibility support for the widget.
5510 */
5511 void initializeAccessible() {
5512 final Accessible accessible = getAccessible();
5513 accessible.addAccessibleListener(new AccessibleAdapter() {
5514 public void getName (AccessibleEvent e) {
5515 String name = null;
5516 Label label = getAssociatedLabel ();
5517 if (label !is null) {
5518 name = stripMnemonic (label.getText());
5519 }
5520 e.result = name;
5521 }
5522 public void getHelp(AccessibleEvent e) {
5523 e.result = getToolTipText();
5524 }
5525 public void getKeyboardShortcut(AccessibleEvent e) {
5526 String shortcut = null;
5527 Label label = getAssociatedLabel ();
5528 if (label !is null) {
5529 String text = label.getText ();
5530 if (text !is null) {
5531 char mnemonic = _findMnemonic (text);
5532 if (mnemonic !is '\0') {
5533 shortcut = "Alt+"+mnemonic; //$NON-NLS-1$
5534 }
5535 }
5536 }
5537 e.result = shortcut;
5538 }
5539 });
5540 accessible.addAccessibleTextListener(new AccessibleTextAdapter() {
5541 public void getCaretOffset(AccessibleTextEvent e) {
5542 e.offset = StyledText.this.getCaretOffset();
5543 }
5544 public void getSelectionRange(AccessibleTextEvent e) {
5545 Point selection = StyledText.this.getSelectionRange();
5546 e.offset = selection.x;
5547 e.length = selection.y;
5548 }
5549 });
5550 accessible.addAccessibleControlListener(new AccessibleControlAdapter() {
5551 public void getRole(AccessibleControlEvent e) {
5552 e.detail = ACC.ROLE_TEXT;
5553 }
5554 public void getState(AccessibleControlEvent e) {
5555 int state = 0;
5556 if (isEnabled()) state |= ACC.STATE_FOCUSABLE;
5557 if (isFocusControl()) state |= ACC.STATE_FOCUSED;
5558 if (!isVisible()) state |= ACC.STATE_INVISIBLE;
5559 if (!getEditable()) state |= ACC.STATE_READONLY;
5560 e.detail = state;
5561 }
5562 public void getValue(AccessibleControlEvent e) {
5563 e.result = StyledText.this.getText();
5564 }
5565 });
5566 addListener(DWT.FocusIn, new Listener() {
5567 public void handleEvent(Event event) {
5568 accessible.setFocus(ACC.CHILDID_SELF);
5569 }
5570 });
5571 }
5572 /*
5573 * Return the Label immediately preceding the receiver in the z-order,
5574 * or null if none.
5575 */
5576 Label getAssociatedLabel () {
5577 Control[] siblings = getParent ().getChildren ();
5578 for (int i = 0; i < siblings.length; i++) {
5579 if (siblings [i] is StyledText.this) {
5580 if (i > 0 && siblings [i-1] instanceof Label) {
5581 return (Label) siblings [i-1];
5582 }
5583 }
5584 }
5585 return null;
5586 }
5587 String stripMnemonic (String String) {
5588 int index = 0;
5589 int length = String.length ();
5590 do {
5591 while ((index < length) && (String.charAt (index) !is '&')) index++;
5592 if (++index >= length) return String;
5593 if (String.charAt (index) !is '&') {
5594 return String.substring(0, index-1) + String.substring(index, length);
5595 }
5596 index++;
5597 } while (index < length);
5598 return String;
5599 }
5600 /*
5601 * Return the lowercase of the first non-'&' character following
5602 * an '&' character in the given String. If there are no '&'
5603 * characters in the given String, return '\0'.
5604 */
5605 char _findMnemonic (String String) {
5606 if (String is null) return '\0';
5607 int index = 0;
5608 int length = String.length ();
5609 do {
5610 while (index < length && String.charAt (index) !is '&') index++;
5611 if (++index >= length) return '\0';
5612 if (String.charAt (index) !is '&') return Character.toLowerCase (String.charAt (index));
5613 index++;
5614 } while (index < length);
5615 return '\0';
5616 }
5617 /**
5618 * Executes the action.
5619 *
5620 * @param action one of the actions defined in ST.java
5621 */
5622 public void invokeAction(int action) {
5623 checkWidget();
5624 updateCaretDirection = true;
5625 switch (action) {
5626 // Navigation
5627 case ST.LINE_UP:
5628 doLineUp(false);
5629 clearSelection(true);
5630 break;
5631 case ST.LINE_DOWN:
5632 doLineDown(false);
5633 clearSelection(true);
5634 break;
5635 case ST.LINE_START:
5636 doLineStart();
5637 clearSelection(true);
5638 break;
5639 case ST.LINE_END:
5640 doLineEnd();
5641 clearSelection(true);
5642 break;
5643 case ST.COLUMN_PREVIOUS:
5644 doCursorPrevious();
5645 clearSelection(true);
5646 break;
5647 case ST.COLUMN_NEXT:
5648 doCursorNext();
5649 clearSelection(true);
5650 break;
5651 case ST.PAGE_UP:
5652 doPageUp(false, -1);
5653 clearSelection(true);
5654 break;
5655 case ST.PAGE_DOWN:
5656 doPageDown(false, -1);
5657 clearSelection(true);
5658 break;
5659 case ST.WORD_PREVIOUS:
5660 doWordPrevious();
5661 clearSelection(true);
5662 break;
5663 case ST.WORD_NEXT:
5664 doWordNext();
5665 clearSelection(true);
5666 break;
5667 case ST.TEXT_START:
5668 doContentStart();
5669 clearSelection(true);
5670 break;
5671 case ST.TEXT_END:
5672 doContentEnd();
5673 clearSelection(true);
5674 break;
5675 case ST.WINDOW_START:
5676 doPageStart();
5677 clearSelection(true);
5678 break;
5679 case ST.WINDOW_END:
5680 doPageEnd();
5681 clearSelection(true);
5682 break;
5683 // Selection
5684 case ST.SELECT_LINE_UP:
5685 doSelectionLineUp();
5686 break;
5687 case ST.SELECT_ALL:
5688 selectAll();
5689 break;
5690 case ST.SELECT_LINE_DOWN:
5691 doSelectionLineDown();
5692 break;
5693 case ST.SELECT_LINE_START:
5694 doLineStart();
5695 doSelection(ST.COLUMN_PREVIOUS);
5696 break;
5697 case ST.SELECT_LINE_END:
5698 doLineEnd();
5699 doSelection(ST.COLUMN_NEXT);
5700 break;
5701 case ST.SELECT_COLUMN_PREVIOUS:
5702 doSelectionCursorPrevious();
5703 doSelection(ST.COLUMN_PREVIOUS);
5704 break;
5705 case ST.SELECT_COLUMN_NEXT:
5706 doSelectionCursorNext();
5707 doSelection(ST.COLUMN_NEXT);
5708 break;
5709 case ST.SELECT_PAGE_UP:
5710 doSelectionPageUp(-1);
5711 break;
5712 case ST.SELECT_PAGE_DOWN:
5713 doSelectionPageDown(-1);
5714 break;
5715 case ST.SELECT_WORD_PREVIOUS:
5716 doSelectionWordPrevious();
5717 doSelection(ST.COLUMN_PREVIOUS);
5718 break;
5719 case ST.SELECT_WORD_NEXT:
5720 doSelectionWordNext();
5721 doSelection(ST.COLUMN_NEXT);
5722 break;
5723 case ST.SELECT_TEXT_START:
5724 doContentStart();
5725 doSelection(ST.COLUMN_PREVIOUS);
5726 break;
5727 case ST.SELECT_TEXT_END:
5728 doContentEnd();
5729 doSelection(ST.COLUMN_NEXT);
5730 break;
5731 case ST.SELECT_WINDOW_START:
5732 doPageStart();
5733 doSelection(ST.COLUMN_PREVIOUS);
5734 break;
5735 case ST.SELECT_WINDOW_END:
5736 doPageEnd();
5737 doSelection(ST.COLUMN_NEXT);
5738 break;
5739 // Modification
5740 case ST.CUT:
5741 cut();
5742 break;
5743 case ST.COPY:
5744 copy();
5745 break;
5746 case ST.PASTE:
5747 paste();
5748 break;
5749 case ST.DELETE_PREVIOUS:
5750 doBackspace();
5751 break;
5752 case ST.DELETE_NEXT:
5753 doDelete();
5754 break;
5755 case ST.DELETE_WORD_PREVIOUS:
5756 doDeleteWordPrevious();
5757 break;
5758 case ST.DELETE_WORD_NEXT:
5759 doDeleteWordNext();
5760 break;
5761 // Miscellaneous
5762 case ST.TOGGLE_OVERWRITE:
5763 overwrite = !overwrite; // toggle insert/overwrite mode
5764 break;
5765 }
5766 }
5767 /**
5768 * Temporary until DWT provides this
5769 */
5770 bool isBidi() {
5771 return IS_GTK || IS_CARBON || BidiUtil.isBidiPlatform() || isMirrored;
5772 }
5773 bool isBidiCaret() {
5774 return BidiUtil.isBidiPlatform();
5775 }
5776 bool isFixedLineHeight() {
5777 return fixedLineHeight;
5778 }
5779 /**
5780 * Returns whether the given offset is inside a multi byte line delimiter.
5781 * Example:
5782 * "Line1\r\n" isLineDelimiter(5) is false but isLineDelimiter(6) is true
5783 *
5784 * @return true if the given offset is inside a multi byte line delimiter.
5785 * false if the given offset is before or after a line delimiter.
5786 */
5787 bool isLineDelimiter(int offset) {
5788 int line = content.getLineAtOffset(offset);
5789 int lineOffset = content.getOffsetAtLine(line);
5790 int offsetInLine = offset - lineOffset;
5791 // offsetInLine will be greater than line length if the line
5792 // delimiter is longer than one character and the offset is set
5793 // in between parts of the line delimiter.
5794 return offsetInLine > content.getLine(line).length();
5795 }
5796 /**
5797 * Returns whether the widget is mirrored (right oriented/right to left
5798 * writing order).
5799 *
5800 * @return isMirrored true=the widget is right oriented, false=the widget
5801 * is left oriented
5802 */
5803 bool isMirrored() {
5804 return isMirrored;
5805 }
5806 /**
5807 * Returns whether the widget can have only one line.
5808 *
5809 * @return true if widget can have only one line, false if widget can have
5810 * multiple lines
5811 */
5812 bool isSingleLine() {
5813 return (getStyle() & DWT.SINGLE) !is 0;
5814 }
5815 /**
5816 * Sends the specified verify event, replace/insert text as defined by
5817 * the event and send a modify event.
5818 *
5819 * @param event the text change event.
5820 * <ul>
5821 * <li>event.start - the replace start offset</li>
5822 * <li>event.end - the replace end offset</li>
5823 * <li>event.text - the new text</li>
5824 * </ul>
5825 * @param updateCaret whether or not he caret should be set behind
5826 * the new text
5827 */
5828 void modifyContent(Event event, bool updateCaret) {
5829 event.doit = true;
5830 notifyListeners(DWT.Verify, event);
5831 if (event.doit) {
5832 StyledTextEvent styledTextEvent = null;
5833 int replacedLength = event.end - event.start;
5834 if (isListening(ExtendedModify)) {
5835 styledTextEvent = new StyledTextEvent(content);
5836 styledTextEvent.start = event.start;
5837 styledTextEvent.end = event.start + event.text.length();
5838 styledTextEvent.text = content.getTextRange(event.start, replacedLength);
5839 }
5840 if (updateCaret) {
5841 //Fix advancing flag for delete/backspace key on direction boundary
5842 if (event.text.length() is 0) {
5843 int lineIndex = content.getLineAtOffset(event.start);
5844 int lineOffset = content.getOffsetAtLine(lineIndex);
5845 TextLayout layout = renderer.getTextLayout(lineIndex);
5846 int levelStart = layout.getLevel(event.start - lineOffset);
5847 int lineIndexEnd = content.getLineAtOffset(event.end);
5848 if (lineIndex !is lineIndexEnd) {
5849 renderer.disposeTextLayout(layout);
5850 lineOffset = content.getOffsetAtLine(lineIndexEnd);
5851 layout = renderer.getTextLayout(lineIndexEnd);
5852 }
5853 int levelEnd = layout.getLevel(event.end - lineOffset);
5854 renderer.disposeTextLayout(layout);
5855 if (levelStart !is levelEnd) {
5856 caretAlignment = PREVIOUS_OFFSET_TRAILING;
5857 } else {
5858 caretAlignment = OFFSET_LEADING;
5859 }
5860 }
5861 }
5862 content.replaceTextRange(event.start, replacedLength, event.text);
5863 // set the caret position prior to sending the modify event.
5864 // fixes 1GBB8NJ
5865 if (updateCaret) {
5866 // always update the caret location. fixes 1G8FODP
5867 setSelection(event.start + event.text.length(), 0, true);
5868 showCaret();
5869 }
5870 sendModifyEvent(event);
5871 if (isListening(ExtendedModify)) {
5872 notifyListeners(ExtendedModify, styledTextEvent);
5873 }
5874 }
5875 }
5876 void paintObject(GC gc, int x, int y, int ascent, int descent, StyleRange style, Bullet bullet, int bulletIndex) {
5877 if (isListening(PaintObject)) {
5878 StyledTextEvent event = new StyledTextEvent (content) ;
5879 event.gc = gc;
5880 event.x = x;
5881 event.y = y;
5882 event.ascent = ascent;
5883 event.descent = descent;
5884 event.style = style;
5885 event.bullet = bullet;
5886 event.bulletIndex = bulletIndex;
5887 notifyListeners(PaintObject, event);
5888 }
5889 }
5890 /**
5891 * Replaces the selection with the text on the <code>DND.CLIPBOARD</code>
5892 * clipboard or, if there is no selection, inserts the text at the current
5893 * caret offset. If the widget has the DWT.SINGLE style and the
5894 * clipboard text contains more than one line, only the first line without
5895 * line delimiters is inserted in the widget.
5896 *
5897 * @exception DWTException <ul>
5898 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5899 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5900 * </ul>
5901 */
5902 public void paste(){
5903 checkWidget();
5904 String text = (String) getClipboardContent(DND.CLIPBOARD);
5905 if (text !is null && text.length() > 0) {
5906 Event event = new Event();
5907 event.start = selection.x;
5908 event.end = selection.y;
5909 event.text = getModelDelimitedText(text);
5910 sendKeyEvent(event);
5911 }
5912 }
5913 /**
5914 * Prints the widget's text to the default printer.
5915 *
5916 * @exception DWTException <ul>
5917 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5918 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5919 * </ul>
5920 */
5921 public void print() {
5922 checkWidget();
5923 Printer printer = new Printer();
5924 StyledTextPrintOptions options = new StyledTextPrintOptions();
5925 options.printTextForeground = true;
5926 options.printTextBackground = true;
5927 options.printTextFontStyle = true;
5928 options.printLineBackground = true;
5929 new Printing(this, printer, options).run();
5930 printer.dispose();
5931 }
5932 /**
5933 * Returns a runnable that will print the widget's text
5934 * to the specified printer.
5935 * <p>
5936 * The runnable may be run in a non-UI thread.
5937 * </p>
5938 *
5939 * @param printer the printer to print to
5940 * @exception DWTException <ul>
5941 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5942 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5943 * </ul>
5944 * @exception IllegalArgumentException <ul>
5945 * <li>ERROR_NULL_ARGUMENT when printer is null</li>
5946 * </ul>
5947 */
5948 public Runnable print(Printer printer) {
5949 checkWidget();
5950 if (printer is null) {
5951 DWT.error(DWT.ERROR_NULL_ARGUMENT);
5952 }
5953 StyledTextPrintOptions options = new StyledTextPrintOptions();
5954 options.printTextForeground = true;
5955 options.printTextBackground = true;
5956 options.printTextFontStyle = true;
5957 options.printLineBackground = true;
5958 return print(printer, options);
5959 }
5960 /**
5961 * Returns a runnable that will print the widget's text
5962 * to the specified printer.
5963 * <p>
5964 * The runnable may be run in a non-UI thread.
5965 * </p>
5966 *
5967 * @param printer the printer to print to
5968 * @param options print options to use during printing
5969 * @exception DWTException <ul>
5970 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5971 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5972 * </ul>
5973 * @exception IllegalArgumentException <ul>
5974 * <li>ERROR_NULL_ARGUMENT when printer or options is null</li>
5975 * </ul>
5976 * @since 2.1
5977 */
5978 public Runnable print(Printer printer, StyledTextPrintOptions options) {
5979 checkWidget();
5980 if (printer is null || options is null) {
5981 DWT.error(DWT.ERROR_NULL_ARGUMENT);
5982 }
5983 return new Printing(this, printer, options);
5984 }
5985 /**
5986 * Causes the entire bounds of the receiver to be marked
5987 * as needing to be redrawn. The next time a paint request
5988 * is processed, the control will be completely painted.
5989 * <p>
5990 * Recalculates the content width for all lines in the bounds.
5991 * When a <code>LineStyleListener</code> is used a redraw call
5992 * is the only notification to the widget that styles have changed
5993 * and that the content width may have changed.
5994 * </p>
5995 *
5996 * @exception DWTException <ul>
5997 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5998 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5999 * </ul>
6000 *
6001 * @see Control#update()
6002 */
6003 public void redraw() {
6004 super.redraw();
6005 int itemCount = getPartialBottomIndex() - topIndex + 1;
6006 renderer.reset(topIndex, itemCount);
6007 renderer.calculate(topIndex, itemCount);
6008 setScrollBars(false);
6009 }
6010 /**
6011 * Causes the rectangular area of the receiver specified by
6012 * the arguments to be marked as needing to be redrawn.
6013 * The next time a paint request is processed, that area of
6014 * the receiver will be painted. If the <code>all</code> flag
6015 * is <code>true</code>, any children of the receiver which
6016 * intersect with the specified area will also paint their
6017 * intersecting areas. If the <code>all</code> flag is
6018 * <code>false</code>, the children will not be painted.
6019 * <p>
6020 * Marks the content width of all lines in the specified rectangle
6021 * as unknown. Recalculates the content width of all visible lines.
6022 * When a <code>LineStyleListener</code> is used a redraw call
6023 * is the only notification to the widget that styles have changed
6024 * and that the content width may have changed.
6025 * </p>
6026 *
6027 * @param x the x coordinate of the area to draw
6028 * @param y the y coordinate of the area to draw
6029 * @param width the width of the area to draw
6030 * @param height the height of the area to draw
6031 * @param all <code>true</code> if children should redraw, and <code>false</code> otherwise
6032 *
6033 * @exception DWTException <ul>
6034 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6035 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6036 * </ul>
6037 *
6038 * @see Control#update()
6039 */
6040 public void redraw(int x, int y, int width, int height, bool all) {
6041 super.redraw(x, y, width, height, all);
6042 if (height > 0) {
6043 int firstLine = getLineIndex(y);
6044 int lastLine = getLineIndex(y + height);
6045 resetCache(firstLine, lastLine - firstLine + 1);
6046 }
6047 }
6048 void redrawLines(int startLine, int lineCount) {
6049 // do nothing if redraw range is completely invisible
6050 int partialBottomIndex = getPartialBottomIndex();
6051 if (startLine > partialBottomIndex || startLine + lineCount - 1 < topIndex) {
6052 return;
6053 }
6054 // only redraw visible lines
6055 if (startLine < topIndex) {
6056 lineCount -= topIndex - startLine;
6057 startLine = topIndex;
6058 }
6059 if (startLine + lineCount - 1 > partialBottomIndex) {
6060 lineCount = partialBottomIndex - startLine + 1;
6061 }
6062 startLine -= topIndex;
6063 int redrawTop = getLinePixel(startLine);
6064 int redrawBottom = getLinePixel(startLine + lineCount);
6065 int redrawWidth = clientAreaWidth - leftMargin - rightMargin;
6066 super.redraw(leftMargin, redrawTop, redrawWidth, redrawBottom - redrawTop, true);
6067 }
6068 void redrawLinesBullet (int[] redrawLines) {
6069 if (redrawLines is null) return;
6070 int topIndex = getPartialTopIndex();
6071 int bottomIndex = getPartialBottomIndex();
6072 for (int i = 0; i < redrawLines.length; i++) {
6073 int lineIndex = redrawLines[i];
6074 if (!(topIndex <= lineIndex && lineIndex <= bottomIndex)) continue;
6075 int width = -1;
6076 Bullet bullet = renderer.getLineBullet(lineIndex, null);
6077 if (bullet !is null) {
6078 StyleRange style = bullet.style;
6079 GlyphMetrics metrics = style.metrics;
6080 width = metrics.width;
6081 }
6082 if (width is -1) width = getClientArea().width;
6083 int height = renderer.getLineHeight(lineIndex);
6084 int y = getLinePixel(lineIndex);
6085 super.redraw(0, y, width, height, false);
6086 }
6087 }
6088 /**
6089 * Redraws the specified text range.
6090 *
6091 * @param start offset of the first character to redraw
6092 * @param length number of characters to redraw
6093 * @param clearBackground true if the background should be cleared as
6094 * part of the redraw operation. If true, the entire redraw range will
6095 * be cleared before anything is redrawn. If the redraw range includes
6096 * the last character of a line (i.e., the entire line is redrawn) the
6097 * line is cleared all the way to the right border of the widget.
6098 * The redraw operation will be faster and smoother if clearBackground
6099 * is set to false. Whether or not the flag can be set to false depends
6100 * on the type of change that has taken place. If font styles or
6101 * background colors for the redraw range have changed, clearBackground
6102 * should be set to true. If only foreground colors have changed for
6103 * the redraw range, clearBackground can be set to false.
6104 * @exception DWTException <ul>
6105 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6106 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6107 * </ul>
6108 * @exception IllegalArgumentException <ul>
6109 * <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li>
6110 * </ul>
6111 */
6112 public void redrawRange(int start, int length, bool clearBackground) {
6113 checkWidget();
6114 int end = start + length;
6115 int contentLength = content.getCharCount();
6116 if (start > end || start < 0 || end > contentLength) {
6117 DWT.error(DWT.ERROR_INVALID_RANGE);
6118 }
6119 int firstLine = content.getLineAtOffset(start);
6120 int lastLine = content.getLineAtOffset(end);
6121 resetCache(firstLine, lastLine - firstLine + 1);
6122 internalRedrawRange(start, length);
6123 }
6124 /**
6125 * Removes the specified bidirectional segment listener.
6126 *
6127 * @param listener the listener
6128 * @exception DWTException <ul>
6129 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6130 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6131 * </ul>
6132 * @exception IllegalArgumentException <ul>
6133 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
6134 * </ul>
6135 * @since 2.0
6136 */
6137 public void removeBidiSegmentListener(BidiSegmentListener listener) {
6138 checkWidget();
6139 if (listener is null) DWT.error(DWT.ERROR_NULL_ARGUMENT);
6140 removeListener(LineGetSegments, listener);
6141 }
6142 /**
6143 * Removes the specified extended modify listener.
6144 *
6145 * @param extendedModifyListener the listener
6146 * @exception DWTException <ul>
6147 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6148 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6149 * </ul>
6150 * @exception IllegalArgumentException <ul>
6151 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
6152 * </ul>
6153 */
6154 public void removeExtendedModifyListener(ExtendedModifyListener extendedModifyListener) {
6155 checkWidget();
6156 if (extendedModifyListener is null) DWT.error(DWT.ERROR_NULL_ARGUMENT);
6157 removeListener(ExtendedModify, extendedModifyListener);
6158 }
6159 /**
6160 * Removes the specified line background listener.
6161 *
6162 * @param listener the listener
6163 * @exception DWTException <ul>
6164 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6165 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6166 * </ul>
6167 * @exception IllegalArgumentException <ul>
6168 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
6169 * </ul>
6170 */
6171 public void removeLineBackgroundListener(LineBackgroundListener listener) {
6172 checkWidget();
6173 if (listener is null) DWT.error(DWT.ERROR_NULL_ARGUMENT);
6174 removeListener(LineGetBackground, listener);
6175 }
6176 /**
6177 * Removes the specified line style listener.
6178 *
6179 * @param listener the listener
6180 * @exception DWTException <ul>
6181 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6182 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6183 * </ul>
6184 * @exception IllegalArgumentException <ul>
6185 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
6186 * </ul>
6187 */
6188 public void removeLineStyleListener(LineStyleListener listener) {
6189 checkWidget();
6190 if (listener is null) DWT.error(DWT.ERROR_NULL_ARGUMENT);
6191 removeListener(LineGetStyle, listener);
6192 }
6193 /**
6194 * Removes the specified modify listener.
6195 *
6196 * @param modifyListener the listener
6197 * @exception DWTException <ul>
6198 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6199 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6200 * </ul>
6201 * @exception IllegalArgumentException <ul>
6202 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
6203 * </ul>
6204 */
6205 public void removeModifyListener(ModifyListener modifyListener) {
6206 checkWidget();
6207 if (modifyListener is null) DWT.error(DWT.ERROR_NULL_ARGUMENT);
6208 removeListener(DWT.Modify, modifyListener);
6209 }
6210 /**
6211 * Removes the specified listener.
6212 *
6213 * @param listener the listener
6214 * @exception DWTException <ul>
6215 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6216 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6217 * </ul>
6218 * @exception IllegalArgumentException <ul>
6219 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
6220 * </ul>
6221 * @since 3.2
6222 */
6223 public void removePaintObjectListener(PaintObjectListener listener) {
6224 checkWidget();
6225 if (listener is null) DWT.error(DWT.ERROR_NULL_ARGUMENT);
6226 removeListener(PaintObject, listener);
6227 }
6228 /**
6229 * Removes the listener from the collection of listeners who will
6230 * be notified when the user changes the receiver's selection.
6231 *
6232 * @param listener the listener which should no longer be notified
6233 *
6234 * @exception IllegalArgumentException <ul>
6235 * <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
6236 * </ul>
6237 * @exception DWTException <ul>
6238 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6239 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6240 * </ul>
6241 *
6242 * @see SelectionListener
6243 * @see #addSelectionListener
6244 */
6245 public void removeSelectionListener(SelectionListener listener) {
6246 checkWidget();
6247 if (listener is null) DWT.error(DWT.ERROR_NULL_ARGUMENT);
6248 removeListener(DWT.Selection, listener);
6249 }
6250 /**
6251 * Removes the specified verify listener.
6252 *
6253 * @param verifyListener the listener
6254 * @exception DWTException <ul>
6255 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6256 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6257 * </ul>
6258 * @exception IllegalArgumentException <ul>
6259 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
6260 * </ul>
6261 */
6262 public void removeVerifyListener(VerifyListener verifyListener) {
6263 checkWidget();
6264 if (verifyListener is null) DWT.error(DWT.ERROR_NULL_ARGUMENT);
6265 removeListener(DWT.Verify, verifyListener);
6266 }
6267 /**
6268 * Removes the specified key verify listener.
6269 *
6270 * @param listener the listener
6271 * @exception DWTException <ul>
6272 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6273 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6274 * </ul>
6275 * @exception IllegalArgumentException <ul>
6276 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
6277 * </ul>
6278 */
6279 public void removeVerifyKeyListener(VerifyKeyListener listener) {
6280 if (listener is null) DWT.error(DWT.ERROR_NULL_ARGUMENT);
6281 removeListener(VerifyKey, listener);
6282 }
6283 /**
6284 * Removes the specified word movement listener.
6285 *
6286 * @param listener the listener
6287 * @exception DWTException <ul>
6288 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6289 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6290 * </ul>
6291 * @exception IllegalArgumentException <ul>
6292 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
6293 * </ul>
6294 *
6295 * @see MovementEvent
6296 * @see MovementListener
6297 * @see #addWordMovementListener
6298 *
6299 * @since 3.3
6300 */
6301
6302 public void removeWordMovementListener(MovementListener listener) {
6303 checkWidget();
6304 if (listener is null) DWT.error(DWT.ERROR_NULL_ARGUMENT);
6305 removeListener(WordNext, listener);
6306 removeListener(WordPrevious, listener);
6307 }
6308 /**
6309 * Replaces the styles in the given range with new styles. This method
6310 * effectively deletes the styles in the given range and then adds the
6311 * the new styles.
6312 * <p>
6313 * Note: Because a StyleRange includes the start and length, the
6314 * same instance cannot occur multiple times in the array of styles.
6315 * If the same style attributes, such as font and color, occur in
6316 * multiple StyleRanges, <code>setStyleRanges(int, int, int[], StyleRange[])</code>
6317 * can be used to share styles and reduce memory usage.
6318 * </p><p>
6319 * Should not be called if a LineStyleListener has been set since the
6320 * listener maintains the styles.
6321 * </p>
6322 *
6323 * @param start offset of first character where styles will be deleted
6324 * @param length length of the range to delete styles in
6325 * @param ranges StyleRange objects containing the new style information.
6326 * The ranges should not overlap and should be within the specified start
6327 * and length. The style rendering is undefined if the ranges do overlap
6328 * or are ill-defined. Must not be null.
6329 * @exception DWTException <ul>
6330 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6331 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6332 * </ul>
6333 * @exception IllegalArgumentException <ul>
6334 * <li>ERROR_INVALID_RANGE when either start or end is outside the valid range (0 <= offset <= getCharCount())</li>
6335 * <li>ERROR_NULL_ARGUMENT when ranges is null</li>
6336 * </ul>
6337 *
6338 * @since 2.0
6339 *
6340 * @see #setStyleRanges(int, int, int[], StyleRange[])
6341 */
6342 public void replaceStyleRanges(int start, int length, StyleRange[] ranges) {
6343 checkWidget();
6344 if (isListening(LineGetStyle)) return;
6345 if (ranges is null) DWT.error(DWT.ERROR_NULL_ARGUMENT);
6346 setStyleRanges(start, length, null, ranges, false);
6347 }
6348 /**
6349 * Replaces the given text range with new text.
6350 * If the widget has the DWT.SINGLE style and "text" contains more than
6351 * one line, only the first line is rendered but the text is stored
6352 * unchanged. A subsequent call to getText will return the same text
6353 * that was set. Note that only a single line of text should be set when
6354 * the DWT.SINGLE style is used.
6355 * <p>
6356 * <b>NOTE:</b> During the replace operation the current selection is
6357 * changed as follows:
6358 * <ul>
6359 * <li>selection before replaced text: selection unchanged
6360 * <li>selection after replaced text: adjust the selection so that same text
6361 * remains selected
6362 * <li>selection intersects replaced text: selection is cleared and caret
6363 * is placed after inserted text
6364 * </ul>
6365 * </p>
6366 *
6367 * @param start offset of first character to replace
6368 * @param length number of characters to replace. Use 0 to insert text
6369 * @param text new text. May be empty to delete text.
6370 * @exception DWTException <ul>
6371 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6372 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6373 * </ul>
6374 * @exception IllegalArgumentException <ul>
6375 * <li>ERROR_INVALID_RANGE when either start or end is outside the valid range (0 <= offset <= getCharCount())</li>
6376 * <li>ERROR_INVALID_ARGUMENT when either start or end is inside a multi byte line delimiter.
6377 * Splitting a line delimiter for example by inserting text in between the CR and LF and deleting part of a line delimiter is not supported</li>
6378 * <li>ERROR_NULL_ARGUMENT when String is null</li>
6379 * </ul>
6380 */
6381 public void replaceTextRange(int start, int length, String text) {
6382 checkWidget();
6383 if (text is null) {
6384 DWT.error(DWT.ERROR_NULL_ARGUMENT);
6385 }
6386 int contentLength = getCharCount();
6387 int end = start + length;
6388 if (start > end || start < 0 || end > contentLength) {
6389 DWT.error(DWT.ERROR_INVALID_RANGE);
6390 }
6391 Event event = new Event();
6392 event.start = start;
6393 event.end = end;
6394 event.text = text;
6395 modifyContent(event, false);
6396 }
6397 /**
6398 * Resets the caret position, selection and scroll offsets. Recalculate
6399 * the content width and scroll bars. Redraw the widget.
6400 */
6401 void reset() {
6402 ScrollBar verticalBar = getVerticalBar();
6403 ScrollBar horizontalBar = getHorizontalBar();
6404 caretOffset = 0;
6405 topIndex = 0;
6406 topIndexY = 0;
6407 verticalScrollOffset = 0;
6408 horizontalScrollOffset = 0;
6409 resetSelection();
6410 renderer.setContent(content);
6411 if (verticalBar !is null) {
6412 verticalBar.setSelection(0);
6413 }
6414 if (horizontalBar !is null) {
6415 horizontalBar.setSelection(0);
6416 }
6417 resetCache(0, 0);
6418 setCaretLocation();
6419 super.redraw();
6420 }
6421 void resetCache(int firstLine, int count) {
6422 int maxLineIndex = renderer.maxWidthLineIndex;
6423 renderer.reset(firstLine, count);
6424 renderer.calculateClientArea();
6425 if (0 <= maxLineIndex && maxLineIndex < content.getLineCount()) {
6426 renderer.calculate(maxLineIndex, 1);
6427 }
6428 setScrollBars(true);
6429 if (!isFixedLineHeight()) {
6430 if (topIndex > firstLine) {
6431 verticalScrollOffset = -1;
6432 }
6433 renderer.calculateIdle();
6434 }
6435 }
6436 /**
6437 * Resets the selection.
6438 */
6439 void resetSelection() {
6440 selection.x = selection.y = caretOffset;
6441 selectionAnchor = -1;
6442 }
6443
6444 public void scroll(int destX, int destY, int x, int y, int width, int height, bool all) {
6445 super.scroll(destX, destY, x, y, width, height, false);
6446 if (all) {
6447 int deltaX = destX - x, deltaY = destY - y;
6448 Control[] children = getChildren();
6449 for (int i=0; i<children.length; i++) {
6450 Control child = children[i];
6451 Rectangle rect = child.getBounds();
6452 child.setLocation(rect.x + deltaX, rect.y + deltaY);
6453 }
6454 }
6455 }
6456
6457 /**
6458 * Scrolls the widget horizontally.
6459 *
6460 * @param pixels number of pixels to scroll, > 0 = scroll left,
6461 * < 0 scroll right
6462 * @param adjustScrollBar
6463 * true= the scroll thumb will be moved to reflect the new scroll offset.
6464 * false = the scroll thumb will not be moved
6465 * @return
6466 * true=the widget was scrolled
6467 * false=the widget was not scrolled, the given offset is not valid.
6468 */
6469 bool scrollHorizontal(int pixels, bool adjustScrollBar) {
6470 if (pixels is 0) {
6471 return false;
6472 }
6473 ScrollBar horizontalBar = getHorizontalBar();
6474 if (horizontalBar !is null && adjustScrollBar) {
6475 horizontalBar.setSelection(horizontalScrollOffset + pixels);
6476 }
6477 int scrollHeight = clientAreaHeight - topMargin - bottomMargin;
6478 if (pixels > 0) {
6479 int sourceX = leftMargin + pixels;
6480 int scrollWidth = clientAreaWidth - sourceX - rightMargin;
6481 if (scrollWidth > 0) {
6482 scroll(leftMargin, topMargin, sourceX, topMargin, scrollWidth, scrollHeight, true);
6483 }
6484 if (sourceX > scrollWidth) {
6485 super.redraw(leftMargin + scrollWidth, topMargin, pixels - scrollWidth, scrollHeight, true);
6486 }
6487 } else {
6488 int destinationX = leftMargin - pixels;
6489 int scrollWidth = clientAreaWidth - destinationX - rightMargin;
6490 if (scrollWidth > 0) {
6491 scroll(destinationX, topMargin, leftMargin, topMargin, scrollWidth, scrollHeight, true);
6492 }
6493 if (destinationX > scrollWidth) {
6494 super.redraw(leftMargin + scrollWidth, topMargin, -pixels - scrollWidth, scrollHeight, true);
6495 }
6496 }
6497 horizontalScrollOffset += pixels;
6498 setCaretLocation();
6499 return true;
6500 }
6501 /**
6502 * Scrolls the widget vertically.
6503 *
6504 * @param pixel the new vertical scroll offset
6505 * @param adjustScrollBar
6506 * true= the scroll thumb will be moved to reflect the new scroll offset.
6507 * false = the scroll thumb will not be moved
6508 * @return
6509 * true=the widget was scrolled
6510 * false=the widget was not scrolled
6511 */
6512 bool scrollVertical(int pixels, bool adjustScrollBar) {
6513 if (pixels is 0) {
6514 return false;
6515 }
6516 if (verticalScrollOffset !is -1) {
6517 ScrollBar verticalBar = getVerticalBar();
6518 if (verticalBar !is null && adjustScrollBar) {
6519 verticalBar.setSelection(verticalScrollOffset + pixels);
6520 }
6521 int scrollWidth = clientAreaWidth - leftMargin - rightMargin;
6522 if (pixels > 0) {
6523 int sourceY = topMargin + pixels;
6524 int scrollHeight = clientAreaHeight - sourceY - bottomMargin;
6525 if (scrollHeight > 0) {
6526 scroll(leftMargin, topMargin, leftMargin, sourceY, scrollWidth, scrollHeight, true);
6527 }
6528 if (sourceY > scrollHeight) {
6529 int redrawY = Math.max(0, topMargin + scrollHeight);
6530 int redrawHeight = Math.min(clientAreaHeight, pixels - scrollHeight);
6531 super.redraw(leftMargin, redrawY, scrollWidth, redrawHeight, true);
6532 }
6533 } else {
6534 int destinationY = topMargin - pixels;
6535 int scrollHeight = clientAreaHeight - destinationY - bottomMargin;
6536 if (scrollHeight > 0) {
6537 scroll(leftMargin, destinationY, leftMargin, topMargin, scrollWidth, scrollHeight, true);
6538 }
6539 if (destinationY > scrollHeight) {
6540 int redrawY = Math.max(0, topMargin + scrollHeight);
6541 int redrawHeight = Math.min(clientAreaHeight, -pixels - scrollHeight);
6542 super.redraw(leftMargin, redrawY, scrollWidth, redrawHeight, true);
6543 }
6544 }
6545 verticalScrollOffset += pixels;
6546 calculateTopIndex(pixels);
6547 } else {
6548 calculateTopIndex(pixels);
6549 super.redraw();
6550 }
6551 setCaretLocation();
6552 return true;
6553 }
6554 void scrollText(int srcY, int destY) {
6555 if (srcY is destY) return;
6556 int deltaY = destY - srcY;
6557 int scrollWidth = clientAreaWidth - leftMargin - rightMargin, scrollHeight;
6558 if (deltaY > 0) {
6559 scrollHeight = clientAreaHeight - srcY - bottomMargin;
6560 } else {
6561 scrollHeight = clientAreaHeight - destY - bottomMargin;
6562 }
6563 scroll(leftMargin, destY, leftMargin, srcY, scrollWidth, scrollHeight, true);
6564 if ((0 < srcY + scrollHeight) && (topMargin > srcY)) {
6565 super.redraw(leftMargin, deltaY, scrollWidth, topMargin, false);
6566 }
6567 if ((0 < destY + scrollHeight) && (topMargin > destY)) {
6568 super.redraw(leftMargin, 0, scrollWidth, topMargin, false);
6569 }
6570 if ((clientAreaHeight - bottomMargin < srcY + scrollHeight) && (clientAreaHeight > srcY)) {
6571 super.redraw(leftMargin, clientAreaHeight - bottomMargin + deltaY, scrollWidth, bottomMargin, false);
6572 }
6573 if ((clientAreaHeight - bottomMargin < destY + scrollHeight) && (clientAreaHeight > destY)) {
6574 super.redraw(leftMargin, clientAreaHeight - bottomMargin, scrollWidth, bottomMargin, false);
6575 }
6576 }
6577 /**
6578 * Selects all the text.
6579 *
6580 * @exception DWTException <ul>
6581 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6582 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6583 * </ul>
6584 */
6585 public void selectAll() {
6586 checkWidget();
6587 setSelection(0, Math.max(getCharCount(),0));
6588 }
6589 /**
6590 * Replaces/inserts text as defined by the event.
6591 *
6592 * @param event the text change event.
6593 * <ul>
6594 * <li>event.start - the replace start offset</li>
6595 * <li>event.end - the replace end offset</li>
6596 * <li>event.text - the new text</li>
6597 * </ul>
6598 */
6599 void sendKeyEvent(Event event) {
6600 if (editable) {
6601 modifyContent(event, true);
6602 }
6603 }
6604 /**
6605 * Returns a StyledTextEvent that can be used to request data such
6606 * as styles and background color for a line.
6607 * <p>
6608 * The specified line may be a visual (wrapped) line if in word
6609 * wrap mode. The returned object will always be for a logical
6610 * (unwrapped) line.
6611 * </p>
6612 *
6613 * @param lineOffset offset of the line. This may be the offset of
6614 * a visual line if the widget is in word wrap mode.
6615 * @param line line text. This may be the text of a visual line if
6616 * the widget is in word wrap mode.
6617 * @return StyledTextEvent that can be used to request line data
6618 * for the given line.
6619 */
6620 StyledTextEvent sendLineEvent(int eventType, int lineOffset, String line) {
6621 StyledTextEvent event = null;
6622 if (isListening(eventType)) {
6623 event = new StyledTextEvent(content);
6624 event.detail = lineOffset;
6625 event.text = line;
6626 event.alignment = alignment;
6627 event.indent = indent;
6628 event.justify = justify;
6629 notifyListeners(eventType, event);
6630 }
6631 return event;
6632 }
6633 void sendModifyEvent(Event event) {
6634 Accessible accessible = getAccessible();
6635 if (event.text.length() is 0) {
6636 accessible.textChanged(ACC.TEXT_DELETE, event.start, event.end - event.start);
6637 } else {
6638 if (event.start is event.end) {
6639 accessible.textChanged(ACC.TEXT_INSERT, event.start, event.text.length());
6640 } else {
6641 accessible.textChanged(ACC.TEXT_DELETE, event.start, event.end - event.start);
6642 accessible.textChanged(ACC.TEXT_INSERT, event.start, event.text.length());
6643 }
6644 }
6645 notifyListeners(DWT.Modify, event);
6646 }
6647 /**
6648 * Sends the specified selection event.
6649 */
6650 void sendSelectionEvent() {
6651 getAccessible().textSelectionChanged();
6652 Event event = new Event();
6653 event.x = selection.x;
6654 event.y = selection.y;
6655 notifyListeners(DWT.Selection, event);
6656 }
6657 int sendWordBoundaryEvent(int eventType, int movement, int offset, int newOffset, String lineText, int lineOffset) {
6658 if (isListening(eventType)) {
6659 StyledTextEvent event = new StyledTextEvent(content);
6660 event.detail = lineOffset;
6661 event.text = lineText;
6662 event.count = movement;
6663 event.start = offset;
6664 event.end = newOffset;
6665 notifyListeners(eventType, event);
6666 offset = event.end;
6667 if (offset !is newOffset) {
6668 int length = getCharCount();
6669 if (offset < 0) {
6670 offset = 0;
6671 } else if (offset > length) {
6672 offset = length;
6673 } else {
6674 if (isLineDelimiter(offset)) {
6675 DWT.error(DWT.ERROR_INVALID_ARGUMENT);
6676 }
6677 }
6678 }
6679 return offset;
6680 }
6681 return newOffset;
6682 }
6683 /**
6684 * Sets the alignment of the widget. The argument should be one of <code>DWT.LEFT</code>,
6685 * <code>DWT.CENTER</code> or <code>DWT.RIGHT</code>. The alignment applies for all lines.
6686 * </p><p>
6687 * Note that if <code>DWT.MULTI</code> is set, then <code>DWT.WRAP</code> must also be set
6688 * in order to stabilize the right edge before setting alignment.
6689 * </p>
6690 *
6691 * @param alignment the new alignment
6692 *
6693 * @exception DWTException <ul>
6694 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6695 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6696 * </ul>
6697 *
6698 * @see #setLineAlignment(int, int, int)
6699 *
6700 * @since 3.2
6701 */
6702 public void setAlignment(int alignment) {
6703 checkWidget();
6704 alignment &= (DWT.LEFT | DWT.RIGHT | DWT.CENTER);
6705 if (alignment is 0 || this.alignment is alignment) return;
6706 this.alignment = alignment;
6707 resetCache(0, content.getLineCount());
6708 setCaretLocation();
6709 super.redraw();
6710 }
6711 /**
6712 * @see Control#setBackground(Color)
6713 */
6714 public void setBackground(Color color) {
6715 checkWidget();
6716 background = color;
6717 super.setBackground(color);
6718 super.redraw();
6719 }
6720 /**
6721 * Sets the receiver's caret. Set the caret's height and location.
6722 *
6723 * </p>
6724 * @param caret the new caret for the receiver
6725 *
6726 * @exception DWTException <ul>
6727 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6728 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6729 * </ul>
6730 */
6731 public void setCaret(Caret caret) {
6732 checkWidget ();
6733 super.setCaret(caret);
6734 caretDirection = DWT.NULL;
6735 if (caret !is null) {
6736 setCaretLocation();
6737 }
6738 }
6739 /**
6740 * Sets the BIDI coloring mode. When true the BIDI text display
6741 * algorithm is applied to segments of text that are the same
6742 * color.
6743 *
6744 * @param mode the new coloring mode
6745 * @exception DWTException <ul>
6746 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6747 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6748 * </ul>
6749 *
6750 * @deprecated use BidiSegmentListener instead.
6751 */
6752 public void setBidiColoring(bool mode) {
6753 checkWidget();
6754 bidiColoring = mode;
6755 }
6756 /**
6757 * Moves the Caret to the current caret offset.
6758 */
6759 void setCaretLocation() {
6760 Point newCaretPos = getPointAtOffset(caretOffset);
6761 setCaretLocation(newCaretPos, getCaretDirection());
6762 }
6763 void setCaretLocation(Point location, int direction) {
6764 Caret caret = getCaret();
6765 if (caret !is null) {
6766 bool isDefaultCaret = caret is defaultCaret;
6767 int lineHeight = renderer.getLineHeight();
6768 int caretHeight = lineHeight;
6769 if (!isFixedLineHeight() && isDefaultCaret) {
6770 caretHeight = getBoundsAtOffset(caretOffset).height;
6771 if (caretHeight !is lineHeight) {
6772 direction = DWT.DEFAULT;
6773 }
6774 }
6775 int imageDirection = direction;
6776 if (isMirrored()) {
6777 if (imageDirection is DWT.LEFT) {
6778 imageDirection = DWT.RIGHT;
6779 } else if (imageDirection is DWT.RIGHT) {
6780 imageDirection = DWT.LEFT;
6781 }
6782 }
6783 if (isDefaultCaret && imageDirection is DWT.RIGHT) {
6784 location.x -= (caret.getSize().x - 1);
6785 }
6786 if (isDefaultCaret) {
6787 caret.setBounds(location.x, location.y, caretWidth, caretHeight);
6788 } else {
6789 caret.setLocation(location);
6790 }
6791 getAccessible().textCaretMoved(getCaretOffset());
6792 if (direction !is caretDirection) {
6793 caretDirection = direction;
6794 if (isDefaultCaret) {
6795 if (imageDirection is DWT.DEFAULT) {
6796 defaultCaret.setImage(null);
6797 } else if (imageDirection is DWT.LEFT) {
6798 defaultCaret.setImage(leftCaretBitmap);
6799 } else if (imageDirection is DWT.RIGHT) {
6800 defaultCaret.setImage(rightCaretBitmap);
6801 }
6802 }
6803 if (caretDirection is DWT.LEFT) {
6804 BidiUtil.setKeyboardLanguage(BidiUtil.KEYBOARD_NON_BIDI);
6805 } else if (caretDirection is DWT.RIGHT) {
6806 BidiUtil.setKeyboardLanguage(BidiUtil.KEYBOARD_BIDI);
6807 }
6808 }
6809 }
6810 columnX = location.x;
6811 }
6812 /**
6813 * Sets the caret offset.
6814 *
6815 * @param offset caret offset, relative to the first character in the text.
6816 * @exception DWTException <ul>
6817 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6818 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6819 * </ul>
6820 * @exception IllegalArgumentException <ul>
6821 * <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a
6822 * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter)
6823 * </ul>
6824 */
6825 public void setCaretOffset(int offset) {
6826 checkWidget();
6827 int length = getCharCount();
6828 if (length > 0 && offset !is caretOffset) {
6829 if (offset < 0) {
6830 caretOffset = 0;
6831 } else if (offset > length) {
6832 caretOffset = length;
6833 } else {
6834 if (isLineDelimiter(offset)) {
6835 // offset is inside a multi byte line delimiter. This is an
6836 // illegal operation and an exception is thrown. Fixes 1GDKK3R
6837 DWT.error(DWT.ERROR_INVALID_ARGUMENT);
6838 }
6839 caretOffset = offset;
6840 }
6841 caretAlignment = PREVIOUS_OFFSET_TRAILING;
6842 // clear the selection if the caret is moved.
6843 // don't notify listeners about the selection change.
6844 clearSelection(false);
6845 }
6846 setCaretLocation();
6847 }
6848 /**
6849 * Copies the specified text range to the clipboard. The text will be placed
6850 * in the clipboard in plain text format and RTF format.
6851 *
6852 * @param start start index of the text
6853 * @param length length of text to place in clipboard
6854 *
6855 * @exception DWTError, see Clipboard.setContents
6856 * @see dwt.dnd.Clipboard#setContents
6857 */
6858 void setClipboardContent(int start, int length, int clipboardType) throws DWTError {
6859 if (clipboardType is DND.SELECTION_CLIPBOARD && !(IS_MOTIF || IS_GTK)) return;
6860 TextTransfer plainTextTransfer = TextTransfer.getInstance();
6861 TextWriter plainTextWriter = new TextWriter(start, length);
6862 String plainText = getPlatformDelimitedText(plainTextWriter);
6863 Object[] data;
6864 Transfer[] types;
6865 if (clipboardType is DND.SELECTION_CLIPBOARD) {
6866 data = new Object[]{plainText};
6867 types = new Transfer[]{plainTextTransfer};
6868 } else {
6869 RTFTransfer rtfTransfer = RTFTransfer.getInstance();
6870 RTFWriter rtfWriter = new RTFWriter(start, length);
6871 String rtfText = getPlatformDelimitedText(rtfWriter);
6872 data = new Object[]{rtfText, plainText};
6873 types = new Transfer[]{rtfTransfer, plainTextTransfer};
6874 }
6875 clipboard.setContents(data, types, clipboardType);
6876 }
6877 /**
6878 * Sets the content implementation to use for text storage.
6879 *
6880 * @param newContent StyledTextContent implementation to use for text storage.
6881 * @exception DWTException <ul>
6882 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6883 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6884 * </ul>
6885 * @exception IllegalArgumentException <ul>
6886 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
6887 * </ul>
6888 */
6889 public void setContent(StyledTextContent newContent) {
6890 checkWidget();
6891 if (newContent is null) {
6892 DWT.error(DWT.ERROR_NULL_ARGUMENT);
6893 }
6894 if (content !is null) {
6895 content.removeTextChangeListener(textChangeListener);
6896 }
6897 content = newContent;
6898 content.addTextChangeListener(textChangeListener);
6899 reset();
6900 }
6901 /**
6902 * Sets the receiver's cursor to the cursor specified by the
6903 * argument. Overridden to handle the null case since the
6904 * StyledText widget uses an ibeam as its default cursor.
6905 *
6906 * @see Control#setCursor(Cursor)
6907 */
6908 public void setCursor (Cursor cursor) {
6909 if (cursor is null) {
6910 Display display = getDisplay();
6911 super.setCursor(display.getSystemCursor(DWT.CURSOR_IBEAM));
6912 } else {
6913 super.setCursor(cursor);
6914 }
6915 }
6916 /**
6917 * Sets whether the widget : double click mouse behavior.
6918 * </p>
6919 *
6920 * @param enable if true double clicking a word selects the word, if false
6921 * double clicks have the same effect as regular mouse clicks.
6922 * @exception DWTException <ul>
6923 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6924 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6925 * </ul>
6926 */
6927 public void setDoubleClickEnabled(bool enable) {
6928 checkWidget();
6929 doubleClickEnabled = enable;
6930 }
6931 public void setDragDetect (bool dragDetect) {
6932 checkWidget ();
6933 this.dragDetect = dragDetect;
6934 }
6935 /**
6936 * Sets whether the widget content can be edited.
6937 * </p>
6938 *
6939 * @param editable if true content can be edited, if false content can not be
6940 * edited
6941 * @exception DWTException <ul>
6942 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6943 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6944 * </ul>
6945 */
6946 public void setEditable(bool editable) {
6947 checkWidget();
6948 this.editable = editable;
6949 }
6950 /**
6951 * Sets a new font to render text with.
6952 * <p>
6953 * <b>NOTE:</b> Italic fonts are not supported unless they have no overhang
6954 * and the same baseline as regular fonts.
6955 * </p>
6956 *
6957 * @param font new font
6958 * @exception DWTException <ul>
6959 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
6960 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
6961 * </ul>
6962 */
6963 public void setFont(Font font) {
6964 checkWidget();
6965 int oldLineHeight = renderer.getLineHeight();
6966 super.setFont(font);
6967 renderer.setFont(getFont(), tabLength);
6968 // keep the same top line visible. fixes 5815
6969 if (isFixedLineHeight()) {
6970 int lineHeight = renderer.getLineHeight();
6971 if (lineHeight !is oldLineHeight) {
6972 int vscroll = (getVerticalScrollOffset() * lineHeight / oldLineHeight) - getVerticalScrollOffset();
6973 scrollVertical(vscroll, true);
6974 }
6975 }
6976 resetCache(0, content.getLineCount());
6977 claimBottomFreeSpace();
6978 calculateScrollBars();
6979 if (isBidiCaret()) createCaretBitmaps();
6980 caretDirection = DWT.NULL;
6981 setCaretLocation();
6982 super.redraw();
6983 }
6984 /**
6985 * @see dwt.widgets.Control#setForeground
6986 */
6987 public void setForeground(Color color) {
6988 checkWidget();
6989 foreground = color;
6990 super.setForeground(getForeground());
6991 super.redraw();
6992 }
6993 /**
6994 * Sets the horizontal scroll offset relative to the start of the line.
6995 * Do nothing if there is no text set.
6996 * <p>
6997 * <b>NOTE:</b> The horizontal index is reset to 0 when new text is set in the
6998 * widget.
6999 * </p>
7000 *
7001 * @param offset horizontal scroll offset relative to the start
7002 * of the line, measured in character increments starting at 0, if
7003 * equal to 0 the content is not scrolled, if > 0 = the content is scrolled.
7004 * @exception DWTException <ul>
7005 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7006 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7007 * </ul>
7008 */
7009 public void setHorizontalIndex(int offset) {
7010 checkWidget();
7011 if (getCharCount() is 0) {
7012 return;
7013 }
7014 if (offset < 0) {
7015 offset = 0;
7016 }
7017 offset *= getHorizontalIncrement();
7018 // allow any value if client area width is unknown or 0.
7019 // offset will be checked in resize handler.
7020 // don't use isVisible since width is known even if widget
7021 // is temporarily invisible
7022 if (clientAreaWidth > 0) {
7023 int width = renderer.getWidth();
7024 // prevent scrolling if the content fits in the client area.
7025 // align end of longest line with right border of client area
7026 // if offset is out of range.
7027 if (offset > width - clientAreaWidth) {
7028 offset = Math.max(0, width - clientAreaWidth);
7029 }
7030 }
7031 scrollHorizontal(offset - horizontalScrollOffset, true);
7032 }
7033 /**
7034 * Sets the horizontal pixel offset relative to the start of the line.
7035 * Do nothing if there is no text set.
7036 * <p>
7037 * <b>NOTE:</b> The horizontal pixel offset is reset to 0 when new text
7038 * is set in the widget.
7039 * </p>
7040 *
7041 * @param pixel horizontal pixel offset relative to the start
7042 * of the line.
7043 * @exception DWTException <ul>
7044 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7045 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7046 * </ul>
7047 * @since 2.0
7048 */
7049 public void setHorizontalPixel(int pixel) {
7050 checkWidget();
7051 if (getCharCount() is 0) {
7052 return;
7053 }
7054 if (pixel < 0) {
7055 pixel = 0;
7056 }
7057 // allow any value if client area width is unknown or 0.
7058 // offset will be checked in resize handler.
7059 // don't use isVisible since width is known even if widget
7060 // is temporarily invisible
7061 if (clientAreaWidth > 0) {
7062 int width = renderer.getWidth();
7063 // prevent scrolling if the content fits in the client area.
7064 // align end of longest line with right border of client area
7065 // if offset is out of range.
7066 if (pixel > width - clientAreaWidth) {
7067 pixel = Math.max(0, width - clientAreaWidth);
7068 }
7069 }
7070 scrollHorizontal(pixel - horizontalScrollOffset, true);
7071 }
7072 /**
7073 * Sets the line indentation of the widget.
7074 * <p>
7075 * It is the amount of blank space, in pixels, at the beginning of each line.
7076 * When a line wraps in several lines only the first one is indented.
7077 * </p>
7078 *
7079 * @param indent the new indent
7080 *
7081 * @exception DWTException <ul>
7082 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7083 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7084 * </ul>
7085 *
7086 * @see #setLineIndent(int, int, int)
7087 *
7088 * @since 3.2
7089 */
7090 public void setIndent(int indent) {
7091 checkWidget();
7092 if (this.indent is indent || indent < 0) return;
7093 this.indent = indent;
7094 resetCache(0, content.getLineCount());
7095 setCaretLocation();
7096 super.redraw();
7097 }
7098 /**
7099 * Sets whether the widget should justify lines.
7100 *
7101 * @param justify whether lines should be justified
7102 *
7103 * @exception DWTException <ul>
7104 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7105 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7106 * </ul>
7107 *
7108 * @see #setLineJustify(int, int, bool)
7109 *
7110 * @since 3.2
7111 */
7112 public void setJustify(bool justify) {
7113 checkWidget();
7114 if (this.justify is justify) return;
7115 this.justify = justify;
7116 resetCache(0, content.getLineCount());
7117 setCaretLocation();
7118 super.redraw();
7119 }
7120 /**
7121 * Maps a key to an action.
7122 * <p>
7123 * One action can be associated with N keys. However, each key can only
7124 * have one action (key:action is N:1 relation).
7125 * </p>
7126 *
7127 * @param key a key code defined in DWT.java or a character.
7128 * Optionally ORd with a state mask. Preferred state masks are one or more of
7129 * DWT.MOD1, DWT.MOD2, DWT.MOD3, since these masks account for modifier platform
7130 * differences. However, there may be cases where using the specific state masks
7131 * (i.e., DWT.CTRL, DWT.SHIFT, DWT.ALT, DWT.COMMAND) makes sense.
7132 * @param action one of the predefined actions defined in ST.java.
7133 * Use DWT.NULL to remove a key binding.
7134 * @exception DWTException <ul>
7135 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7136 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7137 * </ul>
7138 */
7139 public void setKeyBinding(int key, int action) {
7140 checkWidget();
7141 int modifierValue = key & DWT.MODIFIER_MASK;
7142 char keyChar = (char)(key & DWT.KEY_MASK);
7143 if (Compatibility.isLetter(keyChar)) {
7144 // make the keybinding case insensitive by adding it
7145 // in its upper and lower case form
7146 char ch = Character.toUpperCase(keyChar);
7147 int newKey = ch | modifierValue;
7148 if (action is DWT.NULL) {
7149 keyActionMap.remove(new Integer(newKey));
7150 } else {
7151 keyActionMap.put(new Integer(newKey), new Integer(action));
7152 }
7153 ch = Character.toLowerCase(keyChar);
7154 newKey = ch | modifierValue;
7155 if (action is DWT.NULL) {
7156 keyActionMap.remove(new Integer(newKey));
7157 } else {
7158 keyActionMap.put(new Integer(newKey), new Integer(action));
7159 }
7160 } else {
7161 if (action is DWT.NULL) {
7162 keyActionMap.remove(new Integer(key));
7163 } else {
7164 keyActionMap.put(new Integer(key), new Integer(action));
7165 }
7166 }
7167 }
7168 /**
7169 * Sets the alignment of the specified lines. The argument should be one of <code>DWT.LEFT</code>,
7170 * <code>DWT.CENTER</code> or <code>DWT.RIGHT</code>.
7171 * <p><p>
7172 * Note that if <code>DWT.MULTI</code> is set, then <code>DWT.WRAP</code> must also be set
7173 * in order to stabilize the right edge before setting alignment.
7174 * </p>
7175 * Should not be called if a LineStyleListener has been set since the listener
7176 * maintains the line attributes.
7177 * </p><p>
7178 * All line attributes are maintained relative to the line text, not the
7179 * line index that is specified in this method call.
7180 * During text changes, when entire lines are inserted or removed, the line
7181 * attributes that are associated with the lines after the change
7182 * will "move" with their respective text. An entire line is defined as
7183 * extending from the first character on a line to the last and including the
7184 * line delimiter.
7185 * </p><p>
7186 * When two lines are joined by deleting a line delimiter, the top line
7187 * attributes take precedence and the attributes of the bottom line are deleted.
7188 * For all other text changes line attributes will remain unchanged.
7189 *
7190 * @param startLine first line the alignment is applied to, 0 based
7191 * @param lineCount number of lines the alignment applies to.
7192 * @param alignment line alignment
7193 *
7194 * @exception DWTException <ul>
7195 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7196 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7197 * </ul>
7198 * @exception IllegalArgumentException <ul>
7199 * <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
7200 * </ul>
7201 * @see #setAlignment(int)
7202 * @since 3.2
7203 */
7204 public void setLineAlignment(int startLine, int lineCount, int alignment) {
7205 checkWidget();
7206 if (isListening(LineGetStyle)) return;
7207 if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
7208 DWT.error(DWT.ERROR_INVALID_ARGUMENT);
7209 }
7210
7211 renderer.setLineAlignment(startLine, lineCount, alignment);
7212 resetCache(startLine, lineCount);
7213 redrawLines(startLine, lineCount);
7214 int caretLine = getCaretLine();
7215 if (startLine <= caretLine && caretLine < startLine + lineCount) {
7216 setCaretLocation();
7217 }
7218 }
7219 /**
7220 * Sets the background color of the specified lines.
7221 * <p>
7222 * The background color is drawn for the width of the widget. All
7223 * line background colors are discarded when setText is called.
7224 * The text background color if defined in a StyleRange overlays the
7225 * line background color.
7226 * </p><p>
7227 * Should not be called if a LineBackgroundListener has been set since the
7228 * listener maintains the line backgrounds.
7229 * </p><p>
7230 * All line attributes are maintained relative to the line text, not the
7231 * line index that is specified in this method call.
7232 * During text changes, when entire lines are inserted or removed, the line
7233 * attributes that are associated with the lines after the change
7234 * will "move" with their respective text. An entire line is defined as
7235 * extending from the first character on a line to the last and including the
7236 * line delimiter.
7237 * </p><p>
7238 * When two lines are joined by deleting a line delimiter, the top line
7239 * attributes take precedence and the attributes of the bottom line are deleted.
7240 * For all other text changes line attributes will remain unchanged.
7241 * </p>
7242 *
7243 * @param startLine first line the color is applied to, 0 based
7244 * @param lineCount number of lines the color applies to.
7245 * @param background line background color
7246 * @exception DWTException <ul>
7247 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7248 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7249 * </ul>
7250 * @exception IllegalArgumentException <ul>
7251 * <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
7252 * </ul>
7253 */
7254 public void setLineBackground(int startLine, int lineCount, Color background) {
7255 checkWidget();
7256 if (isListening(LineGetBackground)) return;
7257 if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
7258 DWT.error(DWT.ERROR_INVALID_ARGUMENT);
7259 }
7260 if (background !is null) {
7261 renderer.setLineBackground(startLine, lineCount, background);
7262 } else {
7263 renderer.clearLineBackground(startLine, lineCount);
7264 }
7265 redrawLines(startLine, lineCount);
7266 }
7267 /**
7268 * Sets the bullet of the specified lines.
7269 * <p>
7270 * Should not be called if a LineStyleListener has been set since the listener
7271 * maintains the line attributes.
7272 * </p><p>
7273 * All line attributes are maintained relative to the line text, not the
7274 * line index that is specified in this method call.
7275 * During text changes, when entire lines are inserted or removed, the line
7276 * attributes that are associated with the lines after the change
7277 * will "move" with their respective text. An entire line is defined as
7278 * extending from the first character on a line to the last and including the
7279 * line delimiter.
7280 * </p><p>
7281 * When two lines are joined by deleting a line delimiter, the top line
7282 * attributes take precedence and the attributes of the bottom line are deleted.
7283 * For all other text changes line attributes will remain unchanged.
7284 * </p>
7285 *
7286 * @param startLine first line the bullet is applied to, 0 based
7287 * @param lineCount number of lines the bullet applies to.
7288 * @param bullet line bullet
7289 *
7290 * @exception DWTException <ul>
7291 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7292 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7293 * </ul>
7294 * @exception IllegalArgumentException <ul>
7295 * <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
7296 * </ul>
7297 * @since 3.2
7298 */
7299 public void setLineBullet(int startLine, int lineCount, Bullet bullet) {
7300 checkWidget();
7301 if (isListening(LineGetStyle)) return;
7302 if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
7303 DWT.error(DWT.ERROR_INVALID_ARGUMENT);
7304 }
7305
7306 renderer.setLineBullet(startLine, lineCount, bullet);
7307 resetCache(startLine, lineCount);
7308 redrawLines(startLine, lineCount);
7309 int caretLine = getCaretLine();
7310 if (startLine <= caretLine && caretLine < startLine + lineCount) {
7311 setCaretLocation();
7312 }
7313 }
7314 void setVariableLineHeight () {
7315 if (!fixedLineHeight) return;
7316 fixedLineHeight = false;
7317 renderer.calculateIdle();
7318 }
7319 /**
7320 * Sets the indent of the specified lines.
7321 * <p>
7322 * Should not be called if a LineStyleListener has been set since the listener
7323 * maintains the line attributes.
7324 * </p><p>
7325 * All line attributes are maintained relative to the line text, not the
7326 * line index that is specified in this method call.
7327 * During text changes, when entire lines are inserted or removed, the line
7328 * attributes that are associated with the lines after the change
7329 * will "move" with their respective text. An entire line is defined as
7330 * extending from the first character on a line to the last and including the
7331 * line delimiter.
7332 * </p><p>
7333 * When two lines are joined by deleting a line delimiter, the top line
7334 * attributes take precedence and the attributes of the bottom line are deleted.
7335 * For all other text changes line attributes will remain unchanged.
7336 * </p>
7337 *
7338 * @param startLine first line the indent is applied to, 0 based
7339 * @param lineCount number of lines the indent applies to.
7340 * @param indent line indent
7341 *
7342 * @exception DWTException <ul>
7343 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7344 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7345 * </ul>
7346 * @exception IllegalArgumentException <ul>
7347 * <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
7348 * </ul>
7349 * @see #setIndent(int)
7350 * @since 3.2
7351 */
7352 public void setLineIndent(int startLine, int lineCount, int indent) {
7353 checkWidget();
7354 if (isListening(LineGetStyle)) return;
7355 if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
7356 DWT.error(DWT.ERROR_INVALID_ARGUMENT);
7357 }
7358
7359 renderer.setLineIndent(startLine, lineCount, indent);
7360 resetCache(startLine, lineCount);
7361 redrawLines(startLine, lineCount);
7362 int caretLine = getCaretLine();
7363 if (startLine <= caretLine && caretLine < startLine + lineCount) {
7364 setCaretLocation();
7365 }
7366 }
7367 /**
7368 * Sets the justify of the specified lines.
7369 * <p>
7370 * Should not be called if a LineStyleListener has been set since the listener
7371 * maintains the line attributes.
7372 * </p><p>
7373 * All line attributes are maintained relative to the line text, not the
7374 * line index that is specified in this method call.
7375 * During text changes, when entire lines are inserted or removed, the line
7376 * attributes that are associated with the lines after the change
7377 * will "move" with their respective text. An entire line is defined as
7378 * extending from the first character on a line to the last and including the
7379 * line delimiter.
7380 * </p><p>
7381 * When two lines are joined by deleting a line delimiter, the top line
7382 * attributes take precedence and the attributes of the bottom line are deleted.
7383 * For all other text changes line attributes will remain unchanged.
7384 * </p>
7385 *
7386 * @param startLine first line the justify is applied to, 0 based
7387 * @param lineCount number of lines the justify applies to.
7388 * @param justify true if lines should be justified
7389 *
7390 * @exception DWTException <ul>
7391 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7392 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7393 * </ul>
7394 * @exception IllegalArgumentException <ul>
7395 * <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
7396 * </ul>
7397 * @see #setJustify(bool)
7398 * @since 3.2
7399 */
7400 public void setLineJustify(int startLine, int lineCount, bool justify) {
7401 checkWidget();
7402 if (isListening(LineGetStyle)) return;
7403 if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
7404 DWT.error(DWT.ERROR_INVALID_ARGUMENT);
7405 }
7406
7407 renderer.setLineJustify(startLine, lineCount, justify);
7408 resetCache(startLine, lineCount);
7409 redrawLines(startLine, lineCount);
7410 int caretLine = getCaretLine();
7411 if (startLine <= caretLine && caretLine < startLine + lineCount) {
7412 setCaretLocation();
7413 }
7414 }
7415 /**
7416 * Sets the line spacing of the widget. The line spacing applies for all lines.
7417 *
7418 * @param lineSpacing the line spacing
7419 * @exception DWTException <ul>
7420 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7421 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7422 * </ul>
7423 * @since 3.2
7424 */
7425 public void setLineSpacing(int lineSpacing) {
7426 checkWidget();
7427 if (this.lineSpacing is lineSpacing || lineSpacing < 0) return;
7428 this.lineSpacing = lineSpacing;
7429 setVariableLineHeight();
7430 resetCache(0, content.getLineCount());
7431 setCaretLocation();
7432 super.redraw();
7433 }
7434 void setMargins (int leftMargin, int topMargin, int rightMargin, int bottomMargin) {
7435 checkWidget();
7436 this.leftMargin = leftMargin;
7437 this.topMargin = topMargin;
7438 this.rightMargin = rightMargin;
7439 this.bottomMargin = bottomMargin;
7440 setCaretLocation();
7441 }
7442 /**
7443 * Flips selection anchor based on word selection direction.
7444 */
7445 void setMouseWordSelectionAnchor() {
7446 if (clickCount > 1) {
7447 if (caretOffset < doubleClickSelection.x) {
7448 selectionAnchor = doubleClickSelection.y;
7449 } else if (caretOffset > doubleClickSelection.y) {
7450 selectionAnchor = doubleClickSelection.x;
7451 }
7452 }
7453 }
7454 /**
7455 * Sets the orientation of the receiver, which must be one
7456 * of the constants <code>DWT.LEFT_TO_RIGHT</code> or <code>DWT.RIGHT_TO_LEFT</code>.
7457 *
7458 * @param orientation new orientation style
7459 *
7460 * @exception DWTException <ul>
7461 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7462 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7463 * </ul>
7464 *
7465 * @since 2.1.2
7466 */
7467 public void setOrientation(int orientation) {
7468 if ((orientation & (DWT.RIGHT_TO_LEFT | DWT.LEFT_TO_RIGHT)) is 0) {
7469 return;
7470 }
7471 if ((orientation & DWT.RIGHT_TO_LEFT) !is 0 && (orientation & DWT.LEFT_TO_RIGHT) !is 0) {
7472 return;
7473 }
7474 if ((orientation & DWT.RIGHT_TO_LEFT) !is 0 && isMirrored()) {
7475 return;
7476 }
7477 if ((orientation & DWT.LEFT_TO_RIGHT) !is 0 && !isMirrored()) {
7478 return;
7479 }
7480 if (!BidiUtil.setOrientation(this, orientation)) {
7481 return;
7482 }
7483 isMirrored = (orientation & DWT.RIGHT_TO_LEFT) !is 0;
7484 caretDirection = DWT.NULL;
7485 resetCache(0, content.getLineCount());
7486 setCaretLocation();
7487 keyActionMap.clear();
7488 createKeyBindings();
7489 super.redraw();
7490 }
7491 /**
7492 * Adjusts the maximum and the page size of the scroll bars to
7493 * reflect content width/length changes.
7494 *
7495 * @param vertical indicates if the vertical scrollbar also needs to be set
7496 */
7497 void setScrollBars(bool vertical) {
7498 int inactive = 1;
7499 if (vertical || !isFixedLineHeight()) {
7500 ScrollBar verticalBar = getVerticalBar();
7501 if (verticalBar !is null) {
7502 int maximum = renderer.getHeight();
7503 // only set the real values if the scroll bar can be used
7504 // (ie. because the thumb size is less than the scroll maximum)
7505 // avoids flashing on Motif, fixes 1G7RE1J and 1G5SE92
7506 if (clientAreaHeight < maximum) {
7507 verticalBar.setMaximum(maximum);
7508 verticalBar.setThumb(clientAreaHeight);
7509 verticalBar.setPageIncrement(clientAreaHeight);
7510 } else if (verticalBar.getThumb() !is inactive || verticalBar.getMaximum() !is inactive) {
7511 verticalBar.setValues(
7512 verticalBar.getSelection(),
7513 verticalBar.getMinimum(),
7514 inactive,
7515 inactive,
7516 verticalBar.getIncrement(),
7517 inactive);
7518 }
7519 }
7520 }
7521 ScrollBar horizontalBar = getHorizontalBar();
7522 if (horizontalBar !is null && horizontalBar.getVisible()) {
7523 int maximum = renderer.getWidth();
7524 // only set the real values if the scroll bar can be used
7525 // (ie. because the thumb size is less than the scroll maximum)
7526 // avoids flashing on Motif, fixes 1G7RE1J and 1G5SE92
7527 if (clientAreaWidth < maximum) {
7528 horizontalBar.setMaximum(maximum);
7529 horizontalBar.setThumb(clientAreaWidth - leftMargin - rightMargin);
7530 horizontalBar.setPageIncrement(clientAreaWidth - leftMargin - rightMargin);
7531 } else if (horizontalBar.getThumb() !is inactive || horizontalBar.getMaximum() !is inactive) {
7532 horizontalBar.setValues(
7533 horizontalBar.getSelection(),
7534 horizontalBar.getMinimum(),
7535 inactive,
7536 inactive,
7537 horizontalBar.getIncrement(),
7538 inactive);
7539 }
7540 }
7541 }
7542 /**
7543 * Sets the selection to the given position and scrolls it into view. Equivalent to setSelection(start,start).
7544 *
7545 * @param start new caret position
7546 * @see #setSelection(int,int)
7547 * @exception DWTException <ul>
7548 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7549 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7550 * </ul>
7551 * @exception IllegalArgumentException <ul>
7552 * <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a
7553 * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter)
7554 * </ul>
7555 */
7556 public void setSelection(int start) {
7557 // checkWidget test done in setSelectionRange
7558 setSelection(start, start);
7559 }
7560 /**
7561 * Sets the selection and scrolls it into view.
7562 * <p>
7563 * Indexing is zero based. Text selections are specified in terms of
7564 * caret positions. In a text widget that contains N characters, there are
7565 * N+1 caret positions, ranging from 0..N
7566 * </p>
7567 *
7568 * @param point x=selection start offset, y=selection end offset
7569 * The caret will be placed at the selection start when x > y.
7570 * @see #setSelection(int,int)
7571 * @exception DWTException <ul>
7572 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7573 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7574 * </ul>
7575 * @exception IllegalArgumentException <ul>
7576 * <li>ERROR_NULL_ARGUMENT when point is null</li>
7577 * <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a
7578 * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter)
7579 * </ul>
7580 */
7581 public void setSelection(Point point) {
7582 checkWidget();
7583 if (point is null) DWT.error (DWT.ERROR_NULL_ARGUMENT);
7584 setSelection(point.x, point.y);
7585 }
7586 /**
7587 * Sets the receiver's selection background color to the color specified
7588 * by the argument, or to the default system color for the control
7589 * if the argument is null.
7590 *
7591 * @param color the new color (or null)
7592 *
7593 * @exception IllegalArgumentException <ul>
7594 * <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li>
7595 * </ul>
7596 * @exception DWTException <ul>
7597 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7598 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7599 * </ul>
7600 * @since 2.1
7601 */
7602 public void setSelectionBackground (Color color) {
7603 checkWidget ();
7604 if (color !is null) {
7605 if (color.isDisposed()) DWT.error(DWT.ERROR_INVALID_ARGUMENT);
7606 }
7607 selectionBackground = color;
7608 super.redraw();
7609 }
7610 /**
7611 * Sets the receiver's selection foreground color to the color specified
7612 * by the argument, or to the default system color for the control
7613 * if the argument is null.
7614 * <p>
7615 * Note that this is a <em>HINT</em>. Some platforms do not allow the application
7616 * to change the selection foreground color.
7617 * </p>
7618 * @param color the new color (or null)
7619 *
7620 * @exception IllegalArgumentException <ul>
7621 * <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li>
7622 * </ul>
7623 * @exception DWTException <ul>
7624 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7625 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7626 * </ul>
7627 * @since 2.1
7628 */
7629 public void setSelectionForeground (Color color) {
7630 checkWidget ();
7631 if (color !is null) {
7632 if (color.isDisposed()) DWT.error(DWT.ERROR_INVALID_ARGUMENT);
7633 }
7634 selectionForeground = color;
7635 super.redraw();
7636 }
7637 /**
7638 * Sets the selection and scrolls it into view.
7639 * <p>
7640 * Indexing is zero based. Text selections are specified in terms of
7641 * caret positions. In a text widget that contains N characters, there are
7642 * N+1 caret positions, ranging from 0..N
7643 * </p>
7644 *
7645 * @param start selection start offset. The caret will be placed at the
7646 * selection start when start > end.
7647 * @param end selection end offset
7648 * @see #setSelectionRange(int,int)
7649 * @exception DWTException <ul>
7650 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7651 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7652 * </ul>
7653 * @exception IllegalArgumentException <ul>
7654 * <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a
7655 * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter)
7656 * </ul>
7657 */
7658 public void setSelection(int start, int end) {
7659 setSelectionRange(start, end - start);
7660 showSelection();
7661 }
7662 /**
7663 * Sets the selection.
7664 * <p>
7665 * The new selection may not be visible. Call showSelection to scroll
7666 * the selection into view.
7667 * </p>
7668 *
7669 * @param start offset of the first selected character, start >= 0 must be true.
7670 * @param length number of characters to select, 0 <= start + length
7671 * <= getCharCount() must be true.
7672 * A negative length places the caret at the selection start.
7673 * @param sendEvent a Selection event is sent when set to true and when
7674 * the selection is reset.
7675 */
7676 void setSelection(int start, int length, bool sendEvent) {
7677 int end = start + length;
7678 if (start > end) {
7679 int temp = end;
7680 end = start;
7681 start = temp;
7682 }
7683 // is the selection range different or is the selection direction
7684 // different?
7685 if (selection.x !is start || selection.y !is end ||
7686 (length > 0 && selectionAnchor !is selection.x) ||
7687 (length < 0 && selectionAnchor !is selection.y)) {
7688 clearSelection(sendEvent);
7689 if (length < 0) {
7690 selectionAnchor = selection.y = end;
7691 caretOffset = selection.x = start;
7692 } else {
7693 selectionAnchor = selection.x = start;
7694 caretOffset = selection.y = end;
7695 }
7696 caretAlignment = PREVIOUS_OFFSET_TRAILING;
7697 internalRedrawRange(selection.x, selection.y - selection.x);
7698 }
7699 }
7700 /**
7701 * Sets the selection.
7702 * <p>
7703 * The new selection may not be visible. Call showSelection to scroll the selection
7704 * into view. A negative length places the caret at the visual start of the selection.
7705 * </p>
7706 *
7707 * @param start offset of the first selected character
7708 * @param length number of characters to select
7709 *
7710 * @exception DWTException <ul>
7711 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7712 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7713 * </ul>
7714 * @exception IllegalArgumentException <ul>
7715 * <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a
7716 * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter)
7717 * </ul>
7718 */
7719 public void setSelectionRange(int start, int length) {
7720 checkWidget();
7721 int contentLength = getCharCount();
7722 start = Math.max(0, Math.min (start, contentLength));
7723 int end = start + length;
7724 if (end < 0) {
7725 length = -start;
7726 } else {
7727 if (end > contentLength) length = contentLength - start;
7728 }
7729 if (isLineDelimiter(start) || isLineDelimiter(start + length)) {
7730 // the start offset or end offset of the selection range is inside a
7731 // multi byte line delimiter. This is an illegal operation and an exception
7732 // is thrown. Fixes 1GDKK3R
7733 DWT.error(DWT.ERROR_INVALID_ARGUMENT);
7734 }
7735 setSelection(start, length, false);
7736 setCaretLocation();
7737 }
7738 /**
7739 * Adds the specified style.
7740 * <p>
7741 * The new style overwrites existing styles for the specified range.
7742 * Existing style ranges are adjusted if they partially overlap with
7743 * the new style. To clear an individual style, call setStyleRange
7744 * with a StyleRange that has null attributes.
7745 * </p><p>
7746 * Should not be called if a LineStyleListener has been set since the
7747 * listener maintains the styles.
7748 * </p>
7749 *
7750 * @param range StyleRange object containing the style information.
7751 * Overwrites the old style in the given range. May be null to delete
7752 * all styles.
7753 * @exception DWTException <ul>
7754 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7755 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7756 * </ul>
7757 * @exception IllegalArgumentException <ul>
7758 * <li>ERROR_INVALID_RANGE when the style range is outside the valid range (> getCharCount())</li>
7759 * </ul>
7760 */
7761 public void setStyleRange(StyleRange range) {
7762 checkWidget();
7763 if (isListening(LineGetStyle)) return;
7764 if (range !is null) {
7765 if (range.isUnstyled()) {
7766 setStyleRanges(range.start, range.length, null, null, false);
7767 } else {
7768 setStyleRanges(range.start, 0, null, new StyleRange[]{range}, false);
7769 }
7770 } else {
7771 setStyleRanges(0, 0, null, null, true);
7772 }
7773 }
7774 /**
7775 * Clears the styles in the range specified by <code>start</code> and
7776 * <code>length</code> and adds the new styles.
7777 * <p>
7778 * The ranges array contains start and length pairs. Each pair refers to
7779 * the corresponding style in the styles array. For example, the pair
7780 * that starts at ranges[n] with length ranges[n+1] uses the style
7781 * at styles[n/2]. The range fields within each StyleRange are ignored.
7782 * If ranges or styles is null, the specified range is cleared.
7783 * </p><p>
7784 * Note: It is expected that the same instance of a StyleRange will occur
7785 * multiple times within the styles array, reducing memory usage.
7786 * </p><p>
7787 * Should not be called if a LineStyleListener has been set since the
7788 * listener maintains the styles.
7789 * </p>
7790 *
7791 * @param start offset of first character where styles will be deleted
7792 * @param length length of the range to delete styles in
7793 * @param ranges the array of ranges. The ranges must not overlap and must be in order.
7794 * @param styles the array of StyleRanges. The range fields within the StyleRange are unused.
7795 *
7796 * @exception DWTException <ul>
7797 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7798 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7799 * </ul>
7800 * @exception IllegalArgumentException <ul>
7801 * <li>ERROR_NULL_ARGUMENT when an element in the styles array is null</li>
7802 * <li>ERROR_INVALID_RANGE when the number of ranges and style do not match (ranges.length * 2 is styles.length)</li>
7803 * <li>ERROR_INVALID_RANGE when a range is outside the valid range (> getCharCount() or less than zero)</li>
7804 * <li>ERROR_INVALID_RANGE when a range overlaps</li>
7805 * </ul>
7806 *
7807 * @since 3.2
7808 */
7809 public void setStyleRanges(int start, int length, int[] ranges, StyleRange[] styles) {
7810 checkWidget();
7811 if (isListening(LineGetStyle)) return;
7812 if (ranges is null || styles is null) {
7813 setStyleRanges(start, length, null, null, false);
7814 } else {
7815 setStyleRanges(start, length, ranges, styles, false);
7816 }
7817 }
7818 /**
7819 * Sets styles to be used for rendering the widget content.
7820 * <p>
7821 * All styles in the widget will be replaced with the given set of ranges and styles.
7822 * The ranges array contains start and length pairs. Each pair refers to
7823 * the corresponding style in the styles array. For example, the pair
7824 * that starts at ranges[n] with length ranges[n+1] uses the style
7825 * at styles[n/2]. The range fields within each StyleRange are ignored.
7826 * If either argument is null, the styles are cleared.
7827 * </p><p>
7828 * Note: It is expected that the same instance of a StyleRange will occur
7829 * multiple times within the styles array, reducing memory usage.
7830 * </p><p>
7831 * Should not be called if a LineStyleListener has been set since the
7832 * listener maintains the styles.
7833 * </p>
7834 *
7835 * @param ranges the array of ranges. The ranges must not overlap and must be in order.
7836 * @param styles the array of StyleRanges. The range fields within the StyleRange are unused.
7837 *
7838 * @exception DWTException <ul>
7839 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7840 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7841 * </ul>
7842 * @exception IllegalArgumentException <ul>
7843 * <li>ERROR_NULL_ARGUMENT when an element in the styles array is null</li>
7844 * <li>ERROR_INVALID_RANGE when the number of ranges and style do not match (ranges.length * 2 is styles.length)</li>
7845 * <li>ERROR_INVALID_RANGE when a range is outside the valid range (> getCharCount() or less than zero)</li>
7846 * <li>ERROR_INVALID_RANGE when a range overlaps</li>
7847 * </ul>
7848 *
7849 * @since 3.2
7850 */
7851 public void setStyleRanges(int[] ranges, StyleRange[] styles) {
7852 checkWidget();
7853 if (isListening(LineGetStyle)) return;
7854 if (ranges is null || styles is null) {
7855 setStyleRanges(0, 0, null, null, true);
7856 } else {
7857 setStyleRanges(0, 0, ranges, styles, true);
7858 }
7859 }
7860 void setStyleRanges(int start, int length, int[] ranges, StyleRange[] styles, bool reset) {
7861 int charCount = content.getCharCount();
7862 int end = start + length;
7863 if (start > end || start < 0) {
7864 DWT.error(DWT.ERROR_INVALID_RANGE);
7865 }
7866 if (styles !is null) {
7867 if (end > charCount) {
7868 DWT.error(DWT.ERROR_INVALID_RANGE);
7869 }
7870 if (ranges !is null) {
7871 if (ranges.length !is styles.length << 1) DWT.error(DWT.ERROR_INVALID_ARGUMENT);
7872 }
7873 int lastOffset = 0;
7874 bool variableHeight = false;
7875 for (int i = 0; i < styles.length; i ++) {
7876 if (styles[i] is null) DWT.error(DWT.ERROR_INVALID_ARGUMENT);
7877 int rangeStart, rangeLength;
7878 if (ranges !is null) {
7879 rangeStart = ranges[i << 1];
7880 rangeLength = ranges[(i << 1) + 1];
7881 } else {
7882 rangeStart = styles[i].start;
7883 rangeLength = styles[i].length;
7884 }
7885 if (rangeLength < 0) DWT.error(DWT.ERROR_INVALID_ARGUMENT);
7886 if (!(0 <= rangeStart && rangeStart + rangeLength <= charCount)) DWT.error(DWT.ERROR_INVALID_ARGUMENT);
7887 if (lastOffset > rangeStart) DWT.error(DWT.ERROR_INVALID_ARGUMENT);
7888 variableHeight |= styles[i].isVariableHeight();
7889 lastOffset = rangeStart + rangeLength;
7890 }
7891 if (variableHeight) setVariableLineHeight();
7892 }
7893 int rangeStart = start, rangeEnd = end;
7894 if (styles !is null && styles.length > 0) {
7895 if (ranges !is null) {
7896 rangeStart = ranges[0];
7897 rangeEnd = ranges[ranges.length - 2] + ranges[ranges.length - 1];
7898 } else {
7899 rangeStart = styles[0].start;
7900 rangeEnd = styles[styles.length - 1].start + styles[styles.length - 1].length;
7901 }
7902 }
7903 int lastLineBottom = 0;
7904 if (!isFixedLineHeight() && !reset) {
7905 int lineEnd = content.getLineAtOffset(Math.max(end, rangeEnd));
7906 int partialTopIndex = getPartialTopIndex();
7907 int partialBottomIndex = getPartialBottomIndex();
7908 if (partialTopIndex <= lineEnd && lineEnd <= partialBottomIndex) {
7909 lastLineBottom = getLinePixel(lineEnd + 1);
7910 }
7911 }
7912 if (reset) {
7913 renderer.setStyleRanges(null, null);
7914 } else {
7915 renderer.updateRanges(start, length, length);
7916 }
7917 if (styles !is null && styles.length > 0) {
7918 renderer.setStyleRanges(ranges, styles);
7919 }
7920 if (reset) {
7921 resetCache(0, content.getLineCount());
7922 super.redraw();
7923 } else {
7924 int lineStart = content.getLineAtOffset(Math.min(start, rangeStart));
7925 int lineEnd = content.getLineAtOffset(Math.max(end, rangeEnd));
7926 resetCache(lineStart, lineEnd - lineStart + 1);
7927 int partialTopIndex = getPartialTopIndex();
7928 int partialBottomIndex = getPartialBottomIndex();
7929 if (!(lineStart > partialBottomIndex || lineEnd < partialTopIndex)) {
7930 int y = 0;
7931 int height = clientAreaHeight;
7932 if (partialTopIndex <= lineStart && lineStart <= partialBottomIndex) {
7933 int lineTop = Math.max(y, getLinePixel(lineStart));
7934 y = lineTop;
7935 height -= lineTop;
7936 }
7937 if (partialTopIndex <= lineEnd && lineEnd <= partialBottomIndex) {
7938 int newLastLineBottom = getLinePixel(lineEnd + 1);
7939 if (!isFixedLineHeight()) {
7940 scrollText(lastLineBottom, newLastLineBottom);
7941 }
7942 height = newLastLineBottom - y;
7943 }
7944 super.redraw(0, y, clientAreaWidth, height, false);
7945 }
7946 }
7947 setCaretLocation();
7948 }
7949 /**
7950 * Sets styles to be used for rendering the widget content. All styles
7951 * in the widget will be replaced with the given set of styles.
7952 * <p>
7953 * Note: Because a StyleRange includes the start and length, the
7954 * same instance cannot occur multiple times in the array of styles.
7955 * If the same style attributes, such as font and color, occur in
7956 * multiple StyleRanges, <code>setStyleRanges(int[], StyleRange[])</code>
7957 * can be used to share styles and reduce memory usage.
7958 * </p><p>
7959 * Should not be called if a LineStyleListener has been set since the
7960 * listener maintains the styles.
7961 * </p>
7962 *
7963 * @param ranges StyleRange objects containing the style information.
7964 * The ranges should not overlap. The style rendering is undefined if
7965 * the ranges do overlap. Must not be null. The styles need to be in order.
7966 * @exception DWTException <ul>
7967 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7968 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7969 * </ul>
7970 * @exception IllegalArgumentException <ul>
7971 * <li>ERROR_NULL_ARGUMENT when the list of ranges is null</li>
7972 * <li>ERROR_INVALID_RANGE when the last of the style ranges is outside the valid range (> getCharCount())</li>
7973 * </ul>
7974 *
7975 * @see #setStyleRanges(int[], StyleRange[])
7976 */
7977 public void setStyleRanges(StyleRange[] ranges) {
7978 checkWidget();
7979 if (isListening(LineGetStyle)) return;
7980 if (ranges is null) DWT.error(DWT.ERROR_NULL_ARGUMENT);
7981 setStyleRanges(0, 0, null, ranges, true);
7982 }
7983 /**
7984 * Sets the tab width.
7985 *
7986 * @param tabs tab width measured in characters.
7987 * @exception DWTException <ul>
7988 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7989 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7990 * </ul>
7991 */
7992 public void setTabs(int tabs) {
7993 checkWidget();
7994 tabLength = tabs;
7995 renderer.setFont(null, tabs);
7996 resetCache(0, content.getLineCount());
7997 setCaretLocation();
7998 super.redraw();
7999 }
8000 /**
8001 * Sets the widget content.
8002 * If the widget has the DWT.SINGLE style and "text" contains more than
8003 * one line, only the first line is rendered but the text is stored
8004 * unchanged. A subsequent call to getText will return the same text
8005 * that was set.
8006 * <p>
8007 * <b>Note:</b> Only a single line of text should be set when the DWT.SINGLE
8008 * style is used.
8009 * </p>
8010 *
8011 * @param text new widget content. Replaces existing content. Line styles
8012 * that were set using StyledText API are discarded. The
8013 * current selection is also discarded.
8014 * @exception DWTException <ul>
8015 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8016 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8017 * </ul>
8018 * @exception IllegalArgumentException <ul>
8019 * <li>ERROR_NULL_ARGUMENT when String is null</li>
8020 * </ul>
8021 */
8022 public void setText(String text) {
8023 checkWidget();
8024 if (text is null) {
8025 DWT.error(DWT.ERROR_NULL_ARGUMENT);
8026 }
8027 Event event = new Event();
8028 event.start = 0;
8029 event.end = getCharCount();
8030 event.text = text;
8031 event.doit = true;
8032 notifyListeners(DWT.Verify, event);
8033 if (event.doit) {
8034 StyledTextEvent styledTextEvent = null;
8035 if (isListening(ExtendedModify)) {
8036 styledTextEvent = new StyledTextEvent(content);
8037 styledTextEvent.start = event.start;
8038 styledTextEvent.end = event.start + event.text.length();
8039 styledTextEvent.text = content.getTextRange(event.start, event.end - event.start);
8040 }
8041 content.setText(event.text);
8042 sendModifyEvent(event);
8043 if (styledTextEvent !is null) {
8044 notifyListeners(ExtendedModify, styledTextEvent);
8045 }
8046 }
8047 }
8048 /**
8049 * Sets the text limit to the specified number of characters.
8050 * <p>
8051 * The text limit specifies the amount of text that
8052 * the user can type into the widget.
8053 * </p>
8054 *
8055 * @param limit the new text limit.
8056 * @exception DWTException <ul>
8057 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8058 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8059 * </ul>
8060 * @exception IllegalArgumentException <ul>
8061 * <li>ERROR_CANNOT_BE_ZERO when limit is 0</li>
8062 * </ul>
8063 */
8064 public void setTextLimit(int limit) {
8065 checkWidget();
8066 if (limit is 0) {
8067 DWT.error(DWT.ERROR_CANNOT_BE_ZERO);
8068 }
8069 textLimit = limit;
8070 }
8071 /**
8072 * Sets the top index. Do nothing if there is no text set.
8073 * <p>
8074 * The top index is the index of the line that is currently at the top
8075 * of the widget. The top index changes when the widget is scrolled.
8076 * Indexing starts from zero.
8077 * Note: The top index is reset to 0 when new text is set in the widget.
8078 * </p>
8079 *
8080 * @param topIndex new top index. Must be between 0 and
8081 * getLineCount() - fully visible lines per page. If no lines are fully
8082 * visible the maximum value is getLineCount() - 1. An out of range
8083 * index will be adjusted accordingly.
8084 * @exception DWTException <ul>
8085 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8086 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8087 * </ul>
8088 */
8089 public void setTopIndex(int topIndex) {
8090 checkWidget();
8091 if (getCharCount() is 0) {
8092 return;
8093 }
8094 int lineCount = content.getLineCount(), pixel;
8095 if (isFixedLineHeight()) {
8096 int pageSize = Math.max(1, Math.min(lineCount, getLineCountWhole()));
8097 if (topIndex < 0) {
8098 topIndex = 0;
8099 } else if (topIndex > lineCount - pageSize) {
8100 topIndex = lineCount - pageSize;
8101 }
8102 pixel = getLinePixel(topIndex);
8103 } else {
8104 topIndex = Math.max(0, Math.min(lineCount - 1, topIndex));
8105 pixel = getLinePixel(topIndex);
8106 if (pixel > 0) {
8107 pixel = getAvailableHeightBellow(pixel);
8108 } else {
8109 pixel = getAvailableHeightAbove(pixel);
8110 }
8111 }
8112 scrollVertical(pixel, true);
8113 }
8114 /**
8115 * Sets the top pixel offset. Do nothing if there is no text set.
8116 * <p>
8117 * The top pixel offset is the vertical pixel offset of the widget. The
8118 * widget is scrolled so that the given pixel position is at the top.
8119 * The top index is adjusted to the corresponding top line.
8120 * Note: The top pixel is reset to 0 when new text is set in the widget.
8121 * </p>
8122 *
8123 * @param pixel new top pixel offset. Must be between 0 and
8124 * (getLineCount() - visible lines per page) / getLineHeight()). An out
8125 * of range offset will be adjusted accordingly.
8126 * @exception DWTException <ul>
8127 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8128 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8129 * </ul>
8130 * @since 2.0
8131 */
8132 public void setTopPixel(int pixel) {
8133 checkWidget();
8134 if (getCharCount() is 0) {
8135 return;
8136 }
8137 if (pixel < 0) pixel = 0;
8138 int lineCount = content.getLineCount();
8139 int height = clientAreaHeight - topMargin - bottomMargin;
8140 int verticalOffset = getVerticalScrollOffset();
8141 if (isFixedLineHeight()) {
8142 int maxTopPixel = Math.max(0, lineCount * getVerticalIncrement() - height);
8143 if (pixel > maxTopPixel) pixel = maxTopPixel;
8144 pixel -= verticalOffset;
8145 } else {
8146 pixel -= verticalOffset;
8147 if (pixel > 0) {
8148 pixel = getAvailableHeightBellow(pixel);
8149 }
8150 }
8151 scrollVertical(pixel, true);
8152 }
8153 /**
8154 * Sets whether the widget wraps lines.
8155 * <p>
8156 * This overrides the creation style bit DWT.WRAP.
8157 * </p>
8158 *
8159 * @param wrap true=widget wraps lines, false=widget does not wrap lines
8160 * @since 2.0
8161 */
8162 public void setWordWrap(bool wrap) {
8163 checkWidget();
8164 if ((getStyle() & DWT.SINGLE) !is 0) return;
8165 if (wordWrap is wrap) return;
8166 wordWrap = wrap;
8167 setVariableLineHeight();
8168 resetCache(0, content.getLineCount());
8169 horizontalScrollOffset = 0;
8170 ScrollBar horizontalBar = getHorizontalBar();
8171 if (horizontalBar !is null) {
8172 horizontalBar.setVisible(!wordWrap);
8173 }
8174 setScrollBars(true);
8175 setCaretLocation();
8176 super.redraw();
8177 }
8178 bool showLocation(Rectangle rect, bool scrollPage) {
8179 int clientAreaWidth = this.clientAreaWidth - leftMargin - rightMargin;
8180 int clientAreaHeight = this.clientAreaHeight - topMargin - bottomMargin;
8181 bool scrolled = false;
8182 if (rect.y <= topMargin) {
8183 scrolled = scrollVertical(rect.y - topMargin, true);
8184 } else if (rect.y + rect.height > clientAreaHeight) {
8185 if (clientAreaHeight is 0) {
8186 scrolled = scrollVertical(rect.y, true);
8187 } else {
8188 scrolled = scrollVertical(rect.y + rect.height - clientAreaHeight, true);
8189 }
8190 }
8191 if (clientAreaWidth > 0) {
8192 int minScroll = scrollPage ? clientAreaWidth / 4 : 0;
8193 if (rect.x < leftMargin) {
8194 int scrollWidth = Math.max(leftMargin - rect.x, minScroll);
8195 int maxScroll = horizontalScrollOffset;
8196 scrolled = scrollHorizontal(-Math.min(maxScroll, scrollWidth), true);
8197 } else if (rect.x + rect.width > clientAreaWidth) {
8198 int scrollWidth = Math.max(rect.x + rect.width - clientAreaWidth, minScroll);
8199 int maxScroll = renderer.getWidth() - horizontalScrollOffset - this.clientAreaWidth;
8200 scrolled = scrollHorizontal(Math.min(maxScroll, scrollWidth), true);
8201 }
8202 }
8203 return scrolled;
8204 }
8205 /**
8206 * Sets the caret location and scrolls the caret offset into view.
8207 */
8208 void showCaret() {
8209 Rectangle bounds = getBoundsAtOffset(caretOffset);
8210 if (!showLocation(bounds, true)) {
8211 setCaretLocation();
8212 }
8213 }
8214 /**
8215 * Scrolls the selection into view.
8216 * <p>
8217 * The end of the selection will be scrolled into view.
8218 * Note that if a right-to-left selection exists, the end of the selection is
8219 * the visual beginning of the selection (i.e., where the caret is located).
8220 * </p>
8221 *
8222 * @exception DWTException <ul>
8223 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8224 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8225 * </ul>
8226 */
8227 public void showSelection() {
8228 checkWidget();
8229 // is selection from right-to-left?
8230 bool rightToLeft = caretOffset is selection.x;
8231 int startOffset, endOffset;
8232 if (rightToLeft) {
8233 startOffset = selection.y;
8234 endOffset = selection.x;
8235 } else {
8236 startOffset = selection.x;
8237 endOffset = selection.y;
8238 }
8239
8240 Rectangle startBounds = getBoundsAtOffset(startOffset);
8241 Rectangle endBounds = getBoundsAtOffset(endOffset);
8242
8243 // can the selection be fully displayed within the widget's visible width?
8244 int w = clientAreaWidth - leftMargin - rightMargin;
8245 bool selectionFits = rightToLeft ? startBounds.x - endBounds.x <= w : endBounds.x - startBounds.x <= w;
8246 if (selectionFits) {
8247 // show as much of the selection as possible by first showing
8248 // the start of the selection
8249 if (showLocation(startBounds, false)) {
8250 // endX value could change if showing startX caused a scroll to occur
8251 endBounds = getBoundsAtOffset(endOffset);
8252 }
8253 // the character at endOffset is not part of the selection
8254 endBounds.width = 0;
8255 showLocation(endBounds, false);
8256 } else {
8257 // just show the end of the selection since the selection start
8258 // will not be visible
8259 showLocation(endBounds, true);
8260 }
8261 }
8262 /**
8263 * Updates the selection and caret position depending on the text change.
8264 * <p>
8265 * If the selection intersects with the replaced text, the selection is
8266 * reset and the caret moved to the end of the new text.
8267 * If the selection is behind the replaced text it is moved so that the
8268 * same text remains selected. If the selection is before the replaced text
8269 * it is left unchanged.
8270 * </p>
8271 *
8272 * @param startOffset offset of the text change
8273 * @param replacedLength length of text being replaced
8274 * @param newLength length of new text
8275 */
8276 void updateSelection(int startOffset, int replacedLength, int newLength) {
8277 if (selection.y <= startOffset) {
8278 // selection ends before text change
8279 return;
8280 }
8281 if (selection.x < startOffset) {
8282 // clear selection fragment before text change
8283 internalRedrawRange(selection.x, startOffset - selection.x);
8284 }
8285 if (selection.y > startOffset + replacedLength && selection.x < startOffset + replacedLength) {
8286 // clear selection fragment after text change.
8287 // do this only when the selection is actually affected by the
8288 // change. Selection is only affected if it intersects the change (1GDY217).
8289 int netNewLength = newLength - replacedLength;
8290 int redrawStart = startOffset + newLength;
8291 internalRedrawRange(redrawStart, selection.y + netNewLength - redrawStart);
8292 }
8293 if (selection.y > startOffset && selection.x < startOffset + replacedLength) {
8294 // selection intersects replaced text. set caret behind text change
8295 setSelection(startOffset + newLength, 0, true);
8296 } else {
8297 // move selection to keep same text selected
8298 setSelection(selection.x + newLength - replacedLength, selection.y - selection.x, true);
8299 }
8300 setCaretLocation();
8301 }
8302 }