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