1 /*******************************************************************************
2 * Copyright (c) 2000, 2020 IBM Corporation and others.
3 *
4 * This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License 2.0
6 * which accompanies this distribution, and is available at
7 * https://www.eclipse.org/legal/epl-2.0/
8 *
9 * SPDX-License-Identifier: EPL-2.0
10 *
11 * Contributors:
12 * IBM Corporation - initial API and implementation
13 * Andrey Loskutov <loskutov@gmx.de> - bug 488172
14 * Stefan Xenos (Google) - bug 487254 - StyledText.getTopIndex() can return negative values
15 * Angelo Zerr <angelo.zerr@gmail.com> - Customize different line spacing of StyledText - Bug 522020
16 * Karsten Thoms <thoms@itemis.de> - bug 528746 add getOffsetAtPoint(Point)
17 *******************************************************************************/
18 package org.eclipse.swt.custom;
19
20
21 import java.util.*;
22 import java.util.List;
23
24 import org.eclipse.swt.*;
25 import org.eclipse.swt.accessibility.*;
26 import org.eclipse.swt.dnd.*;
27 import org.eclipse.swt.events.*;
28 import org.eclipse.swt.graphics.*;
29 import org.eclipse.swt.internal.*;
30 import org.eclipse.swt.printing.*;
31 import org.eclipse.swt.widgets.*;
32
33 /**
34 * A StyledText is an editable user interface object that displays lines
35 * of text. The following style attributes can be defined for the text:
36 * <ul>
37 * <li>foreground color
38 * <li>background color
39 * <li>font style (bold, italic, bold-italic, regular)
40 * <li>underline
41 * <li>strikeout
42 * </ul>
43 * <p>
44 * In addition to text style attributes, the background color of a line may
45 * be specified.
46 * </p><p>
47 * There are two ways to use this widget when specifying text style information.
48 * You may use the API that is defined for StyledText or you may define your own
49 * LineStyleListener. If you define your own listener, you will be responsible
50 * for maintaining the text style information for the widget. IMPORTANT: You may
51 * not define your own listener and use the StyledText API. The following
52 * StyledText API is not supported if you have defined a LineStyleListener:</p>
53 * <ul>
54 * <li>getStyleRangeAtOffset(int)
55 * <li>getStyleRanges()
56 * <li>replaceStyleRanges(int,int,StyleRange[])
57 * <li>setStyleRange(StyleRange)
58 * <li>setStyleRanges(StyleRange[])
59 * </ul>
60 * <p>
61 * There are two ways to use this widget when specifying line background colors.
62 * You may use the API that is defined for StyledText or you may define your own
63 * LineBackgroundListener. If you define your own listener, you will be responsible
64 * for maintaining the line background color information for the widget.
65 * IMPORTANT: You may not define your own listener and use the StyledText API.
66 * The following StyledText API is not supported if you have defined a
67 * LineBackgroundListener:</p>
68 * <ul>
69 * <li>getLineBackground(int)
70 * <li>setLineBackground(int,int,Color)
71 * </ul>
72 * <p>
73 * The content implementation for this widget may also be user-defined. To do so,
74 * you must implement the StyledTextContent interface and use the StyledText API
75 * setContent(StyledTextContent) to initialize the widget.
76 * </p>
77 * <dl>
78 * <dt><b>Styles:</b><dd>FULL_SELECTION, MULTI, READ_ONLY, SINGLE, WRAP
79 * <dt><b>Events:</b><dd>ExtendedModify, LineGetBackground, LineGetSegments, LineGetStyle, Modify, Selection, Verify, VerifyKey, OrientationChange
80 * </dl>
81 * <p>
82 * IMPORTANT: This class is <em>not</em> intended to be subclassed.
83 * </p>
84 *
85 * @see <a href="http://www.eclipse.org/swt/snippets/#styledtext">StyledText snippets</a>
86 * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Examples: CustomControlExample, TextEditor</a>
87 * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
88 * @noextend This class is not intended to be subclassed by clients.
89 */
90 public class StyledText extends Canvas {
91 static final char TAB = '\t';
92 static final String PlatformLineDelimiter = System.lineSeparator();
93 static final int BIDI_CARET_WIDTH = 3;
94 static final int DEFAULT_WIDTH = 64;
95 static final int DEFAULT_HEIGHT = 64;
96 static final int V_SCROLL_RATE = 50;
97 static final int H_SCROLL_RATE = 10;
98 static final int PREVIOUS_OFFSET_TRAILING = 0;
99 static final int OFFSET_LEADING = 1;
100
101 static final String STYLEDTEXT_KEY = "org.eclipse.swt.internal.cocoa.styledtext"; //$NON-NLS-1$
102
103 Color selectionBackground; // selection background color
104 Color selectionForeground; // selection foreground color
105 StyledTextContent content; // native content (default or user specified)
106 StyledTextRenderer renderer;
107 Listener listener;
108 TextChangeListener textChangeListener; // listener for TextChanging, TextChanged and TextSet events from StyledTextContent
109 int verticalScrollOffset = 0; // pixel based
110 int horizontalScrollOffset = 0; // pixel based
111 boolean alwaysShowScroll = true;
112 int ignoreResize = 0;
113 int topIndex = 0; // top visible line
114 int topIndexY;
115 int clientAreaHeight = 0; // the client area height. Needed to calculate content width for new visible lines during Resize callback
116 int clientAreaWidth = 0; // the client area width. Needed during Resize callback to determine if line wrap needs to be recalculated
117 int tabLength = 4; // number of characters in a tab
118 int [] tabs;
119 int leftMargin;
120 int topMargin;
121 int rightMargin;
122 int bottomMargin;
123 Color marginColor;
124 int columnX; // keep track of the horizontal caret position when changing lines/pages. Fixes bug 5935
125 int caretOffset;
126 int caretAlignment;
127 Point selection = new Point(0, 0); // x and y are start and end caret offsets of selection (x <= y)
128 Point clipboardSelection; // x and y are start and end caret offsets of previous selection
129 int selectionAnchor; // position of selection anchor. 0 based offset from beginning of text
130 Point doubleClickSelection; // selection after last mouse double click
131 boolean editable = true;
132 boolean wordWrap = false; // text is wrapped automatically
133 boolean visualWrap = false; // process line breaks inside logical lines (inserted by BidiSegmentEvent)
134 boolean hasStyleWithVariableHeight = false;
135 boolean hasVerticalIndent = false;
136 boolean doubleClickEnabled = true; // see getDoubleClickEnabled
137 boolean overwrite = false; // insert/overwrite edit mode
138 int textLimit = -1; // limits the number of characters the user can type in the widget. Unlimited by default.
139 Map<Integer, Integer> keyActionMap = new HashMap<>();
140 Color background = null; // workaround for bug 4791
141 Color foreground = null; //
142 /** True if a non-default background color is set */
143 boolean customBackground;
144 /** True if a non-default foreground color is set */
145 boolean customForeground;
146 /** False iff the widget is disabled */
147 boolean enabled = true;
148 /** True iff the widget is in the midst of being enabled or disabled */
149 boolean insideSetEnableCall;
150 Clipboard clipboard;
151 int clickCount;
152 int autoScrollDirection = SWT.NULL; // the direction of autoscrolling (up, down, right, left)
153 int autoScrollDistance = 0;
154 int lastTextChangeStart; // cache data of the
155 int lastTextChangeNewLineCount; // last text changing
156 int lastTextChangeNewCharCount; // event for use in the
157 int lastTextChangeReplaceLineCount; // text changed handler
158 int lastTextChangeReplaceCharCount;
159 int lastCharCount = 0;
160 int lastLineBottom; // the bottom pixel of the last line been replaced
161 boolean bidiColoring = false; // apply the BIDI algorithm on text segments of the same color
162 Image leftCaretBitmap = null;
163 Image rightCaretBitmap = null;
164 int caretDirection = SWT.NULL;
165 int caretWidth = 0;
166 Caret defaultCaret = null;
167 boolean updateCaretDirection = true;
168 boolean dragDetect = true;
169 IME ime;
170 Cursor cursor;
171 int alignment;
172 boolean justify;
173 int indent, wrapIndent;
174 int lineSpacing;
175 int alignmentMargin;
176 int newOrientation = SWT.NONE;
177 int accCaretOffset;
178 Accessible acc;
179 AccessibleControlAdapter accControlAdapter;
180 AccessibleAttributeAdapter accAttributeAdapter;
181 AccessibleEditableTextListener accEditableTextListener;
182 AccessibleTextExtendedAdapter accTextExtendedAdapter;
183 AccessibleAdapter accAdapter;
184 MouseNavigator mouseNavigator;
185 boolean middleClickPressed;
186
187 //block selection
188 boolean blockSelection;
189 int blockXAnchor = -1, blockYAnchor = -1;
190 int blockXLocation = -1, blockYLocation = -1;
191
192 final static boolean IS_MAC, IS_GTK;
193 static {
194 String platform = SWT.getPlatform();
195 IS_MAC = "cocoa".equals(platform);
196 IS_GTK = "gtk".equals(platform);
197 }
198
199 /**
200 * The Printing class implements printing of a range of text.
201 * An instance of <code>Printing</code> is returned in the
202 * StyledText#print(Printer) API. The run() method may be
203 * invoked from any thread.
204 */
205 static class Printing implements Runnable {
206 final static int LEFT = 0; // left aligned header/footer segment
207 final static int CENTER = 1; // centered header/footer segment
208 final static int RIGHT = 2; // right aligned header/footer segment
209
210 Printer printer;
211 StyledTextRenderer printerRenderer;
212 StyledTextPrintOptions printOptions;
213 Rectangle clientArea;
214 FontData fontData;
215 Font printerFont;
216 Map<Resource, Resource> resources;
217 int tabLength;
218 GC gc; // printer GC
219 int pageWidth; // width of a printer page in pixels
220 int startPage; // first page to print
221 int endPage; // last page to print
222 int scope; // scope of print job
223 int startLine; // first (wrapped) line to print
224 int endLine; // last (wrapped) line to print
225 boolean singleLine; // widget single line mode
226 Point selection = null; // selected text
227 boolean mirrored; // indicates the printing gc should be mirrored
228 int lineSpacing;
229 int printMargin;
230
231 /**
232 * Creates an instance of <code>Printing</code>.
233 * Copies the widget content and rendering data that needs
234 * to be requested from listeners.
235 *
236 * @param parent StyledText widget to print.
237 * @param printer printer device to print on.
238 * @param printOptions print options
239 */
Printing(StyledText styledText, Printer printer, StyledTextPrintOptions printOptions)240 Printing(StyledText styledText, Printer printer, StyledTextPrintOptions printOptions) {
241 this.printer = printer;
242 this.printOptions = printOptions;
243 this.mirrored = (styledText.getStyle() & SWT.MIRRORED) != 0;
244 singleLine = styledText.isSingleLine();
245 startPage = 1;
246 endPage = Integer.MAX_VALUE;
247 PrinterData data = printer.getPrinterData();
248 scope = data.scope;
249 if (scope == PrinterData.PAGE_RANGE) {
250 startPage = data.startPage;
251 endPage = data.endPage;
252 if (endPage < startPage) {
253 int temp = endPage;
254 endPage = startPage;
255 startPage = temp;
256 }
257 } else if (scope == PrinterData.SELECTION) {
258 selection = styledText.getSelectionRange();
259 }
260 printerRenderer = new StyledTextRenderer(printer, null);
261 printerRenderer.setContent(copyContent(styledText.getContent()));
262 cacheLineData(styledText);
263 }
264 /**
265 * Caches all line data that needs to be requested from a listener.
266 *
267 * @param printerContent <code>StyledTextContent</code> to request
268 * line data for.
269 */
cacheLineData(StyledText styledText)270 void cacheLineData(StyledText styledText) {
271 StyledTextRenderer renderer = styledText.renderer;
272 renderer.copyInto(printerRenderer);
273 fontData = styledText.getFont().getFontData()[0];
274 tabLength = styledText.tabLength;
275 int lineCount = printerRenderer.lineCount;
276 if (styledText.isListening(ST.LineGetBackground) || (styledText.isListening(ST.LineGetSegments)) || styledText.isListening(ST.LineGetStyle)) {
277 StyledTextContent content = printerRenderer.content;
278 for (int i = 0; i < lineCount; i++) {
279 String line = content.getLine(i);
280 int lineOffset = content.getOffsetAtLine(i);
281 StyledTextEvent event = styledText.getLineBackgroundData(lineOffset, line);
282 if (event != null && event.lineBackground != null) {
283 printerRenderer.setLineBackground(i, 1, event.lineBackground);
284 }
285 event = styledText.getBidiSegments(lineOffset, line);
286 if (event != null) {
287 printerRenderer.setLineSegments(i, 1, event.segments);
288 printerRenderer.setLineSegmentChars(i, 1, event.segmentsChars);
289 }
290 event = styledText.getLineStyleData(lineOffset, line);
291 if (event != null) {
292 printerRenderer.setLineIndent(i, 1, event.indent);
293 printerRenderer.setLineAlignment(i, 1, event.alignment);
294 printerRenderer.setLineJustify(i, 1, event.justify);
295 printerRenderer.setLineBullet(i, 1, event.bullet);
296 StyleRange[] styles = event.styles;
297 if (styles != null && styles.length > 0) {
298 printerRenderer.setStyleRanges(event.ranges, styles);
299 }
300 }
301 }
302 }
303 Point screenDPI = styledText.getDisplay().getDPI();
304 Point printerDPI = printer.getDPI();
305 resources = new HashMap<> ();
306 for (int i = 0; i < lineCount; i++) {
307 Color color = printerRenderer.getLineBackground(i, null);
308 if (color != null) {
309 if (printOptions.printLineBackground) {
310 Color printerColor = (Color)resources.get(color);
311 if (printerColor == null) {
312 printerColor = new Color (color.getRGB());
313 resources.put(color, printerColor);
314 }
315 printerRenderer.setLineBackground(i, 1, printerColor);
316 } else {
317 printerRenderer.setLineBackground(i, 1, null);
318 }
319 }
320 int indent = printerRenderer.getLineIndent(i, 0);
321 if (indent != 0) {
322 printerRenderer.setLineIndent(i, 1, indent * printerDPI.x / screenDPI.x);
323 }
324 }
325 StyleRange[] styles = printerRenderer.styles;
326 for (int i = 0; i < printerRenderer.styleCount; i++) {
327 StyleRange style = styles[i];
328 Font font = style.font;
329 if (style.font != null) {
330 Font printerFont = (Font)resources.get(font);
331 if (printerFont == null) {
332 printerFont = new Font (printer, font.getFontData());
333 resources.put(font, printerFont);
334 }
335 style.font = printerFont;
336 }
337 Color color = style.foreground;
338 if (color != null) {
339 Color printerColor = (Color)resources.get(color);
340 if (printOptions.printTextForeground) {
341 if (printerColor == null) {
342 printerColor = new Color (color.getRGB());
343 resources.put(color, printerColor);
344 }
345 style.foreground = printerColor;
346 } else {
347 style.foreground = null;
348 }
349 }
350 color = style.background;
351 if (color != null) {
352 Color printerColor = (Color)resources.get(color);
353 if (printOptions.printTextBackground) {
354 if (printerColor == null) {
355 printerColor = new Color (color.getRGB());
356 resources.put(color, printerColor);
357 }
358 style.background = printerColor;
359 } else {
360 style.background = null;
361 }
362 }
363 if (!printOptions.printTextFontStyle) {
364 style.fontStyle = SWT.NORMAL;
365 }
366 style.rise = style.rise * printerDPI.y / screenDPI.y;
367 GlyphMetrics metrics = style.metrics;
368 if (metrics != null) {
369 metrics.ascent = metrics.ascent * printerDPI.y / screenDPI.y;
370 metrics.descent = metrics.descent * printerDPI.y / screenDPI.y;
371 metrics.width = metrics.width * printerDPI.x / screenDPI.x;
372 }
373 }
374 lineSpacing = styledText.lineSpacing * printerDPI.y / screenDPI.y;
375 if (printOptions.printLineNumbers) {
376 printMargin = 3 * printerDPI.x / screenDPI.x;
377 }
378 }
379 /**
380 * Copies the text of the specified <code>StyledTextContent</code>.
381 *
382 * @param original the <code>StyledTextContent</code> to copy.
383 */
copyContent(StyledTextContent original)384 StyledTextContent copyContent(StyledTextContent original) {
385 StyledTextContent printerContent = new DefaultContent();
386 int insertOffset = 0;
387 for (int i = 0; i < original.getLineCount(); i++) {
388 int insertEndOffset;
389 if (i < original.getLineCount() - 1) {
390 insertEndOffset = original.getOffsetAtLine(i + 1);
391 } else {
392 insertEndOffset = original.getCharCount();
393 }
394 printerContent.replaceTextRange(insertOffset, 0, original.getTextRange(insertOffset, insertEndOffset - insertOffset));
395 insertOffset = insertEndOffset;
396 }
397 return printerContent;
398 }
399 /**
400 * Disposes of the resources and the <code>PrintRenderer</code>.
401 */
dispose()402 void dispose() {
403 if (gc != null) {
404 gc.dispose();
405 gc = null;
406 }
407 if (resources != null) {
408 for (Resource resource : resources.values()) {
409 resource.dispose();
410 }
411 resources = null;
412 }
413 if (printerFont != null) {
414 printerFont.dispose();
415 printerFont = null;
416 }
417 if (printerRenderer != null) {
418 printerRenderer.dispose();
419 printerRenderer = null;
420 }
421 }
init()422 void init() {
423 Rectangle trim = printer.computeTrim(0, 0, 0, 0);
424 Point dpi = printer.getDPI();
425
426 printerFont = new Font(printer, fontData.getName(), fontData.getHeight(), SWT.NORMAL);
427 clientArea = printer.getClientArea();
428 pageWidth = clientArea.width;
429 // one inch margin around text
430 clientArea.x = dpi.x + trim.x;
431 clientArea.y = dpi.y + trim.y;
432 clientArea.width -= (clientArea.x + trim.width);
433 clientArea.height -= (clientArea.y + trim.height);
434
435 int style = mirrored ? SWT.RIGHT_TO_LEFT : SWT.LEFT_TO_RIGHT;
436 gc = new GC(printer, style);
437 gc.setFont(printerFont);
438 printerRenderer.setFont(printerFont, tabLength);
439 int lineHeight = printerRenderer.getLineHeight();
440 if (printOptions.header != null) {
441 clientArea.y += lineHeight * 2;
442 clientArea.height -= lineHeight * 2;
443 }
444 if (printOptions.footer != null) {
445 clientArea.height -= lineHeight * 2;
446 }
447
448 // TODO not wrapped
449 StyledTextContent content = printerRenderer.content;
450 startLine = 0;
451 endLine = singleLine ? 0 : content.getLineCount() - 1;
452 if (scope == PrinterData.PAGE_RANGE) {
453 int pageSize = clientArea.height / lineHeight;//WRONG
454 startLine = (startPage - 1) * pageSize;
455 } else if (scope == PrinterData.SELECTION) {
456 startLine = content.getLineAtOffset(selection.x);
457 if (selection.y > 0) {
458 endLine = content.getLineAtOffset(selection.x + selection.y - 1);
459 } else {
460 endLine = startLine - 1;
461 }
462 }
463 }
464 /**
465 * Prints the lines in the specified page range.
466 */
print()467 void print() {
468 Color background = gc.getBackground();
469 Color foreground = gc.getForeground();
470 int paintY = clientArea.y;
471 int paintX = clientArea.x;
472 int width = clientArea.width;
473 int page = startPage;
474 int pageBottom = clientArea.y + clientArea.height;
475 int orientation = gc.getStyle() & (SWT.RIGHT_TO_LEFT | SWT.LEFT_TO_RIGHT);
476 TextLayout printLayout = null;
477 if (printOptions.printLineNumbers || printOptions.header != null || printOptions.footer != null) {
478 printLayout = new TextLayout(printer);
479 printLayout.setFont(printerFont);
480 }
481 if (printOptions.printLineNumbers) {
482 int numberingWidth = 0;
483 int count = endLine - startLine + 1;
484 String[] lineLabels = printOptions.lineLabels;
485 if (lineLabels != null) {
486 for (int i = startLine; i < Math.min(count, lineLabels.length); i++) {
487 if (lineLabels[i] != null) {
488 printLayout.setText(lineLabels[i]);
489 int lineWidth = printLayout.getBounds().width;
490 numberingWidth = Math.max(numberingWidth, lineWidth);
491 }
492 }
493 } else {
494 StringBuilder buffer = new StringBuilder("0");
495 while ((count /= 10) > 0) buffer.append("0");
496 printLayout.setText(buffer.toString());
497 numberingWidth = printLayout.getBounds().width;
498 }
499 numberingWidth += printMargin;
500 if (numberingWidth > width) numberingWidth = width;
501 paintX += numberingWidth;
502 width -= numberingWidth;
503 }
504 for (int i = startLine; i <= endLine && page <= endPage; i++) {
505 if (paintY == clientArea.y) {
506 printer.startPage();
507 printDecoration(page, true, printLayout);
508 }
509 TextLayout layout = printerRenderer.getTextLayout(i, orientation, width, lineSpacing);
510 Color lineBackground = printerRenderer.getLineBackground(i, background);
511 int paragraphBottom = paintY + layout.getBounds().height;
512 if (paragraphBottom <= pageBottom) {
513 //normal case, the whole paragraph fits in the current page
514 printLine(paintX, paintY, gc, foreground, lineBackground, layout, printLayout, i);
515 paintY = paragraphBottom;
516 } else {
517 int lineCount = layout.getLineCount();
518 while (paragraphBottom > pageBottom && lineCount > 0) {
519 lineCount--;
520 paragraphBottom -= layout.getLineBounds(lineCount).height + layout.getSpacing();
521 }
522 if (lineCount == 0) {
523 //the whole paragraph goes to the next page
524 printDecoration(page, false, printLayout);
525 printer.endPage();
526 page++;
527 if (page <= endPage) {
528 printer.startPage();
529 printDecoration(page, true, printLayout);
530 paintY = clientArea.y;
531 printLine(paintX, paintY, gc, foreground, lineBackground, layout, printLayout, i);
532 paintY += layout.getBounds().height;
533 }
534 } else {
535 //draw paragraph top in the current page and paragraph bottom in the next
536 int height = paragraphBottom - paintY;
537 gc.setClipping(clientArea.x, paintY, clientArea.width, height);
538 printLine(paintX, paintY, gc, foreground, lineBackground, layout, printLayout, i);
539 gc.setClipping((Rectangle)null);
540 printDecoration(page, false, printLayout);
541 printer.endPage();
542 page++;
543 if (page <= endPage) {
544 printer.startPage();
545 printDecoration(page, true, printLayout);
546 paintY = clientArea.y - height;
547 int layoutHeight = layout.getBounds().height;
548 gc.setClipping(clientArea.x, clientArea.y, clientArea.width, layoutHeight - height);
549 printLine(paintX, paintY, gc, foreground, lineBackground, layout, printLayout, i);
550 gc.setClipping((Rectangle)null);
551 paintY += layoutHeight;
552 }
553 }
554 }
555 printerRenderer.disposeTextLayout(layout);
556 }
557 if (page <= endPage && paintY > clientArea.y) {
558 // close partial page
559 printDecoration(page, false, printLayout);
560 printer.endPage();
561 }
562 if (printLayout != null) printLayout.dispose();
563 }
564 /**
565 * Print header or footer decorations.
566 *
567 * @param page page number to print, if specified in the StyledTextPrintOptions header or footer.
568 * @param header true = print the header, false = print the footer
569 */
printDecoration(int page, boolean header, TextLayout layout)570 void printDecoration(int page, boolean header, TextLayout layout) {
571 String text = header ? printOptions.header : printOptions.footer;
572 if (text == null) return;
573 int lastSegmentIndex = 0;
574 for (int i = 0; i < 3; i++) {
575 int segmentIndex = text.indexOf(StyledTextPrintOptions.SEPARATOR, lastSegmentIndex);
576 String segment;
577 if (segmentIndex == -1) {
578 segment = text.substring(lastSegmentIndex);
579 printDecorationSegment(segment, i, page, header, layout);
580 break;
581 } else {
582 segment = text.substring(lastSegmentIndex, segmentIndex);
583 printDecorationSegment(segment, i, page, header, layout);
584 lastSegmentIndex = segmentIndex + StyledTextPrintOptions.SEPARATOR.length();
585 }
586 }
587 }
588 /**
589 * Print one segment of a header or footer decoration.
590 * Headers and footers have three different segments.
591 * One each for left aligned, centered, and right aligned text.
592 *
593 * @param segment decoration segment to print
594 * @param alignment alignment of the segment. 0=left, 1=center, 2=right
595 * @param page page number to print, if specified in the decoration segment.
596 * @param header true = print the header, false = print the footer
597 */
printDecorationSegment(String segment, int alignment, int page, boolean header, TextLayout layout)598 void printDecorationSegment(String segment, int alignment, int page, boolean header, TextLayout layout) {
599 int pageIndex = segment.indexOf(StyledTextPrintOptions.PAGE_TAG);
600 if (pageIndex != -1) {
601 int pageTagLength = StyledTextPrintOptions.PAGE_TAG.length();
602 StringBuilder buffer = new StringBuilder(segment.substring (0, pageIndex));
603 buffer.append (page);
604 buffer.append (segment.substring(pageIndex + pageTagLength));
605 segment = buffer.toString();
606 }
607 if (segment.length() > 0) {
608 layout.setText(segment);
609 int segmentWidth = layout.getBounds().width;
610 int segmentHeight = printerRenderer.getLineHeight();
611 int drawX = 0, drawY;
612 if (alignment == LEFT) {
613 drawX = clientArea.x;
614 } else if (alignment == CENTER) {
615 drawX = (pageWidth - segmentWidth) / 2;
616 } else if (alignment == RIGHT) {
617 drawX = clientArea.x + clientArea.width - segmentWidth;
618 }
619 if (header) {
620 drawY = clientArea.y - segmentHeight * 2;
621 } else {
622 drawY = clientArea.y + clientArea.height + segmentHeight;
623 }
624 layout.draw(gc, drawX, drawY);
625 }
626 }
printLine(int x, int y, GC gc, Color foreground, Color background, TextLayout layout, TextLayout printLayout, int index)627 void printLine(int x, int y, GC gc, Color foreground, Color background, TextLayout layout, TextLayout printLayout, int index) {
628 if (background != null) {
629 Rectangle rect = layout.getBounds();
630 gc.setBackground(background);
631 gc.fillRectangle(x, y, rect.width, rect.height);
632
633 // int lineCount = layout.getLineCount();
634 // for (int i = 0; i < lineCount; i++) {
635 // Rectangle rect = layout.getLineBounds(i);
636 // rect.x += paintX;
637 // rect.y += paintY + layout.getSpacing();
638 // rect.width = width;//layout bounds
639 // gc.fillRectangle(rect);
640 // }
641 }
642 if (printOptions.printLineNumbers) {
643 FontMetrics metrics = layout.getLineMetrics(0);
644 printLayout.setAscent(metrics.getAscent() + metrics.getLeading());
645 printLayout.setDescent(metrics.getDescent());
646 String[] lineLabels = printOptions.lineLabels;
647 if (lineLabels != null) {
648 if (0 <= index && index < lineLabels.length && lineLabels[index] != null) {
649 printLayout.setText(lineLabels[index]);
650 } else {
651 printLayout.setText("");
652 }
653 } else {
654 printLayout.setText(String.valueOf(index));
655 }
656 int paintX = x - printMargin - printLayout.getBounds().width;
657 printLayout.draw(gc, paintX, y);
658 printLayout.setAscent(-1);
659 printLayout.setDescent(-1);
660 }
661 gc.setForeground(foreground);
662 layout.draw(gc, x, y);
663 }
664 /**
665 * Starts a print job and prints the pages specified in the constructor.
666 */
667 @Override
run()668 public void run() {
669 String jobName = printOptions.jobName;
670 if (jobName == null) {
671 jobName = "Printing";
672 }
673 if (printer.startJob(jobName)) {
674 init();
675 print();
676 dispose();
677 printer.endJob();
678 }
679 }
680 }
681 /**
682 * The <code>RTFWriter</code> class is used to write widget content as
683 * rich text. The implementation complies with the RTF specification
684 * version 1.5.
685 * <p>
686 * toString() is guaranteed to return a valid RTF string only after
687 * close() has been called.
688 * </p><p>
689 * Whole and partial lines and line breaks can be written. Lines will be
690 * formatted using the styles queried from the LineStyleListener, if
691 * set, or those set directly in the widget. All styles are applied to
692 * the RTF stream like they are rendered by the widget. In addition, the
693 * widget font name and size is used for the whole text.
694 * </p>
695 */
696 class RTFWriter extends TextWriter {
697 static final int DEFAULT_FOREGROUND = 0;
698 static final int DEFAULT_BACKGROUND = 1;
699 List<Color> colorTable;
700 List<Font> fontTable;
701
702 /**
703 * Creates a RTF writer that writes content starting at offset "start"
704 * in the document. <code>start</code> and <code>length</code>can be set to specify partial
705 * lines.
706 *
707 * @param start start offset of content to write, 0 based from
708 * beginning of document
709 * @param length length of content to write
710 */
RTFWriter(int start, int length)711 public RTFWriter(int start, int length) {
712 super(start, length);
713 colorTable = new ArrayList<>();
714 fontTable = new ArrayList<>();
715 colorTable.add(getForeground());
716 colorTable.add(getBackground());
717 fontTable.add(getFont());
718 }
719 /**
720 * Closes the RTF writer. Once closed no more content can be written.
721 * <b>NOTE:</b> <code>toString()</code> does not return a valid RTF string until
722 * <code>close()</code> has been called.
723 */
724 @Override
close()725 public void close() {
726 if (!isClosed()) {
727 writeHeader();
728 write("\n}}\0");
729 super.close();
730 }
731 }
732 /**
733 * Returns the index of the specified color in the RTF color table.
734 *
735 * @param color the color
736 * @param defaultIndex return value if color is null
737 * @return the index of the specified color in the RTF color table
738 * or "defaultIndex" if "color" is null.
739 */
getColorIndex(Color color, int defaultIndex)740 int getColorIndex(Color color, int defaultIndex) {
741 if (color == null) return defaultIndex;
742 int index = colorTable.indexOf(color);
743 if (index == -1) {
744 index = colorTable.size();
745 colorTable.add(color);
746 }
747 return index;
748 }
749 /**
750 * Returns the index of the specified color in the RTF color table.
751 *
752 * @param color the color
753 * @param defaultIndex return value if color is null
754 * @return the index of the specified color in the RTF color table
755 * or "defaultIndex" if "color" is null.
756 */
getFontIndex(Font font)757 int getFontIndex(Font font) {
758 int index = fontTable.indexOf(font);
759 if (index == -1) {
760 index = fontTable.size();
761 fontTable.add(font);
762 }
763 return index;
764 }
765 /**
766 * Appends the specified segment of "string" to the RTF data.
767 * Copy from <code>start</code> up to, but excluding, <code>end</code>.
768 *
769 * @param string string to copy a segment from. Must not contain
770 * line breaks. Line breaks should be written using writeLineDelimiter()
771 * @param start start offset of segment. 0 based.
772 * @param end end offset of segment
773 */
write(String string, int start, int end)774 void write(String string, int start, int end) {
775 for (int index = start; index < end; index++) {
776 char ch = string.charAt(index);
777 if (ch > 0x7F) {
778 // write the sub string from the last escaped character
779 // to the current one. Fixes bug 21698.
780 if (index > start) {
781 write(string.substring(start, index));
782 }
783 write("\\u");
784 write(Integer.toString((short) ch));
785 write('?'); // ANSI representation (1 byte long, \\uc1)
786 start = index + 1;
787 } else if (ch == '}' || ch == '{' || ch == '\\') {
788 // write the sub string from the last escaped character
789 // to the current one. Fixes bug 21698.
790 if (index > start) {
791 write(string.substring(start, index));
792 }
793 write('\\');
794 write(ch);
795 start = index + 1;
796 }
797 }
798 // write from the last escaped character to the end.
799 // Fixes bug 21698.
800 if (start < end) {
801 write(string.substring(start, end));
802 }
803 }
804 /**
805 * Writes the RTF header including font table and color table.
806 */
writeHeader()807 void writeHeader() {
808 StringBuilder header = new StringBuilder();
809 FontData fontData = getFont().getFontData()[0];
810 header.append("{\\rtf1\\ansi");
811 // specify code page, necessary for copy to work in bidi
812 // systems that don't support Unicode RTF.
813 String cpg = System.getProperty("file.encoding").toLowerCase();
814 if (cpg.startsWith("cp") || cpg.startsWith("ms")) {
815 cpg = cpg.substring(2, cpg.length());
816 header.append("\\ansicpg");
817 header.append(cpg);
818 }
819 header.append("\\uc1\\deff0{\\fonttbl{\\f0\\fnil ");
820 header.append(fontData.getName());
821 header.append(";");
822 for (int i = 1; i < fontTable.size(); i++) {
823 header.append("\\f");
824 header.append(i);
825 header.append(" ");
826 FontData fd = fontTable.get(i).getFontData()[0];
827 header.append(fd.getName());
828 header.append(";");
829 }
830 header.append("}}\n{\\colortbl");
831 for (Color color : colorTable) {
832 header.append("\\red");
833 header.append(color.getRed());
834 header.append("\\green");
835 header.append(color.getGreen());
836 header.append("\\blue");
837 header.append(color.getBlue());
838 header.append(";");
839 }
840 // some RTF readers ignore the deff0 font tag. Explicitly
841 // set the font for the whole document to work around this.
842 header.append("}\n{\\f0\\fs");
843 // font size is specified in half points
844 header.append(fontData.getHeight() * 2);
845 header.append(" ");
846 write(header.toString(), 0);
847 }
848 /**
849 * Appends the specified line text to the RTF data. Lines will be formatted
850 * using the styles queried from the LineStyleListener, if set, or those set
851 * directly in the widget.
852 *
853 * @param line line text to write as RTF. Must not contain line breaks
854 * Line breaks should be written using writeLineDelimiter()
855 * @param lineOffset offset of the line. 0 based from the start of the
856 * widget document. Any text occurring before the start offset or after the
857 * end offset specified during object creation is ignored.
858 * @exception SWTException <ul>
859 * <li>ERROR_IO when the writer is closed.</li>
860 * </ul>
861 */
862 @Override
writeLine(String line, int lineOffset)863 public void writeLine(String line, int lineOffset) {
864 if (isClosed()) {
865 SWT.error(SWT.ERROR_IO);
866 }
867 int lineIndex = content.getLineAtOffset(lineOffset);
868 int lineAlignment, lineIndent;
869 boolean lineJustify;
870 int[] ranges;
871 StyleRange[] styles;
872 StyledTextEvent event = getLineStyleData(lineOffset, line);
873 if (event != null) {
874 lineAlignment = event.alignment;
875 lineIndent = event.indent;
876 lineJustify = event.justify;
877 ranges = event.ranges;
878 styles = event.styles;
879 } else {
880 lineAlignment = renderer.getLineAlignment(lineIndex, alignment);
881 lineIndent = renderer.getLineIndent(lineIndex, indent);
882 lineJustify = renderer.getLineJustify(lineIndex, justify);
883 ranges = renderer.getRanges(lineOffset, line.length());
884 styles = renderer.getStyleRanges(lineOffset, line.length(), false);
885 }
886 if (styles == null) styles = new StyleRange[0];
887 Color lineBackground = renderer.getLineBackground(lineIndex, null);
888 event = getLineBackgroundData(lineOffset, line);
889 if (event != null && event.lineBackground != null) lineBackground = event.lineBackground;
890 writeStyledLine(line, lineOffset, ranges, styles, lineBackground, lineIndent, lineAlignment, lineJustify);
891 }
892 /**
893 * Appends the specified line delimiter to the RTF data.
894 *
895 * @param lineDelimiter line delimiter to write as RTF.
896 * @exception SWTException <ul>
897 * <li>ERROR_IO when the writer is closed.</li>
898 * </ul>
899 */
900 @Override
writeLineDelimiter(String lineDelimiter)901 public void writeLineDelimiter(String lineDelimiter) {
902 if (isClosed()) {
903 SWT.error(SWT.ERROR_IO);
904 }
905 write(lineDelimiter, 0, lineDelimiter.length());
906 write("\\par ");
907 }
908 /**
909 * Appends the specified line text to the RTF data.
910 * <p>
911 * Use the colors and font styles specified in "styles" and "lineBackground".
912 * Formatting is written to reflect the text rendering by the text widget.
913 * Style background colors take precedence over the line background color.
914 * Background colors are written using the \chshdng0\chcbpat tag (vs. the \cb tag).
915 * </p>
916 *
917 * @param line line text to write as RTF. Must not contain line breaks
918 * Line breaks should be written using writeLineDelimiter()
919 * @param lineOffset offset of the line. 0 based from the start of the
920 * widget document. Any text occurring before the start offset or after the
921 * end offset specified during object creation is ignored.
922 * @param styles styles to use for formatting. Must not be null.
923 * @param lineBackground line background color to use for formatting.
924 * May be null.
925 */
writeStyledLine(String line, int lineOffset, int ranges[], StyleRange[] styles, Color lineBackground, int indent, int alignment, boolean justify)926 void writeStyledLine(String line, int lineOffset, int ranges[], StyleRange[] styles, Color lineBackground, int indent, int alignment, boolean justify) {
927 int lineLength = line.length();
928 int startOffset = getStart();
929 int writeOffset = startOffset - lineOffset;
930 if (writeOffset >= lineLength) return;
931 int lineIndex = Math.max(0, writeOffset);
932
933 write("\\fi");
934 write(indent);
935 switch (alignment) {
936 case SWT.LEFT: write("\\ql"); break;
937 case SWT.CENTER: write("\\qc"); break;
938 case SWT.RIGHT: write("\\qr"); break;
939 }
940 if (justify) write("\\qj");
941 write(" ");
942
943 if (lineBackground != null) {
944 write("{\\chshdng0\\chcbpat");
945 write(getColorIndex(lineBackground, DEFAULT_BACKGROUND));
946 write(" ");
947 }
948 int endOffset = startOffset + super.getCharCount();
949 int lineEndOffset = Math.min(lineLength, endOffset - lineOffset);
950 for (int i = 0; i < styles.length; i++) {
951 StyleRange style = styles[i];
952 int start, end;
953 if (ranges != null) {
954 start = ranges[i << 1] - lineOffset;
955 end = start + ranges[(i << 1) + 1];
956 } else {
957 start = style.start - lineOffset;
958 end = start + style.length;
959 }
960 // skip over partial first line
961 if (end < writeOffset) {
962 continue;
963 }
964 // style starts beyond line end or RTF write end
965 if (start >= lineEndOffset) {
966 break;
967 }
968 // write any unstyled text
969 if (lineIndex < start) {
970 // copy to start of style
971 // style starting beyond end of write range or end of line
972 // is guarded against above.
973 write(line, lineIndex, start);
974 lineIndex = start;
975 }
976 // write styled text
977 write("{\\cf");
978 write(getColorIndex(style.foreground, DEFAULT_FOREGROUND));
979 int colorIndex = getColorIndex(style.background, DEFAULT_BACKGROUND);
980 if (colorIndex != DEFAULT_BACKGROUND) {
981 write("\\chshdng0\\chcbpat");
982 write(colorIndex);
983 }
984 int fontStyle = style.fontStyle;
985 Font font = style.font;
986 if (font != null) {
987 int fontIndex = getFontIndex(font);
988 write("\\f");
989 write(fontIndex);
990 FontData fontData = font.getFontData()[0];
991 write("\\fs");
992 write(fontData.getHeight() * 2);
993 fontStyle = fontData.getStyle();
994 }
995 if ((fontStyle & SWT.BOLD) != 0) {
996 write("\\b");
997 }
998 if ((fontStyle & SWT.ITALIC) != 0) {
999 write("\\i");
1000 }
1001 if (style.underline) {
1002 write("\\ul");
1003 }
1004 if (style.strikeout) {
1005 write("\\strike");
1006 }
1007 write(" ");
1008 // copy to end of style or end of write range or end of line
1009 int copyEnd = Math.min(end, lineEndOffset);
1010 // guard against invalid styles and let style processing continue
1011 copyEnd = Math.max(copyEnd, lineIndex);
1012 write(line, lineIndex, copyEnd);
1013 if ((fontStyle & SWT.BOLD) != 0) {
1014 write("\\b0");
1015 }
1016 if ((style.fontStyle & SWT.ITALIC) != 0) {
1017 write("\\i0");
1018 }
1019 if (style.underline) {
1020 write("\\ul0");
1021 }
1022 if (style.strikeout) {
1023 write("\\strike0");
1024 }
1025 write("}");
1026 lineIndex = copyEnd;
1027 }
1028 // write unstyled text at the end of the line
1029 if (lineIndex < lineEndOffset) {
1030 write(line, lineIndex, lineEndOffset);
1031 }
1032 if (lineBackground != null) write("}");
1033 }
1034 }
1035 /**
1036 * The <code>TextWriter</code> class is used to write widget content to
1037 * a string. Whole and partial lines and line breaks can be written. To write
1038 * partial lines, specify the start and length of the desired segment
1039 * during object creation.
1040 * <p>
1041 * <b>NOTE:</b> <code>toString()</code> is guaranteed to return a valid string only after close()
1042 * has been called.
1043 * </p>
1044 */
1045 class TextWriter {
1046 private StringBuilder buffer;
1047 private int startOffset; // offset of first character that will be written
1048 private int endOffset; // offset of last character that will be written.
1049 // 0 based from the beginning of the widget text.
1050 private boolean isClosed = false;
1051
1052 /**
1053 * Creates a writer that writes content starting at offset "start"
1054 * in the document. <code>start</code> and <code>length</code> can be set to specify partial lines.
1055 *
1056 * @param start start offset of content to write, 0 based from beginning of document
1057 * @param length length of content to write
1058 */
TextWriter(int start, int length)1059 public TextWriter(int start, int length) {
1060 buffer = new StringBuilder(length);
1061 startOffset = start;
1062 endOffset = start + length;
1063 }
1064 /**
1065 * Closes the writer. Once closed no more content can be written.
1066 * <b>NOTE:</b> <code>toString()</code> is not guaranteed to return a valid string unless
1067 * the writer is closed.
1068 */
close()1069 public void close() {
1070 if (!isClosed) {
1071 isClosed = true;
1072 }
1073 }
1074 /**
1075 * Returns the number of characters to write.
1076 * @return the integer number of characters to write
1077 */
getCharCount()1078 public int getCharCount() {
1079 return endOffset - startOffset;
1080 }
1081 /**
1082 * Returns the offset where writing starts. 0 based from the start of
1083 * the widget text. Used to write partial lines.
1084 * @return the integer offset where writing starts
1085 */
getStart()1086 public int getStart() {
1087 return startOffset;
1088 }
1089 /**
1090 * Returns whether the writer is closed.
1091 * @return a boolean specifying whether or not the writer is closed
1092 */
isClosed()1093 public boolean isClosed() {
1094 return isClosed;
1095 }
1096 /**
1097 * Returns the string. <code>close()</code> must be called before <code>toString()</code>
1098 * is guaranteed to return a valid string.
1099 *
1100 * @return the string
1101 */
1102 @Override
toString()1103 public String toString() {
1104 return buffer.toString();
1105 }
1106 /**
1107 * Appends the given string to the data.
1108 */
write(String string)1109 void write(String string) {
1110 buffer.append(string);
1111 }
1112 /**
1113 * Inserts the given string to the data at the specified offset.
1114 * <p>
1115 * Do nothing if "offset" is < 0 or > getCharCount()
1116 * </p>
1117 *
1118 * @param string text to insert
1119 * @param offset offset in the existing data to insert "string" at.
1120 */
write(String string, int offset)1121 void write(String string, int offset) {
1122 if (offset < 0 || offset > buffer.length()) {
1123 return;
1124 }
1125 buffer.insert(offset, string);
1126 }
1127 /**
1128 * Appends the given int to the data.
1129 */
write(int i)1130 void write(int i) {
1131 buffer.append(i);
1132 }
1133 /**
1134 * Appends the given character to the data.
1135 */
write(char i)1136 void write(char i) {
1137 buffer.append(i);
1138 }
1139 /**
1140 * Appends the specified line text to the data.
1141 *
1142 * @param line line text to write. Must not contain line breaks
1143 * Line breaks should be written using writeLineDelimiter()
1144 * @param lineOffset offset of the line. 0 based from the start of the
1145 * widget document. Any text occurring before the start offset or after the
1146 * end offset specified during object creation is ignored.
1147 * @exception SWTException <ul>
1148 * <li>ERROR_IO when the writer is closed.</li>
1149 * </ul>
1150 */
writeLine(String line, int lineOffset)1151 public void writeLine(String line, int lineOffset) {
1152 if (isClosed) {
1153 SWT.error(SWT.ERROR_IO);
1154 }
1155 int writeOffset = startOffset - lineOffset;
1156 int lineLength = line.length();
1157 int lineIndex;
1158 if (writeOffset >= lineLength) {
1159 return; // whole line is outside write range
1160 } else if (writeOffset > 0) {
1161 lineIndex = writeOffset; // line starts before write start
1162 } else {
1163 lineIndex = 0;
1164 }
1165 int copyEnd = Math.min(lineLength, endOffset - lineOffset);
1166 if (lineIndex < copyEnd) {
1167 write(line.substring(lineIndex, copyEnd));
1168 }
1169 }
1170 /**
1171 * Appends the specified line delimiter to the data.
1172 *
1173 * @param lineDelimiter line delimiter to write
1174 * @exception SWTException <ul>
1175 * <li>ERROR_IO when the writer is closed.</li>
1176 * </ul>
1177 */
writeLineDelimiter(String lineDelimiter)1178 public void writeLineDelimiter(String lineDelimiter) {
1179 if (isClosed) {
1180 SWT.error(SWT.ERROR_IO);
1181 }
1182 write(lineDelimiter);
1183 }
1184 }
1185
1186 /**
1187 * Constructs a new instance of this class given its parent
1188 * and a style value describing its behavior and appearance.
1189 * <p>
1190 * The style value is either one of the style constants defined in
1191 * class <code>SWT</code> which is applicable to instances of this
1192 * class, or must be built by <em>bitwise OR</em>'ing together
1193 * (that is, using the <code>int</code> "|" operator) two or more
1194 * of those <code>SWT</code> style constants. The class description
1195 * lists the style constants that are applicable to the class.
1196 * Style bits are also inherited from superclasses.
1197 * </p>
1198 *
1199 * @param parent a widget which will be the parent of the new instance (cannot be null)
1200 * @param style the style of widget to construct
1201 *
1202 * @exception IllegalArgumentException <ul>
1203 * <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
1204 * </ul>
1205 * @exception SWTException <ul>
1206 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
1207 * </ul>
1208 *
1209 * @see SWT#FULL_SELECTION
1210 * @see SWT#MULTI
1211 * @see SWT#READ_ONLY
1212 * @see SWT#SINGLE
1213 * @see SWT#WRAP
1214 * @see #getStyle
1215 */
StyledText(Composite parent, int style)1216 public StyledText(Composite parent, int style) {
1217 super(parent, checkStyle(style));
1218 // set the fg in the OS to ensure that these are the same as StyledText, necessary
1219 // for ensuring that the bg/fg the IME box uses is the same as what StyledText uses
1220 super.setForeground(getForeground());
1221 super.setDragDetect(false);
1222 Display display = getDisplay();
1223 if ((style & SWT.READ_ONLY) != 0) {
1224 setEditable(false);
1225 }
1226 leftMargin = rightMargin = isBidiCaret() ? BIDI_CARET_WIDTH - 1: 0;
1227 if ((style & SWT.SINGLE) != 0 && (style & SWT.BORDER) != 0) {
1228 leftMargin = topMargin = rightMargin = bottomMargin = 2;
1229 }
1230 alignment = style & (SWT.LEFT | SWT.RIGHT | SWT.CENTER);
1231 if (alignment == 0) alignment = SWT.LEFT;
1232 clipboard = new Clipboard(display);
1233 installDefaultContent();
1234 renderer = new StyledTextRenderer(getDisplay(), this);
1235 renderer.setContent(content);
1236 renderer.setFont(getFont(), tabLength);
1237 ime = new IME(this, SWT.NONE);
1238 defaultCaret = new Caret(this, SWT.NONE);
1239 if ((style & SWT.WRAP) != 0) {
1240 setWordWrap(true);
1241 }
1242 if (isBidiCaret()) {
1243 createCaretBitmaps();
1244 Runnable runnable = () -> {
1245 int direction = BidiUtil.getKeyboardLanguage() == BidiUtil.KEYBOARD_BIDI ? SWT.RIGHT : SWT.LEFT;
1246 if (direction == caretDirection) return;
1247 if (getCaret() != defaultCaret) return;
1248 Point newCaretPos = getPointAtOffset(caretOffset);
1249 setCaretLocation(newCaretPos, direction);
1250 };
1251 BidiUtil.addLanguageListener(this, runnable);
1252 }
1253 setCaret(defaultCaret);
1254 calculateScrollBars();
1255 createKeyBindings();
1256 super.setCursor(display.getSystemCursor(SWT.CURSOR_IBEAM));
1257 installListeners();
1258 initializeAccessible();
1259 setData("DEFAULT_DROP_TARGET_EFFECT", new StyledTextDropTargetEffect(this));
1260 if (IS_MAC) setData(STYLEDTEXT_KEY);
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 SWTException <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 */
addExtendedModifyListener(ExtendedModifyListener extendedModifyListener)1275 public void addExtendedModifyListener(ExtendedModifyListener extendedModifyListener) {
1276 checkWidget();
1277 if (extendedModifyListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1278 StyledTextListener typedListener = new StyledTextListener(extendedModifyListener);
1279 addListener(ST.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. You 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 SWTException <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 */
addBidiSegmentListener(BidiSegmentListener listener)1306 public void addBidiSegmentListener(BidiSegmentListener listener) {
1307 checkWidget();
1308 if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1309 addListener(ST.LineGetSegments, new StyledTextListener(listener));
1310 resetCache(0, content.getLineCount());
1311 setCaretLocation();
1312 super.redraw();
1313 }
1314 /**
1315 * Adds a caret listener. CaretEvent is sent when the caret offset changes.
1316 *
1317 * @param listener the listener
1318 * @exception SWTException <ul>
1319 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1320 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1321 * </ul>
1322 * @exception IllegalArgumentException <ul>
1323 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
1324 * </ul>
1325 *
1326 * @since 3.5
1327 */
addCaretListener(CaretListener listener)1328 public void addCaretListener(CaretListener listener) {
1329 checkWidget();
1330 if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1331 addListener(ST.CaretMoved, new StyledTextListener(listener));
1332 }
1333 /**
1334 * Adds a line background listener. A LineGetBackground event is sent by the
1335 * widget to determine the background color for a line.
1336 *
1337 * @param listener the listener
1338 * @exception SWTException <ul>
1339 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1340 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1341 * </ul>
1342 * @exception IllegalArgumentException <ul>
1343 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
1344 * </ul>
1345 */
addLineBackgroundListener(LineBackgroundListener listener)1346 public void addLineBackgroundListener(LineBackgroundListener listener) {
1347 checkWidget();
1348 if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1349 if (!isListening(ST.LineGetBackground)) {
1350 renderer.clearLineBackground(0, content.getLineCount());
1351 }
1352 addListener(ST.LineGetBackground, new StyledTextListener(listener));
1353 }
1354 /**
1355 * Adds a line style listener. A LineGetStyle event is sent by the widget to
1356 * determine the styles for a line.
1357 *
1358 * @param listener the listener
1359 * @exception SWTException <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 */
addLineStyleListener(LineStyleListener listener)1367 public void addLineStyleListener(LineStyleListener listener) {
1368 checkWidget();
1369 if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1370 if (!isListening(ST.LineGetStyle)) {
1371 setStyleRanges(0, 0, null, null, true);
1372 renderer.clearLineStyle(0, content.getLineCount());
1373 }
1374 addListener(ST.LineGetStyle, new StyledTextListener(listener));
1375 setCaretLocation();
1376 }
1377 /**
1378 * Adds a modify listener. A Modify event is sent by the widget when the widget text
1379 * has changed.
1380 *
1381 * @param modifyListener the listener
1382 * @exception SWTException <ul>
1383 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1384 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1385 * </ul>
1386 * @exception IllegalArgumentException <ul>
1387 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
1388 * </ul>
1389 */
addModifyListener(ModifyListener modifyListener)1390 public void addModifyListener(ModifyListener modifyListener) {
1391 checkWidget();
1392 if (modifyListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1393 addListener(SWT.Modify, new TypedListener(modifyListener));
1394 }
1395 /**
1396 * Adds a paint object listener. A paint object event is sent by the widget when an object
1397 * needs to be drawn.
1398 *
1399 * @param listener the listener
1400 * @exception SWTException <ul>
1401 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1402 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1403 * </ul>
1404 * @exception IllegalArgumentException <ul>
1405 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
1406 * </ul>
1407 *
1408 * @since 3.2
1409 *
1410 * @see PaintObjectListener
1411 * @see PaintObjectEvent
1412 */
addPaintObjectListener(PaintObjectListener listener)1413 public void addPaintObjectListener(PaintObjectListener listener) {
1414 checkWidget();
1415 if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1416 addListener(ST.PaintObject, new StyledTextListener(listener));
1417 }
1418 /**
1419 * Adds a selection listener. A Selection event is sent by the widget when the
1420 * user changes the selection.
1421 * <p>
1422 * When <code>widgetSelected</code> is called, the event x and y fields contain
1423 * the start and end caret indices of the selection. The selection values returned are visual
1424 * (i.e., x will always always be <= y).
1425 * No event is sent when the caret is moved while the selection length is 0.
1426 * </p><p>
1427 * <code>widgetDefaultSelected</code> is not called for StyledTexts.
1428 * </p>
1429 *
1430 * @param listener the listener which should be notified when the user changes the receiver's selection
1431
1432 * @exception IllegalArgumentException <ul>
1433 * <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
1434 * </ul>
1435 * @exception SWTException <ul>
1436 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1437 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1438 * </ul>
1439 *
1440 * @see SelectionListener
1441 * @see #removeSelectionListener
1442 * @see SelectionEvent
1443 */
addSelectionListener(SelectionListener listener)1444 public void addSelectionListener(SelectionListener listener) {
1445 checkWidget();
1446 if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1447 addListener(SWT.Selection, new TypedListener(listener));
1448 }
1449 /**
1450 * Adds a verify key listener. A VerifyKey event is sent by the widget when a key
1451 * is pressed. The widget ignores the key press if the listener sets the doit field
1452 * of the event to false.
1453 *
1454 * @param listener the listener
1455 * @exception SWTException <ul>
1456 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1457 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1458 * </ul>
1459 * @exception IllegalArgumentException <ul>
1460 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
1461 * </ul>
1462 */
addVerifyKeyListener(VerifyKeyListener listener)1463 public void addVerifyKeyListener(VerifyKeyListener listener) {
1464 checkWidget();
1465 if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1466 addListener(ST.VerifyKey, new StyledTextListener(listener));
1467 }
1468 /**
1469 * Adds a verify listener. A Verify event is sent by the widget when the widget text
1470 * is about to change. The listener can set the event text and the doit field to
1471 * change the text that is set in the widget or to force the widget to ignore the
1472 * text change.
1473 *
1474 * @param verifyListener the listener
1475 * @exception SWTException <ul>
1476 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1477 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1478 * </ul>
1479 * @exception IllegalArgumentException <ul>
1480 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
1481 * </ul>
1482 */
addVerifyListener(VerifyListener verifyListener)1483 public void addVerifyListener(VerifyListener verifyListener) {
1484 checkWidget();
1485 if (verifyListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1486 addListener(SWT.Verify, new TypedListener(verifyListener));
1487 }
1488 /**
1489 * Adds a word movement listener. A movement event is sent when the boundary
1490 * of a word is needed. For example, this occurs during word next and word
1491 * previous actions.
1492 *
1493 * @param movementListener the listener
1494 * @exception SWTException <ul>
1495 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1496 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1497 * </ul>
1498 * @exception IllegalArgumentException <ul>
1499 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
1500 * </ul>
1501 *
1502 * @see MovementEvent
1503 * @see MovementListener
1504 * @see #removeWordMovementListener
1505 *
1506 * @since 3.3
1507 */
addWordMovementListener(MovementListener movementListener)1508 public void addWordMovementListener(MovementListener movementListener) {
1509 checkWidget();
1510 if (movementListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
1511 addListener(ST.WordNext, new StyledTextListener(movementListener));
1512 addListener(ST.WordPrevious, new StyledTextListener(movementListener));
1513 }
1514 /**
1515 * Appends a string to the text at the end of the widget.
1516 *
1517 * @param string the string to be appended
1518 * @see #replaceTextRange(int,int,String)
1519 * @exception SWTException <ul>
1520 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1521 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1522 * </ul>
1523 * @exception IllegalArgumentException <ul>
1524 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
1525 * </ul>
1526 */
append(String string)1527 public void append(String string) {
1528 checkWidget();
1529 if (string == null) {
1530 SWT.error(SWT.ERROR_NULL_ARGUMENT);
1531 }
1532 int lastChar = Math.max(getCharCount(), 0);
1533 replaceTextRange(lastChar, 0, string);
1534 }
1535 /**
1536 * Calculates the scroll bars
1537 */
calculateScrollBars()1538 void calculateScrollBars() {
1539 ScrollBar horizontalBar = getHorizontalBar();
1540 ScrollBar verticalBar = getVerticalBar();
1541 setScrollBars(true);
1542 if (verticalBar != null) {
1543 verticalBar.setIncrement(getVerticalIncrement());
1544 }
1545 if (horizontalBar != null) {
1546 horizontalBar.setIncrement(getHorizontalIncrement());
1547 }
1548 }
1549 /**
1550 * Calculates the top index based on the current vertical scroll offset.
1551 * The top index is the index of the topmost fully visible line or the
1552 * topmost partially visible line if no line is fully visible.
1553 * The top index starts at 0.
1554 */
calculateTopIndex(int delta)1555 void calculateTopIndex(int delta) {
1556 int oldDelta = delta;
1557 int oldTopIndex = topIndex;
1558 int oldTopIndexY = topIndexY;
1559 if (isFixedLineHeight()) {
1560 int verticalIncrement = getVerticalIncrement();
1561 if (verticalIncrement == 0) {
1562 return;
1563 }
1564 topIndex = Compatibility.ceil(getVerticalScrollOffset(), verticalIncrement);
1565 // Set top index to partially visible top line if no line is fully
1566 // visible but at least some of the widget client area is visible.
1567 // Fixes bug 15088.
1568 if (topIndex > 0) {
1569 if (clientAreaHeight > 0) {
1570 int bottomPixel = getVerticalScrollOffset() + clientAreaHeight;
1571 topIndexY = getLinePixel(topIndex);
1572 int fullLineTopPixel = topIndex * verticalIncrement;
1573 int fullLineVisibleHeight = bottomPixel - fullLineTopPixel;
1574 // set top index to partially visible line if no line fully fits in
1575 // client area or if space is available but not used (the latter should
1576 // never happen because we use claimBottomFreeSpace)
1577 if (fullLineVisibleHeight < verticalIncrement) {
1578 topIndex = getVerticalScrollOffset() / verticalIncrement;
1579 }
1580 } else if (topIndex >= content.getLineCount()) {
1581 topIndex = content.getLineCount() - 1;
1582 }
1583 }
1584 } else {
1585 if (delta >= 0) {
1586 delta -= topIndexY;
1587 int lineIndex = topIndex;
1588 int lineCount = content.getLineCount();
1589 while (lineIndex < lineCount) {
1590 if (delta <= 0) break;
1591 delta -= renderer.getCachedLineHeight(lineIndex++);
1592 }
1593 if (lineIndex < lineCount && -delta + renderer.getCachedLineHeight(lineIndex) <= clientAreaHeight - topMargin - bottomMargin) {
1594 topIndex = lineIndex;
1595 topIndexY = -delta;
1596 } else {
1597 topIndex = lineIndex - 1;
1598 topIndexY = -renderer.getCachedLineHeight(topIndex) - delta;
1599 }
1600 } else {
1601 delta -= topIndexY;
1602 int lineIndex = topIndex;
1603 while (lineIndex > 0) {
1604 int lineHeight = renderer.getCachedLineHeight(lineIndex - 1);
1605 if (delta + lineHeight > 0) break;
1606 delta += lineHeight;
1607 lineIndex--;
1608 }
1609 if (lineIndex == 0 || -delta + renderer.getCachedLineHeight(lineIndex) <= clientAreaHeight - topMargin - bottomMargin) {
1610 topIndex = lineIndex;
1611 topIndexY = - delta;
1612 } else {
1613 topIndex = lineIndex - 1;
1614 topIndexY = - renderer.getCachedLineHeight(topIndex) - delta;
1615 }
1616 }
1617 }
1618 if (topIndex < 0) {
1619 // TODO: This logging is in place to determine why topIndex is getting set to negative values.
1620 // It should be deleted once we fix the root cause of this issue. See bug 487254 for details.
1621 System.err.println("StyledText: topIndex was " + topIndex
1622 + ", isFixedLineHeight() = " + isFixedLineHeight()
1623 + ", delta = " + delta
1624 + ", content.getLineCount() = " + content.getLineCount()
1625 + ", clientAreaHeight = " + clientAreaHeight
1626 + ", oldTopIndex = " + oldTopIndex
1627 + ", oldTopIndexY = " + oldTopIndexY
1628 + ", getVerticalScrollOffset = " + getVerticalScrollOffset()
1629 + ", oldDelta = " + oldDelta
1630 + ", getVerticalIncrement() = " + getVerticalIncrement());
1631 topIndex = 0;
1632 }
1633 if (topIndex != oldTopIndex || oldTopIndexY != topIndexY) {
1634 int width = renderer.getWidth();
1635 renderer.calculateClientArea();
1636 if (width != renderer.getWidth()) {
1637 setScrollBars(false);
1638 }
1639 }
1640 }
1641 /**
1642 * Hides the scroll bars if widget is created in single line mode.
1643 */
checkStyle(int style)1644 static int checkStyle(int style) {
1645 if ((style & SWT.SINGLE) != 0) {
1646 style &= ~(SWT.H_SCROLL | SWT.V_SCROLL | SWT.WRAP | SWT.MULTI);
1647 } else {
1648 style |= SWT.MULTI;
1649 if ((style & SWT.WRAP) != 0) {
1650 style &= ~SWT.H_SCROLL;
1651 }
1652 }
1653 style |= SWT.NO_REDRAW_RESIZE | SWT.DOUBLE_BUFFERED | SWT.NO_BACKGROUND;
1654 /* Clear SWT.CENTER to avoid the conflict with SWT.EMBEDDED */
1655 return style & ~SWT.CENTER;
1656 }
1657 /**
1658 * Scrolls down the text to use new space made available by a resize or by
1659 * deleted lines.
1660 */
claimBottomFreeSpace()1661 void claimBottomFreeSpace() {
1662 if (ime.getCompositionOffset() != -1) return;
1663 if (isFixedLineHeight()) {
1664 int newVerticalOffset = Math.max(0, renderer.getHeight() - clientAreaHeight);
1665 if (newVerticalOffset < getVerticalScrollOffset()) {
1666 scrollVertical(newVerticalOffset - getVerticalScrollOffset(), true);
1667 }
1668 } else {
1669 int bottomIndex = getPartialBottomIndex();
1670 int height = getLinePixel(bottomIndex + 1);
1671 if (clientAreaHeight > height) {
1672 scrollVertical(-getAvailableHeightAbove(clientAreaHeight - height), true);
1673 }
1674 }
1675 }
1676 /**
1677 * Scrolls text to the right to use new space made available by a resize.
1678 */
claimRightFreeSpace()1679 void claimRightFreeSpace() {
1680 int newHorizontalOffset = Math.max(0, renderer.getWidth() - clientAreaWidth);
1681 if (newHorizontalOffset < horizontalScrollOffset) {
1682 // item is no longer drawn past the right border of the client area
1683 // align the right end of the item with the right border of the
1684 // client area (window is scrolled right).
1685 scrollHorizontal(newHorizontalOffset - horizontalScrollOffset, true);
1686 }
1687 }
clearBlockSelection(boolean reset, boolean sendEvent)1688 void clearBlockSelection(boolean reset, boolean sendEvent) {
1689 if (reset) resetSelection();
1690 blockXAnchor = blockYAnchor = -1;
1691 blockXLocation = blockYLocation = -1;
1692 caretDirection = SWT.NULL;
1693 updateCaretVisibility();
1694 super.redraw();
1695 if (sendEvent) sendSelectionEvent();
1696 }
1697 /**
1698 * Removes the widget selection.
1699 *
1700 * @param sendEvent a Selection event is sent when set to true and when the selection is actually reset.
1701 */
clearSelection(boolean sendEvent)1702 void clearSelection(boolean sendEvent) {
1703 int selectionStart = selection.x;
1704 int selectionEnd = selection.y;
1705 resetSelection();
1706 // redraw old selection, if any
1707 if (selectionEnd - selectionStart > 0) {
1708 int length = content.getCharCount();
1709 // called internally to remove selection after text is removed
1710 // therefore make sure redraw range is valid.
1711 int redrawStart = Math.min(selectionStart, length);
1712 int redrawEnd = Math.min(selectionEnd, length);
1713 if (redrawEnd - redrawStart > 0) {
1714 internalRedrawRange(redrawStart, redrawEnd - redrawStart);
1715 }
1716 if (sendEvent) {
1717 sendSelectionEvent();
1718 }
1719 }
1720 }
1721 @Override
computeSize(int wHint, int hHint, boolean changed)1722 public Point computeSize (int wHint, int hHint, boolean changed) {
1723 checkWidget();
1724 int lineCount = (getStyle() & SWT.SINGLE) != 0 ? 1 : content.getLineCount();
1725 int width = 0;
1726 int height = 0;
1727 if (wHint == SWT.DEFAULT || hHint == SWT.DEFAULT) {
1728 Display display = getDisplay();
1729 int maxHeight = display.getClientArea().height;
1730 for (int lineIndex = 0; lineIndex < lineCount; lineIndex++) {
1731 TextLayout layout = renderer.getTextLayout(lineIndex);
1732 int wrapWidth = layout.getWidth();
1733 if (wordWrap) layout.setWidth(wHint == 0 ? 1 : wHint == SWT.DEFAULT ? SWT.DEFAULT : Math.max(1, wHint - leftMargin - rightMargin));
1734 Rectangle rect = layout.getBounds();
1735 height += rect.height;
1736 width = Math.max(width, rect.width);
1737 layout.setWidth(wrapWidth);
1738 renderer.disposeTextLayout(layout);
1739 if (isFixedLineHeight() && height > maxHeight) break;
1740 }
1741 if (isFixedLineHeight()) {
1742 height = lineCount * renderer.getLineHeight();
1743 }
1744 }
1745 // Use default values if no text is defined.
1746 if (width == 0) width = DEFAULT_WIDTH;
1747 if (height == 0) height = DEFAULT_HEIGHT;
1748 if (wHint != SWT.DEFAULT) width = wHint;
1749 if (hHint != SWT.DEFAULT) height = hHint;
1750 int wTrim = getLeftMargin() + rightMargin + getCaretWidth();
1751 int hTrim = topMargin + bottomMargin;
1752 Rectangle rect = computeTrim(0, 0, width + wTrim, height + hTrim);
1753 return new Point (rect.width, rect.height);
1754 }
1755 /**
1756 * Copies the selected text to the <code>DND.CLIPBOARD</code> clipboard.
1757 * <p>
1758 * The text will be put on the clipboard in plain text format and RTF format.
1759 * The <code>DND.CLIPBOARD</code> clipboard is used for data that is
1760 * transferred by keyboard accelerator (such as Ctrl+C/Ctrl+V) or
1761 * by menu action.
1762 * </p>
1763 *
1764 * @exception SWTException <ul>
1765 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1766 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1767 * </ul>
1768 */
copy()1769 public void copy() {
1770 checkWidget();
1771 copySelection(DND.CLIPBOARD);
1772 }
1773 /**
1774 * Copies the selected text to the specified clipboard. The text will be put in the
1775 * clipboard in plain text format and RTF format.
1776 * <p>
1777 * The clipboardType is one of the clipboard constants defined in class
1778 * <code>DND</code>. The <code>DND.CLIPBOARD</code> clipboard is
1779 * used for data that is transferred by keyboard accelerator (such as Ctrl+C/Ctrl+V)
1780 * or by menu action. The <code>DND.SELECTION_CLIPBOARD</code>
1781 * clipboard is used for data that is transferred by selecting text and pasting
1782 * with the middle mouse button.
1783 * </p>
1784 *
1785 * @param clipboardType indicates the type of clipboard
1786 *
1787 * @exception SWTException <ul>
1788 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1789 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1790 * </ul>
1791 *
1792 * @since 3.1
1793 */
copy(int clipboardType)1794 public void copy(int clipboardType) {
1795 checkWidget();
1796 copySelection(clipboardType);
1797 }
copySelection(int type)1798 boolean copySelection(int type) {
1799 if (type != DND.CLIPBOARD && type != DND.SELECTION_CLIPBOARD) return false;
1800 try {
1801 if (blockSelection && blockXLocation != -1) {
1802 String text = getBlockSelectionText(PlatformLineDelimiter);
1803 if (text.length() > 0) {
1804 //TODO RTF support
1805 TextTransfer plainTextTransfer = TextTransfer.getInstance();
1806 Object[] data = new Object[]{text};
1807 Transfer[] types = new Transfer[]{plainTextTransfer};
1808 clipboard.setContents(data, types, type);
1809 return true;
1810 }
1811 } else {
1812 int length = selection.y - selection.x;
1813 if (length > 0) {
1814 setClipboardContent(selection.x, length, type);
1815 return true;
1816 }
1817 }
1818 } catch (SWTError error) {
1819 // Copy to clipboard failed. This happens when another application
1820 // is accessing the clipboard while we copy. Ignore the error.
1821 // Rethrow all other errors. Fixes bug 17578.
1822 if (error.code != DND.ERROR_CANNOT_SET_CLIPBOARD) {
1823 throw error;
1824 }
1825 }
1826 return false;
1827 }
1828 /**
1829 * Returns the alignment of the widget.
1830 *
1831 * @return the alignment
1832 *
1833 * @exception SWTException <ul>
1834 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1835 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1836 * </ul>
1837 *
1838 * @see #getLineAlignment(int)
1839 *
1840 * @since 3.2
1841 */
getAlignment()1842 public int getAlignment() {
1843 checkWidget();
1844 return alignment;
1845 }
1846 /**
1847 * Returns the Always Show Scrollbars flag. True if the scrollbars are
1848 * always shown even if they are not required. False if the scrollbars are only
1849 * visible when some part of the content needs to be scrolled to be seen.
1850 * The H_SCROLL and V_SCROLL style bits are also required to enable scrollbars in the
1851 * horizontal and vertical directions.
1852 *
1853 * @return the Always Show Scrollbars flag value
1854 *
1855 * @exception SWTException <ul>
1856 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1857 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1858 * </ul>
1859 *
1860 * @since 3.8
1861 */
getAlwaysShowScrollBars()1862 public boolean getAlwaysShowScrollBars() {
1863 checkWidget();
1864 return alwaysShowScroll;
1865 }
getAvailableHeightAbove(int height)1866 int getAvailableHeightAbove(int height) {
1867 int maxHeight = verticalScrollOffset;
1868 if (maxHeight == -1) {
1869 int lineIndex = topIndex - 1;
1870 maxHeight = -topIndexY;
1871 if (topIndexY > 0) {
1872 maxHeight += renderer.getLineHeight(lineIndex--);
1873 }
1874 while (height > maxHeight && lineIndex >= 0) {
1875 maxHeight += renderer.getLineHeight(lineIndex--);
1876 }
1877 }
1878 return Math.min(height, maxHeight);
1879 }
getAvailableHeightBellow(int height)1880 int getAvailableHeightBellow(int height) {
1881 int partialBottomIndex = getPartialBottomIndex();
1882 int topY = getLinePixel(partialBottomIndex);
1883 int lineHeight = renderer.getLineHeight(partialBottomIndex);
1884 int availableHeight = 0;
1885 int clientAreaHeight = this.clientAreaHeight - topMargin - bottomMargin;
1886 if (topY + lineHeight > clientAreaHeight) {
1887 availableHeight = lineHeight - (clientAreaHeight - topY);
1888 }
1889 int lineIndex = partialBottomIndex + 1;
1890 int lineCount = content.getLineCount();
1891 while (height > availableHeight && lineIndex < lineCount) {
1892 availableHeight += renderer.getLineHeight(lineIndex++);
1893 }
1894 return Math.min(height, availableHeight);
1895 }
1896 /**
1897 * Returns the color of the margins.
1898 *
1899 * @return the color of the margins.
1900 * @exception SWTException <ul>
1901 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
1902 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
1903 * </ul>
1904 *
1905 * @since 3.5
1906 */
getMarginColor()1907 public Color getMarginColor() {
1908 checkWidget();
1909 return marginColor != null ? marginColor : getBackground();
1910 }
1911 /**
1912 * Returns a string that uses only the line delimiter specified by the
1913 * StyledTextContent implementation.
1914 * <p>
1915 * Returns only the first line if the widget has the SWT.SINGLE style.
1916 * </p>
1917 *
1918 * @param text the text that may have line delimiters that don't
1919 * match the model line delimiter. Possible line delimiters
1920 * are CR ('\r'), LF ('\n'), CR/LF ("\r\n")
1921 * @return the converted text that only uses the line delimiter
1922 * specified by the model. Returns only the first line if the widget
1923 * has the SWT.SINGLE style.
1924 */
getModelDelimitedText(String text)1925 String getModelDelimitedText(String text) {
1926 int length = text.length();
1927 if (length == 0) {
1928 return text;
1929 }
1930 int crIndex = 0;
1931 int lfIndex = 0;
1932 int i = 0;
1933 StringBuilder convertedText = new StringBuilder(length);
1934 String delimiter = getLineDelimiter();
1935 while (i < length) {
1936 if (crIndex != -1) {
1937 crIndex = text.indexOf(SWT.CR, i);
1938 }
1939 if (lfIndex != -1) {
1940 lfIndex = text.indexOf(SWT.LF, i);
1941 }
1942 if (lfIndex == -1 && crIndex == -1) { // no more line breaks?
1943 break;
1944 } else if ((crIndex < lfIndex && crIndex != -1) || lfIndex == -1) {
1945 convertedText.append(text.substring(i, crIndex));
1946 if (lfIndex == crIndex + 1) { // CR/LF combination?
1947 i = lfIndex + 1;
1948 } else {
1949 i = crIndex + 1;
1950 }
1951 } else { // LF occurs before CR!
1952 convertedText.append(text.substring(i, lfIndex));
1953 i = lfIndex + 1;
1954 }
1955 if (isSingleLine()) {
1956 break;
1957 }
1958 convertedText.append(delimiter);
1959 }
1960 // copy remaining text if any and if not in single line mode or no
1961 // text copied thus far (because there only is one line)
1962 if (i < length && (!isSingleLine() || convertedText.length() == 0)) {
1963 convertedText.append(text.substring(i));
1964 }
1965 return convertedText.toString();
1966 }
checkDragDetect(Event event)1967 boolean checkDragDetect(Event event) {
1968 if (!isListening(SWT.DragDetect)) return false;
1969 if (event.button != 1) return false;
1970 if (blockSelection && blockXLocation != -1) {
1971 Rectangle rect = getBlockSelectionRectangle();
1972 if (rect.contains(event.x, event.y)) {
1973 return dragDetect(event);
1974 }
1975 } else {
1976 if (selection.x == selection.y) return false;
1977 int offset = getOffsetAtPoint(event.x, event.y, null, true);
1978 if (selection.x <= offset && offset < selection.y) {
1979 return dragDetect(event);
1980 }
1981
1982 }
1983 return false;
1984 }
1985
1986 /**
1987 * Creates default key bindings.
1988 */
createKeyBindings()1989 void createKeyBindings() {
1990 int nextKey = isMirrored() ? SWT.ARROW_LEFT : SWT.ARROW_RIGHT;
1991 int previousKey = isMirrored() ? SWT.ARROW_RIGHT : SWT.ARROW_LEFT;
1992
1993 // Navigation
1994 setKeyBinding(SWT.ARROW_UP, ST.LINE_UP);
1995 setKeyBinding(SWT.ARROW_DOWN, ST.LINE_DOWN);
1996 if (IS_MAC) {
1997 setKeyBinding(previousKey | SWT.MOD1, ST.LINE_START);
1998 setKeyBinding(nextKey | SWT.MOD1, ST.LINE_END);
1999 setKeyBinding(SWT.HOME, ST.TEXT_START);
2000 setKeyBinding(SWT.END, ST.TEXT_END);
2001 setKeyBinding(SWT.ARROW_UP | SWT.MOD1, ST.TEXT_START);
2002 setKeyBinding(SWT.ARROW_DOWN | SWT.MOD1, ST.TEXT_END);
2003 setKeyBinding(nextKey | SWT.MOD3, ST.WORD_NEXT);
2004 setKeyBinding(previousKey | SWT.MOD3, ST.WORD_PREVIOUS);
2005 } else {
2006 setKeyBinding(SWT.HOME, ST.LINE_START);
2007 setKeyBinding(SWT.END, ST.LINE_END);
2008 setKeyBinding(SWT.HOME | SWT.MOD1, ST.TEXT_START);
2009 setKeyBinding(SWT.END | SWT.MOD1, ST.TEXT_END);
2010 setKeyBinding(nextKey | SWT.MOD1, ST.WORD_NEXT);
2011 setKeyBinding(previousKey | SWT.MOD1, ST.WORD_PREVIOUS);
2012 }
2013 setKeyBinding(SWT.PAGE_UP, ST.PAGE_UP);
2014 setKeyBinding(SWT.PAGE_DOWN, ST.PAGE_DOWN);
2015 setKeyBinding(SWT.PAGE_UP | SWT.MOD1, ST.WINDOW_START);
2016 setKeyBinding(SWT.PAGE_DOWN | SWT.MOD1, ST.WINDOW_END);
2017 setKeyBinding(nextKey, ST.COLUMN_NEXT);
2018 setKeyBinding(previousKey, ST.COLUMN_PREVIOUS);
2019
2020 // Selection
2021 setKeyBinding(SWT.ARROW_UP | SWT.MOD2, ST.SELECT_LINE_UP);
2022 setKeyBinding(SWT.ARROW_DOWN | SWT.MOD2, ST.SELECT_LINE_DOWN);
2023 if (IS_MAC) {
2024 setKeyBinding(previousKey | SWT.MOD1 | SWT.MOD2, ST.SELECT_LINE_START);
2025 setKeyBinding(nextKey | SWT.MOD1 | SWT.MOD2, ST.SELECT_LINE_END);
2026 setKeyBinding(SWT.HOME | SWT.MOD2, ST.SELECT_TEXT_START);
2027 setKeyBinding(SWT.END | SWT.MOD2, ST.SELECT_TEXT_END);
2028 setKeyBinding(SWT.ARROW_UP | SWT.MOD1 | SWT.MOD2, ST.SELECT_TEXT_START);
2029 setKeyBinding(SWT.ARROW_DOWN | SWT.MOD1 | SWT.MOD2, ST.SELECT_TEXT_END);
2030 setKeyBinding(nextKey | SWT.MOD2 | SWT.MOD3, ST.SELECT_WORD_NEXT);
2031 setKeyBinding(previousKey | SWT.MOD2 | SWT.MOD3, ST.SELECT_WORD_PREVIOUS);
2032 } else {
2033 setKeyBinding(SWT.HOME | SWT.MOD2, ST.SELECT_LINE_START);
2034 setKeyBinding(SWT.END | SWT.MOD2, ST.SELECT_LINE_END);
2035 setKeyBinding(SWT.HOME | SWT.MOD1 | SWT.MOD2, ST.SELECT_TEXT_START);
2036 setKeyBinding(SWT.END | SWT.MOD1 | SWT.MOD2, ST.SELECT_TEXT_END);
2037 setKeyBinding(nextKey | SWT.MOD1 | SWT.MOD2, ST.SELECT_WORD_NEXT);
2038 setKeyBinding(previousKey | SWT.MOD1 | SWT.MOD2, ST.SELECT_WORD_PREVIOUS);
2039 }
2040 setKeyBinding(SWT.PAGE_UP | SWT.MOD2, ST.SELECT_PAGE_UP);
2041 setKeyBinding(SWT.PAGE_DOWN | SWT.MOD2, ST.SELECT_PAGE_DOWN);
2042 setKeyBinding(SWT.PAGE_UP | SWT.MOD1 | SWT.MOD2, ST.SELECT_WINDOW_START);
2043 setKeyBinding(SWT.PAGE_DOWN | SWT.MOD1 | SWT.MOD2, ST.SELECT_WINDOW_END);
2044 setKeyBinding(nextKey | SWT.MOD2, ST.SELECT_COLUMN_NEXT);
2045 setKeyBinding(previousKey | SWT.MOD2, ST.SELECT_COLUMN_PREVIOUS);
2046
2047 // Modification
2048 // Cut, Copy, Paste
2049 setKeyBinding('X' | SWT.MOD1, ST.CUT);
2050 setKeyBinding('C' | SWT.MOD1, ST.COPY);
2051 setKeyBinding('V' | SWT.MOD1, ST.PASTE);
2052 if (IS_MAC) {
2053 setKeyBinding(SWT.DEL | SWT.MOD2, ST.DELETE_NEXT);
2054 setKeyBinding(SWT.BS | SWT.MOD3, ST.DELETE_WORD_PREVIOUS);
2055 setKeyBinding(SWT.DEL | SWT.MOD3, ST.DELETE_WORD_NEXT);
2056 } else {
2057 // Cut, Copy, Paste Wordstar style
2058 setKeyBinding(SWT.DEL | SWT.MOD2, ST.CUT);
2059 setKeyBinding(SWT.INSERT | SWT.MOD1, ST.COPY);
2060 setKeyBinding(SWT.INSERT | SWT.MOD2, ST.PASTE);
2061 }
2062 setKeyBinding(SWT.BS | SWT.MOD2, ST.DELETE_PREVIOUS);
2063 setKeyBinding(SWT.BS, ST.DELETE_PREVIOUS);
2064 setKeyBinding(SWT.DEL, ST.DELETE_NEXT);
2065 setKeyBinding(SWT.BS | SWT.MOD1, ST.DELETE_WORD_PREVIOUS);
2066 setKeyBinding(SWT.DEL | SWT.MOD1, ST.DELETE_WORD_NEXT);
2067
2068 // Miscellaneous
2069 setKeyBinding(SWT.INSERT, ST.TOGGLE_OVERWRITE);
2070 }
2071 /**
2072 * Create the bitmaps to use for the caret in bidi mode. This
2073 * method only needs to be called upon widget creation and when the
2074 * font changes (the caret bitmap height needs to match font height).
2075 */
createCaretBitmaps()2076 void createCaretBitmaps() {
2077 int caretWidth = BIDI_CARET_WIDTH;
2078 Display display = getDisplay();
2079 if (leftCaretBitmap != null) {
2080 if (defaultCaret != null && leftCaretBitmap.equals(defaultCaret.getImage())) {
2081 defaultCaret.setImage(null);
2082 }
2083 leftCaretBitmap.dispose();
2084 }
2085 int lineHeight = renderer.getLineHeight();
2086 leftCaretBitmap = new Image(display, caretWidth, lineHeight);
2087 GC gc = new GC (leftCaretBitmap);
2088 gc.setBackground(display.getSystemColor(SWT.COLOR_BLACK));
2089 gc.fillRectangle(0, 0, caretWidth, lineHeight);
2090 gc.setForeground(display.getSystemColor(SWT.COLOR_WHITE));
2091 gc.drawLine(0,0,0,lineHeight);
2092 gc.drawLine(0,0,caretWidth-1,0);
2093 gc.drawLine(0,1,1,1);
2094 gc.dispose();
2095
2096 if (rightCaretBitmap != null) {
2097 if (defaultCaret != null && rightCaretBitmap.equals(defaultCaret.getImage())) {
2098 defaultCaret.setImage(null);
2099 }
2100 rightCaretBitmap.dispose();
2101 }
2102 rightCaretBitmap = new Image(display, caretWidth, lineHeight);
2103 gc = new GC (rightCaretBitmap);
2104 gc.setBackground(display.getSystemColor(SWT.COLOR_BLACK));
2105 gc.fillRectangle(0, 0, caretWidth, lineHeight);
2106 gc.setForeground(display.getSystemColor(SWT.COLOR_WHITE));
2107 gc.drawLine(caretWidth-1,0,caretWidth-1,lineHeight);
2108 gc.drawLine(0,0,caretWidth-1,0);
2109 gc.drawLine(caretWidth-1,1,1,1);
2110 gc.dispose();
2111 }
2112 /**
2113 * Moves the selected text to the clipboard. The text will be put in the
2114 * clipboard in plain text format and RTF format.
2115 *
2116 * @exception SWTException <ul>
2117 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
2118 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
2119 * </ul>
2120 */
cut()2121 public void cut() {
2122 checkWidget();
2123 // Abort cut operation if copy to clipboard fails.
2124 // Fixes bug 21030.
2125 if (copySelection(DND.CLIPBOARD)) {
2126 if (blockSelection && blockXLocation != -1) {
2127 insertBlockSelectionText((char)0, SWT.NULL);
2128 } else {
2129 doDelete();
2130 }
2131 }
2132 }
2133 /**
2134 * A mouse move event has occurred. See if we should start autoscrolling. If
2135 * the move position is outside of the client area, initiate autoscrolling.
2136 * Otherwise, we've moved back into the widget so end autoscrolling.
2137 */
doAutoScroll(Event event)2138 void doAutoScroll(Event event) {
2139 int caretLine = getCaretLine();
2140 if (event.y > clientAreaHeight - bottomMargin && caretLine != content.getLineCount() - 1) {
2141 doAutoScroll(SWT.DOWN, event.y - (clientAreaHeight - bottomMargin));
2142 } else if (event.y < topMargin && caretLine != 0) {
2143 doAutoScroll(SWT.UP, topMargin - event.y);
2144 } else if (event.x < leftMargin && !wordWrap) {
2145 doAutoScroll(ST.COLUMN_PREVIOUS, leftMargin - event.x);
2146 } else if (event.x > clientAreaWidth - rightMargin && !wordWrap) {
2147 doAutoScroll(ST.COLUMN_NEXT, event.x - (clientAreaWidth - rightMargin));
2148 } else {
2149 endAutoScroll();
2150 }
2151 }
2152 /**
2153 * Initiates autoscrolling.
2154 *
2155 * @param direction SWT.UP, SWT.DOWN, SWT.COLUMN_NEXT, SWT.COLUMN_PREVIOUS
2156 */
doAutoScroll(int direction, int distance)2157 void doAutoScroll(int direction, int distance) {
2158 autoScrollDistance = distance;
2159 // If we're already autoscrolling in the given direction do nothing
2160 if (autoScrollDirection == direction) {
2161 return;
2162 }
2163
2164 Runnable timer = null;
2165 final Display display = getDisplay();
2166 // Set a timer that will simulate the user pressing and holding
2167 // down a cursor key (i.e., arrowUp, arrowDown).
2168 if (direction == SWT.UP) {
2169 timer = new Runnable() {
2170 @Override
2171 public void run() {
2172 /* Bug 437357 - NPE in StyledText.getCaretLine
2173 * StyledText.content is null at times, probably because the
2174 * widget itself has been disposed.
2175 */
2176 if (isDisposed()) return;
2177 if (autoScrollDirection == SWT.UP) {
2178 if (blockSelection) {
2179 int verticalScrollOffset = getVerticalScrollOffset();
2180 int y = blockYLocation - verticalScrollOffset;
2181 int pixels = Math.max(-autoScrollDistance, -verticalScrollOffset);
2182 if (pixels != 0) {
2183 setBlockSelectionLocation(blockXLocation - horizontalScrollOffset, y + pixels, true);
2184 scrollVertical(pixels, true);
2185 }
2186 } else {
2187 doSelectionPageUp(autoScrollDistance);
2188 }
2189 display.timerExec(V_SCROLL_RATE, this);
2190 }
2191 }
2192 };
2193 autoScrollDirection = direction;
2194 display.timerExec(V_SCROLL_RATE, timer);
2195 } else if (direction == SWT.DOWN) {
2196 timer = new Runnable() {
2197 @Override
2198 public void run() {
2199 /* Bug 437357 - NPE in StyledText.getCaretLine
2200 * StyledText.content is null at times, probably because the
2201 * widget itself has been disposed.
2202 */
2203 if (isDisposed()) return;
2204 if (autoScrollDirection == SWT.DOWN) {
2205 if (blockSelection) {
2206 int verticalScrollOffset = getVerticalScrollOffset();
2207 int y = blockYLocation - verticalScrollOffset;
2208 int max = renderer.getHeight() - verticalScrollOffset - clientAreaHeight;
2209 int pixels = Math.min(autoScrollDistance, Math.max(0,max));
2210 if (pixels != 0) {
2211 setBlockSelectionLocation(blockXLocation - horizontalScrollOffset, y + pixels, true);
2212 scrollVertical(pixels, true);
2213 }
2214 } else {
2215 doSelectionPageDown(autoScrollDistance);
2216 }
2217 display.timerExec(V_SCROLL_RATE, this);
2218 }
2219 }
2220 };
2221 autoScrollDirection = direction;
2222 display.timerExec(V_SCROLL_RATE, timer);
2223 } else if (direction == ST.COLUMN_NEXT) {
2224 timer = new Runnable() {
2225 @Override
2226 public void run() {
2227 /* Bug 437357 - NPE in StyledText.getCaretLine
2228 * StyledText.content is null at times, probably because the
2229 * widget itself has been disposed.
2230 */
2231 if (isDisposed()) return;
2232 if (autoScrollDirection == ST.COLUMN_NEXT) {
2233 if (blockSelection) {
2234 int x = blockXLocation - horizontalScrollOffset;
2235 int max = renderer.getWidth() - horizontalScrollOffset - clientAreaWidth;
2236 int pixels = Math.min(autoScrollDistance, Math.max(0,max));
2237 if (pixels != 0) {
2238 setBlockSelectionLocation(x + pixels, blockYLocation - getVerticalScrollOffset(), true);
2239 scrollHorizontal(pixels, true);
2240 }
2241 } else {
2242 doVisualNext();
2243 setMouseWordSelectionAnchor();
2244 doMouseSelection();
2245 }
2246 display.timerExec(H_SCROLL_RATE, this);
2247 }
2248 }
2249 };
2250 autoScrollDirection = direction;
2251 display.timerExec(H_SCROLL_RATE, timer);
2252 } else if (direction == ST.COLUMN_PREVIOUS) {
2253 timer = new Runnable() {
2254 @Override
2255 public void run() {
2256 /* Bug 437357 - NPE in StyledText.getCaretLine
2257 * StyledText.content is null at times, probably because the
2258 * widget itself has been disposed.
2259 */
2260 if (isDisposed()) return;
2261 if (autoScrollDirection == ST.COLUMN_PREVIOUS) {
2262 if (blockSelection) {
2263 int x = blockXLocation - horizontalScrollOffset;
2264 int pixels = Math.max(-autoScrollDistance, -horizontalScrollOffset);
2265 if (pixels != 0) {
2266 setBlockSelectionLocation(x + pixels, blockYLocation - getVerticalScrollOffset(), true);
2267 scrollHorizontal(pixels, true);
2268 }
2269 } else {
2270 doVisualPrevious();
2271 setMouseWordSelectionAnchor();
2272 doMouseSelection();
2273 }
2274 display.timerExec(H_SCROLL_RATE, this);
2275 }
2276 }
2277 };
2278 autoScrollDirection = direction;
2279 display.timerExec(H_SCROLL_RATE, timer);
2280 }
2281 }
2282 /**
2283 * Deletes the previous character. Delete the selected text if any.
2284 * Move the caret in front of the deleted text.
2285 */
doBackspace()2286 void doBackspace() {
2287 Event event = new Event();
2288 event.text = "";
2289 if (selection.x != selection.y) {
2290 event.start = selection.x;
2291 event.end = selection.y;
2292 sendKeyEvent(event);
2293 } else if (caretOffset > 0) {
2294 int lineIndex = content.getLineAtOffset(caretOffset);
2295 int lineOffset = content.getOffsetAtLine(lineIndex);
2296 if (caretOffset == lineOffset) {
2297 lineOffset = content.getOffsetAtLine(lineIndex - 1);
2298 event.start = lineOffset + content.getLine(lineIndex - 1).length();
2299 event.end = caretOffset;
2300 } else {
2301 boolean isSurrogate = false;
2302 String lineText = content.getLine(lineIndex);
2303 char ch = lineText.charAt(caretOffset - lineOffset - 1);
2304 if (0xDC00 <= ch && ch <= 0xDFFF) {
2305 if (caretOffset - lineOffset - 2 >= 0) {
2306 ch = lineText.charAt(caretOffset - lineOffset - 2);
2307 isSurrogate = 0xD800 <= ch && ch <= 0xDBFF;
2308 }
2309 }
2310 TextLayout layout = renderer.getTextLayout(lineIndex);
2311 int start = layout.getPreviousOffset(caretOffset - lineOffset, isSurrogate ? SWT.MOVEMENT_CLUSTER : SWT.MOVEMENT_CHAR);
2312 renderer.disposeTextLayout(layout);
2313 event.start = start + lineOffset;
2314 event.end = caretOffset;
2315 }
2316 sendKeyEvent(event);
2317 }
2318 }
doBlockColumn(boolean next)2319 void doBlockColumn(boolean next) {
2320 if (blockXLocation == -1) setBlockSelectionOffset(caretOffset, false);
2321 int x = blockXLocation - horizontalScrollOffset;
2322 int y = blockYLocation - getVerticalScrollOffset();
2323 int[] trailing = new int[1];
2324 int offset = getOffsetAtPoint(x, y, trailing, true);
2325 if (offset != -1) {
2326 offset += trailing[0];
2327 int lineIndex = content.getLineAtOffset(offset);
2328 int newOffset;
2329 if (next) {
2330 newOffset = getClusterNext(offset, lineIndex);
2331 } else {
2332 newOffset = getClusterPrevious(offset, lineIndex);
2333 }
2334 offset = newOffset != offset ? newOffset : -1;
2335 }
2336 if (offset != -1) {
2337 setBlockSelectionOffset(offset, true);
2338 showCaret();
2339 } else {
2340 int width = next ? renderer.averageCharWidth : -renderer.averageCharWidth;
2341 int maxWidth = Math.max(clientAreaWidth - rightMargin - leftMargin, renderer.getWidth());
2342 x = Math.max(0, Math.min(blockXLocation + width, maxWidth)) - horizontalScrollOffset;
2343 setBlockSelectionLocation(x, y, true);
2344 Rectangle rect = new Rectangle(x, y, 0, 0);
2345 showLocation(rect, true);
2346 }
2347 }
doBlockContentStartEnd(boolean end)2348 void doBlockContentStartEnd(boolean end) {
2349 if (blockXLocation == -1) setBlockSelectionOffset(caretOffset, false);
2350 int offset = end ? content.getCharCount() : 0;
2351 setBlockSelectionOffset(offset, true);
2352 showCaret();
2353 }
doBlockWord(boolean next)2354 void doBlockWord(boolean next) {
2355 if (blockXLocation == -1) setBlockSelectionOffset(caretOffset, false);
2356 int x = blockXLocation - horizontalScrollOffset;
2357 int y = blockYLocation - getVerticalScrollOffset();
2358 int[] trailing = new int[1];
2359 int offset = getOffsetAtPoint(x, y, trailing, true);
2360 if (offset != -1) {
2361 offset += trailing[0];
2362 int lineIndex = content.getLineAtOffset(offset);
2363 int lineOffset = content.getOffsetAtLine(lineIndex);
2364 String lineText = content.getLine(lineIndex);
2365 int lineLength = lineText.length();
2366 int newOffset = offset;
2367 if (next) {
2368 if (offset < lineOffset + lineLength) {
2369 newOffset = getWordNext(offset, SWT.MOVEMENT_WORD);
2370 }
2371 } else {
2372 if (offset > lineOffset) {
2373 newOffset = getWordPrevious(offset, SWT.MOVEMENT_WORD);
2374 }
2375 }
2376 offset = newOffset != offset ? newOffset : -1;
2377 }
2378 if (offset != -1) {
2379 setBlockSelectionOffset(offset, true);
2380 showCaret();
2381 } else {
2382 int width = (next ? renderer.averageCharWidth : -renderer.averageCharWidth) * 6;
2383 int maxWidth = Math.max(clientAreaWidth - rightMargin - leftMargin, renderer.getWidth());
2384 x = Math.max(0, Math.min(blockXLocation + width, maxWidth)) - horizontalScrollOffset;
2385 setBlockSelectionLocation(x, y, true);
2386 Rectangle rect = new Rectangle(x, y, 0, 0);
2387 showLocation(rect, true);
2388 }
2389 }
doBlockLineVertical(boolean up)2390 void doBlockLineVertical(boolean up) {
2391 if (blockXLocation == -1) setBlockSelectionOffset(caretOffset, false);
2392 int y = blockYLocation - getVerticalScrollOffset();
2393 int lineIndex = getLineIndex(y);
2394 if (up) {
2395 if (lineIndex > 0) {
2396 y = getLinePixel(lineIndex - 1);
2397 setBlockSelectionLocation(blockXLocation - horizontalScrollOffset, y, true);
2398 if (y < topMargin) {
2399 scrollVertical(y - topMargin, true);
2400 }
2401 }
2402 } else {
2403 int lineCount = content.getLineCount();
2404 if (lineIndex + 1 < lineCount) {
2405 y = getLinePixel(lineIndex + 2) - 1;
2406 setBlockSelectionLocation(blockXLocation - horizontalScrollOffset, y, true);
2407 int bottom = clientAreaHeight - bottomMargin;
2408 if (y > bottom) {
2409 scrollVertical(y - bottom, true);
2410 }
2411 }
2412 }
2413 }
doBlockLineHorizontal(boolean end)2414 void doBlockLineHorizontal(boolean end) {
2415 if (blockXLocation == -1) setBlockSelectionOffset(caretOffset, false);
2416 int x = blockXLocation - horizontalScrollOffset;
2417 int y = blockYLocation - getVerticalScrollOffset();
2418 int lineIndex = getLineIndex(y);
2419 int lineOffset = content.getOffsetAtLine(lineIndex);
2420 String lineText = content.getLine(lineIndex);
2421 int lineLength = lineText.length();
2422 int[] trailing = new int[1];
2423 int offset = getOffsetAtPoint(x, y, trailing, true);
2424 if (offset != -1) {
2425 offset += trailing[0];
2426 int newOffset = offset;
2427 if (end) {
2428 if (offset < lineOffset + lineLength) {
2429 newOffset = lineOffset + lineLength;
2430 }
2431 } else {
2432 if (offset > lineOffset) {
2433 newOffset = lineOffset;
2434 }
2435 }
2436 offset = newOffset != offset ? newOffset : -1;
2437 } else {
2438 if (!end) offset = lineOffset + lineLength;
2439 }
2440 if (offset != -1) {
2441 setBlockSelectionOffset(offset, true);
2442 showCaret();
2443 } else {
2444 int maxWidth = Math.max(clientAreaWidth - rightMargin - leftMargin, renderer.getWidth());
2445 x = (end ? maxWidth : 0) - horizontalScrollOffset;
2446 setBlockSelectionLocation(x, y, true);
2447 Rectangle rect = new Rectangle(x, y, 0, 0);
2448 showLocation(rect, true);
2449 }
2450 }
doBlockSelection(boolean sendEvent)2451 void doBlockSelection(boolean sendEvent) {
2452 if (caretOffset > selectionAnchor) {
2453 selection.x = selectionAnchor;
2454 selection.y = caretOffset;
2455 } else {
2456 selection.x = caretOffset;
2457 selection.y = selectionAnchor;
2458 }
2459 updateCaretVisibility();
2460 setCaretLocation();
2461 super.redraw();
2462 if (sendEvent) {
2463 sendSelectionEvent();
2464 }
2465 sendAccessibleTextCaretMoved();
2466 }
2467 /**
2468 * Replaces the selection with the character or insert the character at the
2469 * current caret position if no selection exists.
2470 * <p>
2471 * If a carriage return was typed replace it with the line break character
2472 * used by the widget on this platform.
2473 * </p>
2474 *
2475 * @param key the character typed by the user
2476 */
doContent(char key)2477 void doContent(char key) {
2478 if (blockSelection && blockXLocation != -1) {
2479 insertBlockSelectionText(key, SWT.NULL);
2480 return;
2481 }
2482
2483 Event event = new Event();
2484 event.start = selection.x;
2485 event.end = selection.y;
2486 // replace a CR line break with the widget line break
2487 // CR does not make sense on Windows since most (all?) applications
2488 // don't recognize CR as a line break.
2489 if (key == SWT.CR || key == SWT.LF) {
2490 if (!isSingleLine()) {
2491 event.text = getLineDelimiter();
2492 }
2493 } else if (selection.x == selection.y && overwrite && key != TAB) {
2494 // no selection and overwrite mode is on and the typed key is not a
2495 // tab character (tabs are always inserted without overwriting)?
2496 int lineIndex = content.getLineAtOffset(event.end);
2497 int lineOffset = content.getOffsetAtLine(lineIndex);
2498 String line = content.getLine(lineIndex);
2499 // replace character at caret offset if the caret is not at the
2500 // end of the line
2501 if (event.end < lineOffset + line.length()) {
2502 event.end++;
2503 }
2504 event.text = new String(new char[] {key});
2505 } else {
2506 event.text = new String(new char[] {key});
2507 }
2508 if (event.text != null) {
2509 if (textLimit > 0 && content.getCharCount() - (event.end - event.start) >= textLimit) {
2510 return;
2511 }
2512 sendKeyEvent(event);
2513 }
2514 }
2515 /**
2516 * Moves the caret after the last character of the widget content.
2517 */
doContentEnd()2518 void doContentEnd() {
2519 // place caret at end of first line if receiver is in single
2520 // line mode. fixes 4820.
2521 if (isSingleLine()) {
2522 doLineEnd();
2523 } else {
2524 int length = content.getCharCount();
2525 setCaretOffset(length, SWT.DEFAULT);
2526 showCaret();
2527 }
2528 }
2529 /**
2530 * Moves the caret in front of the first character of the widget content.
2531 */
doContentStart()2532 void doContentStart() {
2533 setCaretOffset(0, SWT.DEFAULT);
2534 showCaret();
2535 }
2536 /**
2537 * Moves the caret to the start of the selection if a selection exists.
2538 * Otherwise, if no selection exists move the cursor according to the
2539 * cursor selection rules.
2540 *
2541 * @see #doSelectionCursorPrevious
2542 */
doCursorPrevious()2543 void doCursorPrevious() {
2544 if (selection.y - selection.x > 0) {
2545 setCaretOffset(selection.x, OFFSET_LEADING);
2546 showCaret();
2547 } else {
2548 doSelectionCursorPrevious();
2549 }
2550 }
2551 /**
2552 * Moves the caret to the end of the selection if a selection exists.
2553 * Otherwise, if no selection exists move the cursor according to the
2554 * cursor selection rules.
2555 *
2556 * @see #doSelectionCursorNext
2557 */
doCursorNext()2558 void doCursorNext() {
2559 if (selection.y - selection.x > 0) {
2560 setCaretOffset(selection.y, PREVIOUS_OFFSET_TRAILING);
2561 showCaret();
2562 } else {
2563 doSelectionCursorNext();
2564 }
2565 }
2566 /**
2567 * Deletes the next character. Delete the selected text if any.
2568 */
doDelete()2569 void doDelete() {
2570 Event event = new Event();
2571 event.text = "";
2572 if (selection.x != selection.y) {
2573 event.start = selection.x;
2574 event.end = selection.y;
2575 sendKeyEvent(event);
2576 } else if (caretOffset < content.getCharCount()) {
2577 int line = content.getLineAtOffset(caretOffset);
2578 int lineOffset = content.getOffsetAtLine(line);
2579 int lineLength = content.getLine(line).length();
2580 if (caretOffset == lineOffset + lineLength) {
2581 event.start = caretOffset;
2582 event.end = content.getOffsetAtLine(line + 1);
2583 } else {
2584 event.start = caretOffset;
2585 event.end = getClusterNext(caretOffset, line);
2586 }
2587 sendKeyEvent(event);
2588 }
2589 }
2590 /**
2591 * Deletes the next word.
2592 */
doDeleteWordNext()2593 void doDeleteWordNext() {
2594 if (selection.x != selection.y) {
2595 // if a selection exists, treat the as if
2596 // only the delete key was pressed
2597 doDelete();
2598 } else {
2599 Event event = new Event();
2600 event.text = "";
2601 event.start = caretOffset;
2602 event.end = getWordNext(caretOffset, SWT.MOVEMENT_WORD);
2603 sendKeyEvent(event);
2604 }
2605 }
2606 /**
2607 * Deletes the previous word.
2608 */
doDeleteWordPrevious()2609 void doDeleteWordPrevious() {
2610 if (selection.x != selection.y) {
2611 // if a selection exists, treat as if
2612 // only the backspace key was pressed
2613 doBackspace();
2614 } else {
2615 Event event = new Event();
2616 event.text = "";
2617 event.start = getWordPrevious(caretOffset, SWT.MOVEMENT_WORD);
2618 event.end = caretOffset;
2619 sendKeyEvent(event);
2620 }
2621 }
2622 /**
2623 * Moves the caret one line down and to the same character offset relative
2624 * to the beginning of the line. Move the caret to the end of the new line
2625 * if the new line is shorter than the character offset. Moves the caret to
2626 * the end of the text if the caret already is on the last line.
2627 */
doLineDown(boolean select)2628 void doLineDown(boolean select) {
2629 int caretLine = getCaretLine();
2630 int lineCount = content.getLineCount();
2631 int y = 0;
2632 boolean lastLine = false;
2633 if (isWordWrap()) {
2634 int lineOffset = content.getOffsetAtLine(caretLine);
2635 int offsetInLine = caretOffset - lineOffset;
2636 TextLayout layout = renderer.getTextLayout(caretLine);
2637 int lineIndex = getVisualLineIndex(layout, offsetInLine);
2638 int layoutLineCount = layout.getLineCount();
2639 if (lineIndex == layoutLineCount - 1) {
2640 lastLine = caretLine == lineCount - 1;
2641 caretLine++;
2642 } else {
2643 y = layout.getLineBounds(lineIndex + 1).y;
2644 y++; // bug 485722: workaround for fractional line heights
2645 }
2646 renderer.disposeTextLayout(layout);
2647 } else {
2648 lastLine = caretLine == lineCount - 1;
2649 caretLine++;
2650 }
2651 if (lastLine) {
2652 setCaretOffset(content.getCharCount(), SWT.DEFAULT);
2653 } else {
2654 int[] alignment = new int[1];
2655 int offset = getOffsetAtPoint(columnX, y, caretLine, alignment);
2656 setCaretOffset(offset, alignment[0]);
2657 }
2658 int oldColumnX = columnX;
2659 int oldHScrollOffset = horizontalScrollOffset;
2660 if (select) {
2661 setMouseWordSelectionAnchor();
2662 // select first and then scroll to reduce flash when key
2663 // repeat scrolls lots of lines
2664 doSelection(ST.COLUMN_NEXT);
2665 }
2666 showCaret();
2667 int hScrollChange = oldHScrollOffset - horizontalScrollOffset;
2668 columnX = oldColumnX + hScrollChange;
2669 }
2670 /**
2671 * Moves the caret to the end of the line.
2672 */
doLineEnd()2673 void doLineEnd() {
2674 int caretLine = getCaretLine();
2675 int lineOffset = content.getOffsetAtLine(caretLine);
2676 int lineEndOffset;
2677 if (isWordWrap()) {
2678 TextLayout layout = renderer.getTextLayout(caretLine);
2679 int offsetInLine = caretOffset - lineOffset;
2680 int lineIndex = getVisualLineIndex(layout, offsetInLine);
2681 int[] offsets = layout.getLineOffsets();
2682 lineEndOffset = lineOffset + offsets[lineIndex + 1];
2683 renderer.disposeTextLayout(layout);
2684 } else {
2685 int lineLength = content.getLine(caretLine).length();
2686 lineEndOffset = lineOffset + lineLength;
2687 }
2688 setCaretOffset(lineEndOffset, PREVIOUS_OFFSET_TRAILING);
2689 showCaret();
2690 }
2691 /**
2692 * Moves the caret to the beginning of the line.
2693 */
doLineStart()2694 void doLineStart() {
2695 int caretLine = getCaretLine();
2696 int lineOffset = content.getOffsetAtLine(caretLine);
2697 if (isWordWrap()) {
2698 TextLayout layout = renderer.getTextLayout(caretLine);
2699 int offsetInLine = caretOffset - lineOffset;
2700 int lineIndex = getVisualLineIndex(layout, offsetInLine);
2701 int[] offsets = layout.getLineOffsets();
2702 lineOffset += offsets[lineIndex];
2703 renderer.disposeTextLayout(layout);
2704 }
2705 setCaretOffset(lineOffset, OFFSET_LEADING);
2706 showCaret();
2707 }
2708 /**
2709 * Moves the caret one line up and to the same character offset relative
2710 * to the beginning of the line. Move the caret to the end of the new line
2711 * if the new line is shorter than the character offset. Moves the caret to
2712 * the beginning of the document if it is already on the first line.
2713 */
doLineUp(boolean select)2714 void doLineUp(boolean select) {
2715 int caretLine = getCaretLine(), y = 0;
2716 boolean firstLine = false;
2717 if (isWordWrap()) {
2718 int lineOffset = content.getOffsetAtLine(caretLine);
2719 int offsetInLine = caretOffset - lineOffset;
2720 TextLayout layout = renderer.getTextLayout(caretLine);
2721 int lineIndex = getVisualLineIndex(layout, offsetInLine);
2722 if (lineIndex == 0) {
2723 firstLine = caretLine == 0;
2724 if (!firstLine) {
2725 caretLine--;
2726 y = renderer.getLineHeight(caretLine) - 1;
2727 y--; // bug 485722: workaround for fractional line heights
2728 }
2729 } else {
2730 y = layout.getLineBounds(lineIndex - 1).y;
2731 y++; // bug 485722: workaround for fractional line heights
2732 }
2733 renderer.disposeTextLayout(layout);
2734 } else {
2735 firstLine = caretLine == 0;
2736 caretLine--;
2737 }
2738 if (firstLine) {
2739 setCaretOffset(0, SWT.DEFAULT);
2740 } else {
2741 int[] alignment = new int[1];
2742 int offset = getOffsetAtPoint(columnX, y, caretLine, alignment);
2743 setCaretOffset(offset, alignment[0]);
2744 }
2745 int oldColumnX = columnX;
2746 int oldHScrollOffset = horizontalScrollOffset;
2747 if (select) setMouseWordSelectionAnchor();
2748 showCaret();
2749 if (select) doSelection(ST.COLUMN_PREVIOUS);
2750 int hScrollChange = oldHScrollOffset - horizontalScrollOffset;
2751 columnX = oldColumnX + hScrollChange;
2752 }
doMouseLinkCursor()2753 void doMouseLinkCursor() {
2754 Display display = getDisplay();
2755 Point point = display.getCursorLocation();
2756 point = display.map(null, this, point);
2757 doMouseLinkCursor(point.x, point.y);
2758 }
doMouseLinkCursor(int x, int y)2759 void doMouseLinkCursor(int x, int y) {
2760 int offset = getOffsetAtPoint(x, y, null, true);
2761 Display display = getDisplay();
2762 Cursor newCursor = cursor;
2763 if (renderer.hasLink(offset)) {
2764 newCursor = display.getSystemCursor(SWT.CURSOR_HAND);
2765 } else {
2766 if (cursor == null) {
2767 int type = blockSelection ? SWT.CURSOR_CROSS : SWT.CURSOR_IBEAM;
2768 newCursor = display.getSystemCursor(type);
2769 }
2770 }
2771 if (newCursor != getCursor()) super.setCursor(newCursor);
2772 }
2773 /**
2774 * Moves the caret to the specified location.
2775 *
2776 * @param x x location of the new caret position
2777 * @param y y location of the new caret position
2778 * @param select the location change is a selection operation.
2779 * include the line delimiter in the selection
2780 */
doMouseLocationChange(int x, int y, boolean select)2781 void doMouseLocationChange(int x, int y, boolean select) {
2782 int line = getLineIndex(y);
2783
2784 updateCaretDirection = true;
2785
2786 if (blockSelection) {
2787 x = Math.max(leftMargin, Math.min(x, clientAreaWidth - rightMargin));
2788 y = Math.max(topMargin, Math.min(y, clientAreaHeight - bottomMargin));
2789 if (doubleClickEnabled && clickCount > 1) {
2790 boolean wordSelect = (clickCount & 1) == 0;
2791 if (wordSelect) {
2792 Point left = getPointAtOffset(doubleClickSelection.x);
2793 int[] trailing = new int[1];
2794 int offset = getOffsetAtPoint(x, y, trailing, true);
2795 if (offset != -1) {
2796 if (x > left.x) {
2797 offset = getWordNext(offset + trailing[0], SWT.MOVEMENT_WORD_END);
2798 setBlockSelectionOffset(doubleClickSelection.x, offset, true);
2799 } else {
2800 offset = getWordPrevious(offset + trailing[0], SWT.MOVEMENT_WORD_START);
2801 setBlockSelectionOffset(doubleClickSelection.y, offset, true);
2802 }
2803 } else {
2804 if (x > left.x) {
2805 setBlockSelectionLocation(left.x, left.y, x, y, true);
2806 } else {
2807 Point right = getPointAtOffset(doubleClickSelection.y);
2808 setBlockSelectionLocation(right.x, right.y, x, y, true);
2809 }
2810 }
2811 } else {
2812 setBlockSelectionLocation(blockXLocation, y, true);
2813 }
2814 return;
2815 } else {
2816 if (select) {
2817 if (blockXLocation == -1) {
2818 setBlockSelectionOffset(caretOffset, false);
2819 }
2820 } else {
2821 clearBlockSelection(true, false);
2822 }
2823 int[] trailing = new int[1];
2824 int offset = getOffsetAtPoint(x, y, trailing, true);
2825 if (offset != -1) {
2826 if (select) {
2827 setBlockSelectionOffset(offset + trailing[0], true);
2828 return;
2829 }
2830 } else {
2831 if (isFixedLineHeight() && renderer.fixedPitch) {
2832 int avg = renderer.averageCharWidth;
2833 x = ((x + avg / 2 - leftMargin + horizontalScrollOffset) / avg * avg) + leftMargin - horizontalScrollOffset;
2834 }
2835 setBlockSelectionLocation(x, y, true);
2836 return;
2837 }
2838 }
2839 }
2840
2841 // allow caret to be placed below first line only if receiver is
2842 // not in single line mode. fixes 4820.
2843 if (line < 0 || (isSingleLine() && line > 0)) {
2844 return;
2845 }
2846 int[] alignment = new int[1];
2847 int newCaretOffset = getOffsetAtPoint(x, y, alignment);
2848 int newCaretAlignemnt = alignment[0];
2849
2850 if (doubleClickEnabled && clickCount > 1) {
2851 newCaretOffset = doMouseWordSelect(x, newCaretOffset, line);
2852 }
2853
2854 int newCaretLine = content.getLineAtOffset(newCaretOffset);
2855
2856 // Is the mouse within the left client area border or on
2857 // a different line? If not the autoscroll selection
2858 // could be incorrectly reset. Fixes 1GKM3XS
2859 boolean vchange = 0 <= y && y < clientAreaHeight || newCaretLine == 0 || newCaretLine == content.getLineCount() - 1;
2860 boolean hchange = 0 <= x && x < clientAreaWidth || wordWrap || newCaretLine != content.getLineAtOffset(caretOffset);
2861 if (vchange && hchange && (newCaretOffset != caretOffset || newCaretAlignemnt != caretAlignment)) {
2862 setCaretOffset(newCaretOffset, newCaretAlignemnt);
2863 if (select) doMouseSelection();
2864 showCaret();
2865 }
2866 if (!select) {
2867 setCaretOffset(newCaretOffset, newCaretAlignemnt);
2868 clearSelection(true);
2869 }
2870 }
2871 /**
2872 * Updates the selection based on the caret position
2873 */
2874 void doMouseSelection() {
2875 if (caretOffset <= selection.x ||
2876 (caretOffset > selection.x &&
2877 caretOffset < selection.y && selectionAnchor == selection.x)) {
2878 doSelection(ST.COLUMN_PREVIOUS);
2879 } else {
2880 doSelection(ST.COLUMN_NEXT);
2881 }
2882 }
2883 /**
2884 * Returns the offset of the word at the specified offset.
2885 * If the current selection extends from high index to low index
2886 * (i.e., right to left, or caret is at left border of selection on
2887 * non-bidi platforms) the start offset of the word preceding the
2888 * selection is returned. If the current selection extends from
2889 * low index to high index the end offset of the word following
2890 * the selection is returned.
2891 *
2892 * @param x mouse x location
2893 * @param newCaretOffset caret offset of the mouse cursor location
2894 * @param line line index of the mouse cursor location
2895 */
2896 int doMouseWordSelect(int x, int newCaretOffset, int line) {
2897 // flip selection anchor based on word selection direction from
2898 // base double click. Always do this here (and don't rely on doAutoScroll)
2899 // because auto scroll only does not cover all possible mouse selections
2900 // (e.g., mouse x < 0 && mouse y > caret line y)
2901 if (newCaretOffset < selectionAnchor && selectionAnchor == selection.x) {
2902 selectionAnchor = doubleClickSelection.y;
2903 } else if (newCaretOffset > selectionAnchor && selectionAnchor == selection.y) {
2904 selectionAnchor = doubleClickSelection.x;
2905 }
2906 if (0 <= x && x < clientAreaWidth) {
2907 boolean wordSelect = (clickCount & 1) == 0;
2908 if (caretOffset == selection.x) {
2909 if (wordSelect) {
2910 newCaretOffset = getWordPrevious(newCaretOffset, SWT.MOVEMENT_WORD_START);
2911 } else {
2912 newCaretOffset = content.getOffsetAtLine(line);
2913 }
2914 } else {
2915 if (wordSelect) {
2916 newCaretOffset = getWordNext(newCaretOffset, SWT.MOVEMENT_WORD_END);
2917 } else {
2918 int lineEnd = content.getCharCount();
2919 if (line + 1 < content.getLineCount()) {
2920 lineEnd = content.getOffsetAtLine(line + 1);
2921 }
2922 newCaretOffset = lineEnd;
2923 }
2924 }
2925 }
2926 return newCaretOffset;
2927 }
2928 /**
2929 * Scrolls one page down so that the last line (truncated or whole)
2930 * of the current page becomes the fully visible top line.
2931 * <p>
2932 * The caret is scrolled the same number of lines so that its location
2933 * relative to the top line remains the same. The exception is the end
2934 * of the text where a full page scroll is not possible. In this case
2935 * the caret is moved after the last character.
2936 * </p>
2937 *
2938 * @param select whether or not to select the page
2939 */
2940 void doPageDown(boolean select, int height) {
2941 if (isSingleLine()) return;
2942 int oldColumnX = columnX;
2943 int oldHScrollOffset = horizontalScrollOffset;
2944 if (isFixedLineHeight()) {
2945 int lineCount = content.getLineCount();
2946 int caretLine = getCaretLine();
2947 if (caretLine < lineCount - 1) {
2948 int lineHeight = renderer.getLineHeight();
2949 int lines = (height == -1 ? clientAreaHeight : height) / lineHeight;
2950 int scrollLines = Math.min(lineCount - caretLine - 1, lines);
2951 // ensure that scrollLines never gets negative and at least one
2952 // line is scrolled. fixes bug 5602.
2953 scrollLines = Math.max(1, scrollLines);
2954 int[] alignment = new int[1];
2955 int offset = getOffsetAtPoint(columnX, getLinePixel(caretLine + scrollLines), alignment);
2956 setCaretOffset(offset, alignment[0]);
2957 if (select) {
2958 doSelection(ST.COLUMN_NEXT);
2959 }
2960 // scroll one page down or to the bottom
2961 int verticalMaximum = lineCount * getVerticalIncrement();
2962 int pageSize = clientAreaHeight;
2963 int verticalScrollOffset = getVerticalScrollOffset();
2964 int scrollOffset = verticalScrollOffset + scrollLines * getVerticalIncrement();
2965 if (scrollOffset + pageSize > verticalMaximum) {
2966 scrollOffset = verticalMaximum - pageSize;
2967 }
2968 if (scrollOffset > verticalScrollOffset) {
2969 scrollVertical(scrollOffset - verticalScrollOffset, true);
2970 }
2971 }
2972 } else {
2973 int lineCount = content.getLineCount();
2974 int caretLine = getCaretLine();
2975 int lineIndex, lineHeight;
2976 if (height == -1) {
2977 lineIndex = getPartialBottomIndex();
2978 int topY = getLinePixel(lineIndex);
2979 lineHeight = renderer.getLineHeight(lineIndex);
2980 height = topY;
2981 if (topY + lineHeight <= clientAreaHeight) {
2982 height += lineHeight;
2983 } else {
2984 if (isWordWrap()) {
2985 TextLayout layout = renderer.getTextLayout(lineIndex);
2986 int y = clientAreaHeight - topY;
2987 for (int i = 0; i < layout.getLineCount(); i++) {
2988 Rectangle bounds = layout.getLineBounds(i);
2989 if (bounds.contains(bounds.x, y)) {
2990 height += bounds.y;
2991 break;
2992 }
2993 }
2994 renderer.disposeTextLayout(layout);
2995 }
2996 }
2997 } else {
2998 lineIndex = getLineIndex(height);
2999 int topLineY = getLinePixel(lineIndex);
3000 if (isWordWrap()) {
3001 TextLayout layout = renderer.getTextLayout(lineIndex);
3002 int y = height - topLineY;
3003 for (int i = 0; i < layout.getLineCount(); i++) {
3004 Rectangle bounds = layout.getLineBounds(i);
3005 if (bounds.contains(bounds.x, y)) {
3006 height = topLineY + bounds.y + bounds.height;
3007 break;
3008 }
3009 }
3010 renderer.disposeTextLayout(layout);
3011 } else {
3012 height = topLineY + renderer.getLineHeight(lineIndex);
3013 }
3014 }
3015 int caretHeight = height;
3016 if (isWordWrap()) {
3017 TextLayout layout = renderer.getTextLayout(caretLine);
3018 int offsetInLine = caretOffset - content.getOffsetAtLine(caretLine);
3019 lineIndex = getVisualLineIndex(layout, offsetInLine);
3020 caretHeight += layout.getLineBounds(lineIndex).y;
3021 renderer.disposeTextLayout(layout);
3022 }
3023 lineIndex = caretLine;
3024 lineHeight = renderer.getLineHeight(lineIndex);
3025 while (caretHeight - lineHeight >= 0 && lineIndex < lineCount - 1) {
3026 caretHeight -= lineHeight;
3027 lineHeight = renderer.getLineHeight(++lineIndex);
3028 }
3029 int[] alignment = new int[1];
3030 int offset = getOffsetAtPoint(columnX, caretHeight, lineIndex, alignment);
3031 setCaretOffset(offset, alignment[0]);
3032 if (select) doSelection(ST.COLUMN_NEXT);
3033 height = getAvailableHeightBellow(height);
3034 scrollVertical(height, true);
3035 if (height == 0) setCaretLocation();
3036 }
3037 showCaret();
3038 int hScrollChange = oldHScrollOffset - horizontalScrollOffset;
3039 columnX = oldColumnX + hScrollChange;
3040 }
3041 /**
3042 * Moves the cursor to the end of the last fully visible line.
3043 */
3044 void doPageEnd() {
3045 // go to end of line if in single line mode. fixes 5673
3046 if (isSingleLine()) {
3047 doLineEnd();
3048 } else {
3049 int bottomOffset;
3050 if (isWordWrap()) {
3051 int lineIndex = getPartialBottomIndex();
3052 TextLayout layout = renderer.getTextLayout(lineIndex);
3053 int y = (clientAreaHeight - bottomMargin) - getLinePixel(lineIndex);
3054 int index = layout.getLineCount() - 1;
3055 while (index >= 0) {
3056 Rectangle bounds = layout.getLineBounds(index);
3057 if (y >= bounds.y + bounds.height) break;
3058 index--;
3059 }
3060 if (index == -1 && lineIndex > 0) {
3061 bottomOffset = content.getOffsetAtLine(lineIndex - 1) + content.getLine(lineIndex - 1).length();
3062 } else {
3063 bottomOffset = content.getOffsetAtLine(lineIndex) + Math.max(0, layout.getLineOffsets()[index + 1] - 1);
3064 }
3065 renderer.disposeTextLayout(layout);
3066 } else {
3067 int lineIndex = getBottomIndex();
3068 bottomOffset = content.getOffsetAtLine(lineIndex) + content.getLine(lineIndex).length();
3069 }
3070 if (caretOffset < bottomOffset) {
3071 setCaretOffset(bottomOffset, OFFSET_LEADING);
3072 showCaret();
3073 }
3074 }
3075 }
3076 /**
3077 * Moves the cursor to the beginning of the first fully visible line.
3078 */
3079 void doPageStart() {
3080 int topOffset;
3081 if (isWordWrap()) {
3082 int y, lineIndex;
3083 if (topIndexY > 0) {
3084 lineIndex = topIndex - 1;
3085 y = renderer.getLineHeight(lineIndex) - topIndexY;
3086 } else {
3087 lineIndex = topIndex;
3088 y = -topIndexY;
3089 }
3090 TextLayout layout = renderer.getTextLayout(lineIndex);
3091 int index = 0;
3092 int lineCount = layout.getLineCount();
3093 while (index < lineCount) {
3094 Rectangle bounds = layout.getLineBounds(index);
3095 if (y <= bounds.y) break;
3096 index++;
3097 }
3098 if (index == lineCount) {
3099 topOffset = content.getOffsetAtLine(lineIndex + 1);
3100 } else {
3101 topOffset = content.getOffsetAtLine(lineIndex) + layout.getLineOffsets()[index];
3102 }
3103 renderer.disposeTextLayout(layout);
3104 } else {
3105 topOffset = content.getOffsetAtLine(topIndex);
3106 }
3107 if (caretOffset > topOffset) {
3108 setCaretOffset(topOffset, OFFSET_LEADING);
3109 showCaret();
3110 }
3111 }
3112 /**
3113 * Scrolls one page up so that the first line (truncated or whole)
3114 * of the current page becomes the fully visible last line.
3115 * The caret is scrolled the same number of lines so that its location
3116 * relative to the top line remains the same. The exception is the beginning
3117 * of the text where a full page scroll is not possible. In this case the
3118 * caret is moved in front of the first character.
3119 */
3120 void doPageUp(boolean select, int height) {
3121 if (isSingleLine()) return;
3122 int oldHScrollOffset = horizontalScrollOffset;
3123 int oldColumnX = columnX;
3124 if (isFixedLineHeight()) {
3125 int caretLine = getCaretLine();
3126 if (caretLine > 0) {
3127 int lineHeight = renderer.getLineHeight();
3128 int lines = (height == -1 ? clientAreaHeight : height) / lineHeight;
3129 int scrollLines = Math.max(1, Math.min(caretLine, lines));
3130 caretLine -= scrollLines;
3131 int[] alignment = new int[1];
3132 int offset = getOffsetAtPoint(columnX, getLinePixel(caretLine), alignment);
3133 setCaretOffset(offset, alignment[0]);
3134 if (select) {
3135 doSelection(ST.COLUMN_PREVIOUS);
3136 }
3137 int verticalScrollOffset = getVerticalScrollOffset();
3138 int scrollOffset = Math.max(0, verticalScrollOffset - scrollLines * getVerticalIncrement());
3139 if (scrollOffset < verticalScrollOffset) {
3140 scrollVertical(scrollOffset - verticalScrollOffset, true);
3141 }
3142 }
3143 } else {
3144 int caretLine = getCaretLine();
3145 int lineHeight, lineIndex;
3146 if (height == -1) {
3147 if (topIndexY == 0) {
3148 height = clientAreaHeight;
3149 } else {
3150 int y;
3151 if (topIndex > 0) {
3152 lineIndex = topIndex - 1;
3153 lineHeight = renderer.getLineHeight(lineIndex);
3154 height = clientAreaHeight - topIndexY;
3155 y = lineHeight - topIndexY;
3156 } else {
3157 lineIndex = topIndex;
3158 lineHeight = renderer.getLineHeight(lineIndex);
3159 height = clientAreaHeight - (lineHeight + topIndexY);
3160 y = -topIndexY;
3161 }
3162 if (isWordWrap()) {
3163 TextLayout layout = renderer.getTextLayout(lineIndex);
3164 for (int i = 0; i < layout.getLineCount(); i++) {
3165 Rectangle bounds = layout.getLineBounds(i);
3166 if (bounds.contains(bounds.x, y)) {
3167 height += lineHeight - (bounds.y + bounds.height);
3168 break;
3169 }
3170 }
3171 renderer.disposeTextLayout(layout);
3172 }
3173 }
3174 } else {
3175 lineIndex = getLineIndex(clientAreaHeight - height);
3176 int topLineY = getLinePixel(lineIndex);
3177 if (isWordWrap()) {
3178 TextLayout layout = renderer.getTextLayout(lineIndex);
3179 int y = topLineY;
3180 for (int i = 0; i < layout.getLineCount(); i++) {
3181 Rectangle bounds = layout.getLineBounds(i);
3182 if (bounds.contains(bounds.x, y)) {
3183 height = clientAreaHeight - (topLineY + bounds.y);
3184 break;
3185 }
3186 }
3187 renderer.disposeTextLayout(layout);
3188 } else {
3189 height = clientAreaHeight - topLineY;
3190 }
3191 }
3192 int caretHeight = height;
3193 if (isWordWrap()) {
3194 TextLayout layout = renderer.getTextLayout(caretLine);
3195 int offsetInLine = caretOffset - content.getOffsetAtLine(caretLine);
3196 lineIndex = getVisualLineIndex(layout, offsetInLine);
3197 caretHeight += layout.getBounds().height - layout.getLineBounds(lineIndex).y;
3198 renderer.disposeTextLayout(layout);
3199 }
3200 lineIndex = caretLine;
3201 lineHeight = renderer.getLineHeight(lineIndex);
3202 while (caretHeight - lineHeight >= 0 && lineIndex > 0) {
3203 caretHeight -= lineHeight;
3204 lineHeight = renderer.getLineHeight(--lineIndex);
3205 }
3206 lineHeight = renderer.getLineHeight(lineIndex);
3207 int[] alignment = new int[1];
3208 int offset = getOffsetAtPoint(columnX, lineHeight - caretHeight, lineIndex, alignment);
3209 setCaretOffset(offset, alignment[0]);
3210 if (select) doSelection(ST.COLUMN_PREVIOUS);
3211 height = getAvailableHeightAbove(height);
3212 scrollVertical(-height, true);
3213 if (height == 0) setCaretLocation();
3214 }
3215 showCaret();
3216 int hScrollChange = oldHScrollOffset - horizontalScrollOffset;
3217 columnX = oldColumnX + hScrollChange;
3218 }
3219 /**
3220 * Updates the selection to extend to the current caret position.
3221 */
3222 void doSelection(int direction) {
3223 int redrawStart = -1;
3224 int redrawEnd = -1;
3225 if (selectionAnchor == -1) {
3226 selectionAnchor = selection.x;
3227 }
3228 if (direction == ST.COLUMN_PREVIOUS) {
3229 if (caretOffset < selection.x) {
3230 // grow selection
3231 redrawEnd = selection.x;
3232 redrawStart = selection.x = caretOffset;
3233 // check if selection has reversed direction
3234 if (selection.y != selectionAnchor) {
3235 redrawEnd = selection.y;
3236 selection.y = selectionAnchor;
3237 }
3238 // test whether selection actually changed. Fixes 1G71EO1
3239 } else if (selectionAnchor == selection.x && caretOffset < selection.y) {
3240 // caret moved towards selection anchor (left side of selection).
3241 // shrink selection
3242 redrawEnd = selection.y;
3243 redrawStart = selection.y = caretOffset;
3244 }
3245 } else {
3246 if (caretOffset > selection.y) {
3247 // grow selection
3248 redrawStart = selection.y;
3249 redrawEnd = selection.y = caretOffset;
3250 // check if selection has reversed direction
3251 if (selection.x != selectionAnchor) {
3252 redrawStart = selection.x;
3253 selection.x = selectionAnchor;
3254 }
3255 // test whether selection actually changed. Fixes 1G71EO1
3256 } else if (selectionAnchor == selection.y && caretOffset > selection.x) {
3257 // caret moved towards selection anchor (right side of selection).
3258 // shrink selection
3259 redrawStart = selection.x;
3260 redrawEnd = selection.x = caretOffset;
3261 }
3262 }
3263 if (redrawStart != -1 && redrawEnd != -1) {
3264 internalRedrawRange(redrawStart, redrawEnd - redrawStart);
3265 sendSelectionEvent();
3266 }
3267 sendAccessibleTextCaretMoved();
3268 }
3269 /**
3270 * Moves the caret to the next character or to the beginning of the
3271 * next line if the cursor is at the end of a line.
3272 */
3273 void doSelectionCursorNext() {
3274 int caretLine = getCaretLine();
3275 int lineOffset = content.getOffsetAtLine(caretLine);
3276 int offsetInLine = caretOffset - lineOffset;
3277 int offset, alignment;
3278 if (offsetInLine < content.getLine(caretLine).length()) {
3279 TextLayout layout = renderer.getTextLayout(caretLine);
3280 offsetInLine = layout.getNextOffset(offsetInLine, SWT.MOVEMENT_CLUSTER);
3281 int lineStart = layout.getLineOffsets()[layout.getLineIndex(offsetInLine)];
3282 renderer.disposeTextLayout(layout);
3283 offset = offsetInLine + lineOffset;
3284 alignment = offsetInLine == lineStart ? OFFSET_LEADING : PREVIOUS_OFFSET_TRAILING;
3285 setCaretOffset(offset, alignment);
3286 showCaret();
3287 } else if (caretLine < content.getLineCount() - 1 && !isSingleLine()) {
3288 caretLine++;
3289 offset = content.getOffsetAtLine(caretLine);
3290 alignment = PREVIOUS_OFFSET_TRAILING;
3291 setCaretOffset(offset, alignment);
3292 showCaret();
3293 }
3294 }
3295 /**
3296 * Moves the caret to the previous character or to the end of the previous
3297 * line if the cursor is at the beginning of a line.
3298 */
3299 void doSelectionCursorPrevious() {
3300 int caretLine = getCaretLine();
3301 int lineOffset = content.getOffsetAtLine(caretLine);
3302 int offsetInLine = caretOffset - lineOffset;
3303 if (offsetInLine > 0) {
3304 int offset = getClusterPrevious(caretOffset, caretLine);
3305 setCaretOffset(offset, OFFSET_LEADING);
3306 showCaret();
3307 } else if (caretLine > 0) {
3308 caretLine--;
3309 lineOffset = content.getOffsetAtLine(caretLine);
3310 int offset = lineOffset + content.getLine(caretLine).length();
3311 setCaretOffset(offset, OFFSET_LEADING);
3312 showCaret();
3313 }
3314 }
3315 /**
3316 * Moves the caret one line down and to the same character offset relative
3317 * to the beginning of the line. Moves the caret to the end of the new line
3318 * if the new line is shorter than the character offset.
3319 * Moves the caret to the end of the text if the caret already is on the
3320 * last line.
3321 * Adjusts the selection according to the caret change. This can either add
3322 * to or subtract from the old selection, depending on the previous selection
3323 * direction.
3324 */
3325 void doSelectionLineDown() {
3326 int oldColumnX = columnX = getPointAtOffset(caretOffset).x;
3327 doLineDown(true);
3328 columnX = oldColumnX;
3329 }
3330 /**
3331 * Moves the caret one line up and to the same character offset relative
3332 * to the beginning of the line. Moves the caret to the end of the new line
3333 * if the new line is shorter than the character offset.
3334 * Moves the caret to the beginning of the document if it is already on the
3335 * first line.
3336 * Adjusts the selection according to the caret change. This can either add
3337 * to or subtract from the old selection, depending on the previous selection
3338 * direction.
3339 */
3340 void doSelectionLineUp() {
3341 int oldColumnX = columnX = getPointAtOffset(caretOffset).x;
3342 doLineUp(true);
3343 columnX = oldColumnX;
3344 }
3345 /**
3346 * Scrolls one page down so that the last line (truncated or whole)
3347 * of the current page becomes the fully visible top line.
3348 * <p>
3349 * The caret is scrolled the same number of lines so that its location
3350 * relative to the top line remains the same. The exception is the end
3351 * of the text where a full page scroll is not possible. In this case
3352 * the caret is moved after the last character.
3353 * </p><p>
3354 * Adjusts the selection according to the caret change. This can either add
3355 * to or subtract from the old selection, depending on the previous selection
3356 * direction.
3357 * </p>
3358 */
3359 void doSelectionPageDown(int pixels) {
3360 int oldColumnX = columnX = getPointAtOffset(caretOffset).x;
3361 doPageDown(true, pixels);
3362 columnX = oldColumnX;
3363 }
3364 /**
3365 * Scrolls one page up so that the first line (truncated or whole)
3366 * of the current page becomes the fully visible last line.
3367 * <p>
3368 * The caret is scrolled the same number of lines so that its location
3369 * relative to the top line remains the same. The exception is the beginning
3370 * of the text where a full page scroll is not possible. In this case the
3371 * caret is moved in front of the first character.
3372 * </p><p>
3373 * Adjusts the selection according to the caret change. This can either add
3374 * to or subtract from the old selection, depending on the previous selection
3375 * direction.
3376 * </p>
3377 */
3378 void doSelectionPageUp(int pixels) {
3379 int oldColumnX = columnX = getPointAtOffset(caretOffset).x;
3380 doPageUp(true, pixels);
3381 columnX = oldColumnX;
3382 }
3383 /**
3384 * Moves the caret to the end of the next word .
3385 */
3386 void doSelectionWordNext() {
3387 int offset = getWordNext(caretOffset, SWT.MOVEMENT_WORD);
3388 // don't change caret position if in single line mode and the cursor
3389 // would be on a different line. fixes 5673
3390 if (!isSingleLine() ||
3391 content.getLineAtOffset(caretOffset) == content.getLineAtOffset(offset)) {
3392 // Force symmetrical movement for word next and previous. Fixes 14536
3393 setCaretOffset(offset, OFFSET_LEADING);
3394 showCaret();
3395 }
3396 }
3397 /**
3398 * Moves the caret to the start of the previous word.
3399 */
3400 void doSelectionWordPrevious() {
3401 int offset = getWordPrevious(caretOffset, SWT.MOVEMENT_WORD);
3402 setCaretOffset(offset, OFFSET_LEADING);
3403 showCaret();
3404 }
3405 /**
3406 * Moves the caret one character to the left. Do not go to the previous line.
3407 * When in a bidi locale and at a R2L character the caret is moved to the
3408 * beginning of the R2L segment (visually right) and then one character to the
3409 * left (visually left because it's now in a L2R segment).
3410 */
3411 void doVisualPrevious() {
3412 int offset = getClusterPrevious(caretOffset, getCaretLine());
3413 setCaretOffset(offset, SWT.DEFAULT);
3414 showCaret();
3415 }
3416 /**
3417 * Moves the caret one character to the right. Do not go to the next line.
3418 * When in a bidi locale and at a R2L character the caret is moved to the
3419 * end of the R2L segment (visually left) and then one character to the
3420 * right (visually right because it's now in a L2R segment).
3421 */
3422 void doVisualNext() {
3423 int offset = getClusterNext(caretOffset, getCaretLine());
3424 setCaretOffset(offset, SWT.DEFAULT);
3425 showCaret();
3426 }
3427 /**
3428 * Moves the caret to the end of the next word.
3429 * If a selection exists, move the caret to the end of the selection
3430 * and remove the selection.
3431 */
3432 void doWordNext() {
3433 if (selection.y - selection.x > 0) {
3434 setCaretOffset(selection.y, SWT.DEFAULT);
3435 showCaret();
3436 } else {
3437 doSelectionWordNext();
3438 }
3439 }
3440 /**
3441 * Moves the caret to the start of the previous word.
3442 * If a selection exists, move the caret to the start of the selection
3443 * and remove the selection.
3444 */
3445 void doWordPrevious() {
3446 if (selection.y - selection.x > 0) {
3447 setCaretOffset(selection.x, SWT.DEFAULT);
3448 showCaret();
3449 } else {
3450 doSelectionWordPrevious();
3451 }
3452 }
3453 /**
3454 * Ends the autoscroll process.
3455 */
3456 void endAutoScroll() {
3457 autoScrollDirection = SWT.NULL;
3458 }
3459 @Override
3460 public Color getBackground() {
3461 checkWidget();
3462 if (background == null) {
3463 return getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND);
3464 }
3465 return background;
3466 }
3467 /**
3468 * Returns the baseline, in points.
3469 *
3470 * Note: this API should not be used if a StyleRange attribute causes lines to
3471 * have different heights (i.e. different fonts, rise, etc).
3472 *
3473 * @return baseline the baseline
3474 * @exception SWTException <ul>
3475 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3476 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3477 * </ul>
3478 * @since 3.0
3479 *
3480 * @see #getBaseline(int)
3481 */
3482 public int getBaseline() {
3483 checkWidget();
3484 return renderer.getBaseline();
3485 }
3486 /**
3487 * Returns the baseline at the given offset, in points.
3488 *
3489 * @param offset the offset
3490 *
3491 * @return baseline the baseline
3492 *
3493 * @exception SWTException <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_RANGE when the offset is outside the valid range (< 0 or > getCharCount())</li>
3499 * </ul>
3500 *
3501 * @since 3.2
3502 */
3503 public int getBaseline(int offset) {
3504 checkWidget();
3505 if (!(0 <= offset && offset <= content.getCharCount())) {
3506 SWT.error(SWT.ERROR_INVALID_RANGE);
3507 }
3508 if (isFixedLineHeight()) {
3509 return renderer.getBaseline();
3510 }
3511 int lineIndex = content.getLineAtOffset(offset);
3512 int lineOffset = content.getOffsetAtLine(lineIndex);
3513 TextLayout layout = renderer.getTextLayout(lineIndex);
3514 int lineInParagraph = layout.getLineIndex(Math.min(offset - lineOffset, layout.getText().length()));
3515 FontMetrics metrics = layout.getLineMetrics(lineInParagraph);
3516 renderer.disposeTextLayout(layout);
3517 return metrics.getAscent() + metrics.getLeading();
3518 }
3519 /**
3520 * Gets the BIDI coloring mode. When true the BIDI text display
3521 * algorithm is applied to segments of text that are the same
3522 * color.
3523 *
3524 * @return the current coloring mode
3525 * @exception SWTException <ul>
3526 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3527 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3528 * </ul>
3529 *
3530 * @deprecated use BidiSegmentListener instead.
3531 */
3532 @Deprecated
3533 public boolean getBidiColoring() {
3534 checkWidget();
3535 return bidiColoring;
3536 }
3537 /**
3538 * Returns whether the widget is in block selection mode.
3539 *
3540 * @return true if widget is in block selection mode, false otherwise
3541 *
3542 * @exception SWTException <ul>
3543 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3544 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3545 * </ul>
3546 *
3547 * @since 3.5
3548 */
3549 public boolean getBlockSelection() {
3550 checkWidget();
3551 return blockSelection;
3552 }
3553 Rectangle getBlockSelectionPosition() {
3554 int firstLine = getLineIndex(blockYAnchor - getVerticalScrollOffset());
3555 int lastLine = getLineIndex(blockYLocation - getVerticalScrollOffset());
3556 if (firstLine > lastLine) {
3557 int temp = firstLine;
3558 firstLine = lastLine;
3559 lastLine = temp;
3560 }
3561 int left = blockXAnchor;
3562 int right = blockXLocation;
3563 if (left > right) {
3564 left = blockXLocation;
3565 right = blockXAnchor;
3566 }
3567 return new Rectangle (left - horizontalScrollOffset, firstLine, right - horizontalScrollOffset, lastLine);
3568 }
3569 /**
3570 * Returns the block selection bounds. The bounds is
3571 * relative to the upper left corner of the document.
3572 *
3573 * @return the block selection bounds
3574 *
3575 * @exception SWTException <ul>
3576 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3577 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3578 * </ul>
3579 *
3580 * @since 3.5
3581 */
3582 public Rectangle getBlockSelectionBounds() {
3583 Rectangle rect;
3584 if (blockSelection && blockXLocation != -1) {
3585 rect = getBlockSelectionRectangle();
3586 } else {
3587 Point startPoint = getPointAtOffset(selection.x);
3588 Point endPoint = getPointAtOffset(selection.y);
3589 int height = getLineHeight(selection.y);
3590 rect = new Rectangle(startPoint.x, startPoint.y, endPoint.x - startPoint.x, endPoint.y + height - startPoint.y);
3591 if (selection.x == selection.y) {
3592 rect.width = getCaretWidth();
3593 }
3594 }
3595 rect.x += horizontalScrollOffset;
3596 rect.y += getVerticalScrollOffset();
3597 return rect;
3598 }
3599 Rectangle getBlockSelectionRectangle() {
3600 Rectangle rect = getBlockSelectionPosition();
3601 rect.y = getLinePixel(rect.y);
3602 rect.width = rect.width - rect.x;
3603 rect.height = getLinePixel(rect.height + 1) - rect.y;
3604 return rect;
3605 }
3606 String getBlockSelectionText(String delimiter) {
3607 Rectangle rect = getBlockSelectionPosition();
3608 int firstLine = rect.y;
3609 int lastLine = rect.height;
3610 int left = rect.x;
3611 int right = rect.width;
3612 StringBuilder buffer = new StringBuilder();
3613 for (int lineIndex = firstLine; lineIndex <= lastLine; lineIndex++) {
3614 int start = getOffsetAtPoint(left, 0, lineIndex, null);
3615 int end = getOffsetAtPoint(right, 0, lineIndex, null);
3616 if (start > end) {
3617 int temp = start;
3618 start = end;
3619 end = temp;
3620 }
3621 String text = content.getTextRange(start, end - start);
3622 buffer.append(text);
3623 if (lineIndex < lastLine) buffer.append(delimiter);
3624 }
3625 return buffer.toString();
3626 }
3627 /**
3628 * Returns the index of the last fully visible line.
3629 *
3630 * @return index of the last fully visible line.
3631 */
3632 int getBottomIndex() {
3633 int bottomIndex;
3634 if (isFixedLineHeight()) {
3635 int lineCount = 1;
3636 int lineHeight = renderer.getLineHeight();
3637 if (lineHeight != 0) {
3638 // calculate the number of lines that are fully visible
3639 int partialTopLineHeight = topIndex * lineHeight - getVerticalScrollOffset();
3640 lineCount = (clientAreaHeight - partialTopLineHeight) / lineHeight;
3641 }
3642 bottomIndex = Math.min(content.getLineCount() - 1, topIndex + Math.max(0, lineCount - 1));
3643 } else {
3644 int clientAreaHeight = this.clientAreaHeight - bottomMargin;
3645 bottomIndex = getLineIndex(clientAreaHeight);
3646 if (bottomIndex > 0) {
3647 int linePixel = getLinePixel(bottomIndex);
3648 int lineHeight = renderer.getLineHeight(bottomIndex);
3649 if (linePixel + lineHeight > clientAreaHeight) {
3650 if (getLinePixel(bottomIndex - 1) >= topMargin) {
3651 bottomIndex--;
3652 }
3653 }
3654 }
3655 }
3656 return bottomIndex;
3657 }
3658 /**
3659 * Returns the bottom margin.
3660 *
3661 * @return the bottom margin.
3662 * @exception SWTException <ul>
3663 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3664 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3665 * </ul>
3666 *
3667 * @since 3.5
3668 */
3669 public int getBottomMargin() {
3670 checkWidget();
3671 return bottomMargin;
3672 }
3673 Rectangle getBoundsAtOffset(int offset) {
3674 int lineIndex = content.getLineAtOffset(offset);
3675 int lineOffset = content.getOffsetAtLine(lineIndex);
3676 String line = content.getLine(lineIndex);
3677 Rectangle bounds;
3678 if (line.length() != 0) {
3679 TextLayout layout = renderer.getTextLayout(lineIndex);
3680 int offsetInLine = Math.min (layout.getText().length(), Math.max (0, offset - lineOffset));
3681 bounds = layout.getBounds(offsetInLine, offsetInLine);
3682 if (getListeners(ST.LineGetSegments).length > 0 && caretAlignment == PREVIOUS_OFFSET_TRAILING && offsetInLine != 0) {
3683 offsetInLine = layout.getPreviousOffset(offsetInLine, SWT.MOVEMENT_CLUSTER);
3684 Point point = layout.getLocation(offsetInLine, true);
3685 bounds = new Rectangle (point.x, point.y, 0, bounds.height);
3686 }
3687 renderer.disposeTextLayout(layout);
3688 } else {
3689 bounds = new Rectangle (0, 0, 0, renderer.getLineHeight());
3690 }
3691 if (offset == caretOffset && !isWordWrap()) {
3692 int lineEnd = lineOffset + line.length();
3693 if (offset == lineEnd) {
3694 bounds.width += getCaretWidth();
3695 }
3696 }
3697 bounds.x += leftMargin - horizontalScrollOffset;
3698 bounds.y += getLinePixel(lineIndex);
3699 return bounds;
3700 }
3701 /**
3702 * Returns the caret position relative to the start of the text.
3703 *
3704 * @return the caret position relative to the start of the text.
3705 * @exception SWTException <ul>
3706 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3707 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3708 * </ul>
3709 */
3710 public int getCaretOffset() {
3711 checkWidget();
3712 return caretOffset;
3713 }
3714 /**
3715 * Returns the caret width.
3716 *
3717 * @return the caret width, 0 if caret is null.
3718 */
3719 int getCaretWidth() {
3720 Caret caret = getCaret();
3721 if (caret == null) return 0;
3722 return caret.getSize().x;
3723 }
3724 Object getClipboardContent(int clipboardType) {
3725 TextTransfer plainTextTransfer = TextTransfer.getInstance();
3726 return clipboard.getContents(plainTextTransfer, clipboardType);
3727 }
3728 int getClusterNext(int offset, int lineIndex) {
3729 int lineOffset = content.getOffsetAtLine(lineIndex);
3730 TextLayout layout = renderer.getTextLayout(lineIndex);
3731 offset -= lineOffset;
3732 offset = layout.getNextOffset(offset, SWT.MOVEMENT_CLUSTER);
3733 offset += lineOffset;
3734 renderer.disposeTextLayout(layout);
3735 return offset;
3736 }
3737 int getClusterPrevious(int offset, int lineIndex) {
3738 int lineOffset = content.getOffsetAtLine(lineIndex);
3739 TextLayout layout = renderer.getTextLayout(lineIndex);
3740 offset -= lineOffset;
3741 offset = layout.getPreviousOffset(offset, SWT.MOVEMENT_CLUSTER);
3742 offset += lineOffset;
3743 renderer.disposeTextLayout(layout);
3744 return offset;
3745 }
3746 /**
3747 * Returns the content implementation that is used for text storage.
3748 *
3749 * @return content the user defined content implementation that is used for
3750 * text storage or the default content implementation if no user defined
3751 * content implementation has been set.
3752 * @exception SWTException <ul>
3753 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3754 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3755 * </ul>
3756 */
3757 public StyledTextContent getContent() {
3758 checkWidget();
3759 return content;
3760 }
3761 @Override
3762 public boolean getDragDetect () {
3763 checkWidget ();
3764 return dragDetect;
3765 }
3766 /**
3767 * Returns whether the widget implements double click mouse behavior.
3768 *
3769 * @return true if double clicking a word selects the word, false if double clicks
3770 * have the same effect as regular mouse clicks
3771 * @exception SWTException <ul>
3772 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3773 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3774 * </ul>
3775 */
3776 public boolean getDoubleClickEnabled() {
3777 checkWidget();
3778 return doubleClickEnabled;
3779 }
3780 /**
3781 * Returns whether the widget content can be edited.
3782 *
3783 * @return true if content can be edited, false otherwise
3784 * @exception SWTException <ul>
3785 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3786 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3787 * </ul>
3788 */
3789 public boolean getEditable() {
3790 checkWidget();
3791 return editable;
3792 }
3793 @Override
3794 public Color getForeground() {
3795 checkWidget();
3796 if (foreground == null) {
3797 return getDisplay().getSystemColor(SWT.COLOR_LIST_FOREGROUND);
3798 }
3799 return foreground;
3800 }
3801 /**
3802 * Returns the horizontal scroll increment.
3803 *
3804 * @return horizontal scroll increment.
3805 */
3806 int getHorizontalIncrement() {
3807 return renderer.averageCharWidth;
3808 }
3809 /**
3810 * Returns the horizontal scroll offset relative to the start of the line.
3811 *
3812 * @return horizontal scroll offset relative to the start of the line,
3813 * measured in character increments starting at 0, if > 0 the content is scrolled
3814 * @exception SWTException <ul>
3815 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3816 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3817 * </ul>
3818 */
3819 public int getHorizontalIndex() {
3820 checkWidget();
3821 return horizontalScrollOffset / getHorizontalIncrement();
3822 }
3823 /**
3824 * Returns the horizontal scroll offset relative to the start of the line.
3825 *
3826 * @return the horizontal scroll offset relative to the start of the line,
3827 * measured in SWT logical point starting at 0, if > 0 the content is scrolled.
3828 * @exception SWTException <ul>
3829 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3830 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3831 * </ul>
3832 */
3833 public int getHorizontalPixel() {
3834 checkWidget();
3835 return horizontalScrollOffset;
3836 }
3837 /**
3838 * Returns the line indentation of the widget.
3839 *
3840 * @return the line indentation
3841 *
3842 * @exception SWTException <ul>
3843 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3844 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3845 * </ul>
3846 *
3847 * @see #getLineIndent(int)
3848 *
3849 * @since 3.2
3850 */
3851 public int getIndent() {
3852 checkWidget();
3853 return indent;
3854 }
3855 /**
3856 * Returns whether the widget justifies lines.
3857 *
3858 * @return whether lines are justified
3859 *
3860 * @exception SWTException <ul>
3861 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3862 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3863 * </ul>
3864 *
3865 * @see #getLineJustify(int)
3866 *
3867 * @since 3.2
3868 */
3869 public boolean getJustify() {
3870 checkWidget();
3871 return justify;
3872 }
3873 /**
3874 * Returns the action assigned to the key.
3875 * Returns SWT.NULL if there is no action associated with the key.
3876 *
3877 * @param key a key code defined in SWT.java or a character.
3878 * Optionally ORd with a state mask. Preferred state masks are one or more of
3879 * SWT.MOD1, SWT.MOD2, SWT.MOD3, since these masks account for modifier platform
3880 * differences. However, there may be cases where using the specific state masks
3881 * (i.e., SWT.CTRL, SWT.SHIFT, SWT.ALT, SWT.COMMAND) makes sense.
3882 * @return one of the predefined actions defined in ST.java or SWT.NULL
3883 * if there is no action associated with the key.
3884 * @exception SWTException <ul>
3885 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3886 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3887 * </ul>
3888 */
3889 public int getKeyBinding(int key) {
3890 checkWidget();
3891 Integer action = keyActionMap.get(key);
3892 return action == null ? SWT.NULL : action.intValue();
3893 }
3894 /**
3895 * Gets the number of characters.
3896 *
3897 * @return number of characters in the widget
3898 * @exception SWTException <ul>
3899 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3900 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3901 * </ul>
3902 */
3903 public int getCharCount() {
3904 checkWidget();
3905 return content.getCharCount();
3906 }
3907 /**
3908 * Returns the line at the given line index without delimiters.
3909 * Index 0 is the first line of the content. When there are not
3910 * any lines, getLine(0) is a valid call that answers an empty string.
3911 * <p>
3912 *
3913 * @param lineIndex index of the line to return.
3914 * @return the line text without delimiters
3915 *
3916 * @exception SWTException <ul>
3917 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3918 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3919 * </ul>
3920 * @exception IllegalArgumentException <ul>
3921 * <li>ERROR_INVALID_RANGE when the line index is outside the valid range (< 0 or >= getLineCount())</li>
3922 * </ul>
3923 * @since 3.4
3924 */
3925 public String getLine(int lineIndex) {
3926 checkWidget();
3927 if (lineIndex < 0 ||
3928 (lineIndex > 0 && lineIndex >= content.getLineCount())) {
3929 SWT.error(SWT.ERROR_INVALID_RANGE);
3930 }
3931 return content.getLine(lineIndex);
3932 }
3933 /**
3934 * Returns the alignment of the line at the given index.
3935 *
3936 * @param index the index of the line
3937 *
3938 * @return the line alignment
3939 *
3940 * @exception SWTException <ul>
3941 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3942 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3943 * </ul>
3944 * @exception IllegalArgumentException <ul>
3945 * <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
3946 * </ul>
3947 *
3948 * @see #getAlignment()
3949 *
3950 * @since 3.2
3951 */
getLineAlignment(int index)3952 public int getLineAlignment(int index) {
3953 checkWidget();
3954 if (index < 0 || index > content.getLineCount()) {
3955 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
3956 }
3957 return renderer.getLineAlignment(index, alignment);
3958 }
3959 /**
3960 * Returns the line at the specified offset in the text
3961 * where 0 < offset < getCharCount() so that getLineAtOffset(getCharCount())
3962 * returns the line of the insert location.
3963 *
3964 * @param offset offset relative to the start of the content.
3965 * 0 <= offset <= getCharCount()
3966 * @return line at the specified offset in the text
3967 * @exception SWTException <ul>
3968 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3969 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3970 * </ul>
3971 * @exception IllegalArgumentException <ul>
3972 * <li>ERROR_INVALID_RANGE when the offset is outside the valid range (< 0 or > getCharCount())</li>
3973 * </ul>
3974 */
getLineAtOffset(int offset)3975 public int getLineAtOffset(int offset) {
3976 checkWidget();
3977 if (offset < 0 || offset > getCharCount()) {
3978 SWT.error(SWT.ERROR_INVALID_RANGE);
3979 }
3980 return content.getLineAtOffset(offset);
3981 }
3982 /**
3983 * Returns the background color of the line at the given index.
3984 * Returns null if a LineBackgroundListener has been set or if no background
3985 * color has been specified for the line. Should not be called if a
3986 * LineBackgroundListener has been set since the listener maintains the
3987 * line background colors.
3988 *
3989 * @param index the index of the line
3990 * @return the background color of the line at the given index.
3991 *
3992 * @exception SWTException <ul>
3993 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
3994 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
3995 * </ul>
3996 * @exception IllegalArgumentException <ul>
3997 * <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
3998 * </ul>
3999 */
getLineBackground(int index)4000 public Color getLineBackground(int index) {
4001 checkWidget();
4002 if (index < 0 || index > content.getLineCount()) {
4003 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
4004 }
4005 return isListening(ST.LineGetBackground) ? null : renderer.getLineBackground(index, null);
4006 }
4007 /**
4008 * Returns the bullet of the line at the given index.
4009 *
4010 * @param index the index of the line
4011 *
4012 * @return the line bullet
4013 *
4014 * @exception SWTException <ul>
4015 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4016 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4017 * </ul>
4018 * @exception IllegalArgumentException <ul>
4019 * <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
4020 * </ul>
4021 *
4022 * @since 3.2
4023 */
getLineBullet(int index)4024 public Bullet getLineBullet(int index) {
4025 checkWidget();
4026 if (index < 0 || index > content.getLineCount()) {
4027 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
4028 }
4029 return isListening(ST.LineGetStyle) ? null : renderer.getLineBullet(index, null);
4030 }
4031 /**
4032 * Returns the line background data for the given line or null if
4033 * there is none.
4034 *
4035 * @param lineOffset offset of the line start relative to the start
4036 * of the content.
4037 * @param line line to get line background data for
4038 * @return line background data for the given line.
4039 */
getLineBackgroundData(int lineOffset, String line)4040 StyledTextEvent getLineBackgroundData(int lineOffset, String line) {
4041 return sendLineEvent(ST.LineGetBackground, lineOffset, line);
4042 }
4043 /**
4044 * Gets the number of text lines.
4045 *
4046 * @return the number of lines in the widget
4047 * @exception SWTException <ul>
4048 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4049 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4050 * </ul>
4051 */
getLineCount()4052 public int getLineCount() {
4053 checkWidget();
4054 return content.getLineCount();
4055 }
4056 /**
4057 * Returns the number of lines that can be completely displayed in the
4058 * widget client area.
4059 *
4060 * @return number of lines that can be completely displayed in the widget
4061 * client area.
4062 */
getLineCountWhole()4063 int getLineCountWhole() {
4064 if (isFixedLineHeight()) {
4065 int lineHeight = renderer.getLineHeight();
4066 return lineHeight != 0 ? clientAreaHeight / lineHeight : 1;
4067 }
4068 return getBottomIndex() - topIndex + 1;
4069 }
4070 /**
4071 * Returns the line delimiter used for entering new lines by key down
4072 * or paste operation.
4073 *
4074 * @return line delimiter used for entering new lines by key down
4075 * or paste operation.
4076 * @exception SWTException <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 */
getLineDelimiter()4081 public String getLineDelimiter() {
4082 checkWidget();
4083 return content.getLineDelimiter();
4084 }
4085 /**
4086 * Returns the line height.
4087 * <p>
4088 * Note: this API should not be used if a StyleRange attribute causes lines to
4089 * have different heights (i.e. different fonts, rise, etc).
4090 * </p>
4091 *
4092 * @return line height in points.
4093 * @exception SWTException <ul>
4094 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4095 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4096 * </ul>
4097 * @see #getLineHeight(int)
4098 */
getLineHeight()4099 public int getLineHeight() {
4100 checkWidget();
4101 return renderer.getLineHeight();
4102 }
4103 /**
4104 * Returns the line height at the given offset.
4105 *
4106 * @param offset the offset
4107 *
4108 * @return line height in points
4109 *
4110 * @exception SWTException <ul>
4111 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4112 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4113 * </ul>
4114 * @exception IllegalArgumentException <ul>
4115 * <li>ERROR_INVALID_RANGE when the offset is outside the valid range (< 0 or > getCharCount())</li>
4116 * </ul>
4117 *
4118 * @since 3.2
4119 */
getLineHeight(int offset)4120 public int getLineHeight(int offset) {
4121 checkWidget();
4122 if (!(0 <= offset && offset <= content.getCharCount())) {
4123 SWT.error(SWT.ERROR_INVALID_RANGE);
4124 }
4125 if (isFixedLineHeight()) {
4126 return renderer.getLineHeight();
4127 }
4128 int lineIndex = content.getLineAtOffset(offset);
4129 int lineOffset = content.getOffsetAtLine(lineIndex);
4130 TextLayout layout = renderer.getTextLayout(lineIndex);
4131 int lineInParagraph = layout.getLineIndex(Math.min(offset - lineOffset, layout.getText().length()));
4132 int height = layout.getLineBounds(lineInParagraph).height;
4133 renderer.disposeTextLayout(layout);
4134 return height;
4135 }
4136 /**
4137 * Returns the indentation of the line at the given index.
4138 *
4139 * @param index the index of the line
4140 *
4141 * @return the line indentation
4142 *
4143 * @exception SWTException <ul>
4144 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4145 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4146 * </ul>
4147 * @exception IllegalArgumentException <ul>
4148 * <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
4149 * </ul>
4150 *
4151 * @see #getIndent()
4152 *
4153 * @since 3.2
4154 */
getLineIndent(int index)4155 public int getLineIndent(int index) {
4156 checkWidget();
4157 if (index < 0 || index > content.getLineCount()) {
4158 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
4159 }
4160 return isListening(ST.LineGetStyle) ? 0 : renderer.getLineIndent(index, indent);
4161 }
4162 /**
4163 * Returns the vertical indentation of the line at the given index.
4164 *
4165 * @param index the index of the line
4166 *
4167 * @return the line vertical indentation
4168 *
4169 * @exception SWTException <ul>
4170 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4171 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4172 * </ul>
4173 * @exception IllegalArgumentException <ul>
4174 * <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
4175 * </ul>
4176 *
4177 * @since 3.109
4178 */
getLineVerticalIndent(int index)4179 public int getLineVerticalIndent(int index) {
4180 checkWidget();
4181 if (index < 0 || index >= content.getLineCount()) {
4182 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
4183 }
4184 return isListening(ST.LineGetStyle) ? 0 : renderer.getLineVerticalIndent(index);
4185 }
4186 /**
4187 * Returns whether the line at the given index is justified.
4188 *
4189 * @param index the index of the line
4190 *
4191 * @return whether the line is justified
4192 *
4193 * @exception SWTException <ul>
4194 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4195 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4196 * </ul>
4197 * @exception IllegalArgumentException <ul>
4198 * <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
4199 * </ul>
4200 *
4201 * @see #getJustify()
4202 *
4203 * @since 3.2
4204 */
getLineJustify(int index)4205 public boolean getLineJustify(int index) {
4206 checkWidget();
4207 if (index < 0 || index > content.getLineCount()) {
4208 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
4209 }
4210 return isListening(ST.LineGetStyle) ? false : renderer.getLineJustify(index, justify);
4211 }
4212 /**
4213 * Returns the line spacing of the widget.
4214 *
4215 * @return the line spacing
4216 *
4217 * @exception SWTException <ul>
4218 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4219 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4220 * </ul>
4221 *
4222 * @since 3.2
4223 */
getLineSpacing()4224 public int getLineSpacing() {
4225 checkWidget();
4226 return lineSpacing;
4227 }
4228 /**
4229 * Returns the line style data for the given line or null if there is
4230 * none.
4231 * <p>
4232 * If there is a LineStyleListener but it does not set any styles,
4233 * the StyledTextEvent.styles field will be initialized to an empty
4234 * array.
4235 * </p>
4236 *
4237 * @param lineOffset offset of the line start relative to the start of
4238 * the content.
4239 * @param line line to get line styles for
4240 * @return line style data for the given line. Styles may start before
4241 * line start and end after line end
4242 */
getLineStyleData(int lineOffset, String line)4243 StyledTextEvent getLineStyleData(int lineOffset, String line) {
4244 return sendLineEvent(ST.LineGetStyle, lineOffset, line);
4245 }
4246 /**
4247 * Returns the top SWT logical point, relative to the client area, of a given line.
4248 * Clamps out of ranges index.
4249 *
4250 * @param lineIndex the line index, the max value is lineCount. If
4251 * lineIndex == lineCount it returns the bottom SWT logical point of the last line.
4252 * It means this function can be used to retrieve the bottom SWT logical point of any line.
4253 *
4254 * @return the top SWT logical point of a given line index
4255 *
4256 * @since 3.2
4257 */
getLinePixel(int lineIndex)4258 public int getLinePixel(int lineIndex) {
4259 checkWidget();
4260 int lineCount = content.getLineCount();
4261 lineIndex = Math.max(0, Math.min(lineCount, lineIndex));
4262 if (isFixedLineHeight()) {
4263 int lineHeight = renderer.getLineHeight();
4264 return lineIndex * lineHeight - getVerticalScrollOffset() + topMargin;
4265 }
4266 if (lineIndex == topIndex)
4267 return topIndexY + topMargin;
4268 int height = topIndexY;
4269 if (lineIndex > topIndex) {
4270 for (int i = topIndex; i < lineIndex; i++) {
4271 height += renderer.getLineHeight(i);
4272 }
4273 } else {
4274 for (int i = topIndex - 1; i >= lineIndex; i--) {
4275 height -= renderer.getLineHeight(i);
4276 }
4277 }
4278 return height + topMargin;
4279 }
4280 /**
4281 * Returns the line index for a y, relative to the client area.
4282 * The line index returned is always in the range 0..lineCount - 1.
4283 *
4284 * @param y the y-coordinate point
4285 *
4286 * @return the line index for a given y-coordinate point
4287 *
4288 * @since 3.2
4289 */
getLineIndex(int y)4290 public int getLineIndex(int y) {
4291 checkWidget();
4292 y -= topMargin;
4293 if (isFixedLineHeight()) {
4294 int lineHeight = renderer.getLineHeight();
4295 int lineIndex = (y + getVerticalScrollOffset()) / lineHeight;
4296 int lineCount = content.getLineCount();
4297 lineIndex = Math.max(0, Math.min(lineCount - 1, lineIndex));
4298 return lineIndex;
4299 }
4300 if (y == topIndexY) return topIndex;
4301 int line = topIndex;
4302 if (y < topIndexY) {
4303 while (y < topIndexY && line > 0) {
4304 y += renderer.getLineHeight(--line);
4305 }
4306 } else {
4307 int lineCount = content.getLineCount();
4308 int lineHeight = renderer.getLineHeight(line);
4309 while (y - lineHeight >= topIndexY && line < lineCount - 1) {
4310 y -= lineHeight;
4311 lineHeight = renderer.getLineHeight(++line);
4312 }
4313 }
4314 return line;
4315 }
4316 /**
4317 * Returns the tab stops of the line at the given <code>index</code>.
4318 *
4319 * @param index the index of the line
4320 *
4321 * @return the tab stops for the line
4322 *
4323 * @exception SWTException <ul>
4324 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4325 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4326 * </ul>
4327 * @exception IllegalArgumentException <ul>
4328 * <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
4329 * </ul>
4330 *
4331 * @see #getTabStops()
4332 *
4333 * @since 3.6
4334 */
getLineTabStops(int index)4335 public int[] getLineTabStops(int index) {
4336 checkWidget();
4337 if (index < 0 || index > content.getLineCount()) {
4338 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
4339 }
4340 if (isListening(ST.LineGetStyle)) return null;
4341 int[] tabs = renderer.getLineTabStops(index, null);
4342 if (tabs == null) tabs = this.tabs;
4343 if (tabs == null) return new int [] {renderer.tabWidth};
4344 int[] result = new int[tabs.length];
4345 System.arraycopy(tabs, 0, result, 0, tabs.length);
4346 return result;
4347 }
4348 /**
4349 * Returns the wrap indentation of the line at the given <code>index</code>.
4350 *
4351 * @param index the index of the line
4352 *
4353 * @return the wrap indentation
4354 *
4355 * @exception SWTException <ul>
4356 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4357 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4358 * </ul>
4359 * @exception IllegalArgumentException <ul>
4360 * <li>ERROR_INVALID_ARGUMENT when the index is invalid</li>
4361 * </ul>
4362 *
4363 * @see #getWrapIndent()
4364 *
4365 * @since 3.6
4366 */
getLineWrapIndent(int index)4367 public int getLineWrapIndent(int index) {
4368 checkWidget();
4369 if (index < 0 || index > content.getLineCount()) {
4370 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
4371 }
4372 return isListening(ST.LineGetStyle) ? 0 : renderer.getLineWrapIndent(index, wrapIndent);
4373 }
4374 /**
4375 * Returns the left margin.
4376 *
4377 * @return the left margin.
4378 * @exception SWTException <ul>
4379 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4380 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4381 * </ul>
4382 *
4383 * @since 3.5
4384 */
getLeftMargin()4385 public int getLeftMargin() {
4386 checkWidget();
4387 return leftMargin - alignmentMargin;
4388 }
4389 /**
4390 * Returns the x, y location of the upper left corner of the character
4391 * bounding box at the specified offset in the text. The point is
4392 * relative to the upper left corner of the widget client area.
4393 *
4394 * @param offset offset relative to the start of the content.
4395 * 0 <= offset <= getCharCount()
4396 * @return x, y location of the upper left corner of the character
4397 * bounding box at the specified offset in the text.
4398 * @exception SWTException <ul>
4399 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4400 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4401 * </ul>
4402 * @exception IllegalArgumentException <ul>
4403 * <li>ERROR_INVALID_RANGE when the offset is outside the valid range (< 0 or > getCharCount())</li>
4404 * </ul>
4405 */
getLocationAtOffset(int offset)4406 public Point getLocationAtOffset(int offset) {
4407 checkWidget();
4408 if (offset < 0 || offset > getCharCount()) {
4409 SWT.error(SWT.ERROR_INVALID_RANGE);
4410 }
4411 return getPointAtOffset(offset);
4412 }
4413 /**
4414 * Returns <code>true</code> if the mouse navigator is enabled.
4415 * When mouse navigator is enabled, the user can navigate through the widget by pressing the middle button and moving the cursor
4416 *
4417 * @return the mouse navigator's enabled state
4418 *
4419 * @exception SWTException <ul>
4420 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4421 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4422 * </ul>
4423 *
4424 * @see #getEnabled
4425 * @since 3.110
4426 */
getMouseNavigatorEnabled()4427 public boolean getMouseNavigatorEnabled () {
4428 checkWidget ();
4429 return mouseNavigator != null;
4430 }
4431 /**
4432 * Returns the character offset of the first character of the given line.
4433 *
4434 * @param lineIndex index of the line, 0 based relative to the first
4435 * line in the content. 0 <= lineIndex < getLineCount(), except
4436 * lineIndex may always be 0
4437 * @return offset offset of the first character of the line, relative to
4438 * the beginning of the document. The first character of the document is
4439 * at offset 0.
4440 * When there are not any lines, getOffsetAtLine(0) is a valid call that
4441 * answers 0.
4442 * @exception SWTException <ul>
4443 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4444 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4445 * </ul>
4446 * @exception IllegalArgumentException <ul>
4447 * <li>ERROR_INVALID_RANGE when the line index is outside the valid range (< 0 or >= getLineCount())</li>
4448 * </ul>
4449 * @since 2.0
4450 */
getOffsetAtLine(int lineIndex)4451 public int getOffsetAtLine(int lineIndex) {
4452 checkWidget();
4453 if (lineIndex < 0 ||
4454 (lineIndex > 0 && lineIndex >= content.getLineCount())) {
4455 SWT.error(SWT.ERROR_INVALID_RANGE);
4456 }
4457 return content.getOffsetAtLine(lineIndex);
4458 }
4459 /**
4460 * Returns the offset of the character at the given location relative
4461 * to the first character in the document.
4462 * <p>
4463 * The return value reflects the character offset that the caret will
4464 * be placed at if a mouse click occurred at the specified location.
4465 * If the x coordinate of the location is beyond the center of a character
4466 * the returned offset will be behind the character.
4467 * </p>
4468 *
4469 * @param point the origin of character bounding box relative to
4470 * the origin of the widget client area.
4471 * @return offset of the character at the given location relative
4472 * to the first character in the document.
4473 * @exception SWTException <ul>
4474 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4475 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4476 * </ul>
4477 * @exception IllegalArgumentException <ul>
4478 * <li>ERROR_NULL_ARGUMENT when point is null</li>
4479 * <li>ERROR_INVALID_ARGUMENT when there is no character at the specified location</li>
4480 * </ul>
4481 *
4482 * @deprecated Use {@link #getOffsetAtPoint(Point)} instead for better performance
4483 */
4484 @Deprecated
getOffsetAtLocation(Point point)4485 public int getOffsetAtLocation(Point point) {
4486 checkWidget();
4487 if (point == null) {
4488 SWT.error(SWT.ERROR_NULL_ARGUMENT);
4489 }
4490 int[] trailing = new int[1];
4491 int offset = getOffsetAtPoint(point.x, point.y, trailing, true);
4492 if (offset == -1) {
4493 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
4494 }
4495 return offset + trailing[0];
4496 }
4497
4498 /**
4499 * Returns the offset of the character at the given point relative
4500 * to the first character in the document.
4501 * <p>
4502 * The return value reflects the character offset that the caret will
4503 * be placed at if a mouse click occurred at the specified point.
4504 * If the x coordinate of the point is beyond the center of a character
4505 * the returned offset will be behind the character.
4506 * </p>
4507 * Note: This method is functionally similar to {@link #getOffsetAtLocation(Point)} except that
4508 * it does not throw an exception when no character is found and thus performs faster.
4509 *
4510 * @param point the origin of character bounding box relative to
4511 * the origin of the widget client area.
4512 * @return offset of the character at the given point relative
4513 * to the first character in the document.
4514 * -1 when there is no character at the specified location.
4515 * @exception SWTException <ul>
4516 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4517 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4518 * </ul>
4519 * @exception IllegalArgumentException <ul>
4520 * <li>ERROR_NULL_ARGUMENT when point is <code>null</code></li>
4521 * </ul>
4522 *
4523 * @since 3.107
4524 */
getOffsetAtPoint(Point point)4525 public int getOffsetAtPoint(Point point) {
4526 checkWidget();
4527 if (point == null) {
4528 SWT.error(SWT.ERROR_NULL_ARGUMENT);
4529 }
4530 int[] trailing = new int[1];
4531 int offset = getOffsetAtPoint(point.x, point.y, trailing, true);
4532 return offset != -1 ? offset + trailing[0] : -1;
4533 }
4534
getOffsetAtPoint(int x, int y, int[] alignment)4535 int getOffsetAtPoint(int x, int y, int[] alignment) {
4536 int lineIndex = getLineIndex(y);
4537 y -= getLinePixel(lineIndex);
4538 return getOffsetAtPoint(x, y, lineIndex, alignment);
4539 }
getOffsetAtPoint(int x, int y, int lineIndex, int[] alignment)4540 int getOffsetAtPoint(int x, int y, int lineIndex, int[] alignment) {
4541 TextLayout layout = renderer.getTextLayout(lineIndex);
4542 x += horizontalScrollOffset - leftMargin;
4543 int[] trailing = new int[1];
4544 int offsetInLine = layout.getOffset(x, y, trailing);
4545 if (alignment != null) alignment[0] = OFFSET_LEADING;
4546 if (trailing[0] != 0) {
4547 int lineInParagraph = layout.getLineIndex(offsetInLine + trailing[0]);
4548 int lineStart = layout.getLineOffsets()[lineInParagraph];
4549 if (offsetInLine + trailing[0] == lineStart) {
4550 offsetInLine += trailing[0];
4551 if (alignment != null) alignment[0] = PREVIOUS_OFFSET_TRAILING;
4552 } else {
4553 String line = content.getLine(lineIndex);
4554 int level = 0;
4555 if (alignment != null) {
4556 int offset = offsetInLine;
4557 while (offset > 0 && Character.isDigit(line.charAt(offset))) offset--;
4558 if (offset == 0 && Character.isDigit(line.charAt(offset))) {
4559 level = isMirrored() ? 1 : 0;
4560 } else {
4561 level = layout.getLevel(offset) & 0x1;
4562 }
4563 }
4564 offsetInLine += trailing[0];
4565 if (alignment != null) {
4566 int trailingLevel = layout.getLevel(offsetInLine) & 0x1;
4567 if (level != trailingLevel) {
4568 alignment[0] = PREVIOUS_OFFSET_TRAILING;
4569 } else {
4570 alignment[0] = OFFSET_LEADING;
4571 }
4572 }
4573 }
4574 }
4575 renderer.disposeTextLayout(layout);
4576 return offsetInLine + content.getOffsetAtLine(lineIndex);
4577 }
getOffsetAtPoint(int x, int y, int[] trailing, boolean inTextOnly)4578 int getOffsetAtPoint(int x, int y, int[] trailing, boolean inTextOnly) {
4579 if (inTextOnly && y + getVerticalScrollOffset() < 0 || x + horizontalScrollOffset < 0) {
4580 return -1;
4581 }
4582 int bottomIndex = getPartialBottomIndex();
4583 int height = getLinePixel(bottomIndex + 1);
4584 if (inTextOnly && y > height) {
4585 return -1;
4586 }
4587 int lineIndex = getLineIndex(y);
4588 int lineOffset = content.getOffsetAtLine(lineIndex);
4589 TextLayout layout = renderer.getTextLayout(lineIndex);
4590 x += horizontalScrollOffset - leftMargin;
4591 y -= getLinePixel(lineIndex);
4592 int offset = layout.getOffset(x, y, trailing);
4593 Rectangle rect = layout.getLineBounds(layout.getLineIndex(offset));
4594 renderer.disposeTextLayout(layout);
4595 if (inTextOnly && !(rect.x <= x && x <= rect.x + rect.width)) {
4596 return -1;
4597 }
4598 return offset + lineOffset;
4599 }
4600 /**
4601 * Returns the orientation of the receiver.
4602 *
4603 * @return the orientation style
4604 *
4605 * @exception SWTException <ul>
4606 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4607 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4608 * </ul>
4609 *
4610 * @since 2.1.2
4611 */
4612 @Override
getOrientation()4613 public int getOrientation () {
4614 return super.getOrientation ();
4615 }
4616 /**
4617 * Returns the index of the last partially visible line.
4618 *
4619 * @return index of the last partially visible line.
4620 */
getPartialBottomIndex()4621 int getPartialBottomIndex() {
4622 if (isFixedLineHeight()) {
4623 int lineHeight = renderer.getLineHeight();
4624 int partialLineCount = Compatibility.ceil(clientAreaHeight, lineHeight);
4625 return Math.max(0, Math.min(content.getLineCount(), topIndex + partialLineCount) - 1);
4626 }
4627 return getLineIndex(clientAreaHeight - bottomMargin);
4628 }
4629 /**
4630 * Returns the index of the first partially visible line.
4631 *
4632 * @return index of the first partially visible line.
4633 */
getPartialTopIndex()4634 int getPartialTopIndex() {
4635 if (isFixedLineHeight()) {
4636 int lineHeight = renderer.getLineHeight();
4637 return getVerticalScrollOffset() / lineHeight;
4638 }
4639 return topIndexY <= 0 ? topIndex : topIndex - 1;
4640 }
4641 /**
4642 * Returns the content in the specified range using the platform line
4643 * delimiter to separate lines.
4644 *
4645 * @param writer the TextWriter to write line text into
4646 * @return the content in the specified range using the platform line
4647 * delimiter to separate lines as written by the specified TextWriter.
4648 */
getPlatformDelimitedText(TextWriter writer)4649 String getPlatformDelimitedText(TextWriter writer) {
4650 int end = writer.getStart() + writer.getCharCount();
4651 int startLine = content.getLineAtOffset(writer.getStart());
4652 int endLine = content.getLineAtOffset(end);
4653 String endLineText = content.getLine(endLine);
4654 int endLineOffset = content.getOffsetAtLine(endLine);
4655
4656 for (int i = startLine; i <= endLine; i++) {
4657 writer.writeLine(content.getLine(i), content.getOffsetAtLine(i));
4658 if (i < endLine) {
4659 writer.writeLineDelimiter(PlatformLineDelimiter);
4660 }
4661 }
4662 if (end > endLineOffset + endLineText.length()) {
4663 writer.writeLineDelimiter(PlatformLineDelimiter);
4664 }
4665 writer.close();
4666 return writer.toString();
4667 }
4668 /**
4669 * Returns all the ranges of text that have an associated StyleRange.
4670 * Returns an empty array if a LineStyleListener has been set.
4671 * Should not be called if a LineStyleListener has been set since the
4672 * listener maintains the styles.
4673 * <p>
4674 * The ranges array contains start and length pairs. Each pair refers to
4675 * the corresponding style in the styles array. For example, the pair
4676 * that starts at ranges[n] with length ranges[n+1] uses the style
4677 * at styles[n/2] returned by <code>getStyleRanges(int, int, boolean)</code>.
4678 * </p>
4679 *
4680 * @return the ranges or an empty array if a LineStyleListener has been set.
4681 *
4682 * @exception SWTException <ul>
4683 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4684 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4685 * </ul>
4686 *
4687 * @since 3.2
4688 *
4689 * @see #getStyleRanges(boolean)
4690 */
getRanges()4691 public int[] getRanges() {
4692 checkWidget();
4693 if (!isListening(ST.LineGetStyle)) {
4694 int[] ranges = renderer.getRanges(0, content.getCharCount());
4695 if (ranges != null) return ranges;
4696 }
4697 return new int[0];
4698 }
4699 /**
4700 * Returns the ranges of text that have an associated StyleRange.
4701 * Returns an empty array if a LineStyleListener has been set.
4702 * Should not be called if a LineStyleListener has been set since the
4703 * listener maintains the styles.
4704 * <p>
4705 * The ranges array contains start and length pairs. Each pair refers to
4706 * the corresponding style in the styles array. For example, the pair
4707 * that starts at ranges[n] with length ranges[n+1] uses the style
4708 * at styles[n/2] returned by <code>getStyleRanges(int, int, boolean)</code>.
4709 * </p>
4710 *
4711 * @param start the start offset of the style ranges to return
4712 * @param length the number of style ranges to return
4713 *
4714 * @return the ranges or an empty array if a LineStyleListener has been set.
4715 *
4716 * @exception SWTException <ul>
4717 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4718 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4719 * </ul>
4720 * @exception IllegalArgumentException <ul>
4721 * <li>ERROR_INVALID_RANGE if start or length are outside the widget content</li>
4722 * </ul>
4723 *
4724 * @since 3.2
4725 *
4726 * @see #getStyleRanges(int, int, boolean)
4727 */
getRanges(int start, int length)4728 public int[] getRanges(int start, int length) {
4729 checkWidget();
4730 int contentLength = getCharCount();
4731 int end = start + length;
4732 if (start > end || start < 0 || end > contentLength) {
4733 SWT.error(SWT.ERROR_INVALID_RANGE);
4734 }
4735 if (!isListening(ST.LineGetStyle)) {
4736 int[] ranges = renderer.getRanges(start, length);
4737 if (ranges != null) return ranges;
4738 }
4739 return new int[0];
4740 }
4741 /**
4742 * Returns the right margin.
4743 *
4744 * @return the right margin.
4745 * @exception SWTException <ul>
4746 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4747 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4748 * </ul>
4749 *
4750 * @since 3.5
4751 */
getRightMargin()4752 public int getRightMargin() {
4753 checkWidget();
4754 return rightMargin;
4755 }
4756 /**
4757 * Returns the selection.
4758 * <p>
4759 * Text selections are specified in terms of caret positions. In a text
4760 * widget that contains N characters, there are N+1 caret positions,
4761 * ranging from 0..N
4762 * </p>
4763 *
4764 * @return start and end of the selection, x is the offset of the first
4765 * selected character, y is the offset after the last selected character.
4766 * The selection values returned are visual (i.e., x will always always be
4767 * <= y). To determine if a selection is right-to-left (RtoL) vs. left-to-right
4768 * (LtoR), compare the caretOffset to the start and end of the selection
4769 * (e.g., caretOffset == start of selection implies that the selection is RtoL).
4770 * @see #getSelectionRange
4771 * @exception SWTException <ul>
4772 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4773 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4774 * </ul>
4775 */
getSelection()4776 public Point getSelection() {
4777 checkWidget();
4778 return new Point(selection.x, selection.y);
4779 }
4780 /**
4781 * Returns the selection.
4782 *
4783 * @return start and length of the selection, x is the offset of the
4784 * first selected character, relative to the first character of the
4785 * widget content. y is the length of the selection.
4786 * The selection values returned are visual (i.e., length will always always be
4787 * positive). To determine if a selection is right-to-left (RtoL) vs. left-to-right
4788 * (LtoR), compare the caretOffset to the start and end of the selection
4789 * (e.g., caretOffset == start of selection implies that the selection is RtoL).
4790 * @exception SWTException <ul>
4791 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4792 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4793 * </ul>
4794 */
getSelectionRange()4795 public Point getSelectionRange() {
4796 checkWidget();
4797 return new Point(selection.x, selection.y - selection.x);
4798 }
4799 /**
4800 * Returns the ranges of text that are inside the block selection rectangle.
4801 * <p>
4802 * The ranges array contains start and length pairs. When the receiver is not
4803 * in block selection mode the return arrays contains the start and length of
4804 * the regular selection.
4805 *
4806 * @return the ranges array
4807 *
4808 * @exception SWTException <ul>
4809 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4810 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4811 * </ul>
4812 *
4813 * @since 3.5
4814 */
getSelectionRanges()4815 public int[] getSelectionRanges() {
4816 checkWidget();
4817 if (blockSelection && blockXLocation != -1) {
4818 Rectangle rect = getBlockSelectionPosition();
4819 int firstLine = rect.y;
4820 int lastLine = rect.height;
4821 int left = rect.x;
4822 int right = rect.width;
4823 int[] ranges = new int[(lastLine - firstLine + 1) * 2];
4824 int index = 0;
4825 for (int lineIndex = firstLine; lineIndex <= lastLine; lineIndex++) {
4826 int start = getOffsetAtPoint(left, 0, lineIndex, null);
4827 int end = getOffsetAtPoint(right, 0, lineIndex, null);
4828 if (start > end) {
4829 int temp = start;
4830 start = end;
4831 end = temp;
4832 }
4833 ranges[index++] = start;
4834 ranges[index++] = end - start;
4835 }
4836 return ranges;
4837 }
4838 return new int[] {selection.x, selection.y - selection.x};
4839 }
4840 /**
4841 * Returns the receiver's selection background color.
4842 *
4843 * @return the selection background color
4844 *
4845 * @exception SWTException <ul>
4846 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4847 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4848 * </ul>
4849 * @since 2.1
4850 */
getSelectionBackground()4851 public Color getSelectionBackground() {
4852 checkWidget();
4853 if (selectionBackground == null) {
4854 return getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION);
4855 }
4856 return selectionBackground;
4857 }
4858 /**
4859 * Gets the number of selected characters.
4860 *
4861 * @return the number of selected characters.
4862 * @exception SWTException <ul>
4863 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4864 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4865 * </ul>
4866 */
getSelectionCount()4867 public int getSelectionCount() {
4868 checkWidget();
4869 if (blockSelection && blockXLocation != -1) {
4870 return getBlockSelectionText(content.getLineDelimiter()).length();
4871 }
4872 return getSelectionRange().y;
4873 }
4874 /**
4875 * Returns the receiver's selection foreground color.
4876 *
4877 * @return the selection foreground color
4878 *
4879 * @exception SWTException <ul>
4880 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4881 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4882 * </ul>
4883 * @since 2.1
4884 */
getSelectionForeground()4885 public Color getSelectionForeground() {
4886 checkWidget();
4887 if (selectionForeground == null) {
4888 return getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT);
4889 }
4890 return selectionForeground;
4891 }
4892 /**
4893 * Returns the selected text.
4894 *
4895 * @return selected text, or an empty String if there is no selection.
4896 * @exception SWTException <ul>
4897 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
4898 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
4899 * </ul>
4900 */
getSelectionText()4901 public String getSelectionText() {
4902 checkWidget();
4903 if (blockSelection && blockXLocation != -1) {
4904 return getBlockSelectionText(content.getLineDelimiter());
4905 }
4906 return content.getTextRange(selection.x, selection.y - selection.x);
4907 }
getBidiSegments(int lineOffset, String line)4908 StyledTextEvent getBidiSegments(int lineOffset, String line) {
4909 if (!isListening(ST.LineGetSegments)) {
4910 if (!bidiColoring) return null;
4911 StyledTextEvent event = new StyledTextEvent(content);
4912 event.segments = getBidiSegmentsCompatibility(line, lineOffset);
4913 return event;
4914 }
4915 StyledTextEvent event = sendLineEvent(ST.LineGetSegments, lineOffset, line);
4916 if (event == null || event.segments == null || event.segments.length == 0) return null;
4917 int lineLength = line.length();
4918 int[] segments = event.segments;
4919 if (segments[0] > lineLength) {
4920 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
4921 }
4922 char[] segmentsChars = event.segmentsChars;
4923 boolean hasSegmentsChars = segmentsChars != null;
4924 for (int i = 1; i < segments.length; i++) {
4925 if ((hasSegmentsChars ? segments[i] < segments[i - 1] : segments[i] <= segments[i - 1]) || segments[i] > lineLength) {
4926 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
4927 }
4928 }
4929 if (hasSegmentsChars && !visualWrap) {
4930 for (char segmentsChar : segmentsChars) {
4931 if (segmentsChar == '\n' || segmentsChar == '\r') {
4932 visualWrap = true;
4933 break;
4934 }
4935 }
4936 }
4937 return event;
4938 }
4939 /**
4940 * @see #getBidiSegments
4941 * Supports deprecated setBidiColoring API. Remove when API is removed.
4942 */
getBidiSegmentsCompatibility(String line, int lineOffset)4943 int [] getBidiSegmentsCompatibility(String line, int lineOffset) {
4944 int lineLength = line.length();
4945 StyleRange [] styles = null;
4946 StyledTextEvent event = getLineStyleData(lineOffset, line);
4947 if (event != null) {
4948 styles = event.styles;
4949 } else {
4950 styles = renderer.getStyleRanges(lineOffset, lineLength, true);
4951 }
4952 if (styles == null || styles.length == 0) {
4953 return new int[] {0, lineLength};
4954 }
4955 int k=0, count = 1;
4956 while (k < styles.length && styles[k].start == 0 && styles[k].length == lineLength) {
4957 k++;
4958 }
4959 int[] offsets = new int[(styles.length - k) * 2 + 2];
4960 for (int i = k; i < styles.length; i++) {
4961 StyleRange style = styles[i];
4962 int styleLineStart = Math.max(style.start - lineOffset, 0);
4963 int styleLineEnd = Math.max(style.start + style.length - lineOffset, styleLineStart);
4964 styleLineEnd = Math.min (styleLineEnd, line.length ());
4965 if (i > 0 && count > 1 &&
4966 ((styleLineStart >= offsets[count-2] && styleLineStart <= offsets[count-1]) ||
4967 (styleLineEnd >= offsets[count-2] && styleLineEnd <= offsets[count-1])) &&
4968 style.similarTo(styles[i-1])) {
4969 offsets[count-2] = Math.min(offsets[count-2], styleLineStart);
4970 offsets[count-1] = Math.max(offsets[count-1], styleLineEnd);
4971 } else {
4972 if (styleLineStart > offsets[count - 1]) {
4973 offsets[count] = styleLineStart;
4974 count++;
4975 }
4976 offsets[count] = styleLineEnd;
4977 count++;
4978 }
4979 }
4980 // add offset for last non-colored segment in line, if any
4981 if (lineLength > offsets[count-1]) {
4982 offsets [count] = lineLength;
4983 count++;
4984 }
4985 if (count == offsets.length) {
4986 return offsets;
4987 }
4988 int [] result = new int [count];
4989 System.arraycopy (offsets, 0, result, 0, count);
4990 return result;
4991 }
4992 /**
4993 * Returns the style range at the given offset.
4994 * <p>
4995 * Returns null if a LineStyleListener has been set or if a style is not set
4996 * for the offset.
4997 * Should not be called if a LineStyleListener has been set since the
4998 * listener maintains the styles.
4999 * </p>
5000 *
5001 * @param offset the offset to return the style for.
5002 * 0 <= offset < getCharCount() must be true.
5003 * @return a StyleRange with start == offset and length == 1, indicating
5004 * the style at the given offset. null if a LineStyleListener has been set
5005 * or if a style is not set for the given offset.
5006 * @exception SWTException <ul>
5007 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5008 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5009 * </ul>
5010 * @exception IllegalArgumentException <ul>
5011 * <li>ERROR_INVALID_ARGUMENT when the offset is invalid</li>
5012 * </ul>
5013 */
getStyleRangeAtOffset(int offset)5014 public StyleRange getStyleRangeAtOffset(int offset) {
5015 checkWidget();
5016 if (offset < 0 || offset >= getCharCount()) {
5017 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
5018 }
5019 if (!isListening(ST.LineGetStyle)) {
5020 StyleRange[] ranges = renderer.getStyleRanges(offset, 1, true);
5021 if (ranges != null) return ranges[0];
5022 }
5023 return null;
5024 }
5025 /**
5026 * Returns the styles.
5027 * <p>
5028 * Returns an empty array if a LineStyleListener has been set.
5029 * Should not be called if a LineStyleListener has been set since the
5030 * listener maintains the styles.
5031 * </p><p>
5032 * Note: Because a StyleRange includes the start and length, the
5033 * same instance cannot occur multiple times in the array of styles.
5034 * If the same style attributes, such as font and color, occur in
5035 * multiple StyleRanges, <code>getStyleRanges(boolean)</code>
5036 * can be used to get the styles without the ranges.
5037 * </p>
5038 *
5039 * @return the styles or an empty array if a LineStyleListener has been set.
5040 *
5041 * @exception SWTException <ul>
5042 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5043 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5044 * </ul>
5045 *
5046 * @see #getStyleRanges(boolean)
5047 */
getStyleRanges()5048 public StyleRange[] getStyleRanges() {
5049 checkWidget();
5050 return getStyleRanges(0, content.getCharCount(), true);
5051 }
5052 /**
5053 * Returns the styles.
5054 * <p>
5055 * Returns an empty array if a LineStyleListener has been set.
5056 * Should not be called if a LineStyleListener has been set since the
5057 * listener maintains the styles.
5058 * </p><p>
5059 * Note: When <code>includeRanges</code> is true, the start and length
5060 * fields of each StyleRange will be valid, however the StyleRange
5061 * objects may need to be cloned. When <code>includeRanges</code> is
5062 * false, <code>getRanges(int, int)</code> can be used to get the
5063 * associated ranges.
5064 * </p>
5065 *
5066 * @param includeRanges whether the start and length field of the StyleRanges should be set.
5067 *
5068 * @return the styles or an empty array if a LineStyleListener has been set.
5069 *
5070 * @exception SWTException <ul>
5071 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5072 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5073 * </ul>
5074 *
5075 * @since 3.2
5076 *
5077 * @see #getRanges(int, int)
5078 * @see #setStyleRanges(int[], StyleRange[])
5079 */
getStyleRanges(boolean includeRanges)5080 public StyleRange[] getStyleRanges(boolean includeRanges) {
5081 checkWidget();
5082 return getStyleRanges(0, content.getCharCount(), includeRanges);
5083 }
5084 /**
5085 * Returns the styles for the given text range.
5086 * <p>
5087 * Returns an empty array if a LineStyleListener has been set.
5088 * Should not be called if a LineStyleListener has been set since the
5089 * listener maintains the styles.
5090 * </p><p>
5091 * Note: Because the StyleRange includes the start and length, the
5092 * same instance cannot occur multiple times in the array of styles.
5093 * If the same style attributes, such as font and color, occur in
5094 * multiple StyleRanges, <code>getStyleRanges(int, int, boolean)</code>
5095 * can be used to get the styles without the ranges.
5096 * </p>
5097 * @param start the start offset of the style ranges to return
5098 * @param length the number of style ranges to return
5099 *
5100 * @return the styles or an empty array if a LineStyleListener has
5101 * been set. The returned styles will reflect the given range. The first
5102 * returned <code>StyleRange</code> will have a starting offset >= start
5103 * and the last returned <code>StyleRange</code> will have an ending
5104 * offset <= start + length - 1
5105 *
5106 * @exception SWTException <ul>
5107 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5108 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5109 * </ul>
5110 * @exception IllegalArgumentException <ul>
5111 * <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li>
5112 * </ul>
5113 *
5114 * @see #getStyleRanges(int, int, boolean)
5115 *
5116 * @since 3.0
5117 */
getStyleRanges(int start, int length)5118 public StyleRange[] getStyleRanges(int start, int length) {
5119 checkWidget();
5120 return getStyleRanges(start, length, true);
5121 }
5122 /**
5123 * Returns the styles for the given text range.
5124 * <p>
5125 * Returns an empty array if a LineStyleListener has been set.
5126 * Should not be called if a LineStyleListener has been set since the
5127 * listener maintains the styles.
5128 * </p><p>
5129 * Note: When <code>includeRanges</code> is true, the start and length
5130 * fields of each StyleRange will be valid, however the StyleRange
5131 * objects may need to be cloned. When <code>includeRanges</code> is
5132 * false, <code>getRanges(int, int)</code> can be used to get the
5133 * associated ranges.
5134 * </p>
5135 *
5136 * @param start the start offset of the style ranges to return
5137 * @param length the number of style ranges to return
5138 * @param includeRanges whether the start and length field of the StyleRanges should be set.
5139 *
5140 * @return the styles or an empty array if a LineStyleListener has
5141 * been set. The returned styles will reflect the given range. The first
5142 * returned <code>StyleRange</code> will have a starting offset >= start
5143 * and the last returned <code>StyleRange</code> will have an ending
5144 * offset >= start + length - 1
5145 *
5146 * @exception SWTException <ul>
5147 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5148 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5149 * </ul>
5150 * @exception IllegalArgumentException <ul>
5151 * <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li>
5152 * </ul>
5153 *
5154 * @since 3.2
5155 *
5156 * @see #getRanges(int, int)
5157 * @see #setStyleRanges(int[], StyleRange[])
5158 */
getStyleRanges(int start, int length, boolean includeRanges)5159 public StyleRange[] getStyleRanges(int start, int length, boolean includeRanges) {
5160 checkWidget();
5161 int contentLength = getCharCount();
5162 int end = start + length;
5163 if (start > end || start < 0 || end > contentLength) {
5164 SWT.error(SWT.ERROR_INVALID_RANGE);
5165 }
5166 if (!isListening(ST.LineGetStyle)) {
5167 StyleRange[] ranges = renderer.getStyleRanges(start, length, includeRanges);
5168 if (ranges != null) return ranges;
5169 }
5170 return new StyleRange[0];
5171 }
5172 /**
5173 * Returns the tab width measured in characters.
5174 *
5175 * @return tab width measured in characters
5176 * @exception SWTException <ul>
5177 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5178 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5179 * </ul>
5180 *
5181 * @see #getTabStops()
5182 */
getTabs()5183 public int getTabs() {
5184 checkWidget();
5185 return tabLength;
5186 }
5187
5188 /**
5189 * Returns the tab list of the receiver.
5190 *
5191 * @return the tab list
5192 * @exception SWTException <ul>
5193 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5194 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5195 * </ul>
5196 *
5197 * @since 3.6
5198 */
getTabStops()5199 public int[] getTabStops() {
5200 checkWidget();
5201 if (tabs == null) return new int [] {renderer.tabWidth};
5202 int[] result = new int[tabs.length];
5203 System.arraycopy(tabs, 0, result, 0, tabs.length);
5204 return result;
5205 }
5206
5207 /**
5208 * Returns a copy of the widget content.
5209 *
5210 * @return copy of the widget content
5211 * @exception SWTException <ul>
5212 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5213 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5214 * </ul>
5215 */
getText()5216 public String getText() {
5217 checkWidget();
5218 return content.getTextRange(0, getCharCount());
5219 }
5220 /**
5221 * Returns the widget content between the two offsets.
5222 *
5223 * @param start offset of the first character in the returned String
5224 * @param end offset of the last character in the returned String
5225 * @return widget content starting at start and ending at end
5226 * @see #getTextRange(int,int)
5227 * @exception SWTException <ul>
5228 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5229 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5230 * </ul>
5231 * @exception IllegalArgumentException <ul>
5232 * <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li>
5233 * </ul>
5234 */
getText(int start, int end)5235 public String getText(int start, int end) {
5236 checkWidget();
5237 int contentLength = getCharCount();
5238 if (start < 0 || start >= contentLength || end < 0 || end >= contentLength || start > end) {
5239 SWT.error(SWT.ERROR_INVALID_RANGE);
5240 }
5241 return content.getTextRange(start, end - start + 1);
5242 }
5243 /**
5244 * Returns the smallest bounding rectangle that includes the characters between two offsets.
5245 *
5246 * @param start offset of the first character included in the bounding box
5247 * @param end offset of the last character included in the bounding box
5248 * @return bounding box of the text between start and end
5249 * @exception SWTException <ul>
5250 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5251 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5252 * </ul>
5253 * @exception IllegalArgumentException <ul>
5254 * <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li>
5255 * </ul>
5256 * @since 3.1
5257 */
getTextBounds(int start, int end)5258 public Rectangle getTextBounds(int start, int end) {
5259 checkWidget();
5260 int contentLength = getCharCount();
5261 if (start < 0 || start >= contentLength || end < 0 || end >= contentLength || start > end) {
5262 SWT.error(SWT.ERROR_INVALID_RANGE);
5263 }
5264 int lineStart = content.getLineAtOffset(start);
5265 int lineEnd = content.getLineAtOffset(end);
5266 Rectangle rect;
5267 int y = getLinePixel(lineStart);
5268 int height = 0;
5269 int left = 0x7fffffff, right = 0;
5270 for (int i = lineStart; i <= lineEnd; i++) {
5271 int lineOffset = content.getOffsetAtLine(i);
5272 TextLayout layout = renderer.getTextLayout(i);
5273 int length = layout.getText().length();
5274 if (length > 0) {
5275 if (i == lineStart) {
5276 if (i == lineEnd) {
5277 rect = layout.getBounds(start - lineOffset, end - lineOffset);
5278 } else {
5279 rect = layout.getBounds(start - lineOffset, length);
5280 }
5281 y += rect.y;
5282 } else if (i == lineEnd) {
5283 rect = layout.getBounds(0, end - lineOffset);
5284 } else {
5285 rect = layout.getBounds();
5286 }
5287 left = Math.min(left, rect.x);
5288 right = Math.max(right, rect.x + rect.width);
5289 height += rect.height;
5290 } else {
5291 height += renderer.getLineHeight();
5292 }
5293 renderer.disposeTextLayout(layout);
5294 }
5295 rect = new Rectangle (left, y, right-left, height);
5296 rect.x += leftMargin - horizontalScrollOffset;
5297 return rect;
5298 }
5299 /**
5300 * Returns the widget content starting at start for length characters.
5301 *
5302 * @param start offset of the first character in the returned String
5303 * @param length number of characters to return
5304 * @return widget content starting at start and extending length characters.
5305 * @exception SWTException <ul>
5306 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5307 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5308 * </ul>
5309 * @exception IllegalArgumentException <ul>
5310 * <li>ERROR_INVALID_RANGE when start and/or length are outside the widget content</li>
5311 * </ul>
5312 */
getTextRange(int start, int length)5313 public String getTextRange(int start, int length) {
5314 checkWidget();
5315 int contentLength = getCharCount();
5316 int end = start + length;
5317 if (start > end || start < 0 || end > contentLength) {
5318 SWT.error(SWT.ERROR_INVALID_RANGE);
5319 }
5320 return content.getTextRange(start, length);
5321 }
5322 /**
5323 * Returns the maximum number of characters that the receiver is capable of holding.
5324 *
5325 * @return the text limit
5326 *
5327 * @exception SWTException <ul>
5328 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5329 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5330 * </ul>
5331 */
getTextLimit()5332 public int getTextLimit() {
5333 checkWidget();
5334 return textLimit;
5335 }
5336 /**
5337 * Gets the top index.
5338 * <p>
5339 * The top index is the index of the fully visible line that is currently
5340 * at the top of the widget or the topmost partially visible line if no line is fully visible.
5341 * The top index changes when the widget is scrolled. Indexing is zero based.
5342 * </p>
5343 *
5344 * @return the index of the top line
5345 * @exception SWTException <ul>
5346 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5347 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5348 * </ul>
5349 */
getTopIndex()5350 public int getTopIndex() {
5351 checkWidget();
5352 return topIndex;
5353 }
5354 /**
5355 * Returns the top margin.
5356 *
5357 * @return the top margin.
5358 * @exception SWTException <ul>
5359 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5360 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5361 * </ul>
5362 *
5363 * @since 3.5
5364 */
getTopMargin()5365 public int getTopMargin() {
5366 checkWidget();
5367 return topMargin;
5368 }
5369 /**
5370 * Gets the top SWT logical point.
5371 * <p>
5372 * The top point is the SWT logical point position of the line that is
5373 * currently at the top of the widget. The text widget can be scrolled by points
5374 * by dragging the scroll thumb so that a partial line may be displayed at the top
5375 * the widget. The top point changes when the widget is scrolled. The top point
5376 * does not include the widget trimming.
5377 * </p>
5378 *
5379 * @return SWT logical point position of the top line
5380 * @exception SWTException <ul>
5381 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5382 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5383 * </ul>
5384 */
getTopPixel()5385 public int getTopPixel() {
5386 checkWidget();
5387 return getVerticalScrollOffset();
5388 }
5389 /**
5390 * Returns the vertical scroll increment.
5391 *
5392 * @return vertical scroll increment.
5393 */
getVerticalIncrement()5394 int getVerticalIncrement() {
5395 return renderer.getLineHeight();
5396 }
getVerticalScrollOffset()5397 int getVerticalScrollOffset() {
5398 if (verticalScrollOffset == -1) {
5399 renderer.calculate(0, topIndex);
5400 int height = 0;
5401 for (int i = 0; i < topIndex; i++) {
5402 height += renderer.getCachedLineHeight(i);
5403 }
5404 height -= topIndexY;
5405 verticalScrollOffset = height;
5406 }
5407 return verticalScrollOffset;
5408 }
getVisualLineIndex(TextLayout layout, int offsetInLine)5409 int getVisualLineIndex(TextLayout layout, int offsetInLine) {
5410 int lineIndex = layout.getLineIndex(offsetInLine);
5411 int[] offsets = layout.getLineOffsets();
5412 Caret caret = getCaret();
5413 if (caret != null && lineIndex != 0 && offsetInLine == offsets[lineIndex]) {
5414 int lineY = layout.getLineBounds(lineIndex).y;
5415 int caretY = caret.getLocation().y - getLinePixel(getCaretLine());
5416 if (lineY > caretY) lineIndex--;
5417 caretAlignment = OFFSET_LEADING;
5418 }
5419 return lineIndex;
5420 }
getCaretDirection()5421 int getCaretDirection() {
5422 if (!isBidiCaret()) return SWT.DEFAULT;
5423 if (ime.getCompositionOffset() != -1) return SWT.DEFAULT;
5424 if (!updateCaretDirection && caretDirection != SWT.NULL) return caretDirection;
5425 updateCaretDirection = false;
5426 int caretLine = getCaretLine();
5427 int lineOffset = content.getOffsetAtLine(caretLine);
5428 String line = content.getLine(caretLine);
5429 int offset = caretOffset - lineOffset;
5430 int lineLength = line.length();
5431 if (lineLength == 0) return isMirrored() ? SWT.RIGHT : SWT.LEFT;
5432 if (caretAlignment == PREVIOUS_OFFSET_TRAILING && offset > 0) offset--;
5433 if (offset == lineLength && offset > 0) offset--;
5434 while (offset > 0 && Character.isDigit(line.charAt(offset))) offset--;
5435 if (offset == 0 && Character.isDigit(line.charAt(offset))) {
5436 return isMirrored() ? SWT.RIGHT : SWT.LEFT;
5437 }
5438 TextLayout layout = renderer.getTextLayout(caretLine);
5439 int level = layout.getLevel(offset);
5440 renderer.disposeTextLayout(layout);
5441 return ((level & 1) != 0) ? SWT.RIGHT : SWT.LEFT;
5442 }
5443 /*
5444 * Returns the index of the line the caret is on.
5445 */
getCaretLine()5446 int getCaretLine() {
5447 return content.getLineAtOffset(caretOffset);
5448 }
getWrapWidth()5449 int getWrapWidth () {
5450 if (wordWrap && !isSingleLine()) {
5451 int width = clientAreaWidth - leftMargin - rightMargin;
5452 return width > 0 ? width : 1;
5453 }
5454 return -1;
5455 }
getWordNext(int offset, int movement)5456 int getWordNext (int offset, int movement) {
5457 return getWordNext(offset, movement, false);
5458 }
getWordNext(int offset, int movement, boolean ignoreListener)5459 int getWordNext (int offset, int movement, boolean ignoreListener) {
5460 int newOffset, lineOffset;
5461 String lineText;
5462 if (offset >= getCharCount()) {
5463 newOffset = offset;
5464 int lineIndex = content.getLineCount() - 1;
5465 lineOffset = content.getOffsetAtLine(lineIndex);
5466 lineText = content.getLine(lineIndex);
5467 } else {
5468 int lineIndex = content.getLineAtOffset(offset);
5469 lineOffset = content.getOffsetAtLine(lineIndex);
5470 lineText = content.getLine(lineIndex);
5471 int lineLength = lineText.length();
5472 if (offset >= lineOffset + lineLength) {
5473 newOffset = content.getOffsetAtLine(lineIndex + 1);
5474 } else {
5475 TextLayout layout = renderer.getTextLayout(lineIndex);
5476 newOffset = lineOffset + layout.getNextOffset(offset - lineOffset, movement);
5477 renderer.disposeTextLayout(layout);
5478 }
5479 }
5480 if (ignoreListener) return newOffset;
5481 return sendWordBoundaryEvent(ST.WordNext, movement, offset, newOffset, lineText, lineOffset);
5482 }
getWordPrevious(int offset, int movement)5483 int getWordPrevious(int offset, int movement) {
5484 return getWordPrevious(offset, movement, false);
5485 }
getWordPrevious(int offset, int movement, boolean ignoreListener)5486 int getWordPrevious(int offset, int movement, boolean ignoreListener) {
5487 int newOffset, lineOffset;
5488 String lineText;
5489 if (offset <= 0) {
5490 newOffset = 0;
5491 int lineIndex = content.getLineAtOffset(newOffset);
5492 lineOffset = content.getOffsetAtLine(lineIndex);
5493 lineText = content.getLine(lineIndex);
5494 } else {
5495 int lineIndex = content.getLineAtOffset(offset);
5496 lineOffset = content.getOffsetAtLine(lineIndex);
5497 lineText = content.getLine(lineIndex);
5498 if (offset == lineOffset) {
5499 String nextLineText = content.getLine(lineIndex - 1);
5500 int nextLineOffset = content.getOffsetAtLine(lineIndex - 1);
5501 newOffset = nextLineOffset + nextLineText.length();
5502 } else {
5503 int layoutOffset = Math.min(offset - lineOffset, lineText.length());
5504 TextLayout layout = renderer.getTextLayout(lineIndex);
5505 newOffset = lineOffset + layout.getPreviousOffset(layoutOffset, movement);
5506 renderer.disposeTextLayout(layout);
5507 }
5508 }
5509 if (ignoreListener) return newOffset;
5510 return sendWordBoundaryEvent(ST.WordPrevious, movement, offset, newOffset, lineText, lineOffset);
5511 }
5512 /**
5513 * Returns whether the widget wraps lines.
5514 *
5515 * @return true if widget wraps lines, false otherwise
5516 * @since 2.0
5517 */
getWordWrap()5518 public boolean getWordWrap() {
5519 checkWidget();
5520 return wordWrap;
5521 }
5522 /**
5523 * Returns the wrap indentation of the widget.
5524 *
5525 * @return the wrap indentation
5526 *
5527 * @exception SWTException <ul>
5528 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5529 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5530 * </ul>
5531 *
5532 * @see #getLineWrapIndent(int)
5533 *
5534 * @since 3.6
5535 */
getWrapIndent()5536 public int getWrapIndent() {
5537 checkWidget();
5538 return wrapIndent;
5539 }
5540 /**
5541 * Returns the location of the given offset.
5542 * <p>
5543 * <b>NOTE:</b> Does not return correct values for true italic fonts (vs. slanted fonts).
5544 * </p>
5545 *
5546 * @return location of the character at the given offset in the line.
5547 */
getPointAtOffset(int offset)5548 Point getPointAtOffset(int offset) {
5549 int lineIndex = content.getLineAtOffset(offset);
5550 String line = content.getLine(lineIndex);
5551 int lineOffset = content.getOffsetAtLine(lineIndex);
5552 int offsetInLine = Math.max (0, offset - lineOffset);
5553 int lineLength = line.length();
5554 if (lineIndex < content.getLineCount() - 1) {
5555 int endLineOffset = content.getOffsetAtLine(lineIndex + 1) - 1;
5556 if (lineLength < offsetInLine && offsetInLine <= endLineOffset) {
5557 offsetInLine = lineLength;
5558 }
5559 }
5560 Point point;
5561 TextLayout layout = renderer.getTextLayout(lineIndex);
5562 if (lineLength != 0 && offsetInLine <= lineLength) {
5563 if (offsetInLine == lineLength) {
5564 offsetInLine = layout.getPreviousOffset(offsetInLine, SWT.MOVEMENT_CLUSTER);
5565 point = layout.getLocation(offsetInLine, true);
5566 } else {
5567 switch (caretAlignment) {
5568 case OFFSET_LEADING:
5569 point = layout.getLocation(offsetInLine, false);
5570 break;
5571 case PREVIOUS_OFFSET_TRAILING:
5572 default:
5573 boolean lineBegin = offsetInLine == 0;
5574 // If word wrap is enabled, we should also consider offsets
5575 // of wrapped line parts as line begin and do NOT go back.
5576 // This prevents clients to jump one line higher than
5577 // expected, see bug 488172.
5578 // Respect caretAlignment at the caretOffset, unless there's
5579 // a non-empty selection, see bug 488172 comment 6.
5580 if (wordWrap && !lineBegin && (offset != caretOffset || selection.x != selection.y)) {
5581 int[] offsets = layout.getLineOffsets();
5582 for (int i : offsets) {
5583 if (i == offsetInLine) {
5584 lineBegin = true;
5585 break;
5586 }
5587 }
5588 }
5589 if (lineBegin) {
5590 point = layout.getLocation(offsetInLine, false);
5591 } else {
5592 offsetInLine = layout.getPreviousOffset(offsetInLine, SWT.MOVEMENT_CLUSTER);
5593 point = layout.getLocation(offsetInLine, true);
5594 }
5595 break;
5596 }
5597 }
5598 } else {
5599 point = new Point(layout.getIndent(), layout.getVerticalIndent());
5600 }
5601 renderer.disposeTextLayout(layout);
5602 point.x += leftMargin - horizontalScrollOffset;
5603 point.y += getLinePixel(lineIndex);
5604 return point;
5605 }
5606 /**
5607 * Inserts a string. The old selection is replaced with the new text.
5608 *
5609 * @param string the string
5610 * @see #replaceTextRange(int,int,String)
5611 * @exception SWTException <ul>
5612 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
5613 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
5614 * </ul>
5615 * @exception IllegalArgumentException <ul>
5616 * <li>ERROR_NULL_ARGUMENT when string is null</li>
5617 * </ul>
5618 */
insert(String string)5619 public void insert(String string) {
5620 checkWidget();
5621 if (string == null) {
5622 SWT.error(SWT.ERROR_NULL_ARGUMENT);
5623 }
5624 if (blockSelection) {
5625 insertBlockSelectionText(string, false);
5626 } else {
5627 Point sel = getSelectionRange();
5628 replaceTextRange(sel.x, sel.y, string);
5629 }
5630 }
insertBlockSelectionText(String text, boolean fillWithSpaces)5631 int insertBlockSelectionText(String text, boolean fillWithSpaces) {
5632 int lineCount = 1;
5633 for (int i = 0; i < text.length(); i++) {
5634 char ch = text.charAt(i);
5635 if (ch == '\n' || ch == '\r') {
5636 lineCount++;
5637 if (ch == '\r' && i + 1 < text.length() && text.charAt(i + 1) == '\n') {
5638 i++;
5639 }
5640 }
5641 }
5642 String[] lines = new String[lineCount];
5643 int start = 0;
5644 lineCount = 0;
5645 for (int i = 0; i < text.length(); i++) {
5646 char ch = text.charAt(i);
5647 if (ch == '\n' || ch == '\r') {
5648 lines[lineCount++] = text.substring(start, i);
5649 if (ch == '\r' && i + 1 < text.length() && text.charAt(i + 1) == '\n') {
5650 i++;
5651 }
5652 start = i + 1;
5653 }
5654 }
5655 lines[lineCount++] = text.substring(start);
5656 if (fillWithSpaces) {
5657 int maxLength = 0;
5658 for (String line : lines) {
5659 int length = line.length();
5660 maxLength = Math.max(maxLength, length);
5661 }
5662 for (int i = 0; i < lines.length; i++) {
5663 String line = lines[i];
5664 int length = line.length();
5665 if (length < maxLength) {
5666 int numSpaces = maxLength - length;
5667 StringBuilder buffer = new StringBuilder(length + numSpaces);
5668 buffer.append(line);
5669 for (int j = 0; j < numSpaces; j++) buffer.append(' ');
5670 lines[i] = buffer.toString();
5671 }
5672 }
5673 }
5674 int firstLine, lastLine, left, right;
5675 if (blockXLocation != -1) {
5676 Rectangle rect = getBlockSelectionPosition();
5677 firstLine = rect.y;
5678 lastLine = rect.height;
5679 left = rect.x;
5680 right = rect.width;
5681 } else {
5682 firstLine = lastLine = getCaretLine();
5683 left = right = getPointAtOffset(caretOffset).x;
5684 }
5685 start = caretOffset;
5686 int caretLine = getCaretLine();
5687 int index = 0, lineIndex = firstLine;
5688 while (lineIndex <= lastLine) {
5689 String string = index < lineCount ? lines[index++] : "";
5690 int lineStart = sendTextEvent(left, right, lineIndex, string, fillWithSpaces);
5691 if (lineIndex == caretLine) start = lineStart;
5692 lineIndex++;
5693 }
5694 while (index < lineCount) {
5695 int lineStart = sendTextEvent(left, left, lineIndex, lines[index++], fillWithSpaces);
5696 if (lineIndex == caretLine) start = lineStart;
5697 lineIndex++;
5698 }
5699 return start;
5700 }
5701 void insertBlockSelectionText(char key, int action) {
5702 if (key == SWT.CR || key == SWT.LF) return;
5703 Rectangle rect = getBlockSelectionPosition();
5704 int firstLine = rect.y;
5705 int lastLine = rect.height;
5706 int left = rect.x;
5707 int right = rect.width;
5708 int[] trailing = new int[1];
5709 int offset = 0, delta = 0;
5710 String text = key != 0 ? new String(new char[] {key}) : "";
5711 int length = text.length();
5712 for (int lineIndex = firstLine; lineIndex <= lastLine; lineIndex++) {
5713 String line = content.getLine(lineIndex);
5714 int lineOffset = content.getOffsetAtLine(lineIndex);
5715 int lineEndOffset = lineOffset + line.length();
5716 int linePixel = getLinePixel(lineIndex);
5717 int start = getOffsetAtPoint(left, linePixel, trailing, true);
5718 boolean outOfLine = start == -1;
5719 if (outOfLine) {
5720 start = left < leftMargin ? lineOffset : lineEndOffset;
5721 } else {
5722 start += trailing[0];
5723 }
5724 int end = getOffsetAtPoint(right, linePixel, trailing, true);
5725 if (end == -1) {
5726 end = right < leftMargin ? lineOffset : lineEndOffset;
5727 } else {
5728 end += trailing[0];
5729 }
5730 if (start > end) {
5731 int temp = start;
5732 start = end;
5733 end = temp;
5734 }
5735 if (start == end && !outOfLine) {
5736 switch (action) {
5737 case ST.DELETE_PREVIOUS:
5738 if (start > lineOffset) start = getClusterPrevious(start, lineIndex);
5739 break;
5740 case ST.DELETE_NEXT:
5741 if (end < lineEndOffset) end = getClusterNext(end, lineIndex);
5742 break;
5743 }
5744 }
5745 if (outOfLine) {
5746 if (line.length() >= delta) {
5747 delta = line.length();
5748 offset = lineEndOffset + length;
5749 }
5750 } else {
5751 offset = start + length;
5752 delta = content.getCharCount();
5753 }
5754 Event event = new Event();
5755 event.text = text;
5756 event.start = start;
5757 event.end = end;
5758 sendKeyEvent(event);
5759 }
5760 int x = getPointAtOffset(offset).x;
5761 int verticalScrollOffset = getVerticalScrollOffset();
5762 setBlockSelectionLocation(x, blockYAnchor - verticalScrollOffset, x, blockYLocation - verticalScrollOffset, false);
5763 }
5764 /**
5765 * Creates content change listeners and set the default content model.
5766 */
5767 void installDefaultContent() {
5768 textChangeListener = new TextChangeListener() {
5769 @Override
5770 public void textChanging(TextChangingEvent event) {
5771 handleTextChanging(event);
5772 }
5773 @Override
5774 public void textChanged(TextChangedEvent event) {
5775 handleTextChanged(event);
5776 }
5777 @Override
5778 public void textSet(TextChangedEvent event) {
5779 handleTextSet(event);
5780 }
5781 };
5782 content = new DefaultContent();
5783 content.addTextChangeListener(textChangeListener);
5784 }
5785 /**
5786 * Adds event listeners
5787 */
5788 void installListeners() {
5789 ScrollBar verticalBar = getVerticalBar();
5790 ScrollBar horizontalBar = getHorizontalBar();
5791
5792 listener = event -> {
5793 switch (event.type) {
5794 case SWT.Dispose: handleDispose(event); break;
5795 case SWT.KeyDown: handleKeyDown(event); break;
5796 case SWT.KeyUp: handleKeyUp(event); break;
5797 case SWT.MenuDetect: handleMenuDetect(event); break;
5798 case SWT.MouseDown: handleMouseDown(event); break;
5799 case SWT.MouseUp: handleMouseUp(event); break;
5800 case SWT.MouseMove: handleMouseMove(event); break;
5801 case SWT.Paint: handlePaint(event); break;
5802 case SWT.Resize: handleResize(event); break;
5803 case SWT.Traverse: handleTraverse(event); break;
5804 }
5805 };
5806 addListener(SWT.Dispose, listener);
5807 addListener(SWT.KeyDown, listener);
5808 addListener(SWT.KeyUp, listener);
5809 addListener(SWT.MenuDetect, listener);
5810 addListener(SWT.MouseDown, listener);
5811 addListener(SWT.MouseUp, listener);
5812 addListener(SWT.MouseMove, listener);
5813 addListener(SWT.Paint, listener);
5814 addListener(SWT.Resize, listener);
5815 addListener(SWT.Traverse, listener);
5816 ime.addListener(SWT.ImeComposition, event -> {
5817 if (!editable) {
5818 event.doit = false;
5819 event.start = 0;
5820 event.end = 0;
5821 event.text = "";
5822 return;
5823 }
5824
5825 switch (event.detail) {
5826 case SWT.COMPOSITION_SELECTION: handleCompositionSelection(event); break;
5827 case SWT.COMPOSITION_CHANGED: handleCompositionChanged(event); break;
5828 case SWT.COMPOSITION_OFFSET: handleCompositionOffset(event); break;
5829 }
5830 });
5831 if (verticalBar != null) {
5832 verticalBar.addListener(SWT.Selection, this::handleVerticalScroll);
5833 }
5834 if (horizontalBar != null) {
5835 horizontalBar.addListener(SWT.Selection, this::handleHorizontalScroll);
5836 }
5837 }
5838 void internalRedrawRange(int start, int length) {
5839 if (length <= 0) return;
5840 int end = start + length;
5841 int startLine = content.getLineAtOffset(start);
5842 int endLine = content.getLineAtOffset(end);
5843 int partialBottomIndex = getPartialBottomIndex();
5844 int partialTopIndex = getPartialTopIndex();
5845 if (startLine > partialBottomIndex || endLine < partialTopIndex) {
5846 return;
5847 }
5848 if (partialTopIndex > startLine) {
5849 startLine = partialTopIndex;
5850 start = 0;
5851 } else {
5852 start -= content.getOffsetAtLine(startLine);
5853 }
5854 if (partialBottomIndex < endLine) {
5855 endLine = partialBottomIndex + 1;
5856 end = 0;
5857 } else {
5858 end -= content.getOffsetAtLine(endLine);
5859 }
5860
5861 TextLayout layout = renderer.getTextLayout(startLine);
5862 int lineX = leftMargin - horizontalScrollOffset, startLineY = getLinePixel(startLine);
5863 int[] offsets = layout.getLineOffsets();
5864 int startIndex = layout.getLineIndex(Math.min(start, layout.getText().length()));
5865
5866 /* Redraw end of line before start line if wrapped and start offset is first char */
5867 if (isWordWrap() && startIndex > 0 && offsets[startIndex] == start) {
5868 Rectangle rect = layout.getLineBounds(startIndex - 1);
5869 rect.x = rect.width;
5870 rect.width = clientAreaWidth - rightMargin - rect.x;
5871 rect.x += lineX;
5872 rect.y += startLineY;
5873 super.redraw(rect.x, rect.y, rect.width, rect.height, false);
5874 }
5875
5876 if (startLine == endLine) {
5877 int endIndex = layout.getLineIndex(Math.min(end, layout.getText().length()));
5878 if (startIndex == endIndex) {
5879 /* Redraw rect between start and end offset if start and end offsets are in same wrapped line */
5880 Rectangle rect = layout.getBounds(start, end - 1);
5881 rect.x += lineX;
5882 rect.y += startLineY;
5883 super.redraw(rect.x, rect.y, rect.width, rect.height, false);
5884 renderer.disposeTextLayout(layout);
5885 return;
5886 }
5887 }
5888
5889 /* Redraw start line from the start offset to the end of client area */
5890 Rectangle startRect = layout.getBounds(start, offsets[startIndex + 1] - 1);
5891 if (startRect.height == 0) {
5892 Rectangle bounds = layout.getLineBounds(startIndex);
5893 startRect.x = bounds.width;
5894 startRect.y = bounds.y;
5895 startRect.height = bounds.height;
5896 }
5897 startRect.x += lineX;
5898 startRect.y += startLineY;
5899 startRect.width = clientAreaWidth - rightMargin - startRect.x;
5900 super.redraw(startRect.x, startRect.y, startRect.width, startRect.height, false);
5901
5902 /* Redraw end line from the beginning of the line to the end offset */
5903 if (startLine != endLine) {
5904 renderer.disposeTextLayout(layout);
5905 layout = renderer.getTextLayout(endLine);
5906 offsets = layout.getLineOffsets();
5907 }
5908 int endIndex = layout.getLineIndex(Math.min(end, layout.getText().length()));
5909 Rectangle endRect = layout.getBounds(offsets[endIndex], end - 1);
5910 if (endRect.height == 0) {
5911 Rectangle bounds = layout.getLineBounds(endIndex);
5912 endRect.y = bounds.y;
5913 endRect.height = bounds.height;
5914 }
5915 endRect.x += lineX;
5916 endRect.y += getLinePixel(endLine);
5917 super.redraw(endRect.x, endRect.y, endRect.width, endRect.height, false);
5918 renderer.disposeTextLayout(layout);
5919
5920 /* Redraw all lines in between start and end line */
5921 int y = startRect.y + startRect.height;
5922 if (endRect.y > y) {
5923 super.redraw(leftMargin, y, clientAreaWidth - rightMargin - leftMargin, endRect.y - y, false);
5924 }
5925 }
5926 void handleCompositionOffset (Event event) {
5927 int[] trailing = new int [1];
5928 event.index = getOffsetAtPoint(event.x, event.y, trailing, true);
5929 event.count = trailing[0];
5930 }
5931 void handleCompositionSelection (Event event) {
5932 if (event.start != event.end) {
5933 int charCount = getCharCount();
5934 event.start = Math.max(0, Math.min(event.start, charCount));
5935 event.end = Math.max(0, Math.min(event.end, charCount));
5936 if (event.text != null) {
5937 setSelection(event.start, event.end);
5938 } else {
5939 event.text = getTextRange(event.start, event.end - event.start);
5940 }
5941 } else {
5942 event.start = selection.x;
5943 event.end = selection.y;
5944 event.text = getSelectionText();
5945 }
5946 }
5947 void handleCompositionChanged(Event event) {
5948 String text = event.text;
5949 int start = event.start;
5950 int end = event.end;
5951 int charCount = content.getCharCount();
5952 start = Math.min(start, charCount);
5953 end = Math.min(end, charCount);
5954 int length = text.length();
5955 if (length == ime.getCommitCount()) {
5956 content.replaceTextRange(start, end - start, "");
5957 setCaretOffset(ime.getCompositionOffset(), SWT.DEFAULT);
5958 caretWidth = 0;
5959 caretDirection = SWT.NULL;
5960 } else {
5961 content.replaceTextRange(start, end - start, text);
5962 int alignment = SWT.DEFAULT;
5963 if (ime.getWideCaret()) {
5964 start = ime.getCompositionOffset();
5965 int lineIndex = getCaretLine();
5966 int lineOffset = content.getOffsetAtLine(lineIndex);
5967 TextLayout layout = renderer.getTextLayout(lineIndex);
5968 caretWidth = layout.getBounds(start - lineOffset, start + length - 1 - lineOffset).width;
5969 renderer.disposeTextLayout(layout);
5970 alignment = OFFSET_LEADING;
5971 }
5972 setCaretOffset(ime.getCaretOffset(), alignment);
5973 }
5974 resetSelection();
5975 showCaret();
5976 }
5977 /**
5978 * Frees resources.
5979 */
5980 void handleDispose(Event event) {
5981 removeListener(SWT.Dispose, listener);
5982 notifyListeners(SWT.Dispose, event);
5983 event.type = SWT.None;
5984
5985 clipboard.dispose();
5986 if (renderer != null) {
5987 renderer.dispose();
5988 renderer = null;
5989 }
5990 if (content != null) {
5991 content.removeTextChangeListener(textChangeListener);
5992 content = null;
5993 }
5994 if (defaultCaret != null) {
5995 defaultCaret.dispose();
5996 defaultCaret = null;
5997 }
5998 if (leftCaretBitmap != null) {
5999 leftCaretBitmap.dispose();
6000 leftCaretBitmap = null;
6001 }
6002 if (rightCaretBitmap != null) {
6003 rightCaretBitmap.dispose();
6004 rightCaretBitmap = null;
6005 }
6006 if (isBidiCaret()) {
6007 BidiUtil.removeLanguageListener(this);
6008 }
6009 selectionBackground = null;
6010 selectionForeground = null;
6011 marginColor = null;
6012 textChangeListener = null;
6013 selection = null;
6014 doubleClickSelection = null;
6015 keyActionMap = null;
6016 background = null;
6017 foreground = null;
6018 clipboard = null;
6019 tabs = null;
6020 }
6021 /**
6022 * Scrolls the widget horizontally.
6023 */
6024 void handleHorizontalScroll(Event event) {
6025 int scrollPixel = getHorizontalBar().getSelection() - horizontalScrollOffset;
6026 scrollHorizontal(scrollPixel, false);
6027 }
6028 /**
6029 * If an action has been registered for the key stroke execute the action.
6030 * Otherwise, if a character has been entered treat it as new content.
6031 *
6032 * @param event keyboard event
6033 */
6034 void handleKey(Event event) {
6035 int action;
6036 caretAlignment = PREVIOUS_OFFSET_TRAILING;
6037 if (event.keyCode != 0) {
6038 // special key pressed (e.g., F1)
6039 action = getKeyBinding(event.keyCode | event.stateMask);
6040 } else {
6041 // character key pressed
6042 action = getKeyBinding(event.character | event.stateMask);
6043 if (action == SWT.NULL) {
6044 // see if we have a control character
6045 if ((event.stateMask & SWT.CTRL) != 0 && event.character <= 31) {
6046 // get the character from the CTRL+char sequence, the control
6047 // key subtracts 64 from the value of the key that it modifies
6048 int c = event.character + 64;
6049 action = getKeyBinding(c | event.stateMask);
6050 }
6051 }
6052 }
6053 if (action == SWT.NULL) {
6054 boolean ignore = false;
6055
6056 if (IS_MAC) {
6057 // Ignore accelerator key combinations (we do not want to
6058 // insert a character in the text in this instance).
6059 ignore = (event.stateMask & (SWT.COMMAND | SWT.CTRL)) != 0;
6060 } else {
6061 // Ignore accelerator key combinations (we do not want to
6062 // insert a character in the text in this instance). Don't
6063 // ignore CTRL+ALT combinations since that is the Alt Gr
6064 // key on some keyboards. See bug 20953.
6065 ignore = event.stateMask == SWT.ALT ||
6066 event.stateMask == SWT.CTRL ||
6067 event.stateMask == (SWT.ALT | SWT.SHIFT) ||
6068 event.stateMask == (SWT.CTRL | SWT.SHIFT);
6069 }
6070 // -ignore anything below SPACE except for line delimiter keys and tab.
6071 // -ignore DEL
6072 if (!ignore && event.character > 31 && event.character != SWT.DEL ||
6073 event.character == SWT.CR || event.character == SWT.LF ||
6074 event.character == TAB) {
6075 doContent(event.character);
6076 update();
6077 }
6078 } else {
6079 invokeAction(action);
6080 }
6081 }
6082 /**
6083 * If a VerifyKey listener exists, verify that the key that was entered
6084 * should be processed.
6085 *
6086 * @param event keyboard event
6087 */
6088 void handleKeyDown(Event event) {
6089 if (clipboardSelection == null) {
6090 clipboardSelection = new Point(selection.x, selection.y);
6091 }
6092 newOrientation = SWT.NONE;
6093 event.stateMask &= SWT.MODIFIER_MASK;
6094
6095 Event verifyEvent = new Event();
6096 verifyEvent.character = event.character;
6097 verifyEvent.keyCode = event.keyCode;
6098 verifyEvent.keyLocation = event.keyLocation;
6099 verifyEvent.stateMask = event.stateMask;
6100 verifyEvent.doit = event.doit;
6101 notifyListeners(ST.VerifyKey, verifyEvent);
6102 if (verifyEvent.doit) {
6103 if ((event.stateMask & SWT.MODIFIER_MASK) == SWT.CTRL && event.keyCode == SWT.SHIFT && isBidiCaret()) {
6104 newOrientation = event.keyLocation == SWT.LEFT ? SWT.LEFT_TO_RIGHT : SWT.RIGHT_TO_LEFT;
6105 }
6106 handleKey(event);
6107 }
6108 }
6109 /**
6110 * Update the Selection Clipboard.
6111 *
6112 * @param event keyboard event
6113 */
6114 void handleKeyUp(Event event) {
6115 if (clipboardSelection != null) {
6116 if (clipboardSelection.x != selection.x || clipboardSelection.y != selection.y) {
6117 copySelection(DND.SELECTION_CLIPBOARD);
6118 }
6119 }
6120 clipboardSelection = null;
6121
6122 if (newOrientation != SWT.NONE) {
6123 if (newOrientation != getOrientation()) {
6124 Event e = new Event();
6125 e.doit = true;
6126 notifyListeners(SWT.OrientationChange, e);
6127 if (e.doit) {
6128 setOrientation(newOrientation);
6129 }
6130 }
6131 newOrientation = SWT.NONE;
6132 }
6133 }
6134 /**
6135 * Update the event location for focus-based context menu triggers.
6136 *
6137 * @param event menu detect event
6138 */
6139 void handleMenuDetect(Event event) {
6140 if (event.detail == SWT.MENU_KEYBOARD) {
6141 Point point = getDisplay().map(this, null, getPointAtOffset(caretOffset));
6142 event.x = point.x;
6143 event.y = point.y + getLineHeight(caretOffset);
6144 }
6145 }
6146 /**
6147 * Updates the caret location and selection if mouse button 1 has been
6148 * pressed.
6149 */
6150 void handleMouseDown(Event event) {
6151 //force focus (object support)
6152 forceFocus();
6153
6154 //drag detect
6155 if (dragDetect && checkDragDetect(event)) return;
6156
6157 //paste clipboard selection
6158 if (event.button == 2) {
6159 // On GTK, if mouseNavigator is enabled we have to distinguish a short middle-click (to paste content) from
6160 // a long middle-click (mouse navigation started)
6161 if (IS_GTK && mouseNavigator != null) {
6162 middleClickPressed = true;
6163 getDisplay().timerExec(200, ()->{
6164 boolean click = middleClickPressed;
6165 middleClickPressed = false;
6166 if (click && mouseNavigator !=null) {
6167 mouseNavigator.onMouseDown(event);
6168 } else {
6169 pasteOnMiddleClick(event);
6170 }
6171 });
6172 return;
6173 } else {
6174 pasteOnMiddleClick(event);
6175 }
6176 }
6177
6178 //set selection
6179 if ((event.button != 1) || (IS_MAC && (event.stateMask & SWT.MOD4) != 0)) {
6180 return;
6181 }
6182 clickCount = event.count;
6183 if (clickCount == 1) {
6184 boolean select = (event.stateMask & SWT.MOD2) != 0;
6185 doMouseLocationChange(event.x, event.y, select);
6186 } else {
6187 if (doubleClickEnabled) {
6188 boolean wordSelect = (clickCount & 1) == 0;
6189 int offset = getOffsetAtPoint(event.x, event.y, null);
6190 int lineIndex = content.getLineAtOffset(offset);
6191 int lineOffset = content.getOffsetAtLine(lineIndex);
6192 if (wordSelect) {
6193 int min = blockSelection ? lineOffset : 0;
6194 int max = blockSelection ? lineOffset + content.getLine(lineIndex).length() : content.getCharCount();
6195 int start = Math.max(min, getWordPrevious(offset, SWT.MOVEMENT_WORD_START));
6196 int end = Math.min(max, getWordNext(start, SWT.MOVEMENT_WORD_END));
6197 setSelection(start, end - start, false, true);
6198 sendSelectionEvent();
6199 } else {
6200 if (blockSelection) {
6201 setBlockSelectionLocation(leftMargin, event.y, clientAreaWidth - rightMargin, event.y, true);
6202 } else {
6203 int lineEnd = content.getCharCount();
6204 if (lineIndex + 1 < content.getLineCount()) {
6205 lineEnd = content.getOffsetAtLine(lineIndex + 1);
6206 }
6207 setSelection(lineOffset, lineEnd - lineOffset, false, false);
6208 sendSelectionEvent();
6209 }
6210 }
6211 doubleClickSelection = new Point(selection.x, selection.y);
6212 showCaret();
6213 }
6214 }
6215 }
6216 /**
6217 * Updates the caret location and selection if mouse button 1 is pressed
6218 * during the mouse move.
6219 */
6220 void handleMouseMove(Event event) {
6221 if (clickCount > 0) {
6222 update();
6223 doAutoScroll(event);
6224 doMouseLocationChange(event.x, event.y, true);
6225 }
6226 if (renderer.hasLinks) {
6227 doMouseLinkCursor(event.x, event.y);
6228 }
6229 }
6230 /**
6231 * Autoscrolling ends when the mouse button is released.
6232 */
6233 void handleMouseUp(Event event) {
6234 middleClickPressed = false;
6235 clickCount = 0;
6236 endAutoScroll();
6237 if (event.button == 1) {
6238 copySelection(DND.SELECTION_CLIPBOARD);
6239 }
6240 }
6241 /**
6242 * Renders the invalidated area specified in the paint event.
6243 *
6244 * @param event paint event
6245 */
6246 void handlePaint(Event event) {
6247 if (event.width == 0 || event.height == 0) return;
6248 if (clientAreaWidth == 0 || clientAreaHeight == 0) return;
6249
6250 int startLine = getLineIndex(event.y);
6251 int y = getLinePixel(startLine);
6252 int endY = event.y + event.height;
6253 GC gc = event.gc;
6254 Color background = getBackground();
6255 Color foreground = getForeground();
6256 if (endY > 0) {
6257 int lineCount = isSingleLine() ? 1 : content.getLineCount();
6258 int x = leftMargin - horizontalScrollOffset;
6259 for (int i = startLine; y < endY && i < lineCount; i++) {
6260 y += renderer.drawLine(i, x, y, gc, background, foreground);
6261 }
6262 if (y < endY) {
6263 gc.setBackground(background);
6264 drawBackground(gc, 0, y, clientAreaWidth, endY - y);
6265 }
6266 }
6267 if (blockSelection && blockXLocation != -1) {
6268 gc.setBackground(getSelectionBackground());
6269 Rectangle rect = getBlockSelectionRectangle();
6270 gc.drawRectangle(rect.x, rect.y, Math.max(1, rect.width - 1), Math.max(1, rect.height - 1));
6271 gc.setAdvanced(true);
6272 if (gc.getAdvanced()) {
6273 gc.setAlpha(100);
6274 gc.fillRectangle(rect);
6275 gc.setAdvanced(false);
6276 }
6277 }
6278
6279 // fill the margin background
6280 gc.setBackground(marginColor != null ? marginColor : background);
6281 if (topMargin > 0) {
6282 drawBackground(gc, 0, 0, clientAreaWidth, topMargin);
6283 }
6284 if (bottomMargin > 0) {
6285 drawBackground(gc, 0, clientAreaHeight - bottomMargin, clientAreaWidth, bottomMargin);
6286 }
6287 if (leftMargin - alignmentMargin > 0) {
6288 drawBackground(gc, 0, 0, leftMargin - alignmentMargin, clientAreaHeight);
6289 }
6290 if (rightMargin > 0) {
6291 drawBackground(gc, clientAreaWidth - rightMargin, 0, rightMargin, clientAreaHeight);
6292 }
6293 }
6294 /**
6295 * Recalculates the scroll bars. Rewraps all lines when in word
6296 * wrap mode.
6297 *
6298 * @param event resize event
6299 */
6300 void handleResize(Event event) {
6301 int oldHeight = clientAreaHeight;
6302 int oldWidth = clientAreaWidth;
6303 Rectangle clientArea = getClientArea();
6304 clientAreaHeight = clientArea.height;
6305 clientAreaWidth = clientArea.width;
6306 if (!alwaysShowScroll && ignoreResize != 0) return;
6307
6308 redrawMargins(oldHeight, oldWidth);
6309 if (wordWrap) {
6310 if (oldWidth != clientAreaWidth) {
6311 renderer.reset(0, content.getLineCount());
6312 verticalScrollOffset = -1;
6313 renderer.calculateIdle();
6314 super.redraw();
6315 }
6316 if (oldHeight != clientAreaHeight) {
6317 if (oldHeight == 0) topIndexY = 0;
6318 setScrollBars(true);
6319 }
6320 setCaretLocation();
6321 } else {
6322 renderer.calculateClientArea();
6323 setScrollBars(true);
6324 claimRightFreeSpace();
6325 // StyledText allows any value for horizontalScrollOffset when clientArea is zero
6326 // in setHorizontalPixel() and setHorisontalOffset(). Fixes bug 168429.
6327 if (clientAreaWidth != 0) {
6328 ScrollBar horizontalBar = getHorizontalBar();
6329 if (horizontalBar != null && horizontalBar.getVisible()) {
6330 if (horizontalScrollOffset != horizontalBar.getSelection()) {
6331 horizontalBar.setSelection(horizontalScrollOffset);
6332 horizontalScrollOffset = horizontalBar.getSelection();
6333 }
6334 }
6335 }
6336 }
6337 updateCaretVisibility();
6338 claimBottomFreeSpace();
6339 setAlignment();
6340 //TODO FIX TOP INDEX DURING RESIZE
6341 // if (oldHeight != clientAreaHeight || wordWrap) {
6342 // calculateTopIndex(0);
6343 // }
6344 }
6345 /**
6346 * Updates the caret position and selection and the scroll bars to reflect
6347 * the content change.
6348 */
6349 void handleTextChanged(TextChangedEvent event) {
6350 int offset = ime.getCompositionOffset();
6351 if (offset != -1 && lastTextChangeStart < offset) {
6352 ime.setCompositionOffset(offset + lastTextChangeNewCharCount - lastTextChangeReplaceCharCount);
6353 }
6354 int firstLine = content.getLineAtOffset(lastTextChangeStart);
6355 resetCache(firstLine, 0);
6356 if (!isFixedLineHeight() && topIndex > firstLine) {
6357 topIndex = firstLine;
6358 if (topIndex < 0) {
6359 // TODO: This logging is in place to determine why topIndex is getting set to negative values.
6360 // It should be deleted once we fix the root cause of this issue. See bug 487254 for details.
6361 System.err.println("StyledText: topIndex was " + topIndex
6362 + ", lastTextChangeStart = " + lastTextChangeStart
6363 + ", content.getClass() = " + content.getClass()
6364 );
6365 topIndex = 0;
6366 }
6367 topIndexY = 0;
6368 super.redraw();
6369 } else {
6370 int lastLine = firstLine + lastTextChangeNewLineCount;
6371 int firstLineTop = getLinePixel(firstLine);
6372 int newLastLineBottom = getLinePixel(lastLine + 1);
6373 if (lastLineBottom != newLastLineBottom) {
6374 super.redraw();
6375 } else {
6376 super.redraw(0, firstLineTop, clientAreaWidth, newLastLineBottom - firstLineTop, false);
6377 redrawLinesBullet(renderer.redrawLines);
6378 }
6379 }
6380 renderer.redrawLines = null;
6381 // update selection/caret location after styles have been changed.
6382 // otherwise any text measuring could be incorrect
6383 //
6384 // also, this needs to be done after all scrolling. Otherwise,
6385 // selection redraw would be flushed during scroll which is wrong.
6386 // in some cases new text would be drawn in scroll source area even
6387 // though the intent is to scroll it.
6388 if (!(blockSelection && blockXLocation != -1)) {
6389 updateSelection(lastTextChangeStart, lastTextChangeReplaceCharCount, lastTextChangeNewCharCount);
6390 }
6391 if (lastTextChangeReplaceLineCount > 0 || wordWrap || visualWrap) {
6392 claimBottomFreeSpace();
6393 }
6394 if (lastTextChangeReplaceCharCount > 0) {
6395 claimRightFreeSpace();
6396 }
6397
6398 sendAccessibleTextChanged(lastTextChangeStart, lastTextChangeNewCharCount, 0);
6399 lastCharCount += lastTextChangeNewCharCount;
6400 lastCharCount -= lastTextChangeReplaceCharCount;
6401 setAlignment();
6402 }
6403 /**
6404 * Updates the screen to reflect a pending content change.
6405 *
6406 * @param event .start the start offset of the change
6407 * @param event .newText text that is going to be inserted or empty String
6408 * if no text will be inserted
6409 * @param event .replaceCharCount length of text that is going to be replaced
6410 * @param event .newCharCount length of text that is going to be inserted
6411 * @param event .replaceLineCount number of lines that are going to be replaced
6412 * @param event .newLineCount number of new lines that are going to be inserted
6413 */
6414 void handleTextChanging(TextChangingEvent event) {
6415 if (event.replaceCharCount < 0) {
6416 event.start += event.replaceCharCount;
6417 event.replaceCharCount *= -1;
6418 }
6419 lastTextChangeStart = event.start;
6420 lastTextChangeNewLineCount = event.newLineCount;
6421 lastTextChangeNewCharCount = event.newCharCount;
6422 lastTextChangeReplaceLineCount = event.replaceLineCount;
6423 lastTextChangeReplaceCharCount = event.replaceCharCount;
6424 int lineIndex = content.getLineAtOffset(event.start);
6425 int srcY = getLinePixel(lineIndex + event.replaceLineCount + 1);
6426 int destY = getLinePixel(lineIndex + 1) + event.newLineCount * renderer.getLineHeight();
6427 lastLineBottom = destY;
6428 if (srcY < 0 && destY < 0) {
6429 lastLineBottom += srcY - destY;
6430 verticalScrollOffset += destY - srcY;
6431 calculateTopIndex(destY - srcY);
6432 setScrollBars(true);
6433 } else {
6434 scrollText(srcY, destY);
6435 }
6436 sendAccessibleTextChanged(lastTextChangeStart, 0, lastTextChangeReplaceCharCount);
6437 renderer.textChanging(event);
6438
6439 // Update the caret offset if it is greater than the length of the content.
6440 // This is necessary since style range API may be called between the
6441 // handleTextChanging and handleTextChanged events and this API sets the
6442 // caretOffset.
6443 int newEndOfText = content.getCharCount() - event.replaceCharCount + event.newCharCount;
6444 if (caretOffset > newEndOfText) setCaretOffset(newEndOfText, SWT.DEFAULT);
6445 }
6446 /**
6447 * Called when the widget content is set programmatically, overwriting
6448 * the old content. Resets the caret position, selection and scroll offsets.
6449 * Recalculates the content width and scroll bars. Redraws the widget.
6450 *
6451 * @param event text change event.
6452 */
6453 void handleTextSet(TextChangedEvent event) {
6454 reset();
6455 int newCharCount = getCharCount();
6456 sendAccessibleTextChanged(0, newCharCount, lastCharCount);
6457 lastCharCount = newCharCount;
6458 setAlignment();
6459 }
6460 /**
6461 * Called when a traversal key is pressed.
6462 * Allow tab next traversal to occur when the widget is in single
6463 * line mode or in multi line and non-editable mode .
6464 * When in editable multi line mode we want to prevent the tab
6465 * traversal and receive the tab key event instead.
6466 *
6467 * @param event the event
6468 */
6469 void handleTraverse(Event event) {
6470 switch (event.detail) {
6471 case SWT.TRAVERSE_ESCAPE:
6472 case SWT.TRAVERSE_PAGE_NEXT:
6473 case SWT.TRAVERSE_PAGE_PREVIOUS:
6474 event.doit = true;
6475 break;
6476 case SWT.TRAVERSE_RETURN:
6477 case SWT.TRAVERSE_TAB_NEXT:
6478 case SWT.TRAVERSE_TAB_PREVIOUS:
6479 if ((getStyle() & SWT.SINGLE) != 0) {
6480 event.doit = true;
6481 } else {
6482 if (!editable || (event.stateMask & SWT.MODIFIER_MASK) != 0) {
6483 event.doit = true;
6484 }
6485 }
6486 break;
6487 }
6488 }
6489 /**
6490 * Scrolls the widget vertically.
6491 */
6492 void handleVerticalScroll(Event event) {
6493 int scrollPixel = getVerticalBar().getSelection() - getVerticalScrollOffset();
6494 scrollVertical(scrollPixel, false);
6495 }
6496 /**
6497 * Add accessibility support for the widget.
6498 */
6499 void initializeAccessible() {
6500 acc = getAccessible();
6501
6502 accAdapter = new AccessibleAdapter() {
6503 @Override
6504 public void getName (AccessibleEvent e) {
6505 String name = null;
6506 String text = getAssociatedLabel ();
6507 if (text != null) {
6508 name = stripMnemonic (text);
6509 }
6510 e.result = name;
6511 }
6512 @Override
6513 public void getHelp(AccessibleEvent e) {
6514 e.result = getToolTipText();
6515 }
6516 @Override
6517 public void getKeyboardShortcut(AccessibleEvent e) {
6518 String shortcut = null;
6519 String text = getAssociatedLabel ();
6520 if (text != null) {
6521 char mnemonic = _findMnemonic (text);
6522 if (mnemonic != '\0') {
6523 shortcut = "Alt+"+mnemonic; //$NON-NLS-1$
6524 }
6525 }
6526 e.result = shortcut;
6527 }
6528 };
6529 acc.addAccessibleListener(accAdapter);
6530
6531 accTextExtendedAdapter = new AccessibleTextExtendedAdapter() {
6532 @Override
6533 public void getCaretOffset(AccessibleTextEvent e) {
6534 e.offset = StyledText.this.getCaretOffset();
6535 }
6536 @Override
6537 public void setCaretOffset(AccessibleTextEvent e) {
6538 StyledText.this.setCaretOffset(e.offset);
6539 e.result = ACC.OK;
6540 }
6541 @Override
6542 public void getSelectionRange(AccessibleTextEvent e) {
6543 Point selection = StyledText.this.getSelectionRange();
6544 e.offset = selection.x;
6545 e.length = selection.y;
6546 }
6547 @Override
6548 public void addSelection(AccessibleTextEvent e) {
6549 StyledText st = StyledText.this;
6550 Point point = st.getSelection();
6551 if (point.x == point.y) {
6552 int end = e.end;
6553 if (end == -1) end = st.getCharCount();
6554 st.setSelection(e.start, end);
6555 e.result = ACC.OK;
6556 }
6557 }
6558 @Override
6559 public void getSelection(AccessibleTextEvent e) {
6560 StyledText st = StyledText.this;
6561 if (st.blockSelection && st.blockXLocation != -1) {
6562 Rectangle rect = st.getBlockSelectionPosition();
6563 int lineIndex = rect.y + e.index;
6564 int linePixel = st.getLinePixel(lineIndex);
6565 e.ranges = getRanges(rect.x, linePixel, rect.width, linePixel);
6566 if (e.ranges.length > 0) {
6567 e.start = e.ranges[0];
6568 e.end = e.ranges[e.ranges.length - 1];
6569 }
6570 } else {
6571 if (e.index == 0) {
6572 Point point = st.getSelection();
6573 e.start = point.x;
6574 e.end = point.y;
6575 if (e.start > e.end) {
6576 int temp = e.start;
6577 e.start = e.end;
6578 e.end = temp;
6579 }
6580 }
6581 }
6582 }
6583 @Override
6584 public void getSelectionCount(AccessibleTextEvent e) {
6585 StyledText st = StyledText.this;
6586 if (st.blockSelection && st.blockXLocation != -1) {
6587 Rectangle rect = st.getBlockSelectionPosition();
6588 e.count = rect.height - rect.y + 1;
6589 } else {
6590 Point point = st.getSelection();
6591 e.count = point.x == point.y ? 0 : 1;
6592 }
6593 }
6594 @Override
6595 public void removeSelection(AccessibleTextEvent e) {
6596 StyledText st = StyledText.this;
6597 if (e.index == 0) {
6598 if (st.blockSelection) {
6599 st.clearBlockSelection(true, false);
6600 } else {
6601 st.clearSelection(false);
6602 }
6603 e.result = ACC.OK;
6604 }
6605 }
6606 @Override
6607 public void setSelection(AccessibleTextEvent e) {
6608 if (e.index != 0) return;
6609 StyledText st = StyledText.this;
6610 Point point = st.getSelection();
6611 if (point.x == point.y) return;
6612 int end = e.end;
6613 if (end == -1) end = st.getCharCount();
6614 st.setSelection(e.start, end);
6615 e.result = ACC.OK;
6616 }
6617 @Override
6618 public void getCharacterCount(AccessibleTextEvent e) {
6619 e.count = StyledText.this.getCharCount();
6620 }
6621 @Override
6622 public void getOffsetAtPoint(AccessibleTextEvent e) {
6623 StyledText st = StyledText.this;
6624 Point point = new Point (e.x, e.y);
6625 Display display = st.getDisplay();
6626 point = display.map(null, st, point);
6627 e.offset = st.getOffsetAtPoint(point.x, point.y, null, true);
6628 }
6629 @Override
6630 public void getTextBounds(AccessibleTextEvent e) {
6631 StyledText st = StyledText.this;
6632 int start = e.start;
6633 int end = e.end;
6634 int contentLength = st.getCharCount();
6635 start = Math.max(0, Math.min(start, contentLength));
6636 end = Math.max(0, Math.min(end, contentLength));
6637 if (start > end) {
6638 int temp = start;
6639 start = end;
6640 end = temp;
6641 }
6642 int startLine = st.getLineAtOffset(start);
6643 int endLine = st.getLineAtOffset(end);
6644 Rectangle[] rects = new Rectangle[endLine - startLine + 1];
6645 Rectangle bounds = null;
6646 int index = 0;
6647 Display display = st.getDisplay();
6648 for (int lineIndex = startLine; lineIndex <= endLine; lineIndex++) {
6649 Rectangle rect = new Rectangle(0, 0, 0, 0);
6650 rect.y = st.getLinePixel(lineIndex);
6651 rect.height = st.renderer.getLineHeight(lineIndex);
6652 if (lineIndex == startLine) {
6653 rect.x = st.getPointAtOffset(start).x;
6654 } else {
6655 rect.x = st.leftMargin - st.horizontalScrollOffset;
6656 }
6657 if (lineIndex == endLine) {
6658 rect.width = st.getPointAtOffset(end).x - rect.x;
6659 } else {
6660 TextLayout layout = st.renderer.getTextLayout(lineIndex);
6661 rect.width = layout.getBounds().width - rect.x;
6662 st.renderer.disposeTextLayout(layout);
6663 }
6664 rects [index++] = rect = display.map(st, null, rect);
6665 if (bounds == null) {
6666 bounds = new Rectangle(rect.x, rect.y, rect.width, rect.height);
6667 } else {
6668 bounds.add(rect);
6669 }
6670 }
6671 e.rectangles = rects;
6672 if (bounds != null) {
6673 e.x = bounds.x;
6674 e.y = bounds.y;
6675 e.width = bounds.width;
6676 e.height = bounds.height;
6677 }
6678 }
6679 int[] getRanges(int left, int top, int right, int bottom) {
6680 StyledText st = StyledText.this;
6681 int lineStart = st.getLineIndex(top);
6682 int lineEnd = st.getLineIndex(bottom);
6683 int count = lineEnd - lineStart + 1;
6684 int[] ranges = new int [count * 2];
6685 int index = 0;
6686 for (int lineIndex = lineStart; lineIndex <= lineEnd; lineIndex++) {
6687 String line = st.content.getLine(lineIndex);
6688 int lineOffset = st.content.getOffsetAtLine(lineIndex);
6689 int lineEndOffset = lineOffset + line.length();
6690 int linePixel = st.getLinePixel(lineIndex);
6691 int start = st.getOffsetAtPoint(left, linePixel, null, true);
6692 if (start == -1) {
6693 start = left < st.leftMargin ? lineOffset : lineEndOffset;
6694 }
6695 int[] trailing = new int[1];
6696 int end = st.getOffsetAtPoint(right, linePixel, trailing, true);
6697 if (end == -1) {
6698 end = right < st.leftMargin ? lineOffset : lineEndOffset;
6699 } else {
6700 end += trailing[0];
6701 }
6702 if (start > end) {
6703 int temp = start;
6704 start = end;
6705 end = temp;
6706 }
6707 ranges[index++] = start;
6708 ranges[index++] = end;
6709 }
6710 return ranges;
6711 }
6712 @Override
6713 public void getRanges(AccessibleTextEvent e) {
6714 StyledText st = StyledText.this;
6715 Point point = new Point (e.x, e.y);
6716 Display display = st.getDisplay();
6717 point = display.map(null, st, point);
6718 e.ranges = getRanges(point.x, point.y, point.x + e.width, point.y + e.height);
6719 if (e.ranges.length > 0) {
6720 e.start = e.ranges[0];
6721 e.end = e.ranges[e.ranges.length - 1];
6722 }
6723 }
6724 @Override
6725 public void getText(AccessibleTextEvent e) {
6726 StyledText st = StyledText.this;
6727 int start = e.start;
6728 int end = e.end;
6729 int contentLength = st.getCharCount();
6730 if (end == -1) end = contentLength;
6731 start = Math.max(0, Math.min(start, contentLength));
6732 end = Math.max(0, Math.min(end, contentLength));
6733 if (start > end) {
6734 int temp = start;
6735 start = end;
6736 end = temp;
6737 }
6738 int count = e.count;
6739 switch (e.type) {
6740 case ACC.TEXT_BOUNDARY_ALL:
6741 //nothing to do
6742 break;
6743 case ACC.TEXT_BOUNDARY_CHAR: {
6744 int newCount = 0;
6745 if (count > 0) {
6746 while (count-- > 0) {
6747 int newEnd = st.getWordNext(end, SWT.MOVEMENT_CLUSTER);
6748 if (newEnd == contentLength) break;
6749 if (newEnd == end) break;
6750 end = newEnd;
6751 newCount++;
6752 }
6753 start = end;
6754 end = st.getWordNext(end, SWT.MOVEMENT_CLUSTER);
6755 } else {
6756 while (count++ < 0) {
6757 int newStart = st.getWordPrevious(start, SWT.MOVEMENT_CLUSTER);
6758 if (newStart == start) break;
6759 start = newStart;
6760 newCount--;
6761 }
6762 end = st.getWordNext(start, SWT.MOVEMENT_CLUSTER);
6763 }
6764 count = newCount;
6765 break;
6766 }
6767 case ACC.TEXT_BOUNDARY_WORD: {
6768 int newCount = 0;
6769 if (count > 0) {
6770 while (count-- > 0) {
6771 int newEnd = st.getWordNext(end, SWT.MOVEMENT_WORD_START, true);
6772 if (newEnd == end) break;
6773 newCount++;
6774 end = newEnd;
6775 }
6776 start = end;
6777 end = st.getWordNext(start, SWT.MOVEMENT_WORD_END, true);
6778 } else {
6779 if (st.getWordPrevious(Math.min(start + 1, contentLength), SWT.MOVEMENT_WORD_START, true) == start) {
6780 //start is a word start already
6781 count++;
6782 }
6783 while (count <= 0) {
6784 int newStart = st.getWordPrevious(start, SWT.MOVEMENT_WORD_START, true);
6785 if (newStart == start) break;
6786 count++;
6787 start = newStart;
6788 if (count != 0) newCount--;
6789 }
6790 if (count <= 0 && start == 0) {
6791 end = start;
6792 } else {
6793 end = st.getWordNext(start, SWT.MOVEMENT_WORD_END, true);
6794 }
6795 }
6796 count = newCount;
6797 break;
6798 }
6799 case ACC.TEXT_BOUNDARY_LINE:
6800 //TODO implement line
6801 case ACC.TEXT_BOUNDARY_PARAGRAPH:
6802 case ACC.TEXT_BOUNDARY_SENTENCE: {
6803 int offset = count > 0 ? end : start;
6804 int lineIndex = st.getLineAtOffset(offset) + count;
6805 lineIndex = Math.max(0, Math.min(lineIndex, st.getLineCount() - 1));
6806 start = st.getOffsetAtLine(lineIndex);
6807 String line = st.getLine(lineIndex);
6808 end = start + line.length();
6809 count = lineIndex - st.getLineAtOffset(offset);
6810 break;
6811 }
6812 }
6813 e.start = start;
6814 e.end = end;
6815 e.count = count;
6816 e.result = st.content.getTextRange(start, end - start);
6817 }
6818 @Override
6819 public void getVisibleRanges(AccessibleTextEvent e) {
6820 e.ranges = getRanges(leftMargin, topMargin, clientAreaWidth - rightMargin, clientAreaHeight - bottomMargin);
6821 if (e.ranges.length > 0) {
6822 e.start = e.ranges[0];
6823 e.end = e.ranges[e.ranges.length - 1];
6824 }
6825 }
6826 @Override
6827 public void scrollText(AccessibleTextEvent e) {
6828 StyledText st = StyledText.this;
6829 int topPixel = getTopPixel(), horizontalPixel = st.getHorizontalPixel();
6830 switch (e.type) {
6831 case ACC.SCROLL_TYPE_ANYWHERE:
6832 case ACC.SCROLL_TYPE_TOP_LEFT:
6833 case ACC.SCROLL_TYPE_LEFT_EDGE:
6834 case ACC.SCROLL_TYPE_TOP_EDGE: {
6835 Rectangle rect = st.getBoundsAtOffset(e.start);
6836 if (e.type != ACC.SCROLL_TYPE_TOP_EDGE) {
6837 horizontalPixel = horizontalPixel + rect.x - st.leftMargin;
6838 }
6839 if (e.type != ACC.SCROLL_TYPE_LEFT_EDGE) {
6840 topPixel = topPixel + rect.y - st.topMargin;
6841 }
6842 break;
6843 }
6844 case ACC.SCROLL_TYPE_BOTTOM_RIGHT:
6845 case ACC.SCROLL_TYPE_BOTTOM_EDGE:
6846 case ACC.SCROLL_TYPE_RIGHT_EDGE: {
6847 Rectangle rect = st.getBoundsAtOffset(e.end - 1);
6848 if (e.type != ACC.SCROLL_TYPE_BOTTOM_EDGE) {
6849 horizontalPixel = horizontalPixel - st.clientAreaWidth + rect.x + rect.width + st.rightMargin;
6850 }
6851 if (e.type != ACC.SCROLL_TYPE_RIGHT_EDGE) {
6852 topPixel = topPixel - st.clientAreaHeight + rect.y +rect.height + st.bottomMargin;
6853 }
6854 break;
6855 }
6856 case ACC.SCROLL_TYPE_POINT: {
6857 Point point = new Point(e.x, e.y);
6858 Display display = st.getDisplay();
6859 point = display.map(null, st, point);
6860 Rectangle rect = st.getBoundsAtOffset(e.start);
6861 topPixel = topPixel - point.y + rect.y;
6862 horizontalPixel = horizontalPixel - point.x + rect.x;
6863 break;
6864 }
6865 }
6866 st.setTopPixel(topPixel);
6867 st.setHorizontalPixel(horizontalPixel);
6868 e.result = ACC.OK;
6869 }
6870 };
6871 acc.addAccessibleTextListener(accTextExtendedAdapter);
6872
6873 accEditableTextListener = new AccessibleEditableTextListener() {
6874 @Override
6875 public void setTextAttributes(AccessibleTextAttributeEvent e) {
6876 // This method must be implemented by the application
6877 e.result = ACC.OK;
6878 }
6879 @Override
6880 public void replaceText(AccessibleEditableTextEvent e) {
6881 StyledText st = StyledText.this;
6882 st.replaceTextRange(e.start, e.end - e.start, e.string);
6883 e.result = ACC.OK;
6884 }
6885 @Override
6886 public void pasteText(AccessibleEditableTextEvent e) {
6887 StyledText st = StyledText.this;
6888 st.setSelection(e.start);
6889 st.paste();
6890 e.result = ACC.OK;
6891 }
6892 @Override
6893 public void cutText(AccessibleEditableTextEvent e) {
6894 StyledText st = StyledText.this;
6895 st.setSelection(e.start, e.end);
6896 st.cut();
6897 e.result = ACC.OK;
6898 }
6899 @Override
6900 public void copyText(AccessibleEditableTextEvent e) {
6901 StyledText st = StyledText.this;
6902 st.setSelection(e.start, e.end);
6903 st.copy();
6904 e.result = ACC.OK;
6905 }
6906 };
6907 acc.addAccessibleEditableTextListener(accEditableTextListener);
6908
6909 accAttributeAdapter = new AccessibleAttributeAdapter() {
6910 @Override
6911 public void getAttributes(AccessibleAttributeEvent e) {
6912 StyledText st = StyledText.this;
6913 e.leftMargin = st.getLeftMargin();
6914 e.topMargin = st.getTopMargin();
6915 e.rightMargin = st.getRightMargin();
6916 e.bottomMargin = st.getBottomMargin();
6917 e.tabStops = st.getTabStops();
6918 e.justify = st.getJustify();
6919 e.alignment = st.getAlignment();
6920 e.indent = st.getIndent();
6921 }
6922 @Override
6923 public void getTextAttributes(AccessibleTextAttributeEvent e) {
6924 StyledText st = StyledText.this;
6925 int contentLength = st.getCharCount();
6926 if (!isListening(ST.LineGetStyle) && st.renderer.styleCount == 0) {
6927 e.start = 0;
6928 e.end = contentLength;
6929 e.textStyle = new TextStyle(st.getFont(), st.foreground, st.background);
6930 return;
6931 }
6932 int offset = Math.max(0, Math.min(e.offset, contentLength - 1));
6933 int lineIndex = st.getLineAtOffset(offset);
6934 int lineOffset = st.getOffsetAtLine(lineIndex);
6935 int lineCount = st.getLineCount();
6936 offset = offset - lineOffset;
6937
6938 TextLayout layout = st.renderer.getTextLayout(lineIndex);
6939 int lineLength = layout.getText().length();
6940 if (lineLength > 0) {
6941 e.textStyle = layout.getStyle(Math.max(0, Math.min(offset, lineLength - 1)));
6942 }
6943
6944 // If no override info available, use defaults. Don't supply default colors, though.
6945 if (e.textStyle == null) {
6946 e.textStyle = new TextStyle(st.getFont(), st.foreground, st.background);
6947 } else {
6948 if (e.textStyle.foreground == null || e.textStyle.background == null || e.textStyle.font == null) {
6949 TextStyle textStyle = new TextStyle(e.textStyle);
6950 if (textStyle.foreground == null) textStyle.foreground = st.foreground;
6951 if (textStyle.background == null) textStyle.background = st.background;
6952 if (textStyle.font == null) textStyle.font = st.getFont();
6953 e.textStyle = textStyle;
6954 }
6955 }
6956
6957 //offset at line delimiter case
6958 if (offset >= lineLength) {
6959 e.start = lineOffset + lineLength;
6960 if (lineIndex + 1 < lineCount) {
6961 e.end = st.getOffsetAtLine(lineIndex + 1);
6962 } else {
6963 e.end = contentLength;
6964 }
6965 return;
6966 }
6967
6968 int[] ranges = layout.getRanges();
6969 st.renderer.disposeTextLayout(layout);
6970 int index = 0;
6971 int end = 0;
6972 while (index < ranges.length) {
6973 int styleStart = ranges[index++];
6974 int styleEnd = ranges[index++];
6975 if (styleStart <= offset && offset <= styleEnd) {
6976 e.start = lineOffset + styleStart;
6977 e.end = lineOffset + styleEnd + 1;
6978 return;
6979 }
6980 if (styleStart > offset) {
6981 e.start = lineOffset + end;
6982 e.end = lineOffset + styleStart;
6983 return;
6984 }
6985 end = styleEnd + 1;
6986 }
6987 if (index == ranges.length) {
6988 e.start = lineOffset + end;
6989 if (lineIndex + 1 < lineCount) {
6990 e.end = st.getOffsetAtLine(lineIndex + 1);
6991 } else {
6992 e.end = contentLength;
6993 }
6994 }
6995 }
6996 };
6997 acc.addAccessibleAttributeListener(accAttributeAdapter);
6998
6999 accControlAdapter = new AccessibleControlAdapter() {
7000 @Override
7001 public void getRole(AccessibleControlEvent e) {
7002 e.detail = ACC.ROLE_TEXT;
7003 }
7004 @Override
7005 public void getState(AccessibleControlEvent e) {
7006 int state = 0;
7007 if (isEnabled()) state |= ACC.STATE_FOCUSABLE;
7008 if (isFocusControl()) state |= ACC.STATE_FOCUSED;
7009 if (!isVisible()) state |= ACC.STATE_INVISIBLE;
7010 if (!getEditable()) state |= ACC.STATE_READONLY;
7011 if (isSingleLine()) state |= ACC.STATE_SINGLELINE;
7012 else state |= ACC.STATE_MULTILINE;
7013 e.detail = state;
7014 }
7015 @Override
7016 public void getValue(AccessibleControlEvent e) {
7017 e.result = StyledText.this.getText();
7018 }
7019 };
7020 acc.addAccessibleControlListener(accControlAdapter);
7021
7022 addListener(SWT.FocusIn, event -> acc.setFocus(ACC.CHILDID_SELF));
7023 }
7024
7025 @Override
7026 public void dispose() {
7027 /*
7028 * Note: It is valid to attempt to dispose a widget more than once.
7029 * Added check for this.
7030 */
7031 if (!isDisposed()) {
7032 acc.removeAccessibleControlListener(accControlAdapter);
7033 acc.removeAccessibleAttributeListener(accAttributeAdapter);
7034 acc.removeAccessibleEditableTextListener(accEditableTextListener);
7035 acc.removeAccessibleTextListener(accTextExtendedAdapter);
7036 acc.removeAccessibleListener(accAdapter);
7037 }
7038 super.dispose();
7039 }
7040
7041 /*
7042 * Return the Label immediately preceding the receiver in the z-order,
7043 * or null if none.
7044 */
7045 String getAssociatedLabel () {
7046 Control[] siblings = getParent ().getChildren ();
7047 for (int i = 0; i < siblings.length; i++) {
7048 if (siblings [i] == StyledText.this) {
7049 if (i > 0) {
7050 Control sibling = siblings [i-1];
7051 if (sibling instanceof Label) return ((Label) sibling).getText();
7052 if (sibling instanceof CLabel) return ((CLabel) sibling).getText();
7053 }
7054 break;
7055 }
7056 }
7057 return null;
7058 }
7059 String stripMnemonic (String string) {
7060 int index = 0;
7061 int length = string.length ();
7062 do {
7063 while ((index < length) && (string.charAt (index) != '&')) index++;
7064 if (++index >= length) return string;
7065 if (string.charAt (index) != '&') {
7066 return string.substring(0, index-1) + string.substring(index, length);
7067 }
7068 index++;
7069 } while (index < length);
7070 return string;
7071 }
7072 /*
7073 * Return the lowercase of the first non-'&' character following
7074 * an '&' character in the given string. If there are no '&'
7075 * characters in the given string, return '\0'.
7076 */
7077 char _findMnemonic (String string) {
7078 if (string == null) return '\0';
7079 int index = 0;
7080 int length = string.length ();
7081 do {
7082 while (index < length && string.charAt (index) != '&') index++;
7083 if (++index >= length) return '\0';
7084 if (string.charAt (index) != '&') return Character.toLowerCase (string.charAt (index));
7085 index++;
7086 } while (index < length);
7087 return '\0';
7088 }
7089 /**
7090 * Executes the action.
7091 *
7092 * @param action one of the actions defined in ST.java
7093 */
7094 public void invokeAction(int action) {
7095 checkWidget();
7096 if (blockSelection && invokeBlockAction(action)) return;
7097 updateCaretDirection = true;
7098 switch (action) {
7099 // Navigation
7100 case ST.LINE_UP:
7101 doLineUp(false);
7102 clearSelection(true);
7103 break;
7104 case ST.LINE_DOWN:
7105 doLineDown(false);
7106 clearSelection(true);
7107 break;
7108 case ST.LINE_START:
7109 doLineStart();
7110 clearSelection(true);
7111 break;
7112 case ST.LINE_END:
7113 doLineEnd();
7114 clearSelection(true);
7115 break;
7116 case ST.COLUMN_PREVIOUS:
7117 doCursorPrevious();
7118 clearSelection(true);
7119 break;
7120 case ST.COLUMN_NEXT:
7121 doCursorNext();
7122 clearSelection(true);
7123 break;
7124 case ST.PAGE_UP:
7125 doPageUp(false, -1);
7126 clearSelection(true);
7127 break;
7128 case ST.PAGE_DOWN:
7129 doPageDown(false, -1);
7130 clearSelection(true);
7131 break;
7132 case ST.WORD_PREVIOUS:
7133 doWordPrevious();
7134 clearSelection(true);
7135 break;
7136 case ST.WORD_NEXT:
7137 doWordNext();
7138 clearSelection(true);
7139 break;
7140 case ST.TEXT_START:
7141 doContentStart();
7142 clearSelection(true);
7143 break;
7144 case ST.TEXT_END:
7145 doContentEnd();
7146 clearSelection(true);
7147 break;
7148 case ST.WINDOW_START:
7149 doPageStart();
7150 clearSelection(true);
7151 break;
7152 case ST.WINDOW_END:
7153 doPageEnd();
7154 clearSelection(true);
7155 break;
7156 // Selection
7157 case ST.SELECT_LINE_UP:
7158 doSelectionLineUp();
7159 break;
7160 case ST.SELECT_ALL:
7161 selectAll();
7162 break;
7163 case ST.SELECT_LINE_DOWN:
7164 doSelectionLineDown();
7165 break;
7166 case ST.SELECT_LINE_START:
7167 doLineStart();
7168 doSelection(ST.COLUMN_PREVIOUS);
7169 break;
7170 case ST.SELECT_LINE_END:
7171 doLineEnd();
7172 doSelection(ST.COLUMN_NEXT);
7173 break;
7174 case ST.SELECT_COLUMN_PREVIOUS:
7175 doSelectionCursorPrevious();
7176 doSelection(ST.COLUMN_PREVIOUS);
7177 break;
7178 case ST.SELECT_COLUMN_NEXT:
7179 doSelectionCursorNext();
7180 doSelection(ST.COLUMN_NEXT);
7181 break;
7182 case ST.SELECT_PAGE_UP:
7183 doSelectionPageUp(-1);
7184 break;
7185 case ST.SELECT_PAGE_DOWN:
7186 doSelectionPageDown(-1);
7187 break;
7188 case ST.SELECT_WORD_PREVIOUS:
7189 doSelectionWordPrevious();
7190 doSelection(ST.COLUMN_PREVIOUS);
7191 break;
7192 case ST.SELECT_WORD_NEXT:
7193 doSelectionWordNext();
7194 doSelection(ST.COLUMN_NEXT);
7195 break;
7196 case ST.SELECT_TEXT_START:
7197 doContentStart();
7198 doSelection(ST.COLUMN_PREVIOUS);
7199 break;
7200 case ST.SELECT_TEXT_END:
7201 doContentEnd();
7202 doSelection(ST.COLUMN_NEXT);
7203 break;
7204 case ST.SELECT_WINDOW_START:
7205 doPageStart();
7206 doSelection(ST.COLUMN_PREVIOUS);
7207 break;
7208 case ST.SELECT_WINDOW_END:
7209 doPageEnd();
7210 doSelection(ST.COLUMN_NEXT);
7211 break;
7212 // Modification
7213 case ST.CUT:
7214 cut();
7215 break;
7216 case ST.COPY:
7217 copy();
7218 break;
7219 case ST.PASTE:
7220 paste();
7221 break;
7222 case ST.DELETE_PREVIOUS:
7223 doBackspace();
7224 break;
7225 case ST.DELETE_NEXT:
7226 doDelete();
7227 break;
7228 case ST.DELETE_WORD_PREVIOUS:
7229 doDeleteWordPrevious();
7230 break;
7231 case ST.DELETE_WORD_NEXT:
7232 doDeleteWordNext();
7233 break;
7234 // Miscellaneous
7235 case ST.TOGGLE_OVERWRITE:
7236 overwrite = !overwrite; // toggle insert/overwrite mode
7237 break;
7238 case ST.TOGGLE_BLOCKSELECTION:
7239 setBlockSelection(!blockSelection);
7240 break;
7241 }
7242 }
7243 /**
7244 * Returns true if an action should not be performed when block
7245 * selection in active
7246 */
7247 boolean invokeBlockAction(int action) {
7248 switch (action) {
7249 // Navigation
7250 case ST.LINE_UP:
7251 case ST.LINE_DOWN:
7252 case ST.LINE_START:
7253 case ST.LINE_END:
7254 case ST.COLUMN_PREVIOUS:
7255 case ST.COLUMN_NEXT:
7256 case ST.PAGE_UP:
7257 case ST.PAGE_DOWN:
7258 case ST.WORD_PREVIOUS:
7259 case ST.WORD_NEXT:
7260 case ST.TEXT_START:
7261 case ST.TEXT_END:
7262 case ST.WINDOW_START:
7263 case ST.WINDOW_END:
7264 clearBlockSelection(false, false);
7265 return false;
7266 // Selection
7267 case ST.SELECT_LINE_UP:
7268 doBlockLineVertical(true);
7269 return true;
7270 case ST.SELECT_LINE_DOWN:
7271 doBlockLineVertical(false);
7272 return true;
7273 case ST.SELECT_LINE_START:
7274 doBlockLineHorizontal(false);
7275 return true;
7276 case ST.SELECT_LINE_END:
7277 doBlockLineHorizontal(true);
7278 return false;
7279 case ST.SELECT_COLUMN_PREVIOUS:
7280 doBlockColumn(false);
7281 return true;
7282 case ST.SELECT_COLUMN_NEXT:
7283 doBlockColumn(true);
7284 return true;
7285 case ST.SELECT_WORD_PREVIOUS:
7286 doBlockWord(false);
7287 return true;
7288 case ST.SELECT_WORD_NEXT:
7289 doBlockWord(true);
7290 return true;
7291 case ST.SELECT_ALL:
7292 return false;
7293 case ST.SELECT_TEXT_START:
7294 doBlockContentStartEnd(false);
7295 break;
7296 case ST.SELECT_TEXT_END:
7297 doBlockContentStartEnd(true);
7298 break;
7299 case ST.SELECT_PAGE_UP:
7300 case ST.SELECT_PAGE_DOWN:
7301 case ST.SELECT_WINDOW_START:
7302 case ST.SELECT_WINDOW_END:
7303 //blocked actions
7304 return true;
7305 // Modification
7306 case ST.CUT:
7307 case ST.COPY:
7308 case ST.PASTE:
7309 return false;
7310 case ST.DELETE_PREVIOUS:
7311 case ST.DELETE_NEXT:
7312 if (blockXLocation != -1) {
7313 insertBlockSelectionText((char)0, action);
7314 return true;
7315 }
7316 return false;
7317 case ST.DELETE_WORD_PREVIOUS:
7318 case ST.DELETE_WORD_NEXT:
7319 //blocked actions
7320 return blockXLocation != -1;
7321 }
7322 return false;
7323 }
7324 boolean isBidiCaret() {
7325 return BidiUtil.isBidiPlatform();
7326 }
7327 boolean isFixedLineHeight() {
7328 return !isWordWrap() && lineSpacing == 0 && renderer.lineSpacingProvider == null && !hasStyleWithVariableHeight && !hasVerticalIndent;
7329 }
7330 /**
7331 * Returns whether the given offset is inside a multi byte line delimiter.
7332 * Example:
7333 * "Line1\r\n" isLineDelimiter(5) == false but isLineDelimiter(6) == true
7334 *
7335 * @return true if the given offset is inside a multi byte line delimiter.
7336 * false if the given offset is before or after a line delimiter.
7337 */
7338 boolean isLineDelimiter(int offset) {
7339 int line = content.getLineAtOffset(offset);
7340 int lineOffset = content.getOffsetAtLine(line);
7341 int offsetInLine = offset - lineOffset;
7342 // offsetInLine will be greater than line length if the line
7343 // delimiter is longer than one character and the offset is set
7344 // in between parts of the line delimiter.
7345 return offsetInLine > content.getLine(line).length();
7346 }
7347 /**
7348 * Returns whether the widget is mirrored (right oriented/right to left
7349 * writing order).
7350 *
7351 * @return isMirrored true=the widget is right oriented, false=the widget
7352 * is left oriented
7353 */
7354 boolean isMirrored() {
7355 return (getStyle() & SWT.MIRRORED) != 0;
7356 }
7357 /**
7358 * Returns <code>true</code> if any text in the widget is selected,
7359 * and <code>false</code> otherwise.
7360 *
7361 * @return the text selection state
7362 * @exception SWTException <ul>
7363 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7364 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7365 * </ul>
7366 *
7367 * @since 3.103
7368 */
7369 public boolean isTextSelected() {
7370 checkWidget();
7371 if (blockSelection && blockXLocation != -1) {
7372 Rectangle rect = getBlockSelectionPosition();
7373 return !rect.isEmpty();
7374 }
7375 return selection.y != selection.x;
7376 }
7377 /**
7378 * Returns whether the widget can have only one line.
7379 *
7380 * @return true if widget can have only one line, false if widget can have
7381 * multiple lines
7382 */
7383 boolean isSingleLine() {
7384 return (getStyle() & SWT.SINGLE) != 0;
7385 }
7386
7387 /**
7388 * Sends the specified verify event, replace/insert text as defined by
7389 * the event and send a modify event.
7390 *
7391 * @param event the text change event.
7392 * <ul>
7393 * <li>event.start - the replace start offset</li>
7394 * <li>event.end - the replace end offset</li>
7395 * <li>event.text - the new text</li>
7396 * </ul>
7397 * @param updateCaret whether or not he caret should be set behind
7398 * the new text
7399 */
7400 void modifyContent(Event event, boolean updateCaret) {
7401 event.doit = true;
7402 notifyListeners(SWT.Verify, event);
7403 if (event.doit) {
7404 StyledTextEvent styledTextEvent = null;
7405 int replacedLength = event.end - event.start;
7406 if (isListening(ST.ExtendedModify)) {
7407 styledTextEvent = new StyledTextEvent(content);
7408 styledTextEvent.start = event.start;
7409 styledTextEvent.end = event.start + event.text.length();
7410 styledTextEvent.text = content.getTextRange(event.start, replacedLength);
7411 }
7412 if (updateCaret) {
7413 //Fix advancing flag for delete/backspace key on direction boundary
7414 if (event.text.length() == 0) {
7415 int lineIndex = content.getLineAtOffset(event.start);
7416 int lineOffset = content.getOffsetAtLine(lineIndex);
7417 TextLayout layout = renderer.getTextLayout(lineIndex);
7418 int levelStart = layout.getLevel(event.start - lineOffset);
7419 int lineIndexEnd = content.getLineAtOffset(event.end);
7420 if (lineIndex != lineIndexEnd) {
7421 renderer.disposeTextLayout(layout);
7422 lineOffset = content.getOffsetAtLine(lineIndexEnd);
7423 layout = renderer.getTextLayout(lineIndexEnd);
7424 }
7425 int levelEnd = layout.getLevel(event.end - lineOffset);
7426 renderer.disposeTextLayout(layout);
7427 if (levelStart != levelEnd) {
7428 caretAlignment = PREVIOUS_OFFSET_TRAILING;
7429 } else {
7430 caretAlignment = OFFSET_LEADING;
7431 }
7432 }
7433 }
7434 content.replaceTextRange(event.start, replacedLength, event.text);
7435 // set the caret position prior to sending the modify event.
7436 // fixes 1GBB8NJ
7437 if (updateCaret && !(blockSelection && blockXLocation != -1)) {
7438 // always update the caret location. fixes 1G8FODP
7439 setSelection(event.start + event.text.length(), 0, true, false);
7440 showCaret();
7441 }
7442 notifyListeners(SWT.Modify, event);
7443 if (isListening(ST.ExtendedModify)) {
7444 notifyListeners(ST.ExtendedModify, styledTextEvent);
7445 }
7446 }
7447 }
7448 void paintObject(GC gc, int x, int y, int ascent, int descent, StyleRange style, Bullet bullet, int bulletIndex) {
7449 if (isListening(ST.PaintObject)) {
7450 StyledTextEvent event = new StyledTextEvent (content) ;
7451 event.gc = gc;
7452 event.x = x;
7453 event.y = y;
7454 event.ascent = ascent;
7455 event.descent = descent;
7456 event.style = style;
7457 event.bullet = bullet;
7458 event.bulletIndex = bulletIndex;
7459 notifyListeners(ST.PaintObject, event);
7460 }
7461 }
7462 /**
7463 * Replaces the selection with the text on the <code>DND.CLIPBOARD</code>
7464 * clipboard or, if there is no selection, inserts the text at the current
7465 * caret offset. If the widget has the SWT.SINGLE style and the
7466 * clipboard text contains more than one line, only the first line without
7467 * line delimiters is inserted in the widget.
7468 *
7469 * @exception SWTException <ul>
7470 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7471 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7472 * </ul>
7473 */
7474 public void paste(){
7475 checkWidget();
7476 String text = (String) getClipboardContent(DND.CLIPBOARD);
7477 if (text != null && text.length() > 0) {
7478 if (blockSelection) {
7479 boolean fillWithSpaces = isFixedLineHeight() && renderer.fixedPitch;
7480 int offset = insertBlockSelectionText(text, fillWithSpaces);
7481 setCaretOffset(offset, SWT.DEFAULT);
7482 clearBlockSelection(true, true);
7483 setCaretLocation();
7484 return;
7485 }
7486 Event event = new Event();
7487 event.start = selection.x;
7488 event.end = selection.y;
7489 String delimitedText = getModelDelimitedText(text);
7490 if (textLimit > 0) {
7491 int uneditedTextLength = getCharCount() - (selection.y - selection.x);
7492 if ((uneditedTextLength + delimitedText.length()) > textLimit) {
7493 int endIndex = textLimit - uneditedTextLength;
7494 delimitedText = delimitedText.substring(0, Math.max(endIndex, 0));
7495 }
7496 }
7497 event.text = delimitedText;
7498 sendKeyEvent(event);
7499 }
7500 }
7501 private void pasteOnMiddleClick(Event event) {
7502 String text = (String)getClipboardContent(DND.SELECTION_CLIPBOARD);
7503 if (text != null && text.length() > 0) {
7504 // position cursor
7505 doMouseLocationChange(event.x, event.y, false);
7506 // insert text
7507 Event e = new Event();
7508 e.start = selection.x;
7509 e.end = selection.y;
7510 e.text = getModelDelimitedText(text);
7511 sendKeyEvent(e);
7512 }
7513 }
7514 /**
7515 * Prints the widget's text to the default printer.
7516 *
7517 * @exception SWTException <ul>
7518 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7519 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7520 * </ul>
7521 */
7522 public void print() {
7523 checkWidget();
7524 Printer printer = new Printer();
7525 StyledTextPrintOptions options = new StyledTextPrintOptions();
7526 options.printTextForeground = true;
7527 options.printTextBackground = true;
7528 options.printTextFontStyle = true;
7529 options.printLineBackground = true;
7530 new Printing(this, printer, options).run();
7531 printer.dispose();
7532 }
7533 /**
7534 * Returns a runnable that will print the widget's text
7535 * to the specified printer.
7536 * <p>
7537 * The runnable may be run in a non-UI thread.
7538 * </p>
7539 *
7540 * @param printer the printer to print to
7541 *
7542 * @return a <code>Runnable</code> for printing the receiver's text
7543 *
7544 * @exception SWTException <ul>
7545 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7546 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7547 * </ul>
7548 * @exception IllegalArgumentException <ul>
7549 * <li>ERROR_NULL_ARGUMENT when printer is null</li>
7550 * </ul>
7551 */
7552 public Runnable print(Printer printer) {
7553 checkWidget();
7554 if (printer == null) {
7555 SWT.error(SWT.ERROR_NULL_ARGUMENT);
7556 }
7557 StyledTextPrintOptions options = new StyledTextPrintOptions();
7558 options.printTextForeground = true;
7559 options.printTextBackground = true;
7560 options.printTextFontStyle = true;
7561 options.printLineBackground = true;
7562 return print(printer, options);
7563 }
7564 /**
7565 * Returns a runnable that will print the widget's text
7566 * to the specified printer.
7567 * <p>
7568 * The runnable may be run in a non-UI thread.
7569 * </p>
7570 *
7571 * @param printer the printer to print to
7572 * @param options print options to use during printing
7573 *
7574 * @return a <code>Runnable</code> for printing the receiver's text
7575 *
7576 * @exception SWTException <ul>
7577 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7578 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7579 * </ul>
7580 * @exception IllegalArgumentException <ul>
7581 * <li>ERROR_NULL_ARGUMENT when printer or options is null</li>
7582 * </ul>
7583 * @since 2.1
7584 */
7585 public Runnable print(Printer printer, StyledTextPrintOptions options) {
7586 checkWidget();
7587 if (printer == null || options == null) {
7588 SWT.error(SWT.ERROR_NULL_ARGUMENT);
7589 }
7590 return new Printing(this, printer, options);
7591 }
7592 /**
7593 * Causes the entire bounds of the receiver to be marked
7594 * as needing to be redrawn. The next time a paint request
7595 * is processed, the control will be completely painted.
7596 * <p>
7597 * Recalculates the content width for all lines in the bounds.
7598 * When a <code>LineStyleListener</code> is used a redraw call
7599 * is the only notification to the widget that styles have changed
7600 * and that the content width may have changed.
7601 * </p>
7602 *
7603 * @exception SWTException <ul>
7604 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7605 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7606 * </ul>
7607 *
7608 * @see Control#update()
7609 */
7610 @Override
7611 public void redraw() {
7612 super.redraw();
7613 int itemCount = getPartialBottomIndex() - topIndex + 1;
7614 renderer.reset(topIndex, itemCount);
7615 renderer.calculate(topIndex, itemCount);
7616 setScrollBars(false);
7617 doMouseLinkCursor();
7618 }
7619 /**
7620 * Causes the rectangular area of the receiver specified by
7621 * the arguments to be marked as needing to be redrawn.
7622 * The next time a paint request is processed, that area of
7623 * the receiver will be painted. If the <code>all</code> flag
7624 * is <code>true</code>, any children of the receiver which
7625 * intersect with the specified area will also paint their
7626 * intersecting areas. If the <code>all</code> flag is
7627 * <code>false</code>, the children will not be painted.
7628 * <p>
7629 * Marks the content width of all lines in the specified rectangle
7630 * as unknown. Recalculates the content width of all visible lines.
7631 * When a <code>LineStyleListener</code> is used a redraw call
7632 * is the only notification to the widget that styles have changed
7633 * and that the content width may have changed.
7634 * </p>
7635 *
7636 * @param x the x coordinate of the area to draw
7637 * @param y the y coordinate of the area to draw
7638 * @param width the width of the area to draw
7639 * @param height the height of the area to draw
7640 * @param all <code>true</code> if children should redraw, and <code>false</code> otherwise
7641 *
7642 * @exception SWTException <ul>
7643 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7644 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7645 * </ul>
7646 *
7647 * @see Control#update()
7648 */
7649 @Override
7650 public void redraw(int x, int y, int width, int height, boolean all) {
7651 super.redraw(x, y, width, height, all);
7652 if (height > 0) {
7653 int firstLine = getLineIndex(y);
7654 int lastLine = getLineIndex(y + height);
7655 resetCache(firstLine, lastLine - firstLine + 1);
7656 doMouseLinkCursor();
7657 }
7658 }
7659 void redrawLines(int startLine, int lineCount, boolean bottomChanged) {
7660 // do nothing if redraw range is completely invisible
7661 int endLine = startLine + lineCount - 1;
7662 int partialBottomIndex = getPartialBottomIndex();
7663 int partialTopIndex = getPartialTopIndex();
7664 if (startLine > partialBottomIndex || endLine < partialTopIndex) {
7665 return;
7666 }
7667 // only redraw visible lines
7668 if (startLine < partialTopIndex) {
7669 startLine = partialTopIndex;
7670 }
7671 if (endLine > partialBottomIndex) {
7672 endLine = partialBottomIndex;
7673 }
7674 int redrawTop = getLinePixel(startLine);
7675 int redrawBottom = getLinePixel(endLine + 1);
7676 if (bottomChanged) redrawBottom = clientAreaHeight - bottomMargin;
7677 int redrawWidth = clientAreaWidth - leftMargin - rightMargin;
7678 super.redraw(leftMargin, redrawTop, redrawWidth, redrawBottom - redrawTop, true);
7679 }
7680 void redrawLinesBullet (int[] redrawLines) {
7681 if (redrawLines == null) return;
7682 int topIndex = getPartialTopIndex();
7683 int bottomIndex = getPartialBottomIndex();
7684 for (int redrawLine : redrawLines) {
7685 int lineIndex = redrawLine;
7686 if (!(topIndex <= lineIndex && lineIndex <= bottomIndex)) continue;
7687 int width = -1;
7688 Bullet bullet = renderer.getLineBullet(lineIndex, null);
7689 if (bullet != null) {
7690 StyleRange style = bullet.style;
7691 GlyphMetrics metrics = style.metrics;
7692 width = metrics.width;
7693 }
7694 if (width == -1) width = getClientArea().width;
7695 int height = renderer.getLineHeight(lineIndex);
7696 int y = getLinePixel(lineIndex);
7697 super.redraw(0, y, width, height, false);
7698 }
7699 }
7700 void redrawMargins(int oldHeight, int oldWidth) {
7701 /* Redraw the old or new right/bottom margin if needed */
7702 if (oldWidth != clientAreaWidth) {
7703 if (rightMargin > 0) {
7704 int x = (oldWidth < clientAreaWidth ? oldWidth : clientAreaWidth) - rightMargin;
7705 super.redraw(x, 0, rightMargin, oldHeight, false);
7706 }
7707 }
7708 if (oldHeight != clientAreaHeight) {
7709 if (bottomMargin > 0) {
7710 int y = (oldHeight < clientAreaHeight ? oldHeight : clientAreaHeight) - bottomMargin;
7711 super.redraw(0, y, oldWidth, bottomMargin, false);
7712 }
7713 }
7714 }
7715 /**
7716 * Redraws the specified text range.
7717 *
7718 * @param start offset of the first character to redraw
7719 * @param length number of characters to redraw
7720 * @param clearBackground true if the background should be cleared as
7721 * part of the redraw operation. If true, the entire redraw range will
7722 * be cleared before anything is redrawn. If the redraw range includes
7723 * the last character of a line (i.e., the entire line is redrawn) the
7724 * line is cleared all the way to the right border of the widget.
7725 * The redraw operation will be faster and smoother if clearBackground
7726 * is set to false. Whether or not the flag can be set to false depends
7727 * on the type of change that has taken place. If font styles or
7728 * background colors for the redraw range have changed, clearBackground
7729 * should be set to true. If only foreground colors have changed for
7730 * the redraw range, clearBackground can be set to false.
7731 * @exception SWTException <ul>
7732 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7733 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7734 * </ul>
7735 * @exception IllegalArgumentException <ul>
7736 * <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li>
7737 * </ul>
7738 */
7739 public void redrawRange(int start, int length, boolean clearBackground) {
7740 checkWidget();
7741 int end = start + length;
7742 int contentLength = content.getCharCount();
7743 if (start > end || start < 0 || end > contentLength) {
7744 SWT.error(SWT.ERROR_INVALID_RANGE);
7745 }
7746 int firstLine = content.getLineAtOffset(start);
7747 int lastLine = content.getLineAtOffset(end);
7748 resetCache(firstLine, lastLine - firstLine + 1);
7749 internalRedrawRange(start, length);
7750 doMouseLinkCursor();
7751 }
7752 /**
7753 * Removes the specified bidirectional segment listener.
7754 *
7755 * @param listener the listener which should no longer be notified
7756 *
7757 * @exception SWTException <ul>
7758 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7759 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7760 * </ul>
7761 * @exception IllegalArgumentException <ul>
7762 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
7763 * </ul>
7764 *
7765 * @since 2.0
7766 */
7767 public void removeBidiSegmentListener(BidiSegmentListener listener) {
7768 checkWidget();
7769 if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
7770 removeListener(ST.LineGetSegments, listener);
7771 resetCache(0, content.getLineCount());
7772 setCaretLocation();
7773 super.redraw();
7774 }
7775 /**
7776 * Removes the specified caret listener.
7777 *
7778 * @param listener the listener which should no longer be notified
7779 *
7780 * @exception SWTException <ul>
7781 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7782 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7783 * </ul>
7784 * @exception IllegalArgumentException <ul>
7785 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
7786 * </ul>
7787 *
7788 * @since 3.5
7789 */
7790 public void removeCaretListener(CaretListener listener) {
7791 checkWidget();
7792 if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
7793 removeListener(ST.CaretMoved, listener);
7794 }
7795 /**
7796 * Removes the specified extended modify listener.
7797 *
7798 * @param extendedModifyListener the listener which should no longer be notified
7799 *
7800 * @exception SWTException <ul>
7801 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7802 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7803 * </ul>
7804 * @exception IllegalArgumentException <ul>
7805 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
7806 * </ul>
7807 */
7808 public void removeExtendedModifyListener(ExtendedModifyListener extendedModifyListener) {
7809 checkWidget();
7810 if (extendedModifyListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
7811 removeListener(ST.ExtendedModify, extendedModifyListener);
7812 }
7813 /**
7814 * Removes the specified line background listener.
7815 *
7816 * @param listener the listener which should no longer be notified
7817 *
7818 * @exception SWTException <ul>
7819 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7820 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7821 * </ul>
7822 * @exception IllegalArgumentException <ul>
7823 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
7824 * </ul>
7825 */
7826 public void removeLineBackgroundListener(LineBackgroundListener listener) {
7827 checkWidget();
7828 if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
7829 removeListener(ST.LineGetBackground, listener);
7830 }
7831 /**
7832 * Removes the specified line style listener.
7833 *
7834 * @param listener the listener which should no longer be notified
7835 *
7836 * @exception SWTException <ul>
7837 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7838 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7839 * </ul>
7840 * @exception IllegalArgumentException <ul>
7841 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
7842 * </ul>
7843 */
7844 public void removeLineStyleListener(LineStyleListener listener) {
7845 checkWidget();
7846 if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
7847 removeListener(ST.LineGetStyle, listener);
7848 setCaretLocation();
7849 }
7850 /**
7851 * Removes the specified modify listener.
7852 *
7853 * @param modifyListener the listener which should no longer be notified
7854 *
7855 * @exception SWTException <ul>
7856 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7857 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7858 * </ul>
7859 * @exception IllegalArgumentException <ul>
7860 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
7861 * </ul>
7862 */
7863 public void removeModifyListener(ModifyListener modifyListener) {
7864 checkWidget();
7865 if (modifyListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
7866 removeListener(SWT.Modify, modifyListener);
7867 }
7868 /**
7869 * Removes the specified listener.
7870 *
7871 * @param listener the listener which should no longer be notified
7872 *
7873 * @exception SWTException <ul>
7874 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7875 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7876 * </ul>
7877 * @exception IllegalArgumentException <ul>
7878 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
7879 * </ul>
7880 * @since 3.2
7881 */
7882 public void removePaintObjectListener(PaintObjectListener listener) {
7883 checkWidget();
7884 if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
7885 removeListener(ST.PaintObject, listener);
7886 }
7887 /**
7888 * Removes the listener from the collection of listeners who will
7889 * be notified when the user changes the receiver's selection.
7890 *
7891 * @param listener the listener which should no longer be notified
7892 *
7893 * @exception IllegalArgumentException <ul>
7894 * <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
7895 * </ul>
7896 * @exception SWTException <ul>
7897 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7898 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7899 * </ul>
7900 *
7901 * @see SelectionListener
7902 * @see #addSelectionListener
7903 */
7904 public void removeSelectionListener(SelectionListener listener) {
7905 checkWidget();
7906 if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
7907 removeListener(SWT.Selection, listener);
7908 }
7909 /**
7910 * Removes the specified verify listener.
7911 *
7912 * @param verifyListener the listener which should no longer be notified
7913 *
7914 * @exception SWTException <ul>
7915 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7916 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7917 * </ul>
7918 * @exception IllegalArgumentException <ul>
7919 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
7920 * </ul>
7921 */
7922 public void removeVerifyListener(VerifyListener verifyListener) {
7923 checkWidget();
7924 if (verifyListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
7925 removeListener(SWT.Verify, verifyListener);
7926 }
7927 /**
7928 * Removes the specified key verify listener.
7929 *
7930 * @param listener the listener which should no longer be notified
7931 *
7932 * @exception SWTException <ul>
7933 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7934 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7935 * </ul>
7936 * @exception IllegalArgumentException <ul>
7937 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
7938 * </ul>
7939 */
7940 public void removeVerifyKeyListener(VerifyKeyListener listener) {
7941 if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
7942 removeListener(ST.VerifyKey, listener);
7943 }
7944 /**
7945 * Removes the specified word movement listener.
7946 *
7947 * @param listener the listener which should no longer be notified
7948 *
7949 * @exception SWTException <ul>
7950 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7951 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7952 * </ul>
7953 * @exception IllegalArgumentException <ul>
7954 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
7955 * </ul>
7956 *
7957 * @see MovementEvent
7958 * @see MovementListener
7959 * @see #addWordMovementListener
7960 *
7961 * @since 3.3
7962 */
7963
7964 public void removeWordMovementListener(MovementListener listener) {
7965 checkWidget();
7966 if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
7967 removeListener(ST.WordNext, listener);
7968 removeListener(ST.WordPrevious, listener);
7969 }
7970 /**
7971 * Replaces the styles in the given range with new styles. This method
7972 * effectively deletes the styles in the given range and then adds the
7973 * the new styles.
7974 * <p>
7975 * Note: Because a StyleRange includes the start and length, the
7976 * same instance cannot occur multiple times in the array of styles.
7977 * If the same style attributes, such as font and color, occur in
7978 * multiple StyleRanges, <code>setStyleRanges(int, int, int[], StyleRange[])</code>
7979 * can be used to share styles and reduce memory usage.
7980 * </p><p>
7981 * Should not be called if a LineStyleListener has been set since the
7982 * listener maintains the styles.
7983 * </p>
7984 *
7985 * @param start offset of first character where styles will be deleted
7986 * @param length length of the range to delete styles in
7987 * @param ranges StyleRange objects containing the new style information.
7988 * The ranges should not overlap and should be within the specified start
7989 * and length. The style rendering is undefined if the ranges do overlap
7990 * or are ill-defined. Must not be null.
7991 * @exception SWTException <ul>
7992 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
7993 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
7994 * </ul>
7995 * @exception IllegalArgumentException <ul>
7996 * <li>ERROR_INVALID_RANGE when either start or end is outside the valid range (0 <= offset <= getCharCount())</li>
7997 * <li>ERROR_NULL_ARGUMENT when ranges is null</li>
7998 * </ul>
7999 *
8000 * @since 2.0
8001 *
8002 * @see #setStyleRanges(int, int, int[], StyleRange[])
8003 */
8004 public void replaceStyleRanges(int start, int length, StyleRange[] ranges) {
8005 checkWidget();
8006 if (isListening(ST.LineGetStyle)) return;
8007 if (ranges == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
8008 setStyleRanges(start, length, null, ranges, false);
8009 }
8010 /**
8011 * Replaces the given text range with new text.
8012 * If the widget has the SWT.SINGLE style and "text" contains more than
8013 * one line, only the first line is rendered but the text is stored
8014 * unchanged. A subsequent call to getText will return the same text
8015 * that was set. Note that only a single line of text should be set when
8016 * the SWT.SINGLE style is used.
8017 * <p>
8018 * <b>NOTE:</b> During the replace operation the current selection is
8019 * changed as follows:
8020 * </p>
8021 * <ul>
8022 * <li>selection before replaced text: selection unchanged
8023 * <li>selection after replaced text: adjust the selection so that same text
8024 * remains selected
8025 * <li>selection intersects replaced text: selection is cleared and caret
8026 * is placed after inserted text
8027 * </ul>
8028 *
8029 * @param start offset of first character to replace
8030 * @param length number of characters to replace. Use 0 to insert text
8031 * @param text new text. May be empty to delete text.
8032 * @exception SWTException <ul>
8033 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8034 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8035 * </ul>
8036 * @exception IllegalArgumentException <ul>
8037 * <li>ERROR_INVALID_RANGE when either start or end is outside the valid range (0 <= offset <= getCharCount())</li>
8038 * <li>ERROR_INVALID_ARGUMENT when either start or end is inside a multi byte line delimiter.
8039 * 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>
8040 * <li>ERROR_NULL_ARGUMENT when string is null</li>
8041 * </ul>
8042 */
8043 public void replaceTextRange(int start, int length, String text) {
8044 checkWidget();
8045 if (text == null) {
8046 SWT.error(SWT.ERROR_NULL_ARGUMENT);
8047 }
8048 int contentLength = getCharCount();
8049 int end = start + length;
8050 if (start > end || start < 0 || end > contentLength) {
8051 SWT.error(SWT.ERROR_INVALID_RANGE);
8052 }
8053 Event event = new Event();
8054 event.start = start;
8055 event.end = end;
8056 event.text = text;
8057 modifyContent(event, false);
8058 }
8059 /**
8060 * Resets the caret position, selection and scroll offsets. Recalculate
8061 * the content width and scroll bars. Redraw the widget.
8062 */
8063 void reset() {
8064 ScrollBar verticalBar = getVerticalBar();
8065 ScrollBar horizontalBar = getHorizontalBar();
8066 setCaretOffset(0, SWT.DEFAULT);
8067 topIndex = 0;
8068 topIndexY = 0;
8069 verticalScrollOffset = 0;
8070 horizontalScrollOffset = 0;
8071 resetSelection();
8072 renderer.setContent(content);
8073 if (verticalBar != null) {
8074 verticalBar.setSelection(0);
8075 }
8076 if (horizontalBar != null) {
8077 horizontalBar.setSelection(0);
8078 }
8079 resetCache(0, 0);
8080 setCaretLocation();
8081 super.redraw();
8082 }
8083 void resetBidiData() {
8084 caretDirection = SWT.NULL;
8085 resetCache(0, content.getLineCount());
8086 setCaretLocation();
8087 keyActionMap.clear();
8088 createKeyBindings();
8089 super.redraw();
8090 }
8091 void resetCache(SortedSet<Integer> lines) {
8092 if (lines == null || lines.isEmpty()) return;
8093 int maxLineIndex = renderer.maxWidthLineIndex;
8094 renderer.reset(lines);
8095 renderer.calculateClientArea();
8096 if (0 <= maxLineIndex && maxLineIndex < content.getLineCount()) {
8097 renderer.calculate(maxLineIndex, 1);
8098 }
8099 setScrollBars(true);
8100 if (!isFixedLineHeight()) {
8101 if (topIndex > lines.iterator().next()) {
8102 verticalScrollOffset = -1;
8103 }
8104 renderer.calculateIdle();
8105 }
8106 }
8107 void resetCache(int firstLine, int count) {
8108 int maxLineIndex = renderer.maxWidthLineIndex;
8109 renderer.reset(firstLine, count);
8110 renderer.calculateClientArea();
8111 if (0 <= maxLineIndex && maxLineIndex < content.getLineCount()) {
8112 renderer.calculate(maxLineIndex, 1);
8113 }
8114 setScrollBars(true);
8115 if (!isFixedLineHeight()) {
8116 if (topIndex > firstLine) {
8117 verticalScrollOffset = -1;
8118 }
8119 renderer.calculateIdle();
8120 }
8121 }
8122 /**
8123 * Resets the selection.
8124 */
8125 void resetSelection() {
8126 selection.x = selection.y = caretOffset;
8127 selectionAnchor = -1;
8128 sendAccessibleTextCaretMoved();
8129 }
8130
8131 @Override
8132 public void scroll(int destX, int destY, int x, int y, int width, int height, boolean all) {
8133 super.scroll(destX, destY, x, y, width, height, false);
8134 if (all) {
8135 int deltaX = destX - x, deltaY = destY - y;
8136 for (Control child : getChildren()) {
8137 Rectangle rect = child.getBounds();
8138 child.setLocation(rect.x + deltaX, rect.y + deltaY);
8139 }
8140 }
8141 }
8142
8143 /**
8144 * Scrolls the widget horizontally.
8145 *
8146 * @param pixels number of SWT logical points to scroll, > 0 = scroll left,
8147 * < 0 scroll right
8148 * @param adjustScrollBar
8149 * true= the scroll thumb will be moved to reflect the new scroll offset.
8150 * false = the scroll thumb will not be moved
8151 * @return
8152 * true=the widget was scrolled
8153 * false=the widget was not scrolled, the given offset is not valid.
8154 */
8155 boolean scrollHorizontal(int pixels, boolean adjustScrollBar) {
8156 if (pixels == 0) return false;
8157 if (wordWrap) return false;
8158 ScrollBar horizontalBar = getHorizontalBar();
8159 if (horizontalBar != null && adjustScrollBar) {
8160 horizontalBar.setSelection(horizontalScrollOffset + pixels);
8161 }
8162 int scrollHeight = clientAreaHeight - topMargin - bottomMargin;
8163 if (pixels > 0) {
8164 int sourceX = leftMargin + pixels;
8165 int scrollWidth = clientAreaWidth - sourceX - rightMargin;
8166 if (scrollWidth > 0) {
8167 scroll(leftMargin, topMargin, sourceX, topMargin, scrollWidth, scrollHeight, true);
8168 }
8169 if (sourceX > scrollWidth) {
8170 super.redraw(leftMargin + scrollWidth, topMargin, pixels - scrollWidth, scrollHeight, true);
8171 }
8172 } else {
8173 int destinationX = leftMargin - pixels;
8174 int scrollWidth = clientAreaWidth - destinationX - rightMargin;
8175 if (scrollWidth > 0) {
8176 scroll(destinationX, topMargin, leftMargin, topMargin, scrollWidth, scrollHeight, true);
8177 }
8178 if (destinationX > scrollWidth) {
8179 super.redraw(leftMargin + scrollWidth, topMargin, -pixels - scrollWidth, scrollHeight, true);
8180 }
8181 }
8182 horizontalScrollOffset += pixels;
8183 setCaretLocation();
8184 return true;
8185 }
8186 /**
8187 * Scrolls the widget vertically.
8188 *
8189 * @param pixel the new vertical scroll offset
8190 * @param adjustScrollBar
8191 * true= the scroll thumb will be moved to reflect the new scroll offset.
8192 * false = the scroll thumb will not be moved
8193 * @return
8194 * true=the widget was scrolled
8195 * false=the widget was not scrolled
8196 */
8197 boolean scrollVertical(int pixels, boolean adjustScrollBar) {
8198 if (pixels == 0) {
8199 return false;
8200 }
8201 if (verticalScrollOffset != -1) {
8202 ScrollBar verticalBar = getVerticalBar();
8203 if (verticalBar != null && adjustScrollBar) {
8204 verticalBar.setSelection(verticalScrollOffset + pixels);
8205 }
8206 int deltaY = 0;
8207 if (pixels > 0) {
8208 int sourceY = topMargin + pixels;
8209 int scrollHeight = clientAreaHeight - sourceY - bottomMargin;
8210 if (scrollHeight > 0) {
8211 deltaY = -pixels;
8212 }
8213 } else {
8214 int destinationY = topMargin - pixels;
8215 int scrollHeight = clientAreaHeight - destinationY - bottomMargin;
8216 if (scrollHeight > 0) {
8217 deltaY = -pixels;
8218 }
8219 }
8220 Control[] children = getChildren();
8221 for (Control child : children) {
8222 Rectangle rect = child.getBounds();
8223 child.setLocation(rect.x, rect.y + deltaY);
8224 }
8225 verticalScrollOffset += pixels;
8226 calculateTopIndex(pixels);
8227 super.redraw();
8228 } else {
8229 calculateTopIndex(pixels);
8230 super.redraw();
8231 }
8232 setCaretLocation();
8233 return true;
8234 }
8235 void scrollText(int srcY, int destY) {
8236 if (srcY == destY) return;
8237 int deltaY = destY - srcY;
8238 int scrollWidth = clientAreaWidth - leftMargin - rightMargin, scrollHeight;
8239 if (deltaY > 0) {
8240 scrollHeight = clientAreaHeight - srcY - bottomMargin;
8241 } else {
8242 scrollHeight = clientAreaHeight - destY - bottomMargin;
8243 }
8244 scroll(leftMargin, destY, leftMargin, srcY, scrollWidth, scrollHeight, true);
8245 if ((0 < srcY + scrollHeight) && (topMargin > srcY)) {
8246 super.redraw(leftMargin, deltaY, scrollWidth, topMargin, false);
8247 }
8248 if ((0 < destY + scrollHeight) && (topMargin > destY)) {
8249 super.redraw(leftMargin, 0, scrollWidth, topMargin, false);
8250 }
8251 if ((clientAreaHeight - bottomMargin < srcY + scrollHeight) && (clientAreaHeight > srcY)) {
8252 super.redraw(leftMargin, clientAreaHeight - bottomMargin + deltaY, scrollWidth, bottomMargin, false);
8253 }
8254 if ((clientAreaHeight - bottomMargin < destY + scrollHeight) && (clientAreaHeight > destY)) {
8255 super.redraw(leftMargin, clientAreaHeight - bottomMargin, scrollWidth, bottomMargin, false);
8256 }
8257 }
8258 void sendAccessibleTextCaretMoved() {
8259 if (caretOffset != accCaretOffset) {
8260 accCaretOffset = caretOffset;
8261 getAccessible().textCaretMoved(caretOffset);
8262 }
8263 }
8264 void sendAccessibleTextChanged(int start, int newCharCount, int replaceCharCount) {
8265 Accessible accessible = getAccessible();
8266 if (replaceCharCount != 0) {
8267 accessible.textChanged(ACC.TEXT_DELETE, start, replaceCharCount);
8268 }
8269 if (newCharCount != 0) {
8270 accessible.textChanged(ACC.TEXT_INSERT, start, newCharCount);
8271 }
8272 }
8273 /**
8274 * Selects all the text.
8275 *
8276 * @exception SWTException <ul>
8277 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8278 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8279 * </ul>
8280 */
8281 public void selectAll() {
8282 checkWidget();
8283 if (blockSelection) {
8284 renderer.calculate(0, content.getLineCount());
8285 setScrollBars(false);
8286 int verticalScrollOffset = getVerticalScrollOffset();
8287 int left = leftMargin - horizontalScrollOffset;
8288 int top = topMargin - verticalScrollOffset;
8289 int right = renderer.getWidth() - rightMargin - horizontalScrollOffset;
8290 int bottom = renderer.getHeight() - bottomMargin - verticalScrollOffset;
8291 setBlockSelectionLocation(left, top, right, bottom, false);
8292 return;
8293 }
8294 setSelection(0, Math.max(getCharCount(),0));
8295 }
8296 /**
8297 * Replaces/inserts text as defined by the event.
8298 *
8299 * @param event the text change event.
8300 * <ul>
8301 * <li>event.start - the replace start offset</li>
8302 * <li>event.end - the replace end offset</li>
8303 * <li>event.text - the new text</li>
8304 * </ul>
8305 */
8306 void sendKeyEvent(Event event) {
8307 if (editable) {
8308 modifyContent(event, true);
8309 }
8310 }
8311 /**
8312 * Returns a StyledTextEvent that can be used to request data such
8313 * as styles and background color for a line.
8314 * <p>
8315 * The specified line may be a visual (wrapped) line if in word
8316 * wrap mode. The returned object will always be for a logical
8317 * (unwrapped) line.
8318 * </p>
8319 *
8320 * @param lineOffset offset of the line. This may be the offset of
8321 * a visual line if the widget is in word wrap mode.
8322 * @param line line text. This may be the text of a visual line if
8323 * the widget is in word wrap mode.
8324 * @return StyledTextEvent that can be used to request line data
8325 * for the given line.
8326 */
8327 StyledTextEvent sendLineEvent(int eventType, int lineOffset, String line) {
8328 StyledTextEvent event = null;
8329 if (isListening(eventType)) {
8330 event = new StyledTextEvent(content);
8331 event.detail = lineOffset;
8332 event.text = line;
8333 event.alignment = alignment;
8334 event.indent = indent;
8335 event.wrapIndent = wrapIndent;
8336 event.justify = justify;
8337 notifyListeners(eventType, event);
8338 }
8339 return event;
8340 }
8341 /**
8342 * Sends the specified selection event.
8343 */
8344 void sendSelectionEvent() {
8345 getAccessible().textSelectionChanged();
8346 Event event = new Event();
8347 event.x = selection.x;
8348 event.y = selection.y;
8349 notifyListeners(SWT.Selection, event);
8350 }
8351 int sendTextEvent(int left, int right, int lineIndex, String text, boolean fillWithSpaces) {
8352 int lineWidth = 0, start, end;
8353 StringBuilder buffer = new StringBuilder();
8354 if (lineIndex < content.getLineCount()) {
8355 int[] trailing = new int[1];
8356 start = getOffsetAtPoint(left, getLinePixel(lineIndex), trailing, true);
8357 if (start == -1) {
8358 int lineOffset = content.getOffsetAtLine(lineIndex);
8359 int lineLegth = content.getLine(lineIndex).length();
8360 start = end = lineOffset + lineLegth;
8361 if (fillWithSpaces) {
8362 TextLayout layout = renderer.getTextLayout(lineIndex);
8363 lineWidth = layout.getBounds().width;
8364 renderer.disposeTextLayout(layout);
8365 }
8366 } else {
8367 start += trailing[0];
8368 end = left == right ? start : getOffsetAtPoint(right, 0, lineIndex, null);
8369 fillWithSpaces = false;
8370 }
8371 } else {
8372 start = end = content.getCharCount();
8373 buffer.append(content.getLineDelimiter());
8374 }
8375 if (start > end) {
8376 int temp = start;
8377 start = end;
8378 end = temp;
8379 }
8380 if (fillWithSpaces) {
8381 int spacesWidth = left - lineWidth + horizontalScrollOffset - leftMargin;
8382 int spacesCount = spacesWidth / renderer.averageCharWidth;
8383 for (int i = 0; i < spacesCount; i++) {
8384 buffer.append(' ');
8385 }
8386 }
8387 buffer.append(text);
8388 Event event = new Event();
8389 event.start = start;
8390 event.end = end;
8391 event.text = buffer.toString();
8392 sendKeyEvent(event);
8393 return event.start + event.text.length();
8394 }
8395 int sendWordBoundaryEvent(int eventType, int movement, int offset, int newOffset, String lineText, int lineOffset) {
8396 if (isListening(eventType)) {
8397 StyledTextEvent event = new StyledTextEvent(content);
8398 event.detail = lineOffset;
8399 event.text = lineText;
8400 event.count = movement;
8401 event.start = offset;
8402 event.end = newOffset;
8403 notifyListeners(eventType, event);
8404 offset = event.end;
8405 if (offset != newOffset) {
8406 int length = getCharCount();
8407 if (offset < 0) {
8408 offset = 0;
8409 } else if (offset > length) {
8410 offset = length;
8411 } else {
8412 if (isLineDelimiter(offset)) {
8413 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
8414 }
8415 }
8416 }
8417 return offset;
8418 }
8419 return newOffset;
8420 }
8421 void setAlignment() {
8422 if ((getStyle() & SWT.SINGLE) == 0) return;
8423 int alignment = renderer.getLineAlignment(0, this.alignment);
8424 int newAlignmentMargin = 0;
8425 if (alignment != SWT.LEFT) {
8426 renderer.calculate(0, 1);
8427 int width = renderer.getWidth() - alignmentMargin;
8428 newAlignmentMargin = clientAreaWidth - width;
8429 if (newAlignmentMargin < 0) newAlignmentMargin = 0;
8430 if (alignment == SWT.CENTER) newAlignmentMargin /= 2;
8431 }
8432 if (alignmentMargin != newAlignmentMargin) {
8433 leftMargin -= alignmentMargin;
8434 leftMargin += newAlignmentMargin;
8435 alignmentMargin = newAlignmentMargin;
8436 resetCache(0, 1);
8437 setCaretLocation();
8438 super.redraw();
8439 }
8440 }
8441 /**
8442 * Sets the alignment of the widget. The argument should be one of <code>SWT.LEFT</code>,
8443 * <code>SWT.CENTER</code> or <code>SWT.RIGHT</code>. The alignment applies for all lines.
8444 * <p>
8445 * Note that if <code>SWT.MULTI</code> is set, then <code>SWT.WRAP</code> must also be set
8446 * in order to stabilize the right edge before setting alignment.
8447 * </p>
8448 *
8449 * @param alignment the new alignment
8450 *
8451 * @exception SWTException <ul>
8452 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8453 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8454 * </ul>
8455 *
8456 * @see #setLineAlignment(int, int, int)
8457 *
8458 * @since 3.2
8459 */
8460 public void setAlignment(int alignment) {
8461 checkWidget();
8462 alignment &= (SWT.LEFT | SWT.RIGHT | SWT.CENTER);
8463 if (alignment == 0 || this.alignment == alignment) return;
8464 this.alignment = alignment;
8465 resetCache(0, content.getLineCount());
8466 setCaretLocation();
8467 setAlignment();
8468 super.redraw();
8469 }
8470 /**
8471 * Set the Always Show Scrollbars flag. True if the scrollbars are
8472 * always shown even if they are not required (default value). False if the scrollbars are only
8473 * visible when some part of the content needs to be scrolled to be seen.
8474 * The H_SCROLL and V_SCROLL style bits are also required to enable scrollbars in the
8475 * horizontal and vertical directions.
8476 *
8477 * @param show true to show the scrollbars even when not required, false to show scrollbars only when required
8478 *
8479 * @exception SWTException <ul>
8480 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8481 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8482 * </ul>
8483 *
8484 * @since 3.8
8485 */
8486 public void setAlwaysShowScrollBars(boolean show) {
8487 checkWidget();
8488 if (show == alwaysShowScroll) return;
8489 alwaysShowScroll = show;
8490 setScrollBars(true);
8491 }
8492 /**
8493 * @see Control#setBackground(Color)
8494 */
8495 @Override
8496 public void setBackground(Color color) {
8497 checkWidget();
8498 boolean backgroundDisabled = false;
8499 if (!this.enabled && color == null) {
8500 if (background != null) {
8501 Color disabledBg = getDisplay().getSystemColor(SWT.COLOR_TEXT_DISABLED_BACKGROUND);
8502 if (background.equals(disabledBg)) {
8503 return;
8504 } else {
8505 color = new Color (disabledBg.getRGBA());
8506 backgroundDisabled = true;
8507 }
8508 }
8509 }
8510 customBackground = color != null && !this.insideSetEnableCall && !backgroundDisabled;
8511 background = color;
8512 super.setBackground(color);
8513 resetCache(0, content.getLineCount());
8514 setCaretLocation();
8515 super.redraw();
8516 }
8517 /**
8518 * Sets the block selection mode.
8519 *
8520 * @param blockSelection true=enable block selection, false=disable block selection
8521 *
8522 * @since 3.5
8523 */
8524 public void setBlockSelection(boolean blockSelection) {
8525 checkWidget();
8526 if ((getStyle() & SWT.SINGLE) != 0) return;
8527 if (blockSelection == this.blockSelection) return;
8528 if (wordWrap) return;
8529 this.blockSelection = blockSelection;
8530 if (cursor == null) {
8531 Display display = getDisplay();
8532 int type = blockSelection ? SWT.CURSOR_CROSS : SWT.CURSOR_IBEAM;
8533 super.setCursor(display.getSystemCursor(type));
8534 }
8535 if (blockSelection) {
8536 int start = selection.x;
8537 int end = selection.y;
8538 if (start != end) {
8539 setBlockSelectionOffset(start, end, false);
8540 }
8541 } else {
8542 clearBlockSelection(false, false);
8543 }
8544 }
8545 /**
8546 * Sets the block selection bounds. The bounds is
8547 * relative to the upper left corner of the document.
8548 *
8549 * @param rect the new bounds for the block selection
8550 *
8551 * @see #setBlockSelectionBounds(int, int, int, int)
8552 * @exception SWTException <ul>
8553 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8554 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8555 * </ul>
8556 * @exception IllegalArgumentException <ul>
8557 * <li>ERROR_NULL_ARGUMENT when point is null</li>
8558 * </ul>
8559 *
8560 * @since 3.5
8561 */
8562 public void setBlockSelectionBounds(Rectangle rect) {
8563 checkWidget();
8564 if (rect == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
8565 setBlockSelectionBounds(rect.x, rect.y, rect.width, rect.height);
8566 }
8567 /**
8568 * Sets the block selection bounds. The bounds is
8569 * relative to the upper left corner of the document.
8570 *
8571 * @param x the new x coordinate for the block selection
8572 * @param y the new y coordinate for the block selection
8573 * @param width the new width for the block selection
8574 * @param height the new height for the block selection
8575 *
8576 * @exception SWTException <ul>
8577 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8578 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8579 * </ul>
8580 *
8581 * @since 3.5
8582 */
8583 public void setBlockSelectionBounds(int x, int y, int width, int height) {
8584 checkWidget();
8585 int verticalScrollOffset = getVerticalScrollOffset();
8586 if (!blockSelection) {
8587 x -= horizontalScrollOffset;
8588 y -= verticalScrollOffset;
8589 int start = getOffsetAtPoint(x, y, null);
8590 int end = getOffsetAtPoint(x+width-1, y+height-1, null);
8591 setSelection(start, end - start, false, false);
8592 setCaretLocation();
8593 return;
8594 }
8595 int minY = topMargin;
8596 int minX = leftMargin;
8597 int maxY = renderer.getHeight() - bottomMargin;
8598 int maxX = Math.max(clientAreaWidth, renderer.getWidth()) - rightMargin;
8599 int anchorX = Math.max(minX, Math.min(maxX, x)) - horizontalScrollOffset;
8600 int anchorY = Math.max(minY, Math.min(maxY, y)) - verticalScrollOffset;
8601 int locationX = Math.max(minX, Math.min(maxX, x + width)) - horizontalScrollOffset;
8602 int locationY = Math.max(minY, Math.min(maxY, y + height - 1)) - verticalScrollOffset;
8603 if (isFixedLineHeight() && renderer.fixedPitch) {
8604 int avg = renderer.averageCharWidth;
8605 anchorX = ((anchorX - leftMargin + horizontalScrollOffset) / avg * avg) + leftMargin - horizontalScrollOffset;
8606 locationX = ((locationX + avg / 2 - leftMargin + horizontalScrollOffset) / avg * avg) + leftMargin - horizontalScrollOffset;
8607 }
8608 setBlockSelectionLocation(anchorX, anchorY, locationX, locationY, false);
8609 }
8610 void setBlockSelectionLocation (int x, int y, boolean sendEvent) {
8611 int verticalScrollOffset = getVerticalScrollOffset();
8612 blockXLocation = x + horizontalScrollOffset;
8613 blockYLocation = y + verticalScrollOffset;
8614 int[] alignment = new int[1];
8615 int offset = getOffsetAtPoint(x, y, alignment);
8616 setCaretOffset(offset, alignment[0]);
8617 if (blockXAnchor == -1) {
8618 blockXAnchor = blockXLocation;
8619 blockYAnchor = blockYLocation;
8620 selectionAnchor = caretOffset;
8621 }
8622 doBlockSelection(sendEvent);
8623 }
8624 void setBlockSelectionLocation (int anchorX, int anchorY, int x, int y, boolean sendEvent) {
8625 int verticalScrollOffset = getVerticalScrollOffset();
8626 blockXAnchor = anchorX + horizontalScrollOffset;
8627 blockYAnchor = anchorY + verticalScrollOffset;
8628 selectionAnchor = getOffsetAtPoint(anchorX, anchorY, null);
8629 setBlockSelectionLocation(x, y, sendEvent);
8630 }
8631 void setBlockSelectionOffset (int offset, boolean sendEvent) {
8632 Point point = getPointAtOffset(offset);
8633 int verticalScrollOffset = getVerticalScrollOffset();
8634 blockXLocation = point.x + horizontalScrollOffset;
8635 blockYLocation = point.y + verticalScrollOffset;
8636 setCaretOffset(offset, SWT.DEFAULT);
8637 if (blockXAnchor == -1) {
8638 blockXAnchor = blockXLocation;
8639 blockYAnchor = blockYLocation;
8640 selectionAnchor = caretOffset;
8641 }
8642 doBlockSelection(sendEvent);
8643 }
8644 void setBlockSelectionOffset (int anchorOffset, int offset, boolean sendEvent) {
8645 int verticalScrollOffset = getVerticalScrollOffset();
8646 Point anchorPoint = getPointAtOffset(anchorOffset);
8647 blockXAnchor = anchorPoint.x + horizontalScrollOffset;
8648 blockYAnchor = anchorPoint.y + verticalScrollOffset;
8649 selectionAnchor = anchorOffset;
8650 setBlockSelectionOffset(offset, sendEvent);
8651 }
8652 /**
8653 * Sets the receiver's caret. Set the caret's height and location.
8654 *
8655 * @param caret the new caret for the receiver
8656 *
8657 * @exception SWTException <ul>
8658 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8659 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8660 * </ul>
8661 */
8662 @Override
8663 public void setCaret(Caret caret) {
8664 checkWidget ();
8665 super.setCaret(caret);
8666 caretDirection = SWT.NULL;
8667 if (caret != null) {
8668 setCaretLocation();
8669 }
8670 }
8671 /**
8672 * Sets the BIDI coloring mode. When true the BIDI text display
8673 * algorithm is applied to segments of text that are the same
8674 * color.
8675 *
8676 * @param mode the new coloring mode
8677 * @exception SWTException <ul>
8678 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8679 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8680 * </ul>
8681 *
8682 * @deprecated use BidiSegmentListener instead.
8683 */
8684 @Deprecated
8685 public void setBidiColoring(boolean mode) {
8686 checkWidget();
8687 bidiColoring = mode;
8688 }
8689 /**
8690 * Sets the bottom margin.
8691 *
8692 * @param bottomMargin the bottom margin.
8693 * @exception SWTException <ul>
8694 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8695 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8696 * </ul>
8697 *
8698 * @since 3.5
8699 */
8700 public void setBottomMargin (int bottomMargin) {
8701 checkWidget();
8702 setMargins(getLeftMargin(), topMargin, rightMargin, bottomMargin);
8703 }
8704 /**
8705 * Moves the Caret to the current caret offset.
8706 */
8707 void setCaretLocation() {
8708 Point newCaretPos = getPointAtOffset(caretOffset);
8709 setCaretLocation(newCaretPos, getCaretDirection());
8710 }
8711 void setCaretLocation(final Point location, int direction) {
8712 Caret caret = getCaret();
8713 if (caret != null) {
8714 final boolean isDefaultCaret = caret == defaultCaret;
8715 final StyleRange styleAtOffset = content.getCharCount() > 0 ?
8716 (caretOffset < content.getCharCount() ?
8717 getStyleRangeAtOffset(caretOffset) :
8718 getStyleRangeAtOffset(content.getCharCount() - 1)) : // caret after last char: use last char style
8719 null;
8720
8721 int graphicalLineHeight = getLineHeight(caretOffset);
8722 int caretHeight = getLineHeight();
8723
8724 if (styleAtOffset != null && styleAtOffset.isVariableHeight()) {
8725 if (isDefaultCaret) {
8726 direction = SWT.DEFAULT;
8727 caretHeight = graphicalLineHeight;
8728 } else {
8729 caretHeight = caret.getSize().y;
8730 }
8731 }
8732 if (caretHeight < graphicalLineHeight) {
8733 location.y += (graphicalLineHeight - caretHeight);
8734 }
8735
8736 int imageDirection = direction;
8737 if (isMirrored()) {
8738 if (imageDirection == SWT.LEFT) {
8739 imageDirection = SWT.RIGHT;
8740 } else if (imageDirection == SWT.RIGHT) {
8741 imageDirection = SWT.LEFT;
8742 }
8743 }
8744 if (isDefaultCaret && imageDirection == SWT.RIGHT) {
8745 location.x -= (caret.getSize().x - 1);
8746 }
8747 if (isDefaultCaret) {
8748 caret.setBounds(location.x, location.y, caretWidth, caretHeight);
8749 } else {
8750 caret.setLocation(location);
8751 }
8752 if (direction != caretDirection) {
8753 caretDirection = direction;
8754 if (isDefaultCaret) {
8755 if (imageDirection == SWT.DEFAULT) {
8756 defaultCaret.setImage(null);
8757 } else if (imageDirection == SWT.LEFT) {
8758 defaultCaret.setImage(leftCaretBitmap);
8759 } else if (imageDirection == SWT.RIGHT) {
8760 defaultCaret.setImage(rightCaretBitmap);
8761 }
8762 }
8763 if (caretDirection == SWT.LEFT) {
8764 BidiUtil.setKeyboardLanguage(BidiUtil.KEYBOARD_NON_BIDI);
8765 } else if (caretDirection == SWT.RIGHT) {
8766 BidiUtil.setKeyboardLanguage(BidiUtil.KEYBOARD_BIDI);
8767 }
8768 }
8769 updateCaretVisibility();
8770 }
8771 columnX = location.x;
8772 }
8773 /**
8774 * Sets the caret offset.
8775 *
8776 * @param offset caret offset, relative to the first character in the text.
8777 * @exception SWTException <ul>
8778 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8779 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8780 * </ul>
8781 * @exception IllegalArgumentException <ul>
8782 * <li>ERROR_INVALID_ARGUMENT when the offset is inside a multi byte line
8783 * delimiter (and thus neither clearly in front of or after the line delimiter)
8784 * </ul>
8785 */
8786 public void setCaretOffset(int offset) {
8787 checkWidget();
8788 int length = getCharCount();
8789 if (length > 0 && offset != caretOffset) {
8790 if (offset < 0) {
8791 offset = 0;
8792 } else if (offset > length) {
8793 offset = length;
8794 } else {
8795 if (isLineDelimiter(offset)) {
8796 // offset is inside a multi byte line delimiter. This is an
8797 // illegal operation and an exception is thrown. Fixes 1GDKK3R
8798 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
8799 }
8800 }
8801 setCaretOffset(offset, PREVIOUS_OFFSET_TRAILING);
8802 // clear the selection if the caret is moved.
8803 // don't notify listeners about the selection change.
8804 if (blockSelection) {
8805 clearBlockSelection(true, false);
8806 } else {
8807 clearSelection(false);
8808 }
8809 }
8810 setCaretLocation();
8811 }
8812 void setCaretOffset(int offset, int alignment) {
8813 if (caretOffset != offset) {
8814 caretOffset = offset;
8815 if (isListening(ST.CaretMoved)) {
8816 StyledTextEvent event = new StyledTextEvent(content);
8817 event.end = caretOffset;
8818 notifyListeners(ST.CaretMoved, event);
8819 }
8820 }
8821 if (alignment != SWT.DEFAULT) {
8822 caretAlignment = alignment;
8823 }
8824 }
8825 /**
8826 * Copies the specified text range to the clipboard. The text will be placed
8827 * in the clipboard in plain text format and RTF format.
8828 *
8829 * @param start start index of the text
8830 * @param length length of text to place in clipboard
8831 *
8832 * @exception SWTError
8833 * @see org.eclipse.swt.dnd.Clipboard#setContents
8834 */
8835 void setClipboardContent(int start, int length, int clipboardType) throws SWTError {
8836 if (clipboardType == DND.SELECTION_CLIPBOARD && !IS_GTK) return;
8837 TextTransfer plainTextTransfer = TextTransfer.getInstance();
8838 TextWriter plainTextWriter = new TextWriter(start, length);
8839 String plainText = getPlatformDelimitedText(plainTextWriter);
8840 Object[] data;
8841 Transfer[] types;
8842 if (clipboardType == DND.SELECTION_CLIPBOARD) {
8843 data = new Object[]{plainText};
8844 types = new Transfer[]{plainTextTransfer};
8845 } else {
8846 RTFTransfer rtfTransfer = RTFTransfer.getInstance();
8847 RTFWriter rtfWriter = new RTFWriter(start, length);
8848 String rtfText = getPlatformDelimitedText(rtfWriter);
8849 data = new Object[]{rtfText, plainText};
8850 types = new Transfer[]{rtfTransfer, plainTextTransfer};
8851 }
8852 clipboard.setContents(data, types, clipboardType);
8853 }
8854 /**
8855 * Sets the content implementation to use for text storage.
8856 *
8857 * @param newContent StyledTextContent implementation to use for text storage.
8858 * @exception SWTException <ul>
8859 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8860 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8861 * </ul>
8862 * @exception IllegalArgumentException <ul>
8863 * <li>ERROR_NULL_ARGUMENT when listener is null</li>
8864 * </ul>
8865 */
8866 public void setContent(StyledTextContent newContent) {
8867 checkWidget();
8868 if (newContent == null) {
8869 SWT.error(SWT.ERROR_NULL_ARGUMENT);
8870 }
8871 if (content != null) {
8872 content.removeTextChangeListener(textChangeListener);
8873 }
8874 content = newContent;
8875 content.addTextChangeListener(textChangeListener);
8876 reset();
8877 }
8878 /**
8879 * Sets the receiver's cursor to the cursor specified by the
8880 * argument. Overridden to handle the null case since the
8881 * StyledText widget uses an ibeam as its default cursor.
8882 *
8883 * @see Control#setCursor(Cursor)
8884 */
8885 @Override
8886 public void setCursor (Cursor cursor) {
8887 checkWidget();
8888 if (cursor != null && cursor.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
8889 this.cursor = cursor;
8890 if (cursor == null) {
8891 Display display = getDisplay();
8892 int type = blockSelection ? SWT.CURSOR_CROSS : SWT.CURSOR_IBEAM;
8893 super.setCursor(display.getSystemCursor(type));
8894 } else {
8895 super.setCursor(cursor);
8896 }
8897 }
8898 /**
8899 * Sets whether the widget implements double click mouse behavior.
8900 *
8901 * @param enable if true double clicking a word selects the word, if false
8902 * double clicks have the same effect as regular mouse clicks.
8903 * @exception SWTException <ul>
8904 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8905 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8906 * </ul>
8907 */
8908 public void setDoubleClickEnabled(boolean enable) {
8909 checkWidget();
8910 doubleClickEnabled = enable;
8911 }
8912 @Override
8913 public void setDragDetect (boolean dragDetect) {
8914 checkWidget ();
8915 this.dragDetect = dragDetect;
8916 }
8917 /**
8918 * Sets whether the widget content can be edited.
8919 *
8920 * @param editable if true content can be edited, if false content can not be
8921 * edited
8922 * @exception SWTException <ul>
8923 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8924 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8925 * </ul>
8926 */
8927 public void setEditable(boolean editable) {
8928 checkWidget();
8929 this.editable = editable;
8930 }
8931 @Override
8932 public void setEnabled(boolean enabled) {
8933 super.setEnabled(enabled);
8934 Display display = getDisplay();
8935 this.enabled = enabled;
8936 this.insideSetEnableCall = true;
8937 try {
8938 if (enabled && editable) {
8939 if (!customBackground) setBackground(display.getSystemColor(SWT.COLOR_LIST_BACKGROUND));
8940 if (!customForeground) setForeground(display.getSystemColor(SWT.COLOR_LIST_FOREGROUND));
8941 } else if(!enabled) {
8942 if (!customBackground) setBackground(display.getSystemColor(SWT.COLOR_TEXT_DISABLED_BACKGROUND));
8943 if (!customForeground) setForeground(display.getSystemColor(SWT.COLOR_WIDGET_DISABLED_FOREGROUND));
8944 } else if(!editable) {
8945 if (!customBackground) setBackground(display.getSystemColor(SWT.COLOR_TEXT_DISABLED_BACKGROUND));
8946 if (!customForeground) setForeground(display.getSystemColor(SWT.COLOR_LIST_FOREGROUND));
8947 }
8948 }
8949 finally {
8950 this.insideSetEnableCall = false;
8951 }
8952 }
8953 /**
8954 * Sets a new font to render text with.
8955 * <p>
8956 * <b>NOTE:</b> Italic fonts are not supported unless they have no overhang
8957 * and the same baseline as regular fonts.
8958 * </p>
8959 *
8960 * @param font new font
8961 * @exception SWTException <ul>
8962 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
8963 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
8964 * </ul>
8965 */
8966 @Override
8967 public void setFont(Font font) {
8968 checkWidget();
8969 int oldLineHeight = renderer.getLineHeight();
8970 super.setFont(font);
8971 renderer.setFont(getFont(), tabLength);
8972 // keep the same top line visible. fixes 5815
8973 if (isFixedLineHeight()) {
8974 int lineHeight = renderer.getLineHeight();
8975 if (lineHeight != oldLineHeight) {
8976 int vscroll = (getVerticalScrollOffset() * lineHeight / oldLineHeight) - getVerticalScrollOffset();
8977 scrollVertical(vscroll, true);
8978 }
8979 }
8980 resetCache(0, content.getLineCount());
8981 claimBottomFreeSpace();
8982 calculateScrollBars();
8983 if (isBidiCaret()) createCaretBitmaps();
8984 caretDirection = SWT.NULL;
8985 setCaretLocation();
8986 super.redraw();
8987 }
8988 @Override
8989 public void setForeground(Color color) {
8990 checkWidget();
8991 boolean foregroundDisabled = false;
8992 if (!this.enabled && color == null) {
8993 if (foreground != null) {
8994 Color disabledFg = getDisplay().getSystemColor(SWT.COLOR_WIDGET_DISABLED_FOREGROUND);
8995 if (foreground.equals(disabledFg)) {
8996 return;
8997 } else {
8998 color = new Color (disabledFg.getRGBA());
8999 foregroundDisabled = true;
9000 }
9001 }
9002 }
9003 customForeground = color != null && !this.insideSetEnableCall && !foregroundDisabled;
9004 foreground = color;
9005 super.setForeground(color);
9006 resetCache(0, content.getLineCount());
9007 setCaretLocation();
9008 super.redraw();
9009 }
9010 /**
9011 * Sets the horizontal scroll offset relative to the start of the line.
9012 * Do nothing if there is no text set.
9013 * <p>
9014 * <b>NOTE:</b> The horizontal index is reset to 0 when new text is set in the
9015 * widget.
9016 * </p>
9017 *
9018 * @param offset horizontal scroll offset relative to the start
9019 * of the line, measured in character increments starting at 0, if
9020 * equal to 0 the content is not scrolled, if > 0 = the content is scrolled.
9021 * @exception SWTException <ul>
9022 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9023 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9024 * </ul>
9025 */
9026 public void setHorizontalIndex(int offset) {
9027 checkWidget();
9028 if (getCharCount() == 0) {
9029 return;
9030 }
9031 if (offset < 0) {
9032 offset = 0;
9033 }
9034 offset *= getHorizontalIncrement();
9035 // allow any value if client area width is unknown or 0.
9036 // offset will be checked in resize handler.
9037 // don't use isVisible since width is known even if widget
9038 // is temporarily invisible
9039 if (clientAreaWidth > 0) {
9040 int width = renderer.getWidth();
9041 // prevent scrolling if the content fits in the client area.
9042 // align end of longest line with right border of client area
9043 // if offset is out of range.
9044 if (offset > width - clientAreaWidth) {
9045 offset = Math.max(0, width - clientAreaWidth);
9046 }
9047 }
9048 scrollHorizontal(offset - horizontalScrollOffset, true);
9049 }
9050 /**
9051 * Sets the horizontal SWT logical point offset relative to the start of the line.
9052 * Do nothing if there is no text set.
9053 * <p>
9054 * <b>NOTE:</b> The horizontal SWT logical point offset is reset to 0 when new text
9055 * is set in the widget.
9056 * </p>
9057 *
9058 * @param pixel horizontal SWT logical point offset relative to the start
9059 * of the line.
9060 * @exception SWTException <ul>
9061 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9062 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9063 * </ul>
9064 * @since 2.0
9065 */
9066 public void setHorizontalPixel(int pixel) {
9067 checkWidget();
9068 if (getCharCount() == 0) {
9069 return;
9070 }
9071 if (pixel < 0) {
9072 pixel = 0;
9073 }
9074 // allow any value if client area width is unknown or 0.
9075 // offset will be checked in resize handler.
9076 // don't use isVisible since width is known even if widget
9077 // is temporarily invisible
9078 if (clientAreaWidth > 0) {
9079 int width = renderer.getWidth();
9080 // prevent scrolling if the content fits in the client area.
9081 // align end of longest line with right border of client area
9082 // if offset is out of range.
9083 if (pixel > width - clientAreaWidth) {
9084 pixel = Math.max(0, width - clientAreaWidth);
9085 }
9086 }
9087 scrollHorizontal(pixel - horizontalScrollOffset, true);
9088 }
9089 /**
9090 * Sets the line indentation of the widget.
9091 * <p>
9092 * It is the amount of blank space, in points, at the beginning of each line.
9093 * When a line wraps in several lines only the first one is indented.
9094 * </p>
9095 *
9096 * @param indent the new indent
9097 *
9098 * @exception SWTException <ul>
9099 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9100 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9101 * </ul>
9102 *
9103 * @see #setLineIndent(int, int, int)
9104 *
9105 * @since 3.2
9106 */
9107 public void setIndent(int indent) {
9108 checkWidget();
9109 if (this.indent == indent || indent < 0) return;
9110 this.indent = indent;
9111 resetCache(0, content.getLineCount());
9112 setCaretLocation();
9113 super.redraw();
9114 }
9115 /**
9116 * Sets whether the widget should justify lines.
9117 *
9118 * @param justify whether lines should be justified
9119 *
9120 * @exception SWTException <ul>
9121 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9122 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9123 * </ul>
9124 *
9125 * @see #setLineJustify(int, int, boolean)
9126 *
9127 * @since 3.2
9128 */
9129 public void setJustify(boolean justify) {
9130 checkWidget();
9131 if (this.justify == justify) return;
9132 this.justify = justify;
9133 resetCache(0, content.getLineCount());
9134 setCaretLocation();
9135 super.redraw();
9136 }
9137 /**
9138 * Maps a key to an action.
9139 * <p>
9140 * One action can be associated with N keys. However, each key can only
9141 * have one action (key:action is N:1 relation).
9142 * </p>
9143 *
9144 * @param key a key code defined in SWT.java or a character.
9145 * Optionally ORd with a state mask. Preferred state masks are one or more of
9146 * SWT.MOD1, SWT.MOD2, SWT.MOD3, since these masks account for modifier platform
9147 * differences. However, there may be cases where using the specific state masks
9148 * (i.e., SWT.CTRL, SWT.SHIFT, SWT.ALT, SWT.COMMAND) makes sense.
9149 * @param action one of the predefined actions defined in ST.java.
9150 * Use SWT.NULL to remove a key binding.
9151 * @exception SWTException <ul>
9152 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9153 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9154 * </ul>
9155 */
9156 public void setKeyBinding(int key, int action) {
9157 checkWidget();
9158 int modifierValue = key & SWT.MODIFIER_MASK;
9159 int keyInt = key & SWT.KEY_MASK;
9160 char keyChar = (char)keyInt;
9161 /**
9162 * Bug 440535: Make sure the key getting mapped to letter is in defiened
9163 * character range and filter out incorrect int to char typecasting. For
9164 * Example: SWT.KEYPAD_CR int gets wrongly type-cast to char letter 'p'
9165 */
9166 if (Character.isDefined(keyInt) && Character.isLetter(keyChar)) {
9167 // make the keybinding case insensitive by adding it
9168 // in its upper and lower case form
9169 char ch = Character.toUpperCase(keyChar);
9170 int newKey = ch | modifierValue;
9171 if (action == SWT.NULL) {
9172 keyActionMap.remove(newKey);
9173 } else {
9174 keyActionMap.put(newKey, action);
9175 }
9176 ch = Character.toLowerCase(keyChar);
9177 newKey = ch | modifierValue;
9178 if (action == SWT.NULL) {
9179 keyActionMap.remove(newKey);
9180 } else {
9181 keyActionMap.put(newKey, action);
9182 }
9183 } else {
9184 if (action == SWT.NULL) {
9185 keyActionMap.remove(key);
9186 } else {
9187 keyActionMap.put(key, action);
9188 }
9189 }
9190 }
9191 /**
9192 * Sets the left margin.
9193 *
9194 * @param leftMargin the left margin.
9195 * @exception SWTException <ul>
9196 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9197 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9198 * </ul>
9199 *
9200 * @since 3.5
9201 */
9202 public void setLeftMargin (int leftMargin) {
9203 checkWidget();
9204 setMargins(leftMargin, topMargin, rightMargin, bottomMargin);
9205 }
9206 /**
9207 * Sets the alignment of the specified lines. The argument should be one of <code>SWT.LEFT</code>,
9208 * <code>SWT.CENTER</code> or <code>SWT.RIGHT</code>.
9209 * <p>
9210 * Note that if <code>SWT.MULTI</code> is set, then <code>SWT.WRAP</code> must also be set
9211 * in order to stabilize the right edge before setting alignment.
9212 * </p><p>
9213 * Should not be called if a LineStyleListener has been set since the listener
9214 * maintains the line attributes.
9215 * </p><p>
9216 * All line attributes are maintained relative to the line text, not the
9217 * line index that is specified in this method call.
9218 * During text changes, when entire lines are inserted or removed, the line
9219 * attributes that are associated with the lines after the change
9220 * will "move" with their respective text. An entire line is defined as
9221 * extending from the first character on a line to the last and including the
9222 * line delimiter.
9223 * </p>
9224 * When two lines are joined by deleting a line delimiter, the top line
9225 * attributes take precedence and the attributes of the bottom line are deleted.
9226 * For all other text changes line attributes will remain unchanged.
9227 *
9228 * @param startLine first line the alignment is applied to, 0 based
9229 * @param lineCount number of lines the alignment applies to.
9230 * @param alignment line alignment
9231 *
9232 * @exception SWTException <ul>
9233 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9234 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9235 * </ul>
9236 * @exception IllegalArgumentException <ul>
9237 * <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
9238 * </ul>
9239 * @see #setAlignment(int)
9240 * @since 3.2
9241 */
9242 public void setLineAlignment(int startLine, int lineCount, int alignment) {
9243 checkWidget();
9244 if (isListening(ST.LineGetStyle)) return;
9245 if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
9246 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
9247 }
9248
9249 renderer.setLineAlignment(startLine, lineCount, alignment);
9250 resetCache(startLine, lineCount);
9251 redrawLines(startLine, lineCount, false);
9252 int caretLine = getCaretLine();
9253 if (startLine <= caretLine && caretLine < startLine + lineCount) {
9254 setCaretLocation();
9255 }
9256 setAlignment();
9257 }
9258 /**
9259 * Sets the background color of the specified lines.
9260 * <p>
9261 * The background color is drawn for the width of the widget. All
9262 * line background colors are discarded when setText is called.
9263 * The text background color if defined in a StyleRange overlays the
9264 * line background color.
9265 * </p><p>
9266 * Should not be called if a LineBackgroundListener has been set since the
9267 * listener maintains the line backgrounds.
9268 * </p><p>
9269 * All line attributes are maintained relative to the line text, not the
9270 * line index that is specified in this method call.
9271 * During text changes, when entire lines are inserted or removed, the line
9272 * attributes that are associated with the lines after the change
9273 * will "move" with their respective text. An entire line is defined as
9274 * extending from the first character on a line to the last and including the
9275 * line delimiter.
9276 * </p><p>
9277 * When two lines are joined by deleting a line delimiter, the top line
9278 * attributes take precedence and the attributes of the bottom line are deleted.
9279 * For all other text changes line attributes will remain unchanged.
9280 * </p>
9281 *
9282 * @param startLine first line the color is applied to, 0 based
9283 * @param lineCount number of lines the color applies to.
9284 * @param background line background color
9285 * @exception SWTException <ul>
9286 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9287 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9288 * </ul>
9289 * @exception IllegalArgumentException <ul>
9290 * <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
9291 * </ul>
9292 */
9293 public void setLineBackground(int startLine, int lineCount, Color background) {
9294 checkWidget();
9295 if (isListening(ST.LineGetBackground)) return;
9296 if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
9297 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
9298 }
9299 if (background != null) {
9300 renderer.setLineBackground(startLine, lineCount, background);
9301 } else {
9302 renderer.clearLineBackground(startLine, lineCount);
9303 }
9304 redrawLines(startLine, lineCount, false);
9305 }
9306 /**
9307 * Sets the bullet of the specified lines.
9308 * <p>
9309 * Should not be called if a LineStyleListener has been set since the listener
9310 * maintains the line attributes.
9311 * </p><p>
9312 * All line attributes are maintained relative to the line text, not the
9313 * line index that is specified in this method call.
9314 * During text changes, when entire lines are inserted or removed, the line
9315 * attributes that are associated with the lines after the change
9316 * will "move" with their respective text. An entire line is defined as
9317 * extending from the first character on a line to the last and including the
9318 * line delimiter.
9319 * </p><p>
9320 * When two lines are joined by deleting a line delimiter, the top line
9321 * attributes take precedence and the attributes of the bottom line are deleted.
9322 * For all other text changes line attributes will remain unchanged.
9323 * </p>
9324 *
9325 * @param startLine first line the bullet is applied to, 0 based
9326 * @param lineCount number of lines the bullet applies to.
9327 * @param bullet line bullet
9328 *
9329 * @exception SWTException <ul>
9330 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9331 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9332 * </ul>
9333 * @exception IllegalArgumentException <ul>
9334 * <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
9335 * </ul>
9336 * @since 3.2
9337 */
9338 public void setLineBullet(int startLine, int lineCount, Bullet bullet) {
9339 checkWidget();
9340 if (isListening(ST.LineGetStyle)) return;
9341 if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
9342 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
9343 }
9344 int oldBottom = getLinePixel(startLine + lineCount);
9345 renderer.setLineBullet(startLine, lineCount, bullet);
9346 resetCache(startLine, lineCount);
9347 int newBottom = getLinePixel(startLine + lineCount);
9348 redrawLines(startLine, lineCount, oldBottom != newBottom);
9349 int caretLine = getCaretLine();
9350 if (startLine <= caretLine && caretLine < startLine + lineCount) {
9351 setCaretLocation();
9352 }
9353 }
9354 /**
9355 * Returns true if StyledText is in word wrap mode and false otherwise.
9356 *
9357 * @return true if StyledText is in word wrap mode and false otherwise.
9358 */
9359 boolean isWordWrap() {
9360 return wordWrap || visualWrap;
9361 }
9362 /**
9363 * Sets the indent of the specified lines.
9364 * <p>
9365 * Should not be called if a LineStyleListener has been set since the listener
9366 * maintains the line attributes.
9367 * </p><p>
9368 * All line attributes are maintained relative to the line text, not the
9369 * line index that is specified in this method call.
9370 * During text changes, when entire lines are inserted or removed, the line
9371 * attributes that are associated with the lines after the change
9372 * will "move" with their respective text. An entire line is defined as
9373 * extending from the first character on a line to the last and including the
9374 * line delimiter.
9375 * </p><p>
9376 * When two lines are joined by deleting a line delimiter, the top line
9377 * attributes take precedence and the attributes of the bottom line are deleted.
9378 * For all other text changes line attributes will remain unchanged.
9379 * </p>
9380 *
9381 * @param startLine first line the indent is applied to, 0 based
9382 * @param lineCount number of lines the indent applies to.
9383 * @param indent line indent
9384 *
9385 * @exception SWTException <ul>
9386 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9387 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9388 * </ul>
9389 * @exception IllegalArgumentException <ul>
9390 * <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
9391 * </ul>
9392 * @see #setIndent(int)
9393 * @since 3.2
9394 */
9395 public void setLineIndent(int startLine, int lineCount, int indent) {
9396 checkWidget();
9397 if (isListening(ST.LineGetStyle)) return;
9398 if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
9399 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
9400 }
9401 int oldBottom = getLinePixel(startLine + lineCount);
9402 renderer.setLineIndent(startLine, lineCount, indent);
9403 resetCache(startLine, lineCount);
9404 int newBottom = getLinePixel(startLine + lineCount);
9405 redrawLines(startLine, lineCount, oldBottom != newBottom);
9406 int caretLine = getCaretLine();
9407 if (startLine <= caretLine && caretLine < startLine + lineCount) {
9408 setCaretLocation();
9409 }
9410 }
9411
9412 /**
9413 * Sets the vertical indent of the specified lines.
9414 * <p>
9415 * Should not be called if a LineStyleListener has been set since the listener
9416 * maintains the line attributes.
9417 * </p><p>
9418 * All line attributes are maintained relative to the line text, not the
9419 * line index that is specified in this method call.
9420 * During text changes, when entire lines are inserted or removed, the line
9421 * attributes that are associated with the lines after the change
9422 * will "move" with their respective text. An entire line is defined as
9423 * extending from the first character on a line to the last and including the
9424 * line delimiter.
9425 * </p><p>
9426 * When two lines are joined by deleting a line delimiter, the top line
9427 * attributes take precedence and the attributes of the bottom line are deleted.
9428 * For all other text changes line attributes will remain unchanged.
9429 * </p><p>
9430 * Setting both line spacing and vertical indent on a line would result in the
9431 * spacing and indent add up for the line.
9432 * </p>
9433 *
9434 * @param lineIndex line index the vertical indent is applied to, 0 based
9435 * @param verticalLineIndent vertical line indent
9436 *
9437 * @exception SWTException <ul>
9438 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9439 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9440 * </ul>
9441 * @exception IllegalArgumentException <ul>
9442 * <li>ERROR_INVALID_ARGUMENT when the specified line index is invalid</li>
9443 * </ul>
9444 * @since 3.109
9445 */
9446 public void setLineVerticalIndent(int lineIndex, int verticalLineIndent) {
9447 checkWidget();
9448 if (isListening(ST.LineGetStyle)) return;
9449 if (lineIndex < 0 || lineIndex >= content.getLineCount()) {
9450 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
9451 }
9452 int previousVerticalIndent = renderer.getLineVerticalIndent(lineIndex);
9453 if (verticalLineIndent == previousVerticalIndent) {
9454 return;
9455 }
9456 int initialTopPixel = getTopPixel();
9457 int initialTopIndex = getPartialTopIndex();
9458 int initialBottomIndex = getPartialBottomIndex();
9459 int verticalIndentDiff = verticalLineIndent - previousVerticalIndent;
9460 renderer.setLineVerticalIndent(lineIndex, verticalLineIndent);
9461 this.hasVerticalIndent = verticalLineIndent != 0 || renderer.hasVerticalIndent();
9462 ScrollBar verticalScrollbar = getVerticalBar();
9463 if (lineIndex < initialTopIndex) {
9464 verticalScrollOffset += verticalIndentDiff; // just change value, don't actually scroll/redraw
9465 if (verticalScrollbar != null) {
9466 verticalScrollbar.setSelection(verticalScrollOffset);
9467 verticalScrollbar.setMaximum(verticalScrollbar.getMaximum() + verticalIndentDiff);
9468 }
9469 } else if (lineIndex > initialBottomIndex) {
9470 if (verticalScrollbar != null) {
9471 verticalScrollbar.setMaximum(verticalScrollbar.getMaximum() + verticalIndentDiff);
9472 }
9473 } else {
9474 resetCache(lineIndex, 1);
9475 if (getCaretLine() >= initialTopIndex && getCaretLine() <= initialBottomIndex) { // caret line with caret mustn't move
9476 if (getCaretLine() < lineIndex) {
9477 redrawLines(lineIndex, getPartialBottomIndex() - lineIndex + 1, true);
9478 } else {
9479 setTopPixel(initialTopPixel + verticalIndentDiff);
9480 }
9481 } else { // move as few lines as possible
9482 if (lineIndex - getTopIndex() < getBottomIndex() - lineIndex) {
9483 setTopPixel(initialTopPixel + verticalIndentDiff);
9484 } else {
9485 // redraw below
9486 redrawLines(lineIndex, getPartialBottomIndex() - lineIndex + 1, true);
9487 }
9488 }
9489 setScrollBars(true);
9490 }
9491 }
9492
9493 /**
9494 * Sets the justify of the specified lines.
9495 * <p>
9496 * Should not be called if a LineStyleListener has been set since the listener
9497 * maintains the line attributes.
9498 * </p><p>
9499 * All line attributes are maintained relative to the line text, not the
9500 * line index that is specified in this method call.
9501 * During text changes, when entire lines are inserted or removed, the line
9502 * attributes that are associated with the lines after the change
9503 * will "move" with their respective text. An entire line is defined as
9504 * extending from the first character on a line to the last and including the
9505 * line delimiter.
9506 * </p><p>
9507 * When two lines are joined by deleting a line delimiter, the top line
9508 * attributes take precedence and the attributes of the bottom line are deleted.
9509 * For all other text changes line attributes will remain unchanged.
9510 * </p>
9511 *
9512 * @param startLine first line the justify is applied to, 0 based
9513 * @param lineCount number of lines the justify applies to.
9514 * @param justify true if lines should be justified
9515 *
9516 * @exception SWTException <ul>
9517 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9518 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9519 * </ul>
9520 * @exception IllegalArgumentException <ul>
9521 * <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
9522 * </ul>
9523 * @see #setJustify(boolean)
9524 * @since 3.2
9525 */
9526 public void setLineJustify(int startLine, int lineCount, boolean justify) {
9527 checkWidget();
9528 if (isListening(ST.LineGetStyle)) return;
9529 if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
9530 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
9531 }
9532
9533 renderer.setLineJustify(startLine, lineCount, justify);
9534 resetCache(startLine, lineCount);
9535 redrawLines(startLine, lineCount, false);
9536 int caretLine = getCaretLine();
9537 if (startLine <= caretLine && caretLine < startLine + lineCount) {
9538 setCaretLocation();
9539 }
9540 }
9541 /**
9542 * Sets the line spacing of the widget. The line spacing applies for all lines.
9543 * In the case of #setLineSpacingProvider(StyledTextLineSpacingProvider) is customized,
9544 * the line spacing are applied only for the lines which are not managed by {@link StyledTextLineSpacingProvider}.
9545 *
9546 * @param lineSpacing the line spacing
9547 * @exception SWTException <ul>
9548 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9549 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9550 * </ul>
9551 * @see #setLineSpacingProvider(StyledTextLineSpacingProvider)
9552 * @since 3.2
9553 */
9554 public void setLineSpacing(int lineSpacing) {
9555 checkWidget();
9556 if (this.lineSpacing == lineSpacing || lineSpacing < 0) return;
9557 this.lineSpacing = lineSpacing;
9558 resetCache(0, content.getLineCount());
9559 setCaretLocation();
9560 super.redraw();
9561 }
9562 /**
9563 * Sets the line spacing provider of the widget. The line spacing applies for some lines with customized spacing
9564 * or reset the customized spacing if the argument is null.
9565 *
9566 * @param lineSpacingProvider the line spacing provider (or null)
9567 * @exception SWTException <ul>
9568 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9569 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9570 * </ul>
9571 * @see #setLineSpacingProvider(StyledTextLineSpacingProvider)
9572 * @since 3.107
9573 */
9574 public void setLineSpacingProvider(StyledTextLineSpacingProvider lineSpacingProvider) {
9575 checkWidget();
9576 boolean wasFixedLineHeight = isFixedLineHeight();
9577 if (renderer.getLineSpacingProvider() == null && lineSpacingProvider == null
9578 || (renderer.getLineSpacingProvider() != null
9579 && renderer.getLineSpacingProvider().equals(lineSpacingProvider)))
9580 return;
9581 renderer.setLineSpacingProvider(lineSpacingProvider);
9582 // reset lines cache if needed
9583 if (lineSpacingProvider == null) {
9584 if (!wasFixedLineHeight) {
9585 resetCache(0, content.getLineCount());
9586 }
9587 } else {
9588 if (wasFixedLineHeight) {
9589 int firstLine = -1;
9590 for (int i = 0; i < content.getLineCount(); i++) {
9591 Integer lineSpacing = lineSpacingProvider.getLineSpacing(i);
9592 if (lineSpacing != null && lineSpacing > 0) {
9593 // there is a custom line spacing, set StyledText as variable line height mode
9594 // reset only the line size
9595 renderer.reset(i, 1);
9596 if (firstLine == -1) {
9597 firstLine = i;
9598 }
9599 }
9600 }
9601 if (firstLine != -1) {
9602 // call reset cache for the first line which have changed to recompute scrollbars
9603 resetCache(firstLine, 0);
9604 }
9605 }
9606 }
9607 setCaretLocation();
9608 super.redraw();
9609 }
9610 /**
9611 * Sets the tab stops of the specified lines.
9612 * <p>
9613 * Should not be called if a <code>LineStyleListener</code> has been set since the listener
9614 * maintains the line attributes.
9615 * </p><p>
9616 * All line attributes are maintained relative to the line text, not the
9617 * line index that is specified in this method call.
9618 * During text changes, when entire lines are inserted or removed, the line
9619 * attributes that are associated with the lines after the change
9620 * will "move" with their respective text. An entire line is defined as
9621 * extending from the first character on a line to the last and including the
9622 * line delimiter.
9623 * </p><p>
9624 * When two lines are joined by deleting a line delimiter, the top line
9625 * attributes take precedence and the attributes of the bottom line are deleted.
9626 * For all other text changes line attributes will remain unchanged.
9627 * </p>
9628 *
9629 * @param startLine first line the justify is applied to, 0 based
9630 * @param lineCount number of lines the justify applies to.
9631 * @param tabStops tab stops
9632 *
9633 * @exception SWTException <ul>
9634 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9635 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9636 * </ul>
9637 * @exception IllegalArgumentException <ul>
9638 * <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
9639 * </ul>
9640 * @see #setTabStops(int[])
9641 * @since 3.6
9642 */
9643 public void setLineTabStops(int startLine, int lineCount, int[] tabStops) {
9644 checkWidget();
9645 if (isListening(ST.LineGetStyle)) return;
9646 if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
9647 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
9648 }
9649 if (tabStops != null) {
9650 int pos = 0;
9651 int[] newTabs = new int[tabStops.length];
9652 for (int i = 0; i < tabStops.length; i++) {
9653 if (tabStops[i] < pos) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
9654 newTabs[i] = pos = tabStops[i];
9655 }
9656 renderer.setLineTabStops(startLine, lineCount, newTabs);
9657 } else {
9658 renderer.setLineTabStops(startLine, lineCount, null);
9659 }
9660 resetCache(startLine, lineCount);
9661 redrawLines(startLine, lineCount, false);
9662 int caretLine = getCaretLine();
9663 if (startLine <= caretLine && caretLine < startLine + lineCount) {
9664 setCaretLocation();
9665 }
9666 }
9667 /**
9668 * Sets the wrap indent of the specified lines.
9669 * <p>
9670 * Should not be called if a <code>LineStyleListener</code> has been set since the listener
9671 * maintains the line attributes.
9672 * </p><p>
9673 * All line attributes are maintained relative to the line text, not the
9674 * line index that is specified in this method call.
9675 * During text changes, when entire lines are inserted or removed, the line
9676 * attributes that are associated with the lines after the change
9677 * will "move" with their respective text. An entire line is defined as
9678 * extending from the first character on a line to the last and including the
9679 * line delimiter.
9680 * </p><p>
9681 * When two lines are joined by deleting a line delimiter, the top line
9682 * attributes take precedence and the attributes of the bottom line are deleted.
9683 * For all other text changes line attributes will remain unchanged.
9684 * </p>
9685 *
9686 * @param startLine first line the wrap indent is applied to, 0 based
9687 * @param lineCount number of lines the wrap indent applies to.
9688 * @param wrapIndent line wrap indent
9689 *
9690 * @exception SWTException <ul>
9691 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9692 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9693 * </ul>
9694 * @exception IllegalArgumentException <ul>
9695 * <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li>
9696 * </ul>
9697 * @see #setWrapIndent(int)
9698 * @since 3.6
9699 */
9700 public void setLineWrapIndent(int startLine, int lineCount, int wrapIndent) {
9701 checkWidget();
9702 if (isListening(ST.LineGetStyle)) return;
9703 if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
9704 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
9705 }
9706 int oldBottom = getLinePixel(startLine + lineCount);
9707 renderer.setLineWrapIndent(startLine, lineCount, wrapIndent);
9708 resetCache(startLine, lineCount);
9709 int newBottom = getLinePixel(startLine + lineCount);
9710 redrawLines(startLine, lineCount, oldBottom != newBottom);
9711 int caretLine = getCaretLine();
9712 if (startLine <= caretLine && caretLine < startLine + lineCount) {
9713 setCaretLocation();
9714 }
9715 }
9716
9717 /**
9718 * Sets the color of the margins.
9719 *
9720 * @param color the new color (or null)
9721 * @exception IllegalArgumentException <ul>
9722 * <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li>
9723 * </ul>
9724 * @exception SWTException <ul>
9725 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9726 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9727 * </ul>
9728 *
9729 * @since 3.5
9730 */
9731 public void setMarginColor(Color color) {
9732 checkWidget();
9733 if (color != null && color.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
9734 marginColor = color;
9735 super.redraw();
9736 }
9737 /**
9738 * Sets the margins.
9739 *
9740 * @param leftMargin the left margin.
9741 * @param topMargin the top margin.
9742 * @param rightMargin the right margin.
9743 * @param bottomMargin the bottom margin.
9744 * @exception SWTException <ul>
9745 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9746 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9747 * </ul>
9748 *
9749 * @since 3.5
9750 */
9751 public void setMargins (int leftMargin, int topMargin, int rightMargin, int bottomMargin) {
9752 checkWidget();
9753 this.leftMargin = Math.max(0, leftMargin) + alignmentMargin;
9754 this.topMargin = Math.max(0, topMargin);
9755 this.rightMargin = Math.max(0, rightMargin);
9756 this.bottomMargin = Math.max(0, bottomMargin);
9757 resetCache(0, content.getLineCount());
9758 setScrollBars(true);
9759 setCaretLocation();
9760 setAlignment();
9761 super.redraw();
9762 }
9763 /**
9764 * Sets the enabled state of the mouse navigator. When the mouse navigator is enabled, the user can navigate through the widget
9765 * by pressing the middle button and moving the cursor.
9766 *
9767 * @param enabled if true, the mouse navigator is enabled, if false the mouse navigator is deactivated
9768 * @exception SWTException <ul>
9769 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9770 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9771 * </ul>
9772 * @since 3.110
9773 */
9774 public void setMouseNavigatorEnabled(boolean enabled) {
9775 checkWidget();
9776 if ((enabled && mouseNavigator != null) || (!enabled && mouseNavigator == null)) {
9777 return;
9778 }
9779 if (enabled) {
9780 mouseNavigator = new MouseNavigator(this);
9781 } else {
9782 mouseNavigator.dispose();
9783 mouseNavigator = null;
9784 }
9785 }
9786 /**
9787 * Flips selection anchor based on word selection direction.
9788 */
9789 void setMouseWordSelectionAnchor() {
9790 if (doubleClickEnabled && clickCount > 1) {
9791 if (caretOffset < doubleClickSelection.x) {
9792 selectionAnchor = doubleClickSelection.y;
9793 } else if (caretOffset > doubleClickSelection.y) {
9794 selectionAnchor = doubleClickSelection.x;
9795 }
9796 }
9797 }
9798 /**
9799 * Sets the orientation of the receiver, which must be one
9800 * of the constants <code>SWT.LEFT_TO_RIGHT</code> or <code>SWT.RIGHT_TO_LEFT</code>.
9801 *
9802 * @param orientation new orientation style
9803 *
9804 * @exception SWTException <ul>
9805 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9806 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9807 * </ul>
9808 *
9809 * @since 2.1.2
9810 */
9811 @Override
9812 public void setOrientation(int orientation) {
9813 int oldOrientation = getOrientation();
9814 super.setOrientation(orientation);
9815 int newOrientation = getOrientation();
9816 if (oldOrientation != newOrientation) {
9817 resetBidiData();
9818 }
9819 }
9820 /**
9821 * Sets the right margin.
9822 *
9823 * @param rightMargin the right margin.
9824 * @exception SWTException <ul>
9825 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9826 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9827 * </ul>
9828 *
9829 * @since 3.5
9830 */
9831 public void setRightMargin (int rightMargin) {
9832 checkWidget();
9833 setMargins(getLeftMargin(), topMargin, rightMargin, bottomMargin);
9834 }
9835 void setScrollBar(ScrollBar bar, int clientArea, int maximum, int margin) {
9836 int inactive = 1;
9837 if (clientArea < maximum) {
9838 bar.setMaximum(maximum - margin);
9839 bar.setThumb(clientArea - margin);
9840 bar.setPageIncrement(clientArea - margin);
9841 if (!alwaysShowScroll) bar.setVisible(true);
9842 } else if (bar.getThumb() != inactive || bar.getMaximum() != inactive) {
9843 bar.setValues(bar.getSelection(), bar.getMinimum(), inactive, inactive, bar.getIncrement(), inactive);
9844 }
9845 }
9846 /**
9847 * Adjusts the maximum and the page size of the scroll bars to
9848 * reflect content width/length changes.
9849 *
9850 * @param vertical indicates if the vertical scrollbar also needs to be set
9851 */
9852 void setScrollBars(boolean vertical) {
9853 ignoreResize++;
9854 if (!isFixedLineHeight() || !alwaysShowScroll) vertical = true;
9855 ScrollBar verticalBar = vertical ? getVerticalBar() : null;
9856 ScrollBar horizontalBar = getHorizontalBar();
9857 int oldHeight = clientAreaHeight;
9858 int oldWidth = clientAreaWidth;
9859 if (!alwaysShowScroll) {
9860 if (verticalBar != null) verticalBar.setVisible(false);
9861 if (horizontalBar != null) horizontalBar.setVisible(false);
9862 }
9863 if (verticalBar != null) {
9864 setScrollBar(verticalBar, clientAreaHeight, renderer.getHeight(), topMargin + bottomMargin);
9865 }
9866 if (horizontalBar != null && !wordWrap) {
9867 setScrollBar(horizontalBar, clientAreaWidth, renderer.getWidth(), leftMargin + rightMargin);
9868 if (!alwaysShowScroll && horizontalBar.getVisible() && verticalBar != null) {
9869 setScrollBar(verticalBar, clientAreaHeight, renderer.getHeight(), topMargin + bottomMargin);
9870 if (verticalBar.getVisible()) {
9871 setScrollBar(horizontalBar, clientAreaWidth, renderer.getWidth(), leftMargin + rightMargin);
9872 }
9873 }
9874 }
9875 if (!alwaysShowScroll) {
9876 redrawMargins(oldHeight, oldWidth);
9877 }
9878 ignoreResize--;
9879 }
9880 /**
9881 * Sets the selection to the given position and scrolls it into view. Equivalent to setSelection(start,start).
9882 *
9883 * @param start new caret position
9884 * @see #setSelection(int,int)
9885 * @exception SWTException <ul>
9886 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9887 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9888 * </ul>
9889 * @exception IllegalArgumentException <ul>
9890 * <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a
9891 * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter)
9892 * </ul>
9893 */
9894 public void setSelection(int start) {
9895 // checkWidget test done in setSelectionRange
9896 setSelection(start, start);
9897 }
9898 /**
9899 * Sets the selection and scrolls it into view.
9900 * <p>
9901 * Indexing is zero based. Text selections are specified in terms of
9902 * caret positions. In a text widget that contains N characters, there are
9903 * N+1 caret positions, ranging from 0..N
9904 * </p>
9905 *
9906 * @param point x=selection start offset, y=selection end offset
9907 * The caret will be placed at the selection start when x > y.
9908 * @see #setSelection(int,int)
9909 * @exception SWTException <ul>
9910 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9911 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9912 * </ul>
9913 * @exception IllegalArgumentException <ul>
9914 * <li>ERROR_NULL_ARGUMENT when point is null</li>
9915 * <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a
9916 * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter)
9917 * </ul>
9918 */
9919 public void setSelection(Point point) {
9920 checkWidget();
9921 if (point == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
9922 setSelection(point.x, point.y);
9923 }
9924 /**
9925 * Sets the receiver's selection background color to the color specified
9926 * by the argument, or to the default system color for the control
9927 * if the argument is null.
9928 *
9929 * @param color the new color (or null)
9930 *
9931 * @exception IllegalArgumentException <ul>
9932 * <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li>
9933 * </ul>
9934 * @exception SWTException <ul>
9935 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9936 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9937 * </ul>
9938 * @since 2.1
9939 */
9940 public void setSelectionBackground (Color color) {
9941 checkWidget ();
9942 if (color != null) {
9943 if (color.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
9944 }
9945 selectionBackground = color;
9946 resetCache(0, content.getLineCount());
9947 setCaretLocation();
9948 super.redraw();
9949 }
9950 /**
9951 * Sets the receiver's selection foreground color to the color specified
9952 * by the argument, or to the default system color for the control
9953 * if the argument is null.
9954 * <p>
9955 * Note that this is a <em>HINT</em>. Some platforms do not allow the application
9956 * to change the selection foreground color.
9957 * </p>
9958 * @param color the new color (or null)
9959 *
9960 * @exception IllegalArgumentException <ul>
9961 * <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li>
9962 * </ul>
9963 * @exception SWTException <ul>
9964 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9965 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9966 * </ul>
9967 * @since 2.1
9968 */
9969 public void setSelectionForeground (Color color) {
9970 checkWidget ();
9971 if (color != null) {
9972 if (color.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
9973 }
9974 selectionForeground = color;
9975 resetCache(0, content.getLineCount());
9976 setCaretLocation();
9977 super.redraw();
9978 }
9979 /**
9980 * Sets the selection and scrolls it into view.
9981 * <p>
9982 * Indexing is zero based. Text selections are specified in terms of
9983 * caret positions. In a text widget that contains N characters, there are
9984 * N+1 caret positions, ranging from 0..N
9985 * </p>
9986 *
9987 * @param start selection start offset. The caret will be placed at the
9988 * selection start when start > end.
9989 * @param end selection end offset
9990 * @see #setSelectionRange(int,int)
9991 * @exception SWTException <ul>
9992 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
9993 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
9994 * </ul>
9995 * @exception IllegalArgumentException <ul>
9996 * <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a
9997 * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter)
9998 * </ul>
9999 */
10000 public void setSelection(int start, int end) {
10001 setSelectionRange(start, end - start);
10002 showSelection();
10003 }
10004 /**
10005 * Sets the selection.
10006 * <p>
10007 * The new selection may not be visible. Call showSelection to scroll
10008 * the selection into view.
10009 * </p>
10010 *
10011 * @param start offset of the first selected character, start >= 0 must be true.
10012 * @param length number of characters to select, 0 <= start + length
10013 * <= getCharCount() must be true.
10014 * A negative length places the caret at the selection start.
10015 * @param sendEvent a Selection event is sent when set to true and when
10016 * the selection is reset.
10017 */
10018 void setSelection(int start, int length, boolean sendEvent, boolean doBlock) {
10019 int end = start + length;
10020 if (start > end) {
10021 int temp = end;
10022 end = start;
10023 start = temp;
10024 }
10025 // is the selection range different or is the selection direction
10026 // different?
10027 if (selection.x != start || selection.y != end ||
10028 (length > 0 && selectionAnchor != selection.x) ||
10029 (length < 0 && selectionAnchor != selection.y)) {
10030 if (blockSelection && doBlock) {
10031 if (length < 0) {
10032 setBlockSelectionOffset(end, start, sendEvent);
10033 } else {
10034 setBlockSelectionOffset(start, end, sendEvent);
10035 }
10036 } else {
10037 int oldStart = selection.x;
10038 int oldLength = selection.y - selection.x;
10039 int charCount = content.getCharCount();
10040 // called internally to remove selection after text is removed
10041 // therefore make sure redraw range is valid.
10042 int redrawX = Math.min(selection.x, charCount);
10043 int redrawY = Math.min(selection.y, charCount);
10044 if (length < 0) {
10045 selectionAnchor = selection.y = end;
10046 selection.x = start;
10047 setCaretOffset(start, PREVIOUS_OFFSET_TRAILING);
10048 } else {
10049 selectionAnchor = selection.x = start;
10050 selection.y = end;
10051 setCaretOffset(end, PREVIOUS_OFFSET_TRAILING);
10052 }
10053 redrawX = Math.min(redrawX, selection.x);
10054 redrawY = Math.max(redrawY, selection.y);
10055 if (redrawY - redrawX > 0) {
10056 internalRedrawRange(redrawX, redrawY - redrawX);
10057 }
10058 if (sendEvent && (oldLength != end - start || (oldLength != 0 && oldStart != start))) {
10059 sendSelectionEvent();
10060 }
10061 sendAccessibleTextCaretMoved();
10062 }
10063 }
10064 }
10065 /**
10066 * Sets the selection.
10067 * <p>
10068 * The new selection may not be visible. Call showSelection to scroll the selection
10069 * into view. A negative length places the caret at the visual start of the selection.
10070 * </p>
10071 *
10072 * @param start offset of the first selected character
10073 * @param length number of characters to select
10074 *
10075 * @exception SWTException <ul>
10076 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10077 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10078 * </ul>
10079 * @exception IllegalArgumentException <ul>
10080 * <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a
10081 * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter)
10082 * </ul>
10083 */
10084 public void setSelectionRange(int start, int length) {
10085 checkWidget();
10086 int contentLength = getCharCount();
10087 start = Math.max(0, Math.min (start, contentLength));
10088 int end = start + length;
10089 if (end < 0) {
10090 length = -start;
10091 } else {
10092 if (end > contentLength) length = contentLength - start;
10093 }
10094 if (isLineDelimiter(start) || isLineDelimiter(start + length)) {
10095 // the start offset or end offset of the selection range is inside a
10096 // multi byte line delimiter. This is an illegal operation and an exception
10097 // is thrown. Fixes 1GDKK3R
10098 SWT.error(SWT.ERROR_INVALID_ARGUMENT);
10099 }
10100 setSelection(start, length, false, true);
10101 setCaretLocation();
10102 }
10103 /**
10104 * Adds the specified style.
10105 * <p>
10106 * The new style overwrites existing styles for the specified range.
10107 * Existing style ranges are adjusted if they partially overlap with
10108 * the new style. To clear an individual style, call setStyleRange
10109 * with a StyleRange that has null attributes.
10110 * </p><p>
10111 * Should not be called if a LineStyleListener has been set since the
10112 * listener maintains the styles.
10113 * </p>
10114 *
10115 * @param range StyleRange object containing the style information.
10116 * Overwrites the old style in the given range. May be null to delete
10117 * all styles.
10118 * @exception SWTException <ul>
10119 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10120 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10121 * </ul>
10122 * @exception IllegalArgumentException <ul>
10123 * <li>ERROR_INVALID_RANGE when the style range is outside the valid range (> getCharCount())</li>
10124 * </ul>
10125 */
10126 public void setStyleRange(StyleRange range) {
10127 checkWidget();
10128 if (isListening(ST.LineGetStyle)) return;
10129 if (range != null) {
10130 if (range.isUnstyled()) {
10131 setStyleRanges(range.start, range.length, null, null, false);
10132 } else {
10133 setStyleRanges(range.start, 0, null, new StyleRange[]{range}, false);
10134 }
10135 } else {
10136 setStyleRanges(0, 0, null, null, true);
10137 }
10138 }
10139 /**
10140 * Clears the styles in the range specified by <code>start</code> and
10141 * <code>length</code> and adds the new styles.
10142 * <p>
10143 * The ranges array contains start and length pairs. Each pair refers to
10144 * the corresponding style in the styles array. For example, the pair
10145 * that starts at ranges[n] with length ranges[n+1] uses the style
10146 * at styles[n/2]. The range fields within each StyleRange are ignored.
10147 * If ranges or styles is null, the specified range is cleared.
10148 * </p><p>
10149 * Note: It is expected that the same instance of a StyleRange will occur
10150 * multiple times within the styles array, reducing memory usage.
10151 * </p><p>
10152 * Should not be called if a LineStyleListener has been set since the
10153 * listener maintains the styles.
10154 * </p>
10155 *
10156 * @param start offset of first character where styles will be deleted
10157 * @param length length of the range to delete styles in
10158 * @param ranges the array of ranges. The ranges must not overlap and must be in order.
10159 * @param styles the array of StyleRanges. The range fields within the StyleRange are unused.
10160 *
10161 * @exception SWTException <ul>
10162 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10163 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10164 * </ul>
10165 * @exception IllegalArgumentException <ul>
10166 * <li>ERROR_NULL_ARGUMENT when an element in the styles array is null</li>
10167 * <li>ERROR_INVALID_RANGE when the number of ranges and style do not match (ranges.length * 2 == styles.length)</li>
10168 * <li>ERROR_INVALID_RANGE when a range is outside the valid range (> getCharCount() or less than zero)</li>
10169 * <li>ERROR_INVALID_RANGE when a range overlaps</li>
10170 * </ul>
10171 *
10172 * @since 3.2
10173 */
10174 public void setStyleRanges(int start, int length, int[] ranges, StyleRange[] styles) {
10175 checkWidget();
10176 if (isListening(ST.LineGetStyle)) return;
10177 if (ranges == null || styles == null) {
10178 setStyleRanges(start, length, null, null, false);
10179 } else {
10180 setStyleRanges(start, length, ranges, styles, false);
10181 }
10182 }
10183 /**
10184 * Sets styles to be used for rendering the widget content.
10185 * <p>
10186 * All styles in the widget will be replaced with the given set of ranges and styles.
10187 * The ranges array contains start and length pairs. Each pair refers to
10188 * the corresponding style in the styles array. For example, the pair
10189 * that starts at ranges[n] with length ranges[n+1] uses the style
10190 * at styles[n/2]. The range fields within each StyleRange are ignored.
10191 * If either argument is null, the styles are cleared.
10192 * </p><p>
10193 * Note: It is expected that the same instance of a StyleRange will occur
10194 * multiple times within the styles array, reducing memory usage.
10195 * </p><p>
10196 * Should not be called if a LineStyleListener has been set since the
10197 * listener maintains the styles.
10198 * </p>
10199 *
10200 * @param ranges the array of ranges. The ranges must not overlap and must be in order.
10201 * @param styles the array of StyleRanges. The range fields within the StyleRange are unused.
10202 *
10203 * @exception SWTException <ul>
10204 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10205 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10206 * </ul>
10207 * @exception IllegalArgumentException <ul>
10208 * <li>ERROR_NULL_ARGUMENT when an element in the styles array is null</li>
10209 * <li>ERROR_INVALID_RANGE when the number of ranges and style do not match (ranges.length * 2 == styles.length)</li>
10210 * <li>ERROR_INVALID_RANGE when a range is outside the valid range (> getCharCount() or less than zero)</li>
10211 * <li>ERROR_INVALID_RANGE when a range overlaps</li>
10212 * </ul>
10213 *
10214 * @since 3.2
10215 */
10216 public void setStyleRanges(int[] ranges, StyleRange[] styles) {
10217 checkWidget();
10218 if (isListening(ST.LineGetStyle)) return;
10219 if (ranges == null || styles == null) {
10220 setStyleRanges(0, 0, null, null, true);
10221 } else {
10222 setStyleRanges(0, 0, ranges, styles, true);
10223 }
10224 }
10225 void setStyleRanges(int start, int length, int[] ranges, StyleRange[] styles, boolean reset) {
10226 int charCount = content.getCharCount();
10227 if (reset) {
10228 start = 0;
10229 length = charCount;
10230 }
10231 int[] formerRanges = getRanges(start, length);
10232 StyleRange[] formerStyles = getStyleRanges(start, length);
10233 int end = start + length;
10234 final boolean wasFixedLineHeight = isFixedLineHeight();
10235 if (start > end || start < 0) {
10236 SWT.error(SWT.ERROR_INVALID_RANGE);
10237 }
10238 if (styles != null) {
10239 if (end > charCount) {
10240 SWT.error(SWT.ERROR_INVALID_RANGE);
10241 }
10242 if (ranges != null) {
10243 if (ranges.length != styles.length << 1) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
10244 }
10245 int lastOffset = 0;
10246 for (int i = 0; i < styles.length; i ++) {
10247 if (styles[i] == null) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
10248 int rangeStart, rangeLength;
10249 if (ranges != null) {
10250 rangeStart = ranges[i << 1];
10251 rangeLength = ranges[(i << 1) + 1];
10252 } else {
10253 rangeStart = styles[i].start;
10254 rangeLength = styles[i].length;
10255 }
10256 if (rangeLength < 0) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
10257 if (!(0 <= rangeStart && rangeStart + rangeLength <= charCount)) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
10258 if (lastOffset > rangeStart) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
10259 hasStyleWithVariableHeight |= styles[i].isVariableHeight();
10260 lastOffset = rangeStart + rangeLength;
10261 }
10262 }
10263 int rangeStart = start, rangeEnd = end;
10264 if (styles != null && styles.length > 0) {
10265 if (ranges != null) {
10266 rangeStart = ranges[0];
10267 rangeEnd = ranges[ranges.length - 2] + ranges[ranges.length - 1];
10268 } else {
10269 rangeStart = styles[0].start;
10270 rangeEnd = styles[styles.length - 1].start + styles[styles.length - 1].length;
10271 }
10272 }
10273
10274 // This needs to happen before new styles are applied
10275 int expectedBottom = 0;
10276 if (!isFixedLineHeight() && !reset) {
10277 int lineEnd = content.getLineAtOffset(Math.max(end, rangeEnd));
10278 int partialTopIndex = getPartialTopIndex();
10279 int partialBottomIndex = getPartialBottomIndex();
10280 if (partialTopIndex <= lineEnd && lineEnd <= partialBottomIndex) {
10281 expectedBottom = getLinePixel(lineEnd + 1);
10282 }
10283 }
10284 if (reset) {
10285 renderer.setStyleRanges(null, null);
10286 } else {
10287 renderer.updateRanges(start, length, length);
10288 }
10289 if (styles != null && styles.length > 0) {
10290 renderer.setStyleRanges(ranges, styles);
10291 }
10292
10293 // re-evaluate variable height with all styles (including new ones)
10294 hasStyleWithVariableHeight = false;
10295 for (StyleRange style : getStyleRanges(false)) {
10296 hasStyleWithVariableHeight = style.isVariableHeight();
10297 if (hasStyleWithVariableHeight) break;
10298 }
10299
10300 SortedSet<Integer> modifiedLines = computeModifiedLines(formerRanges, formerStyles, ranges, styles);
10301 resetCache(modifiedLines);
10302 if (reset) {
10303 super.redraw();
10304 } else {
10305 int lineStart = content.getLineAtOffset(Math.min(start, rangeStart));
10306 int lineEnd = content.getLineAtOffset(Math.max(end, rangeEnd));
10307 int partialTopIndex = getPartialTopIndex();
10308 int partialBottomIndex = getPartialBottomIndex();
10309 if (!(lineStart > partialBottomIndex || lineEnd < partialTopIndex)) {
10310 int top = 0;
10311 int bottom = clientAreaHeight;
10312 if (partialTopIndex <= lineStart && lineStart <= partialBottomIndex) {
10313 top = Math.max(0, getLinePixel(lineStart));
10314 }
10315 if (partialTopIndex <= lineEnd && lineEnd <= partialBottomIndex) {
10316 bottom = getLinePixel(lineEnd + 1);
10317 }
10318 if (!(wasFixedLineHeight && isFixedLineHeight()) && bottom != expectedBottom) {
10319 bottom = clientAreaHeight;
10320 }
10321 super.redraw(0, top, clientAreaWidth, bottom - top, false);
10322 }
10323 }
10324 int oldColumnX = columnX;
10325 setCaretLocation();
10326 columnX = oldColumnX;
10327 doMouseLinkCursor();
10328 }
10329
10330 /**
10331 *
10332 * @param referenceRanges former ranges, sorted by order and without overlapping, typically returned {@link #getRanges(int, int)}
10333 * @param referenceStyles
10334 * @param newRanges former ranges, sorted by order and without overlapping
10335 * @param newStyles
10336 * @return
10337 */
10338 private SortedSet<Integer> computeModifiedLines(int[] referenceRanges, StyleRange[] referenceStyles, int[] newRanges, StyleRange[] newStyles) {
10339 if (referenceStyles == null) {
10340 referenceStyles = new StyleRange[0];
10341 }
10342 if (referenceRanges == null) {
10343 referenceRanges = createRanges(referenceStyles);
10344 }
10345 if (newStyles == null) {
10346 newStyles = new StyleRange[0];
10347 }
10348 if (newRanges == null) {
10349 newRanges = createRanges(newStyles);
10350 }
10351 if (referenceRanges.length != 2 * referenceStyles.length) {
10352 throw new IllegalArgumentException();
10353 }
10354 if (newRanges.length != 2 * newStyles.length) {
10355 throw new IllegalArgumentException();
10356 }
10357 SortedSet<Integer> res = new TreeSet<>();
10358 int referenceRangeIndex = 0;
10359 int newRangeIndex = 0;
10360 StyleRange defaultStyle = new StyleRange();
10361 defaultStyle.foreground = this.foreground;
10362 defaultStyle.background = this.background;
10363 defaultStyle.font = getFont();
10364 int currentOffset = referenceRanges.length > 0 ? referenceRanges[0] : Integer.MAX_VALUE;
10365 if (newRanges.length > 0) {
10366 currentOffset = Math.min(currentOffset, newRanges[0]);
10367 }
10368 while (currentOffset < content.getCharCount() && (referenceRangeIndex < referenceStyles.length || newRangeIndex < newRanges.length)) {
10369 int nextMilestoneOffset = Integer.MAX_VALUE; // next new range start/end after current offset
10370
10371 while (referenceRangeIndex < referenceStyles.length && endRangeOffset(referenceRanges, referenceRangeIndex) <= currentOffset) {
10372 referenceRangeIndex++;
10373 }
10374 StyleRange referenceStyleAtCurrentOffset = defaultStyle;
10375 if (isInRange(referenceRanges, referenceRangeIndex, currentOffset)) { // has styling
10376 referenceStyleAtCurrentOffset = referenceStyles[referenceRangeIndex];
10377 nextMilestoneOffset = endRangeOffset(referenceRanges, referenceRangeIndex);
10378 } else if (referenceRangeIndex < referenceStyles.length) { // no range, default styling
10379 nextMilestoneOffset = referenceRanges[2 * referenceRangeIndex]; // beginning of next range
10380 }
10381
10382 while (newRangeIndex < newStyles.length && endRangeOffset(newRanges, newRangeIndex) <= currentOffset) {
10383 newRangeIndex++;
10384 }
10385 StyleRange newStyleAtCurrentOffset = defaultStyle;
10386 if (isInRange(newRanges, newRangeIndex, currentOffset)) {
10387 newStyleAtCurrentOffset = newStyles[newRangeIndex];
10388 nextMilestoneOffset = Math.min(nextMilestoneOffset, endRangeOffset(newRanges, newRangeIndex));
10389 } else if (newRangeIndex < newStyles.length) {
10390 nextMilestoneOffset = Math.min(nextMilestoneOffset, newRanges[2 * newRangeIndex]);
10391 }
10392
10393 if (!referenceStyleAtCurrentOffset.similarTo(newStyleAtCurrentOffset)) {
10394 int fromLine = getLineAtOffset(currentOffset);
10395 int toLine = getLineAtOffset(nextMilestoneOffset - 1);
10396 for (int line = fromLine; line <= toLine; line++) {
10397 res.add(line);
10398 }
10399 currentOffset = toLine + 1 < getLineCount() ? getOffsetAtLine(toLine + 1) : content.getCharCount();
10400 } else {
10401 currentOffset = nextMilestoneOffset;
10402 }
10403 }
10404 return res;
10405 }
10406 private int[] createRanges(StyleRange[] referenceStyles) {
10407 int[] referenceRanges;
10408 referenceRanges = new int[2 * referenceStyles.length];
10409 for (int i = 0; i < referenceStyles.length; i++) {
10410 referenceRanges[2 * i] = referenceStyles[i].start;
10411 referenceRanges[2 * i + 1] = referenceStyles[i].length;
10412 }
10413 return referenceRanges;
10414 }
10415
10416 private boolean isInRange(int[] ranges, int styleIndex, int offset) {
10417 if (ranges == null || ranges.length == 0 || styleIndex < 0 || 2 * styleIndex + 1 > ranges.length) {
10418 return false;
10419 }
10420 int start = ranges[2 * styleIndex];
10421 int length = ranges[2 * styleIndex + 1];
10422 return offset >= start && offset < start + length;
10423 }
10424
10425 /**
10426 * The offset on which the range ends (excluded)
10427 * @param ranges
10428 * @param styleIndex
10429 * @return
10430 */
10431 private int endRangeOffset(int[] ranges, int styleIndex) {
10432 if (styleIndex < 0 || 2 * styleIndex > ranges.length) {
10433 throw new IllegalArgumentException();
10434 }
10435 int start = ranges[2 * styleIndex];
10436 int length = ranges[2 * styleIndex + 1];
10437 return start + length;
10438 }
10439
10440 /**
10441 * Sets styles to be used for rendering the widget content. All styles
10442 * in the widget will be replaced with the given set of styles.
10443 * <p>
10444 * Note: Because a StyleRange includes the start and length, the
10445 * same instance cannot occur multiple times in the array of styles.
10446 * If the same style attributes, such as font and color, occur in
10447 * multiple StyleRanges, <code>setStyleRanges(int[], StyleRange[])</code>
10448 * can be used to share styles and reduce memory usage.
10449 * </p><p>
10450 * Should not be called if a LineStyleListener has been set since the
10451 * listener maintains the styles.
10452 * </p>
10453 *
10454 * @param ranges StyleRange objects containing the style information.
10455 * The ranges should not overlap. The style rendering is undefined if
10456 * the ranges do overlap. Must not be null. The styles need to be in order.
10457 * @exception SWTException <ul>
10458 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10459 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10460 * </ul>
10461 * @exception IllegalArgumentException <ul>
10462 * <li>ERROR_NULL_ARGUMENT when the list of ranges is null</li>
10463 * <li>ERROR_INVALID_RANGE when the last of the style ranges is outside the valid range (> getCharCount())</li>
10464 * </ul>
10465 *
10466 * @see #setStyleRanges(int[], StyleRange[])
10467 */
10468 public void setStyleRanges(StyleRange[] ranges) {
10469 checkWidget();
10470 if (isListening(ST.LineGetStyle)) return;
10471 if (ranges == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
10472 setStyleRanges(0, 0, null, ranges, true);
10473 }
10474 /**
10475 * Sets the tab width.
10476 *
10477 * @param tabs tab width measured in characters.
10478 * @exception SWTException <ul>
10479 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10480 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10481 * </ul>
10482 *
10483 * @see #setTabStops(int[])
10484 */
10485 public void setTabs(int tabs) {
10486 checkWidget();
10487 tabLength = tabs;
10488 renderer.setFont(null, tabs);
10489 resetCache(0, content.getLineCount());
10490 setCaretLocation();
10491 super.redraw();
10492 }
10493
10494 /**
10495 * Sets the receiver's tab list. Each value in the tab list specifies
10496 * the space in points from the origin of the document to the respective
10497 * tab stop. The last tab stop width is repeated continuously.
10498 *
10499 * @param tabs the new tab list (or null)
10500 *
10501 * @exception SWTException <ul>
10502 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10503 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10504 * </ul>
10505 * @exception IllegalArgumentException <ul>
10506 * <li>ERROR_INVALID_ARGUMENT - if a tab stop is negative or less than the previous stop in the list</li>
10507 * </ul>
10508 *
10509 * @see StyledText#getTabStops()
10510 *
10511 * @since 3.6
10512 */
10513 public void setTabStops(int [] tabs) {
10514 checkWidget();
10515 if (tabs != null) {
10516 int pos = 0;
10517 int[] newTabs = new int[tabs.length];
10518 for (int i = 0; i < tabs.length; i++) {
10519 if (tabs[i] < pos) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
10520 newTabs[i] = pos = tabs[i];
10521 }
10522 this.tabs = newTabs;
10523 } else {
10524 this.tabs = null;
10525 }
10526 resetCache(0, content.getLineCount());
10527 setCaretLocation();
10528 super.redraw();
10529 }
10530
10531 /**
10532 * Sets the widget content.
10533 * If the widget has the SWT.SINGLE style and "text" contains more than
10534 * one line, only the first line is rendered but the text is stored
10535 * unchanged. A subsequent call to getText will return the same text
10536 * that was set.
10537 * <p>
10538 * <b>Note:</b> Only a single line of text should be set when the SWT.SINGLE
10539 * style is used.
10540 * </p>
10541 *
10542 * @param text new widget content. Replaces existing content. Line styles
10543 * that were set using StyledText API are discarded. The
10544 * current selection is also discarded.
10545 * @exception SWTException <ul>
10546 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10547 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10548 * </ul>
10549 * @exception IllegalArgumentException <ul>
10550 * <li>ERROR_NULL_ARGUMENT when string is null</li>
10551 * </ul>
10552 */
10553 public void setText(String text) {
10554 checkWidget();
10555 if (text == null) {
10556 SWT.error(SWT.ERROR_NULL_ARGUMENT);
10557 }
10558 Event event = new Event();
10559 event.start = 0;
10560 event.end = getCharCount();
10561 event.text = text;
10562 event.doit = true;
10563 notifyListeners(SWT.Verify, event);
10564 if (event.doit) {
10565 StyledTextEvent styledTextEvent = null;
10566 if (isListening(ST.ExtendedModify)) {
10567 styledTextEvent = new StyledTextEvent(content);
10568 styledTextEvent.start = event.start;
10569 styledTextEvent.end = event.start + event.text.length();
10570 styledTextEvent.text = content.getTextRange(event.start, event.end - event.start);
10571 }
10572 content.setText(event.text);
10573 notifyListeners(SWT.Modify, event);
10574 if (styledTextEvent != null) {
10575 notifyListeners(ST.ExtendedModify, styledTextEvent);
10576 }
10577 }
10578 }
10579
10580 /**
10581 * Sets the base text direction (a.k.a. "paragraph direction") of the receiver,
10582 * which must be one of the constants <code>SWT.LEFT_TO_RIGHT</code> or
10583 * <code>SWT.RIGHT_TO_LEFT</code>.
10584 * <p>
10585 * <code>setOrientation</code> would override this value with the text direction
10586 * that is consistent with the new orientation.
10587 * </p>
10588 * <p>
10589 * <b>Warning</b>: This API is currently only implemented on Windows.
10590 * It doesn't set the base text direction on GTK and Cocoa.
10591 * </p>
10592 *
10593 * @param textDirection the base text direction style
10594 *
10595 * @exception SWTException <ul>
10596 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10597 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10598 * </ul>
10599 *
10600 * @see SWT#FLIP_TEXT_DIRECTION
10601 */
10602 @Override
10603 public void setTextDirection(int textDirection) {
10604 checkWidget();
10605 int oldStyle = getStyle();
10606 super.setTextDirection(textDirection);
10607 if (isAutoDirection () || oldStyle != getStyle()) {
10608 resetBidiData();
10609 }
10610 }
10611
10612 /**
10613 * Sets the text limit to the specified number of characters.
10614 * <p>
10615 * The text limit specifies the amount of text that
10616 * the user can type into the widget.
10617 * </p>
10618 *
10619 * @param limit the new text limit.
10620 * @exception SWTException <ul>
10621 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10622 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10623 * </ul>
10624 * @exception IllegalArgumentException <ul>
10625 * <li>ERROR_CANNOT_BE_ZERO when limit is 0</li>
10626 * </ul>
10627 */
10628 public void setTextLimit(int limit) {
10629 checkWidget();
10630 if (limit == 0) {
10631 SWT.error(SWT.ERROR_CANNOT_BE_ZERO);
10632 }
10633 textLimit = limit;
10634 }
10635 /**
10636 * Sets the top index. Do nothing if there is no text set.
10637 * <p>
10638 * The top index is the index of the line that is currently at the top
10639 * of the widget. The top index changes when the widget is scrolled.
10640 * Indexing starts from zero.
10641 * Note: The top index is reset to 0 when new text is set in the widget.
10642 * </p>
10643 *
10644 * @param topIndex new top index. Must be between 0 and
10645 * getLineCount() - fully visible lines per page. If no lines are fully
10646 * visible the maximum value is getLineCount() - 1. An out of range
10647 * index will be adjusted accordingly.
10648 * @exception SWTException <ul>
10649 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10650 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10651 * </ul>
10652 */
10653 public void setTopIndex(int topIndex) {
10654 checkWidget();
10655 if (getCharCount() == 0) {
10656 return;
10657 }
10658 int lineCount = content.getLineCount(), pixel;
10659 if (isFixedLineHeight()) {
10660 int pageSize = Math.max(1, Math.min(lineCount, getLineCountWhole()));
10661 if (topIndex < 0) {
10662 topIndex = 0;
10663 } else if (topIndex > lineCount - pageSize) {
10664 topIndex = lineCount - pageSize;
10665 }
10666 pixel = getLinePixel(topIndex);
10667 } else {
10668 topIndex = Math.max(0, Math.min(lineCount - 1, topIndex));
10669 pixel = getLinePixel(topIndex);
10670 if (pixel > 0) {
10671 pixel = getAvailableHeightBellow(pixel);
10672 } else {
10673 pixel = getAvailableHeightAbove(pixel);
10674 }
10675 }
10676 scrollVertical(pixel, true);
10677 }
10678 /**
10679 * Sets the top margin.
10680 *
10681 * @param topMargin the top margin.
10682 * @exception SWTException <ul>
10683 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10684 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10685 * </ul>
10686 *
10687 * @since 3.5
10688 */
10689 public void setTopMargin (int topMargin) {
10690 checkWidget();
10691 setMargins(getLeftMargin(), topMargin, rightMargin, bottomMargin);
10692 }
10693 /**
10694 * Sets the top SWT logical point offset. Do nothing if there is no text set.
10695 * <p>
10696 * The top point offset is the vertical SWT logical point offset of the widget. The
10697 * widget is scrolled so that the given SWT logical point position is at the top.
10698 * The top index is adjusted to the corresponding top line.
10699 * Note: The top point is reset to 0 when new text is set in the widget.
10700 * </p>
10701 *
10702 * @param pixel new top point offset. Must be between 0 and
10703 * (getLineCount() - visible lines per page) / getLineHeight()). An out
10704 * of range offset will be adjusted accordingly.
10705 * @exception SWTException <ul>
10706 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10707 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10708 * </ul>
10709 * @since 2.0
10710 */
10711 public void setTopPixel(int pixel) {
10712 checkWidget();
10713 if (getCharCount() == 0) {
10714 return;
10715 }
10716 if (pixel < 0) pixel = 0;
10717 int lineCount = content.getLineCount();
10718 int height = clientAreaHeight - topMargin - bottomMargin;
10719 int verticalOffset = getVerticalScrollOffset();
10720 if (isFixedLineHeight()) {
10721 int maxTopPixel = Math.max(0, lineCount * getVerticalIncrement() - height);
10722 if (pixel > maxTopPixel) pixel = maxTopPixel;
10723 pixel -= verticalOffset;
10724 } else {
10725 pixel -= verticalOffset;
10726 if (pixel > 0) {
10727 pixel = getAvailableHeightBellow(pixel);
10728 }
10729 }
10730 scrollVertical(pixel, true);
10731 }
10732 /**
10733 * Sets whether the widget wraps lines.
10734 * <p>
10735 * This overrides the creation style bit SWT.WRAP.
10736 * </p>
10737 *
10738 * @param wrap true=widget wraps lines, false=widget does not wrap lines
10739 * @since 2.0
10740 */
10741 public void setWordWrap(boolean wrap) {
10742 checkWidget();
10743 if ((getStyle() & SWT.SINGLE) != 0) return;
10744 if (wordWrap == wrap) return;
10745 if (wordWrap && blockSelection) setBlockSelection(false);
10746 wordWrap = wrap;
10747 resetCache(0, content.getLineCount());
10748 horizontalScrollOffset = 0;
10749 ScrollBar horizontalBar = getHorizontalBar();
10750 if (horizontalBar != null) {
10751 horizontalBar.setVisible(!wordWrap);
10752 }
10753 setScrollBars(true);
10754 setCaretLocation();
10755 super.redraw();
10756 }
10757 /**
10758 * Sets the wrap line indentation of the widget.
10759 * <p>
10760 * It is the amount of blank space, in points, at the beginning of each wrapped line.
10761 * When a line wraps in several lines all the lines but the first one is indented
10762 * by this amount.
10763 * </p>
10764 *
10765 * @param wrapIndent the new wrap indent
10766 *
10767 * @exception SWTException <ul>
10768 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10769 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10770 * </ul>
10771 *
10772 * @see #setLineWrapIndent(int, int, int)
10773 *
10774 * @since 3.6
10775 */
10776 public void setWrapIndent(int wrapIndent) {
10777 checkWidget();
10778 if (this.wrapIndent == wrapIndent || wrapIndent < 0) return;
10779 this.wrapIndent = wrapIndent;
10780 resetCache(0, content.getLineCount());
10781 setCaretLocation();
10782 super.redraw();
10783 }
10784 boolean showLocation(Rectangle rect, boolean scrollPage) {
10785 boolean scrolled = false;
10786 if (rect.y < topMargin) {
10787 scrolled = scrollVertical(rect.y - topMargin, true);
10788 } else if (rect.y + rect.height > clientAreaHeight - bottomMargin) {
10789 if (clientAreaHeight - topMargin - bottomMargin <= 0) {
10790 scrolled = scrollVertical(rect.y - topMargin, true);
10791 } else {
10792 scrolled = scrollVertical(rect.y + rect.height - (clientAreaHeight - bottomMargin), true);
10793 }
10794 }
10795 int width = clientAreaWidth - rightMargin - leftMargin;
10796 if (width > 0) {
10797 int minScroll = scrollPage ? width / 4 : 0;
10798 if (rect.x < leftMargin) {
10799 int scrollWidth = Math.max(leftMargin - rect.x, minScroll);
10800 int maxScroll = horizontalScrollOffset;
10801 scrolled = scrollHorizontal(-Math.min(maxScroll, scrollWidth), true);
10802 } else if (rect.x + rect.width > (clientAreaWidth - rightMargin)) {
10803 int scrollWidth = Math.max(rect.x + rect.width - (clientAreaWidth - rightMargin), minScroll);
10804 int maxScroll = renderer.getWidth() - horizontalScrollOffset - clientAreaWidth;
10805 scrolled = scrollHorizontal(Math.min(maxScroll, scrollWidth), true);
10806 }
10807 }
10808 return scrolled;
10809 }
10810 /**
10811 * Sets the caret location and scrolls the caret offset into view.
10812 */
10813 void showCaret() {
10814 Rectangle bounds = getBoundsAtOffset(caretOffset);
10815 if (!showLocation(bounds, true)) {
10816 setCaretLocation();
10817 }
10818 }
10819 /**
10820 * Scrolls the selection into view.
10821 * <p>
10822 * The end of the selection will be scrolled into view.
10823 * Note that if a right-to-left selection exists, the end of the selection is
10824 * the visual beginning of the selection (i.e., where the caret is located).
10825 * </p>
10826 *
10827 * @exception SWTException <ul>
10828 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
10829 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
10830 * </ul>
10831 */
10832 public void showSelection() {
10833 checkWidget();
10834 // is selection from right-to-left?
10835 boolean rightToLeft = caretOffset == selection.x;
10836 int startOffset, endOffset;
10837 if (rightToLeft) {
10838 startOffset = selection.y;
10839 endOffset = selection.x;
10840 } else {
10841 startOffset = selection.x;
10842 endOffset = selection.y;
10843 }
10844
10845 Rectangle startBounds = getBoundsAtOffset(startOffset);
10846 Rectangle endBounds = getBoundsAtOffset(endOffset);
10847
10848 // can the selection be fully displayed within the widget's visible width?
10849 int w = clientAreaWidth - leftMargin - rightMargin;
10850 boolean selectionFits = rightToLeft ? startBounds.x - endBounds.x <= w : endBounds.x - startBounds.x <= w;
10851 if (selectionFits) {
10852 // show as much of the selection as possible by first showing
10853 // the start of the selection
10854 if (showLocation(startBounds, false)) {
10855 // endX value could change if showing startX caused a scroll to occur
10856 endBounds = getBoundsAtOffset(endOffset);
10857 }
10858 // the character at endOffset is not part of the selection
10859 endBounds.width = endOffset == caretOffset ? getCaretWidth() : 0;
10860 showLocation(endBounds, false);
10861 } else {
10862 // just show the end of the selection since the selection start
10863 // will not be visible
10864 showLocation(endBounds, true);
10865 }
10866 }
10867 void updateCaretVisibility() {
10868 Caret caret = getCaret();
10869 if (caret != null) {
10870 if (blockSelection && blockXLocation != -1) {
10871 caret.setVisible(false);
10872 } else {
10873 Point location = caret.getLocation();
10874 Point size = caret.getSize();
10875 boolean visible =
10876 topMargin <= location.y + size.y && location.y <= clientAreaHeight - bottomMargin &&
10877 leftMargin <= location.x + size.x && location.x <= clientAreaWidth - rightMargin;
10878 caret.setVisible(visible);
10879 }
10880 }
10881 }
10882 /**
10883 * Updates the selection and caret position depending on the text change.
10884 * <p>
10885 * If the selection intersects with the replaced text, the selection is
10886 * reset and the caret moved to the end of the new text.
10887 * If the selection is behind the replaced text it is moved so that the
10888 * same text remains selected. If the selection is before the replaced text
10889 * it is left unchanged.
10890 * </p>
10891 *
10892 * @param startOffset offset of the text change
10893 * @param replacedLength length of text being replaced
10894 * @param newLength length of new text
10895 */
10896 void updateSelection(int startOffset, int replacedLength, int newLength) {
10897 if (selection.y <= startOffset) {
10898 // selection ends before text change
10899 if (isWordWrap()) setCaretLocation();
10900 return;
10901 }
10902 if (selection.x < startOffset) {
10903 // clear selection fragment before text change
10904 internalRedrawRange(selection.x, startOffset - selection.x);
10905 }
10906 if (selection.y > startOffset + replacedLength && selection.x < startOffset + replacedLength) {
10907 // clear selection fragment after text change.
10908 // do this only when the selection is actually affected by the
10909 // change. Selection is only affected if it intersects the change (1GDY217).
10910 int netNewLength = newLength - replacedLength;
10911 int redrawStart = startOffset + newLength;
10912 internalRedrawRange(redrawStart, selection.y + netNewLength - redrawStart);
10913 }
10914 if (selection.y > startOffset && selection.x < startOffset + replacedLength) {
10915 // selection intersects replaced text. set caret behind text change
10916 setSelection(startOffset + newLength, 0, true, false);
10917 } else {
10918 // move selection to keep same text selected
10919 setSelection(selection.x + newLength - replacedLength, selection.y - selection.x, true, false);
10920 }
10921 setCaretLocation();
10922 }
10923 }
10924