comparison dwt/graphics/TextLayout.d @ 0:380af2bdd8e5

Upload of whole dwt tree
author Jacob Carlborg <doob@me.com> <jacob.carlborg@gmail.com>
date Sat, 09 Aug 2008 17:00:02 +0200
parents
children 649b8e223d5a
comparison
equal deleted inserted replaced
-1:000000000000 0:380af2bdd8e5
1 /*******************************************************************************
2 * Copyright (c) 2000, 2007 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 * IBM Corporation - initial API and implementation
10 *******************************************************************************/
11 module dwt.graphics.TextLayout;
12
13 import dwt.dwthelper.utils;
14
15 import dwt.DWT;
16 import dwt.DWTException;
17 import dwt.internal.cocoa.NSColor;
18 import dwt.internal.cocoa.NSFont;
19 import dwt.internal.cocoa.NSLayoutManager;
20 import dwt.internal.cocoa.NSMutableParagraphStyle;
21 import dwt.internal.cocoa.NSNumber;
22 import dwt.internal.cocoa.NSPoint;
23 import dwt.internal.cocoa.NSRange;
24 import dwt.internal.cocoa.NSRect;
25 import dwt.internal.cocoa.NSSize;
26 import dwt.internal.cocoa.NSString;
27 import dwt.internal.cocoa.NSTextContainer;
28 import dwt.internal.cocoa.NSTextStorage;
29 import dwt.internal.cocoa.OS;
30
31 /**
32 * <code>TextLayout</code> is a graphic object that represents
33 * styled text.
34 * <p>
35 * Instances of this class provide support for drawing, cursor
36 * navigation, hit testing, text wrapping, alignment, tab expansion
37 * line breaking, etc. These are aspects required for rendering internationalized text.
38 * </p><p>
39 * Application code must explicitly invoke the <code>TextLayout#dispose()</code>
40 * method to release the operating system resources managed by each instance
41 * when those instances are no longer required.
42 * </p>
43 *
44 * @since 3.0
45 */
46 public final class TextLayout extends Resource {
47
48 NSTextStorage textStorage;
49 NSLayoutManager layoutManager;
50 NSTextContainer textContainer;
51 Font font;
52 String text;
53 StyleItem[] styles;
54 int spacing, ascent, descent, indent;
55 bool justify;
56 int alignment;
57 int[] tabs;
58 int[] segments;
59 int wrapWidth;
60 int orientation;
61
62 int[] lineOffsets;
63 NSRect[] lineBounds;
64
65 static class StyleItem {
66 TextStyle style;
67 int start;
68
69 public String toString () {
70 return "StyleItem {" + start + ", " + style + "}";
71 }
72 }
73
74 // static final int TAB_COUNT = 32;
75 // static final char ZWS = '\u200B';
76 //
77 // static final int UNDERLINE_IME_INPUT = 1 << 16;
78 // static final int UNDERLINE_IME_TARGET_CONVERTED = 2 << 16;
79 // static final int UNDERLINE_IME_CONVERTED = 3 << 16;
80
81 /**
82 * Constructs a new instance of this class on the given device.
83 * <p>
84 * You must dispose the text layout when it is no longer required.
85 * </p>
86 *
87 * @param device the device on which to allocate the text layout
88 *
89 * @exception IllegalArgumentException <ul>
90 * <li>ERROR_NULL_ARGUMENT - if device is null and there is no current device</li>
91 * </ul>
92 *
93 * @see #dispose()
94 */
95 public TextLayout (Device device) {
96 super(device);
97 wrapWidth = ascent = descent = -1;
98 alignment = DWT.LEFT;
99 orientation = DWT.LEFT_TO_RIGHT;
100 text = "";
101 styles = new StyleItem[2];
102 styles[0] = new StyleItem();
103 styles[1] = new StyleItem();
104 init();
105 }
106
107 void checkLayout() {
108 if (isDisposed()) DWT.error(DWT.ERROR_GRAPHIC_DISPOSED);
109 }
110
111 void computeRuns() {
112 if (textStorage !is null) return;
113 NSString str = NSString.stringWith(text);
114 textStorage = ((NSTextStorage)new NSTextStorage().alloc());
115 textStorage.initWithString_(str);
116 layoutManager = (NSLayoutManager)new NSLayoutManager().alloc().init();
117 textContainer = (NSTextContainer)new NSTextContainer().alloc();
118 NSSize size = new NSSize();
119 size.width = wrapWidth !is -1 ? wrapWidth : Float.MAX_VALUE;
120 size.height = Float.MAX_VALUE;
121 textContainer.initWithContainerSize(size);
122 textStorage.addLayoutManager(layoutManager);
123 layoutManager.addTextContainer(textContainer);
124
125 textStorage.beginEditing();
126 Font defaultFont = font !is null ? font : device.systemFont;
127 NSRange range = new NSRange();
128 range.length = str.length();
129 textStorage.addAttribute(OS.NSFontAttributeName(), defaultFont.handle, range);
130
131 NSMutableParagraphStyle paragraph = (NSMutableParagraphStyle)new NSMutableParagraphStyle().alloc().init();
132 int align = OS.NSLeftTextAlignment;
133 if (justify) {
134 align = OS.NSJustifiedTextAlignment;
135 } else {
136 switch (alignment) {
137 case DWT.CENTER:
138 align = OS.NSCenterTextAlignment;
139 break;
140 case DWT.RIGHT:
141 align = OS.NSRightTextAlignment;
142 }
143 }
144 paragraph.setAlignment(align);
145 paragraph.setLineSpacing(spacing);
146 paragraph.setFirstLineHeadIndent(indent);
147
148 //TODO tabs ascend descent wrap
149
150 textStorage.addAttribute(OS.NSParagraphStyleAttributeName(), paragraph, range);
151 paragraph.release();
152
153 int textLength = str.length();
154 for (int i = 0; i < styles.length - 1; i++) {
155 StyleItem run = styles[i];
156 if (run.style is null) continue;
157 TextStyle style = run.style;
158 range.location = textLength !is 0 ? translateOffset(run.start) : 0;
159 range.length = translateOffset(styles[i + 1].start) - range.location;
160 Font font = style.font;
161 if (font !is null) {
162 textStorage.addAttribute(OS.NSFontAttributeName(), font.handle, range);
163 }
164 Color foreground = style.foreground;
165 if (foreground !is null) {
166 NSColor color = NSColor.colorWithDeviceRed(foreground.handle[0], foreground.handle[1], foreground.handle[2], 1);
167 textStorage.addAttribute(OS.NSForegroundColorAttributeName(), color, range);
168 }
169 Color background = style.background;
170 if (background !is null) {
171 NSColor color = NSColor.colorWithDeviceRed(background.handle[0], background.handle[1], background.handle[2], 1);
172 textStorage.addAttribute(OS.NSBackgroundColorAttributeName(), color, range);
173 }
174 if (style.strikeout) {
175 textStorage.addAttribute(OS.NSStrikethroughStyleAttributeName(), NSNumber.numberWithInt(OS.NSUnderlineStyleSingle), range);
176 Color strikeColor = style.strikeoutColor;
177 if (strikeColor !is null) {
178 NSColor color = NSColor.colorWithDeviceRed(strikeColor.handle[0], strikeColor.handle[1], strikeColor.handle[2], 1);
179 textStorage.addAttribute(OS.NSStrikethroughColorAttributeName(), color, range);
180 }
181 }
182 if (style.underline) {
183 //TODO - IME - thick
184 int underlineStyle = 0;
185 switch (style.underlineStyle) {
186 case DWT.UNDERLINE_SINGLE:
187 underlineStyle = OS.NSUnderlineStyleSingle;
188 break;
189 case DWT.UNDERLINE_DOUBLE:
190 underlineStyle = OS.NSUnderlineStyleDouble;
191 break;
192 }
193 if (underlineStyle !is 0) {
194 textStorage.addAttribute(OS.NSUnderlineStyleAttributeName(), NSNumber.numberWithInt(underlineStyle), range);
195 Color underlineColor = style.underlineColor;
196 if (underlineColor !is null) {
197 NSColor color = NSColor.colorWithDeviceRed(underlineColor.handle[0], underlineColor.handle[1], underlineColor.handle[2], 1);
198 textStorage.addAttribute(OS.NSUnderlineColorAttributeName(), color, range);
199 }
200 }
201 }
202 if (style.rise !is 0) {
203 textStorage.addAttribute(OS.NSBaselineOffsetAttributeName(), NSNumber.numberWithInt(style.rise), range);
204 }
205 if (style.metrics !is null) {
206 //TODO
207 }
208 }
209 textStorage.endEditing();
210
211 textContainer.setLineFragmentPadding(0);
212 layoutManager.glyphRangeForTextContainer(textContainer);
213
214 int numberOfLines, index, numberOfGlyphs = layoutManager.numberOfGlyphs();
215 int rangePtr = OS.malloc(NSRange.sizeof);
216 NSRange lineRange = new NSRange();
217 for (numberOfLines = 0, index = 0; index < numberOfGlyphs; numberOfLines++){
218 layoutManager.lineFragmentUsedRectForGlyphAtIndex_effectiveRange_withoutAdditionalLayout_(index, rangePtr, true);
219 OS.memmove(lineRange, rangePtr, NSRange.sizeof);
220 index = lineRange.location + lineRange.length;
221 }
222 if (numberOfLines is 0) numberOfLines++;
223 int[] offsets = new int[numberOfLines + 1];
224 NSRect[] bounds = new NSRect[numberOfLines];
225 for (numberOfLines = 0, index = 0; index < numberOfGlyphs; numberOfLines++){
226 bounds[numberOfLines] = layoutManager.lineFragmentUsedRectForGlyphAtIndex_effectiveRange_withoutAdditionalLayout_(index, rangePtr, true);
227 OS.memmove(lineRange, rangePtr, NSRange.sizeof);
228 offsets[numberOfLines] = lineRange.location;
229 index = lineRange.location + lineRange.length;
230 }
231 if (numberOfLines is 0) {
232 Font font = this.font !is null ? this.font : device.systemFont;
233 NSFont nsFont = font.handle;
234 bounds[0] = new NSRect();
235 bounds[0].height = Math.max(layoutManager.defaultLineHeightForFont(nsFont), ascent + descent);
236 }
237 OS.free(rangePtr);
238 offsets[numberOfLines] = textStorage.length();
239 this.lineOffsets = offsets;
240 this.lineBounds = bounds;
241 }
242
243 void destroy() {
244 freeRuns();
245 font = null;
246 text = null;
247 styles = null;
248 }
249
250 /**
251 * Draws the receiver's text using the specified GC at the specified
252 * point.
253 *
254 * @param gc the GC to draw
255 * @param x the x coordinate of the top left corner of the rectangular area where the text is to be drawn
256 * @param y the y coordinate of the top left corner of the rectangular area where the text is to be drawn
257 *
258 * @exception DWTException <ul>
259 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
260 * </ul>
261 * @exception IllegalArgumentException <ul>
262 * <li>ERROR_NULL_ARGUMENT - if the gc is null</li>
263 * </ul>
264 */
265 public void draw(GC gc, int x, int y) {
266 draw(gc, x, y, -1, -1, null, null);
267 }
268
269 /**
270 * Draws the receiver's text using the specified GC at the specified
271 * point.
272 *
273 * @param gc the GC to draw
274 * @param x the x coordinate of the top left corner of the rectangular area where the text is to be drawn
275 * @param y the y coordinate of the top left corner of the rectangular area where the text is to be drawn
276 * @param selectionStart the offset where the selections starts, or -1 indicating no selection
277 * @param selectionEnd the offset where the selections ends, or -1 indicating no selection
278 * @param selectionForeground selection foreground, or NULL to use the system default color
279 * @param selectionBackground selection background, or NULL to use the system default color
280 *
281 * @exception DWTException <ul>
282 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
283 * </ul>
284 * @exception IllegalArgumentException <ul>
285 * <li>ERROR_NULL_ARGUMENT - if the gc is null</li>
286 * </ul>
287 */
288 public void draw(GC gc, int x, int y, int selectionStart, int selectionEnd, Color selectionForeground, Color selectionBackground) {
289 draw(gc, x, y, selectionStart, selectionEnd, selectionForeground, selectionBackground, 0);
290 }
291
292 /**
293 * Draws the receiver's text using the specified GC at the specified
294 * point.
295 * <p>
296 * The parameter <code>flags</code> can include one of <code>DWT.DELIMITER_SELECTION</code>
297 * or <code>DWT.FULL_SELECTION</code> to specify the selection behavior on all lines except
298 * for the last line, and can also include <code>DWT.LAST_LINE_SELECTION</code> to extend
299 * the specified selection behavior to the last line.
300 * </p>
301 * @param gc the GC to draw
302 * @param x the x coordinate of the top left corner of the rectangular area where the text is to be drawn
303 * @param y the y coordinate of the top left corner of the rectangular area where the text is to be drawn
304 * @param selectionStart the offset where the selections starts, or -1 indicating no selection
305 * @param selectionEnd the offset where the selections ends, or -1 indicating no selection
306 * @param selectionForeground selection foreground, or NULL to use the system default color
307 * @param selectionBackground selection background, or NULL to use the system default color
308 * @param flags drawing options
309 *
310 * @exception DWTException <ul>
311 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
312 * </ul>
313 * @exception IllegalArgumentException <ul>
314 * <li>ERROR_NULL_ARGUMENT - if the gc is null</li>
315 * </ul>
316 *
317 * @since 3.3
318 */
319 public void draw(GC gc, int x, int y, int selectionStart, int selectionEnd, Color selectionForeground, Color selectionBackground, int flags) {
320 checkLayout ();
321 computeRuns();
322 if (gc is null) DWT.error(DWT.ERROR_NULL_ARGUMENT);
323 if (gc.isDisposed()) DWT.error(DWT.ERROR_INVALID_ARGUMENT);
324 if (selectionForeground !is null && selectionForeground.isDisposed()) DWT.error(DWT.ERROR_INVALID_ARGUMENT);
325 if (selectionBackground !is null && selectionBackground.isDisposed()) DWT.error(DWT.ERROR_INVALID_ARGUMENT);
326 gc.checkGC(GC.CLIPPING | GC.TRANSFORM | GC.FOREGROUND);
327 // float[] foreground = gc.data.foreground;
328 // NSColor color = NSColor.colorWithDeviceRed(foreground[0], foreground[1], foreground[2], foreground[3]);
329 // textStorage.setForegroundColor(color);
330 NSPoint pt = new NSPoint();
331 pt.x = x;
332 pt.y = y;
333 NSRange range = new NSRange();
334 range.length = layoutManager.numberOfGlyphs();
335 bool hasSelection = selectionStart <= selectionEnd && selectionStart !is -1 && selectionEnd !is -1;
336 NSRange selectionRange = null;
337 if (hasSelection) {
338 selectionRange = new NSRange();
339 selectionRange.location = selectionStart;
340 selectionRange.length = selectionEnd - selectionStart + 1;
341 if (selectionBackground is null) selectionBackground = device.getSystemColor(DWT.COLOR_LIST_SELECTION);
342 NSColor selectionColor = NSColor.colorWithDeviceRed(selectionBackground.handle[0], selectionBackground.handle[1], selectionBackground.handle[2], selectionBackground.handle[3]);
343 layoutManager.addTemporaryAttribute(OS.NSBackgroundColorAttributeName, selectionColor, selectionRange);
344 }
345 //TODO draw selection for flags (LAST_LINE_SELECTION and FULL_SELECTION)
346 if (range.length > 0) {
347 layoutManager.drawBackgroundForGlyphRange(range, pt);
348 layoutManager.drawGlyphsForGlyphRange(range, pt);
349 }
350 if (selectionRange !is null) {
351 layoutManager.removeTemporaryAttribute(OS.NSBackgroundColorAttributeName, selectionRange);
352 }
353 }
354
355 void freeRuns() {
356 if (textStorage is null) return;
357 if (textStorage !is null) {
358 textStorage.release();
359 }
360 if (layoutManager !is null) {
361 layoutManager.release();
362 }
363 if (textContainer !is null) {
364 textContainer.release();
365 }
366 textStorage = null;
367 layoutManager = null;
368 textContainer = null;
369 }
370
371 /**
372 * Returns the receiver's horizontal text alignment, which will be one
373 * of <code>DWT.LEFT</code>, <code>DWT.CENTER</code> or
374 * <code>DWT.RIGHT</code>.
375 *
376 * @return the alignment used to positioned text horizontally
377 *
378 * @exception DWTException <ul>
379 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
380 * </ul>
381 */
382 public int getAlignment() {
383 checkLayout();
384 return alignment;
385 }
386
387 /**
388 * Returns the ascent of the receiver.
389 *
390 * @return the ascent
391 *
392 * @exception DWTException <ul>
393 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
394 * </ul>
395 *
396 * @see #getDescent()
397 * @see #setDescent(int)
398 * @see #setAscent(int)
399 * @see #getLineMetrics(int)
400 */
401 public int getAscent () {
402 checkLayout();
403 return ascent;
404 }
405
406 /**
407 * Returns the bounds of the receiver.
408 *
409 * @return the bounds of the receiver
410 *
411 * @exception DWTException <ul>
412 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
413 * </ul>
414 */
415 public Rectangle getBounds() {
416 checkLayout();
417 computeRuns();
418 NSRect rect = layoutManager.usedRectForTextContainer(textContainer);
419 if (wrapWidth !is -1) rect.width = wrapWidth;
420 if (text.length() is 0) {
421 Font font = this.font !is null ? this.font : device.systemFont;
422 NSFont nsFont = font.handle;
423 rect.height = Math.max(rect.height, layoutManager.defaultLineHeightForFont(nsFont));
424 }
425 rect.height = Math.max(rect.height, ascent + descent);
426 return new Rectangle(0, 0, (int)rect.width, (int)rect.height);
427 }
428
429 /**
430 * Returns the bounds for the specified range of characters. The
431 * bounds is the smallest rectangle that encompasses all characters
432 * in the range. The start and end offsets are inclusive and will be
433 * clamped if out of range.
434 *
435 * @param start the start offset
436 * @param end the end offset
437 * @return the bounds of the character range
438 *
439 * @exception DWTException <ul>
440 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
441 * </ul>
442 */
443 public Rectangle getBounds(int start, int end) {
444 checkLayout();
445 computeRuns();
446 int length = text.length();
447 if (length is 0) return new Rectangle(0, 0, 0, 0);
448 if (start > end) return new Rectangle(0, 0, 0, 0);
449 start = Math.min(Math.max(0, start), length - 1);
450 end = Math.min(Math.max(0, end), length - 1);
451 start = translateOffset(start);
452 end = translateOffset(end);
453 NSRange range = new NSRange();
454 range.location = layoutManager.glyphIndexForCharacterAtIndex(start);
455 range.length = layoutManager.glyphIndexForCharacterAtIndex(end + 1) - range.location;
456 NSRect rect = layoutManager.boundingRectForGlyphRange(range, textContainer);
457 return new Rectangle((int)rect.x, (int)rect.y, (int)Math.ceil(rect.width), (int)Math.ceil(rect.height));
458 }
459
460 /**
461 * Returns the descent of the receiver.
462 *
463 * @return the descent
464 *
465 * @exception DWTException <ul>
466 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
467 * </ul>
468 *
469 * @see #getAscent()
470 * @see #setAscent(int)
471 * @see #setDescent(int)
472 * @see #getLineMetrics(int)
473 */
474 public int getDescent () {
475 checkLayout();
476 return descent;
477 }
478
479 /**
480 * Returns the default font currently being used by the receiver
481 * to draw and measure text.
482 *
483 * @return the receiver's font
484 *
485 * @exception DWTException <ul>
486 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
487 * </ul>
488 */
489 public Font getFont () {
490 checkLayout();
491 return font;
492 }
493
494 /**
495 * Returns the receiver's indent.
496 *
497 * @return the receiver's indent
498 *
499 * @exception DWTException <ul>
500 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
501 * </ul>
502 *
503 * @since 3.2
504 */
505 public int getIndent () {
506 checkLayout();
507 return indent;
508 }
509
510 /**
511 * Returns the receiver's justification.
512 *
513 * @return the receiver's justification
514 *
515 * @exception DWTException <ul>
516 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
517 * </ul>
518 *
519 * @since 3.2
520 */
521 public bool getJustify () {
522 checkLayout();
523 return justify;
524 }
525
526 /**
527 * Returns the embedding level for the specified character offset. The
528 * embedding level is usually used to determine the directionality of a
529 * character in bidirectional text.
530 *
531 * @param offset the character offset
532 * @return the embedding level
533 *
534 * @exception IllegalArgumentException <ul>
535 * <li>ERROR_INVALID_ARGUMENT - if the character offset is out of range</li>
536 * </ul>
537 * @exception DWTException <ul>
538 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
539 */
540 public int getLevel(int offset) {
541 checkLayout();
542 computeRuns();
543 int length = text.length();
544 if (!(0 <= offset && offset <= length)) DWT.error(DWT.ERROR_INVALID_RANGE);
545 offset = translateOffset(offset);
546 int level = 0;
547 //TODO
548 return level;
549 }
550
551 /**
552 * Returns the line offsets. Each value in the array is the
553 * offset for the first character in a line except for the last
554 * value, which contains the length of the text.
555 *
556 * @return the line offsets
557 *
558 * @exception DWTException <ul>
559 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
560 * </ul>
561 */
562 public int[] getLineOffsets() {
563 checkLayout ();
564 computeRuns();
565 int[] offsets = new int[lineOffsets.length];
566 for (int i = 0; i < offsets.length; i++) {
567 offsets[i] = untranslateOffset(lineOffsets[i]);
568 }
569 return offsets;
570 }
571
572 /**
573 * Returns the index of the line that contains the specified
574 * character offset.
575 *
576 * @param offset the character offset
577 * @return the line index
578 *
579 * @exception IllegalArgumentException <ul>
580 * <li>ERROR_INVALID_ARGUMENT - if the character offset is out of range</li>
581 * </ul>
582 * @exception DWTException <ul>
583 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
584 * </ul>
585 */
586 public int getLineIndex(int offset) {
587 checkLayout ();
588 computeRuns();
589 int length = text.length();
590 if (!(0 <= offset && offset <= length)) DWT.error(DWT.ERROR_INVALID_RANGE);
591 offset = translateOffset(offset);
592 for (int line=0; line<lineOffsets.length - 1; line++) {
593 if (lineOffsets[line + 1] > offset) {
594 return line;
595 }
596 }
597 return lineBounds.length - 1;
598 }
599
600 /**
601 * Returns the bounds of the line for the specified line index.
602 *
603 * @param lineIndex the line index
604 * @return the line bounds
605 *
606 * @exception IllegalArgumentException <ul>
607 * <li>ERROR_INVALID_ARGUMENT - if the line index is out of range</li>
608 * </ul>
609 * @exception DWTException <ul>
610 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
611 * </ul>
612 */
613 public Rectangle getLineBounds(int lineIndex) {
614 checkLayout();
615 computeRuns();
616 if (!(0 <= lineIndex && lineIndex < lineBounds.length)) DWT.error(DWT.ERROR_INVALID_RANGE);
617 NSRect rect = lineBounds[lineIndex];
618 return new Rectangle((int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height);
619 }
620
621 /**
622 * Returns the receiver's line count. This includes lines caused
623 * by wrapping.
624 *
625 * @return the line count
626 *
627 * @exception DWTException <ul>
628 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
629 * </ul>
630 */
631 public int getLineCount() {
632 checkLayout ();
633 computeRuns();
634 return lineOffsets.length - 1;
635 }
636
637 /**
638 * Returns the font metrics for the specified line index.
639 *
640 * @param lineIndex the line index
641 * @return the font metrics
642 *
643 * @exception IllegalArgumentException <ul>
644 * <li>ERROR_INVALID_ARGUMENT - if the line index is out of range</li>
645 * </ul>
646 * @exception DWTException <ul>
647 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
648 * </ul>
649 */
650 public FontMetrics getLineMetrics (int lineIndex) {
651 checkLayout ();
652 computeRuns();
653 int lineCount = getLineCount();
654 if (!(0 <= lineIndex && lineIndex < lineCount)) DWT.error(DWT.ERROR_INVALID_RANGE);
655 int length = text.length();
656 if (length is 0) {
657 Font font = this.font !is null ? this.font : device.systemFont;
658 NSFont nsFont = font.handle;
659 int ascent = (int)(0.5f + nsFont.ascender());
660 int descent = (int)(0.5f + (-nsFont.descender() + nsFont.leading()));
661 ascent = Math.max(ascent, this.ascent);
662 descent = Math.max(descent, this.descent);
663 return FontMetrics.cocoa_new(ascent, descent, 0, 0, ascent + descent);
664 }
665 Rectangle rect = getLineBounds(lineIndex);
666 int baseline = (int)layoutManager.typesetter().baselineOffsetInLayoutManager(layoutManager, getLineOffsets()[lineIndex]);
667 return FontMetrics.cocoa_new(rect.height - baseline, baseline, 0, 0, rect.height);
668 }
669
670 /**
671 * Returns the location for the specified character offset. The
672 * <code>trailing</code> argument indicates whether the offset
673 * corresponds to the leading or trailing edge of the cluster.
674 *
675 * @param offset the character offset
676 * @param trailing the trailing flag
677 * @return the location of the character offset
678 *
679 * @exception DWTException <ul>
680 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
681 * </ul>
682 *
683 * @see #getOffset(Point, int[])
684 * @see #getOffset(int, int, int[])
685 */
686 public Point getLocation(int offset, bool trailing) {
687 checkLayout();
688 computeRuns();
689 int length = text.length();
690 if (!(0 <= offset && offset <= length)) DWT.error(DWT.ERROR_INVALID_RANGE);
691 if (length is 0) return new Point(0, 0);
692 offset = translateOffset(offset);
693 int glyphIndex = layoutManager.glyphIndexForCharacterAtIndex(offset);
694 NSRect rect = layoutManager.lineFragmentUsedRectForGlyphAtIndex_effectiveRange_(glyphIndex, 0);
695 NSPoint point = layoutManager.locationForGlyphAtIndex(glyphIndex);
696 if (trailing) {
697 NSRange range = new NSRange();
698 range.location = glyphIndex;
699 range.length = 1;
700 NSRect bounds = layoutManager.boundingRectForGlyphRange(range, textContainer);
701 point.x += bounds.width;
702 }
703 return new Point((int)point.x, (int)rect.y);
704 }
705
706 /**
707 * Returns the next offset for the specified offset and movement
708 * type. The movement is one of <code>DWT.MOVEMENT_CHAR</code>,
709 * <code>DWT.MOVEMENT_CLUSTER</code>, <code>DWT.MOVEMENT_WORD</code>,
710 * <code>DWT.MOVEMENT_WORD_END</code> or <code>DWT.MOVEMENT_WORD_START</code>.
711 *
712 * @param offset the start offset
713 * @param movement the movement type
714 * @return the next offset
715 *
716 * @exception IllegalArgumentException <ul>
717 * <li>ERROR_INVALID_ARGUMENT - if the offset is out of range</li>
718 * </ul>
719 * @exception DWTException <ul>
720 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
721 * </ul>
722 *
723 * @see #getPreviousOffset(int, int)
724 */
725 public int getNextOffset (int offset, int movement) {
726 return _getOffset(offset, movement, true);
727 }
728
729 int _getOffset (int offset, int movement, bool forward) {
730 checkLayout();
731 computeRuns();
732 int length = text.length();
733 if (!(0 <= offset && offset <= length)) DWT.error(DWT.ERROR_INVALID_RANGE);
734 if (length is 0) return 0;
735 offset = translateOffset(offset);
736 switch (movement) {
737 case DWT.MOVEMENT_CLUSTER://TODO cluster
738 case DWT.MOVEMENT_CHAR: {
739 if (forward) {
740 offset++;
741 } else {
742 offset--;
743 }
744 return untranslateOffset(offset);
745 }
746 case DWT.MOVEMENT_WORD: {
747 return untranslateOffset(textStorage.nextWordFromIndex(offset, forward));
748 }
749 case DWT.MOVEMENT_WORD_END: {
750 NSRange range = textStorage.doubleClickAtIndex(length is offset ? length - 1 : offset);
751 return untranslateOffset(range.location + range.length);
752 }
753 case DWT.MOVEMENT_WORD_START: {
754 NSRange range = textStorage.doubleClickAtIndex(length is offset ? length - 1 : offset);
755 return untranslateOffset(range.location);
756 }
757 default:
758 break;
759 }
760 return -1;
761 }
762
763 /**
764 * Returns the character offset for the specified point.
765 * For a typical character, the trailing argument will be filled in to
766 * indicate whether the point is closer to the leading edge (0) or
767 * the trailing edge (1). When the point is over a cluster composed
768 * of multiple characters, the trailing argument will be filled with the
769 * position of the character in the cluster that is closest to
770 * the point.
771 *
772 * @param point the point
773 * @param trailing the trailing buffer
774 * @return the character offset
775 *
776 * @exception IllegalArgumentException <ul>
777 * <li>ERROR_INVALID_ARGUMENT - if the trailing length is less than <code>1</code></li>
778 * <li>ERROR_NULL_ARGUMENT - if the point is null</li>
779 * </ul>
780 * @exception DWTException <ul>
781 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
782 * </ul>
783 *
784 * @see #getLocation(int, bool)
785 */
786 public int getOffset(Point point, int[] trailing) {
787 checkLayout();
788 computeRuns();
789 if (point is null) DWT.error(DWT.ERROR_NULL_ARGUMENT);
790 return getOffset(point.x, point.y, trailing);
791 }
792
793 /**
794 * Returns the character offset for the specified point.
795 * For a typical character, the trailing argument will be filled in to
796 * indicate whether the point is closer to the leading edge (0) or
797 * the trailing edge (1). When the point is over a cluster composed
798 * of multiple characters, the trailing argument will be filled with the
799 * position of the character in the cluster that is closest to
800 * the point.
801 *
802 * @param x the x coordinate of the point
803 * @param y the y coordinate of the point
804 * @param trailing the trailing buffer
805 * @return the character offset
806 *
807 * @exception IllegalArgumentException <ul>
808 * <li>ERROR_INVALID_ARGUMENT - if the trailing length is less than <code>1</code></li>
809 * </ul>
810 * @exception DWTException <ul>
811 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
812 * </ul>
813 *
814 * @see #getLocation(int, bool)
815 */
816 public int getOffset(int x, int y, int[] trailing) {
817 checkLayout();
818 computeRuns();
819 if (trailing !is null && trailing.length < 1) DWT.error(DWT.ERROR_INVALID_ARGUMENT);
820 int length = text.length();
821 if (length is 0) return 0;
822 NSPoint pt = new NSPoint();
823 pt.x = x;
824 pt.y = y;
825 float[] partialFration = new float[1];
826 int glyphIndex = layoutManager.glyphIndexForPoint_inTextContainer_fractionOfDistanceThroughGlyph_(pt, textContainer, partialFration);
827 int offset = layoutManager.characterIndexForGlyphAtIndex(glyphIndex);
828 if (trailing !is null) trailing[0] = Math.round(partialFration[0]);
829 return Math.min(untranslateOffset(offset), length - 1);
830 }
831
832 /**
833 * Returns the orientation of the receiver.
834 *
835 * @return the orientation style
836 *
837 * @exception DWTException <ul>
838 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
839 * </ul>
840 */
841 public int getOrientation() {
842 checkLayout();
843 return orientation;
844 }
845
846 /**
847 * Returns the previous offset for the specified offset and movement
848 * type. The movement is one of <code>DWT.MOVEMENT_CHAR</code>,
849 * <code>DWT.MOVEMENT_CLUSTER</code> or <code>DWT.MOVEMENT_WORD</code>,
850 * <code>DWT.MOVEMENT_WORD_END</code> or <code>DWT.MOVEMENT_WORD_START</code>.
851 *
852 * @param offset the start offset
853 * @param movement the movement type
854 * @return the previous offset
855 *
856 * @exception IllegalArgumentException <ul>
857 * <li>ERROR_INVALID_ARGUMENT - if the offset is out of range</li>
858 * </ul>
859 * @exception DWTException <ul>
860 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
861 * </ul>
862 *
863 * @see #getNextOffset(int, int)
864 */
865 public int getPreviousOffset (int index, int movement) {
866 return _getOffset(index, movement, false);
867 }
868
869 /**
870 * Gets the ranges of text that are associated with a <code>TextStyle</code>.
871 *
872 * @return the ranges, an array of offsets representing the start and end of each
873 * text style.
874 *
875 * @exception DWTException <ul>
876 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
877 * </ul>
878 *
879 * @see #getStyles()
880 *
881 * @since 3.2
882 */
883 public int[] getRanges () {
884 checkLayout();
885 int[] result = new int[styles.length * 2];
886 int count = 0;
887 for (int i=0; i<styles.length - 1; i++) {
888 if (styles[i].style !is null) {
889 result[count++] = styles[i].start;
890 result[count++] = styles[i + 1].start - 1;
891 }
892 }
893 if (count !is result.length) {
894 int[] newResult = new int[count];
895 System.arraycopy(result, 0, newResult, 0, count);
896 result = newResult;
897 }
898 return result;
899 }
900
901 /**
902 * Returns the text segments offsets of the receiver.
903 *
904 * @return the text segments offsets
905 *
906 * @exception DWTException <ul>
907 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
908 * </ul>
909 */
910 public int[] getSegments() {
911 checkLayout();
912 return segments;
913 }
914
915 /**
916 * Returns the line spacing of the receiver.
917 *
918 * @return the line spacing
919 *
920 * @exception DWTException <ul>
921 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
922 * </ul>
923 */
924 public int getSpacing () {
925 checkLayout();
926 return spacing;
927 }
928
929 /**
930 * Gets the style of the receiver at the specified character offset.
931 *
932 * @param offset the text offset
933 * @return the style or <code>null</code> if not set
934 *
935 * @exception IllegalArgumentException <ul>
936 * <li>ERROR_INVALID_ARGUMENT - if the character offset is out of range</li>
937 * </ul>
938 * @exception DWTException <ul>
939 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
940 * </ul>
941 */
942 public TextStyle getStyle (int offset) {
943 checkLayout();
944 int length = text.length();
945 if (!(0 <= offset && offset < length)) DWT.error(DWT.ERROR_INVALID_RANGE);
946 for (int i=1; i<styles.length; i++) {
947 StyleItem item = styles[i];
948 if (item.start > offset) {
949 return styles[i - 1].style;
950 }
951 }
952 return null;
953 }
954
955 /**
956 * Gets all styles of the receiver.
957 *
958 * @return the styles
959 *
960 * @exception DWTException <ul>
961 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
962 * </ul>
963 *
964 * @see #getRanges()
965 *
966 * @since 3.2
967 */
968 public TextStyle[] getStyles () {
969 checkLayout();
970 TextStyle[] result = new TextStyle[styles.length];
971 int count = 0;
972 for (int i=0; i<styles.length; i++) {
973 if (styles[i].style !is null) {
974 result[count++] = styles[i].style;
975 }
976 }
977 if (count !is result.length) {
978 TextStyle[] newResult = new TextStyle[count];
979 System.arraycopy(result, 0, newResult, 0, count);
980 result = newResult;
981 }
982 return result;
983 }
984
985 /**
986 * Returns the tab list of the receiver.
987 *
988 * @return the tab list
989 *
990 * @exception DWTException <ul>
991 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
992 * </ul>
993 */
994 public int[] getTabs() {
995 checkLayout();
996 return tabs;
997 }
998
999 /**
1000 * Gets the receiver's text, which will be an empty
1001 * string if it has never been set.
1002 *
1003 * @return the receiver's text
1004 *
1005 * @exception DWTException <ul>
1006 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1007 * </ul>
1008 */
1009 public String getText () {
1010 checkLayout ();
1011 return text;
1012 }
1013
1014 /**
1015 * Returns the width of the receiver.
1016 *
1017 * @return the width
1018 *
1019 * @exception DWTException <ul>
1020 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1021 * </ul>
1022 */
1023 public int getWidth () {
1024 checkLayout();
1025 return wrapWidth;
1026 }
1027
1028 /**
1029 * Returns <code>true</code> if the text layout has been disposed,
1030 * and <code>false</code> otherwise.
1031 * <p>
1032 * This method gets the dispose state for the text layout.
1033 * When a text layout has been disposed, it is an error to
1034 * invoke any other method using the text layout.
1035 * </p>
1036 *
1037 * @return <code>true</code> when the text layout is disposed and <code>false</code> otherwise
1038 */
1039 public bool isDisposed () {
1040 return device is null;
1041 }
1042
1043 /**
1044 * Sets the text alignment for the receiver. The alignment controls
1045 * how a line of text is positioned horizontally. The argument should
1046 * be one of <code>DWT.LEFT</code>, <code>DWT.RIGHT</code> or <code>DWT.CENTER</code>.
1047 * <p>
1048 * The default alignment is <code>DWT.LEFT</code>. Note that the receiver's
1049 * width must be set in order to use <code>DWT.RIGHT</code> or <code>DWT.CENTER</code>
1050 * alignment.
1051 * </p>
1052 *
1053 * @param alignment the new alignment
1054 *
1055 * @exception DWTException <ul>
1056 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1057 * </ul>
1058 *
1059 * @see #setWidth(int)
1060 */
1061 public void setAlignment (int alignment) {
1062 checkLayout();
1063 int mask = DWT.LEFT | DWT.CENTER | DWT.RIGHT;
1064 alignment &= mask;
1065 if (alignment is 0) return;
1066 if ((alignment & DWT.LEFT) !is 0) alignment = DWT.LEFT;
1067 if ((alignment & DWT.RIGHT) !is 0) alignment = DWT.RIGHT;
1068 if (this.alignment is alignment) return;
1069 freeRuns();
1070 this.alignment = alignment;
1071 }
1072
1073 /**
1074 * Sets the ascent of the receiver. The ascent is distance in pixels
1075 * from the baseline to the top of the line and it is applied to all
1076 * lines. The default value is <code>-1</code> which means that the
1077 * ascent is calculated from the line fonts.
1078 *
1079 * @param ascent the new ascent
1080 *
1081 * @exception IllegalArgumentException <ul>
1082 * <li>ERROR_INVALID_ARGUMENT - if the ascent is less than <code>-1</code></li>
1083 * </ul>
1084 * @exception DWTException <ul>
1085 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1086 * </ul>
1087 *
1088 * @see #setDescent(int)
1089 * @see #getLineMetrics(int)
1090 */
1091 public void setAscent (int ascent) {
1092 checkLayout ();
1093 if (ascent < -1) DWT.error(DWT.ERROR_INVALID_ARGUMENT);
1094 if (this.ascent is ascent) return;
1095 freeRuns();
1096 this.ascent = ascent;
1097 }
1098
1099 /**
1100 * Sets the descent of the receiver. The descent is distance in pixels
1101 * from the baseline to the bottom of the line and it is applied to all
1102 * lines. The default value is <code>-1</code> which means that the
1103 * descent is calculated from the line fonts.
1104 *
1105 * @param descent the new descent
1106 *
1107 * @exception IllegalArgumentException <ul>
1108 * <li>ERROR_INVALID_ARGUMENT - if the descent is less than <code>-1</code></li>
1109 * </ul>
1110 * @exception DWTException <ul>
1111 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1112 * </ul>
1113 *
1114 * @see #setAscent(int)
1115 * @see #getLineMetrics(int)
1116 */
1117 public void setDescent (int descent) {
1118 checkLayout ();
1119 if (descent < -1) DWT.error(DWT.ERROR_INVALID_ARGUMENT);
1120 if (this.descent is descent) return;
1121 freeRuns();
1122 this.descent = descent;
1123 }
1124
1125 /**
1126 * Sets the default font which will be used by the receiver
1127 * to draw and measure text. If the
1128 * argument is null, then a default font appropriate
1129 * for the platform will be used instead. Note that a text
1130 * style can override the default font.
1131 *
1132 * @param font the new font for the receiver, or null to indicate a default font
1133 *
1134 * @exception IllegalArgumentException <ul>
1135 * <li>ERROR_INVALID_ARGUMENT - if the font has been disposed</li>
1136 * </ul>
1137 * @exception DWTException <ul>
1138 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1139 * </ul>
1140 */
1141 public void setFont (Font font) {
1142 checkLayout ();
1143 if (font !is null && font.isDisposed()) DWT.error(DWT.ERROR_INVALID_ARGUMENT);
1144 Font oldFont = this.font;
1145 if (oldFont is font) return;
1146 this.font = font;
1147 if (oldFont !is null && oldFont.equals(font)) return;
1148 freeRuns();
1149 }
1150
1151 /**
1152 * Sets the indent of the receiver. This indent it applied of the first line of
1153 * each paragraph.
1154 *
1155 * @param indent new indent
1156 *
1157 * @exception DWTException <ul>
1158 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1159 * </ul>
1160 *
1161 * @since 3.2
1162 */
1163 public void setIndent (int indent) {
1164 checkLayout ();
1165 if (indent < 0) return;
1166 if (this.indent is indent) return;
1167 freeRuns();
1168 this.indent = indent;
1169 }
1170
1171 /**
1172 * Sets the justification of the receiver. Note that the receiver's
1173 * width must be set in order to use justification.
1174 *
1175 * @param justify new justify
1176 *
1177 * @exception DWTException <ul>
1178 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1179 * </ul>
1180 *
1181 * @since 3.2
1182 */
1183 public void setJustify (bool justify) {
1184 checkLayout ();
1185 if (justify is this.justify) return;
1186 freeRuns();
1187 this.justify = justify;
1188 }
1189
1190 /**
1191 * Sets the orientation of the receiver, which must be one
1192 * of <code>DWT.LEFT_TO_RIGHT</code> or <code>DWT.RIGHT_TO_LEFT</code>.
1193 *
1194 * @param orientation new orientation style
1195 *
1196 * @exception DWTException <ul>
1197 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1198 * </ul>
1199 */
1200 public void setOrientation(int orientation) {
1201 checkLayout();
1202 int mask = DWT.LEFT_TO_RIGHT | DWT.RIGHT_TO_LEFT;
1203 orientation &= mask;
1204 if (orientation is 0) return;
1205 if ((orientation & DWT.LEFT_TO_RIGHT) !is 0) orientation = DWT.LEFT_TO_RIGHT;
1206 if (this.orientation is orientation) return;
1207 this.orientation = orientation;
1208 freeRuns();
1209 }
1210
1211 /**
1212 * Sets the offsets of the receiver's text segments. Text segments are used to
1213 * override the default behaviour of the bidirectional algorithm.
1214 * Bidirectional reordering can happen within a text segment but not
1215 * between two adjacent segments.
1216 * <p>
1217 * Each text segment is determined by two consecutive offsets in the
1218 * <code>segments</code> arrays. The first element of the array should
1219 * always be zero and the last one should always be equals to length of
1220 * the text.
1221 * </p>
1222 *
1223 * @param segments the text segments offset
1224 *
1225 * @exception DWTException <ul>
1226 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1227 * </ul>
1228 */
1229 public void setSegments(int[] segments) {
1230 checkLayout();
1231 if (this.segments is null && segments is null) return;
1232 if (this.segments !is null && segments !isnull) {
1233 if (this.segments.length is segments.length) {
1234 int i;
1235 for (i = 0; i <segments.length; i++) {
1236 if (this.segments[i] !is segments[i]) break;
1237 }
1238 if (i is segments.length) return;
1239 }
1240 }
1241 freeRuns();
1242 this.segments = segments;
1243 }
1244
1245 /**
1246 * Sets the line spacing of the receiver. The line spacing
1247 * is the space left between lines.
1248 *
1249 * @param spacing the new line spacing
1250 *
1251 * @exception IllegalArgumentException <ul>
1252 * <li>ERROR_INVALID_ARGUMENT - if the spacing is negative</li>
1253 * </ul>
1254 * @exception DWTException <ul>
1255 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1256 * </ul>
1257 */
1258 public void setSpacing (int spacing) {
1259 checkLayout();
1260 if (spacing < 0) DWT.error(DWT.ERROR_INVALID_ARGUMENT);
1261 if (this.spacing is spacing) return;
1262 freeRuns();
1263 this.spacing = spacing;
1264 }
1265
1266 /**
1267 * Sets the style of the receiver for the specified range. Styles previously
1268 * set for that range will be overwritten. The start and end offsets are
1269 * inclusive and will be clamped if out of range.
1270 *
1271 * @param style the style
1272 * @param start the start offset
1273 * @param end the end offset
1274 *
1275 * @exception DWTException <ul>
1276 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1277 * </ul>
1278 */
1279 public void setStyle (TextStyle style, int start, int end) {
1280 checkLayout();
1281 int length = text.length();
1282 if (length is 0) return;
1283 if (start > end) return;
1284 start = Math.min(Math.max(0, start), length - 1);
1285 end = Math.min(Math.max(0, end), length - 1);
1286 int low = -1;
1287 int high = styles.length;
1288 while (high - low > 1) {
1289 int index = (high + low) / 2;
1290 if (styles[index + 1].start > start) {
1291 high = index;
1292 } else {
1293 low = index;
1294 }
1295 }
1296 if (0 <= high && high < styles.length) {
1297 StyleItem item = styles[high];
1298 if (item.start is start && styles[high + 1].start - 1 is end) {
1299 if (style is null) {
1300 if (item.style is null) return;
1301 } else {
1302 if (style.equals(item.style)) return;
1303 }
1304 }
1305 }
1306 freeRuns();
1307 int modifyStart = high;
1308 int modifyEnd = modifyStart;
1309 while (modifyEnd < styles.length) {
1310 if (styles[modifyEnd + 1].start > end) break;
1311 modifyEnd++;
1312 }
1313 if (modifyStart is modifyEnd) {
1314 int styleStart = styles[modifyStart].start;
1315 int styleEnd = styles[modifyEnd + 1].start - 1;
1316 if (styleStart is start && styleEnd is end) {
1317 styles[modifyStart].style = style;
1318 return;
1319 }
1320 if (styleStart !is start && styleEnd !is end) {
1321 StyleItem[] newStyles = new StyleItem[styles.length + 2];
1322 System.arraycopy(styles, 0, newStyles, 0, modifyStart + 1);
1323 StyleItem item = new StyleItem();
1324 item.start = start;
1325 item.style = style;
1326 newStyles[modifyStart + 1] = item;
1327 item = new StyleItem();
1328 item.start = end + 1;
1329 item.style = styles[modifyStart].style;
1330 newStyles[modifyStart + 2] = item;
1331 System.arraycopy(styles, modifyEnd + 1, newStyles, modifyEnd + 3, styles.length - modifyEnd - 1);
1332 styles = newStyles;
1333 return;
1334 }
1335 }
1336 if (start is styles[modifyStart].start) modifyStart--;
1337 if (end is styles[modifyEnd + 1].start - 1) modifyEnd++;
1338 int newLength = styles.length + 1 - (modifyEnd - modifyStart - 1);
1339 StyleItem[] newStyles = new StyleItem[newLength];
1340 System.arraycopy(styles, 0, newStyles, 0, modifyStart + 1);
1341 StyleItem item = new StyleItem();
1342 item.start = start;
1343 item.style = style;
1344 newStyles[modifyStart + 1] = item;
1345 styles[modifyEnd].start = end + 1;
1346 System.arraycopy(styles, modifyEnd, newStyles, modifyStart + 2, styles.length - modifyEnd);
1347 styles = newStyles;
1348 }
1349
1350 /**
1351 * Sets the receiver's tab list. Each value in the tab list specifies
1352 * the space in pixels from the origin of the text layout to the respective
1353 * tab stop. The last tab stop width is repeated continuously.
1354 *
1355 * @param tabs the new tab list
1356 *
1357 * @exception DWTException <ul>
1358 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1359 * </ul>
1360 */
1361 public void setTabs(int[] tabs) {
1362 checkLayout();
1363 if (this.tabs is null && tabs is null) return;
1364 if (this.tabs !is null && tabs !isnull) {
1365 if (this.tabs.length is tabs.length) {
1366 int i;
1367 for (i = 0; i < tabs.length; i++) {
1368 if (this.tabs[i] !is tabs[i]) break;
1369 }
1370 if (i is tabs.length) return;
1371 }
1372 }
1373 freeRuns();
1374 this.tabs = tabs;
1375 }
1376
1377 /**
1378 * Sets the receiver's text.
1379 *
1380 * @param text the new text
1381 *
1382 * @exception IllegalArgumentException <ul>
1383 * <li>ERROR_NULL_ARGUMENT - if the text is null</li>
1384 * </ul>
1385 * @exception DWTException <ul>
1386 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1387 * </ul>
1388 */
1389 public void setText (String text) {
1390 checkLayout ();
1391 if (text is null) DWT.error(DWT.ERROR_NULL_ARGUMENT);
1392 if (text.equals(this.text)) return;
1393 freeRuns();
1394 this.text = text;
1395 styles = new StyleItem[2];
1396 styles[0] = new StyleItem();
1397 styles[1] = new StyleItem();
1398 styles[styles.length - 1].start = text.length();
1399 }
1400
1401 /**
1402 * Sets the line width of the receiver, which determines how
1403 * text should be wrapped and aligned. The default value is
1404 * <code>-1</code> which means wrapping is disabled.
1405 *
1406 * @param width the new width
1407 *
1408 * @exception IllegalArgumentException <ul>
1409 * <li>ERROR_INVALID_ARGUMENT - if the width is <code>0</code> or less than <code>-1</code></li>
1410 * </ul>
1411 * @exception DWTException <ul>
1412 * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li>
1413 * </ul>
1414 *
1415 * @see #setAlignment(int)
1416 */
1417 public void setWidth (int width) {
1418 checkLayout();
1419 if (width < -1 || width is 0) DWT.error(DWT.ERROR_INVALID_ARGUMENT);
1420 if (this.wrapWidth is width) return;
1421 freeRuns();
1422 this.wrapWidth = width;
1423 }
1424
1425 /**
1426 * Returns a string containing a concise, human-readable
1427 * description of the receiver.
1428 *
1429 * @return a string representation of the receiver
1430 */
1431 public String toString () {
1432 if (isDisposed()) return "TextLayout {*DISPOSED*}";
1433 return "TextLayout {" + text + "}";
1434 }
1435
1436 /*
1437 * Translate a client offset to an internal offset
1438 */
1439 int translateOffset (int offset) {
1440 return offset;
1441 }
1442
1443 /*
1444 * Translate an internal offset to a client offset
1445 */
1446 int untranslateOffset (int offset) {
1447 return offset;
1448 }
1449
1450 }