diff options
Diffstat (limited to 'bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/graphics/TextLayout.java')
-rwxr-xr-x | bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/graphics/TextLayout.java | 1980 |
1 files changed, 1980 insertions, 0 deletions
diff --git a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/graphics/TextLayout.java b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/graphics/TextLayout.java new file mode 100755 index 0000000000..1d8b96f356 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/graphics/TextLayout.java @@ -0,0 +1,1980 @@ +/******************************************************************************* + * Copyright (c) 2000, 2009 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.graphics; + +import org.eclipse.swt.internal.C; +import org.eclipse.swt.internal.Compatibility; +import org.eclipse.swt.internal.cocoa.*; +import org.eclipse.swt.*; + +/** + * <code>TextLayout</code> is a graphic object that represents + * styled text. + * <p> + * Instances of this class provide support for drawing, cursor + * navigation, hit testing, text wrapping, alignment, tab expansion + * line breaking, etc. These are aspects required for rendering internationalized text. + * </p><p> + * Application code must explicitly invoke the <code>TextLayout#dispose()</code> + * method to release the operating system resources managed by each instance + * when those instances are no longer required. + * </p> + * + * @see <a href="http://www.eclipse.org/swt/snippets/#textlayout">TextLayout, TextStyle snippets</a> + * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: CustomControlExample, StyledText tab</a> + * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> + * + * @since 3.0 + */ +public final class TextLayout extends Resource { + + NSTextStorage textStorage; + NSLayoutManager layoutManager; + NSTextContainer textContainer; + Font font; + String text; + StyleItem[] styles; + int spacing, ascent, descent, indent; + boolean justify; + int alignment; + int[] tabs; + int[] segments; + int wrapWidth; + int orientation; + + int[] lineOffsets; + NSRect[] lineBounds; + + static final int UNDERLINE_THICK = 1 << 16; + static final RGB LINK_FOREGROUND = new RGB (0, 51, 153); + int[] invalidOffsets; + static final char LTR_MARK = '\u200E', RTL_MARK = '\u200F', ZWS = '\u200B'; + + static class StyleItem { + TextStyle style; + int start; + + public String toString () { + return "StyleItem {" + start + ", " + style + "}"; + } + } + +/** + * Constructs a new instance of this class on the given device. + * <p> + * You must dispose the text layout when it is no longer required. + * </p> + * + * @param device the device on which to allocate the text layout + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if device is null and there is no current device</li> + * </ul> + * + * @see #dispose() + */ +public TextLayout (Device device) { + super(device); + wrapWidth = ascent = descent = -1; + alignment = SWT.LEFT; + orientation = SWT.LEFT_TO_RIGHT; + text = ""; + styles = new StyleItem[2]; + styles[0] = new StyleItem(); + styles[1] = new StyleItem(); + init(); +} + +void checkLayout() { + if (isDisposed()) SWT.error(SWT.ERROR_GRAPHIC_DISPOSED); +} + +float[] computePolyline(int left, int top, int right, int bottom) { + int height = bottom - top; // can be any number + int width = 2 * height; // must be even + int peaks = Compatibility.ceil(right - left, width); + if (peaks == 0 && right - left > 2) { + peaks = 1; + } + int length = ((2 * peaks) + 1) * 2; + if (length < 0) return new float[0]; + + float[] coordinates = new float[length]; + for (int i = 0; i < peaks; i++) { + int index = 4 * i; + coordinates[index] = left + (width * i); + coordinates[index+1] = bottom; + coordinates[index+2] = coordinates[index] + width / 2; + coordinates[index+3] = top; + } + coordinates[length-2] = left + (width * peaks); + coordinates[length-1] = bottom; + return coordinates; +} + + +void computeRuns() { + if (textStorage != null) return; + String segmentsText = getSegmentsText(); + NSString str = NSString.stringWith(segmentsText); + textStorage = (NSTextStorage)new NSTextStorage().alloc().init(); + layoutManager = (NSLayoutManager)new NSLayoutManager().alloc().init(); + layoutManager.setBackgroundLayoutEnabled(NSThread.isMainThread()); + textContainer = (NSTextContainer)new NSTextContainer().alloc(); + NSSize size = new NSSize(); + size.width = wrapWidth != -1 ? wrapWidth : Float.MAX_VALUE; + size.height = Float.MAX_VALUE; + textContainer.initWithContainerSize(size); + textStorage.addLayoutManager(layoutManager); + layoutManager.addTextContainer(textContainer); + + /* + * Bug in Cocoa. Adding attributes directly to a NSTextStorage causes + * output to the console and eventually a segmentation fault when printing + * on a thread other than the main thread. The fix is to add attributes to + * a separate NSMutableAttributedString and add it to text storage when done. + */ + NSMutableAttributedString attrStr = (NSMutableAttributedString)new NSMutableAttributedString().alloc(); + attrStr.id = attrStr.initWithString(str).id; + attrStr.beginEditing(); + Font defaultFont = font != null ? font : device.systemFont; + NSRange range = new NSRange(); + range.length = str.length(); + attrStr.addAttribute(OS.NSFontAttributeName, defaultFont.handle, range); + defaultFont.addTraits(attrStr, range); + //TODO ascend descent wrap + NSMutableParagraphStyle paragraph = (NSMutableParagraphStyle)new NSMutableParagraphStyle().alloc().init(); + int align = OS.NSLeftTextAlignment; + if (justify) { + align = OS.NSJustifiedTextAlignment; + } else { + switch (alignment) { + case SWT.CENTER: + align = OS.NSCenterTextAlignment; + break; + case SWT.RIGHT: + align = OS.NSRightTextAlignment; + } + } + paragraph.setAlignment(align); + paragraph.setLineSpacing(spacing); + paragraph.setFirstLineHeadIndent(indent); + paragraph.setLineBreakMode(wrapWidth != -1 ? OS.NSLineBreakByWordWrapping : OS.NSLineBreakByClipping); + paragraph.setTabStops(NSArray.array()); + if (tabs != null) { + int count = tabs.length; + for (int i = 0, pos = 0; i < count; i++) { + pos += tabs[i]; + NSTextTab tab = (NSTextTab)new NSTextTab().alloc(); + tab = tab.initWithType(OS.NSLeftTabStopType, pos); + paragraph.addTabStop(tab); + tab.release(); + } + int width = count - 2 >= 0 ? tabs[count - 1] - tabs[count - 2] : tabs[count - 1]; + paragraph.setDefaultTabInterval(width); + } + attrStr.addAttribute(OS.NSParagraphStyleAttributeName, paragraph, range); + paragraph.release(); + int /*long*/ textLength = str.length(); + for (int i = 0; i < styles.length - 1; i++) { + StyleItem run = styles[i]; + if (run.style == null) continue; + TextStyle style = run.style; + range.location = textLength != 0 ? translateOffset(run.start) : 0; + range.length = translateOffset(styles[i + 1].start) - range.location; + Font font = style.font; + if (font != null) { + attrStr.addAttribute(OS.NSFontAttributeName, font.handle, range); + font.addTraits(attrStr, range); + } + Color foreground = style.foreground; + if (foreground != null) { + NSColor color = NSColor.colorWithDeviceRed(foreground.handle[0], foreground.handle[1], foreground.handle[2], 1); + attrStr.addAttribute(OS.NSForegroundColorAttributeName, color, range); + } + Color background = style.background; + if (background != null) { + NSColor color = NSColor.colorWithDeviceRed(background.handle[0], background.handle[1], background.handle[2], 1); + attrStr.addAttribute(OS.NSBackgroundColorAttributeName, color, range); + } + if (style.strikeout) { + attrStr.addAttribute(OS.NSStrikethroughStyleAttributeName, NSNumber.numberWithInt(OS.NSUnderlineStyleSingle), range); + Color strikeColor = style.strikeoutColor; + if (strikeColor != null) { + NSColor color = NSColor.colorWithDeviceRed(strikeColor.handle[0], strikeColor.handle[1], strikeColor.handle[2], 1); + attrStr.addAttribute(OS.NSStrikethroughColorAttributeName, color, range); + } + } + if (isUnderlineSupported(style)) { + int underlineStyle = 0; + switch (style.underlineStyle) { + case SWT.UNDERLINE_SINGLE: + underlineStyle = OS.NSUnderlineStyleSingle; + break; + case SWT.UNDERLINE_DOUBLE: + underlineStyle = OS.NSUnderlineStyleDouble; + break; + case UNDERLINE_THICK: + underlineStyle = OS.NSUnderlineStyleThick; + break; + case SWT.UNDERLINE_LINK: { + underlineStyle = OS.NSUnderlineStyleSingle; + if (foreground == null) { + NSColor color = NSColor.colorWithDeviceRed(LINK_FOREGROUND.red / 255f, LINK_FOREGROUND.green / 255f, LINK_FOREGROUND.blue / 255f, 1); + attrStr.addAttribute(OS.NSForegroundColorAttributeName, color, range); + } + break; + } + } + if (underlineStyle != 0) { + attrStr.addAttribute(OS.NSUnderlineStyleAttributeName, NSNumber.numberWithInt(underlineStyle), range); + Color underlineColor = style.underlineColor; + if (underlineColor != null) { + NSColor color = NSColor.colorWithDeviceRed(underlineColor.handle[0], underlineColor.handle[1], underlineColor.handle[2], 1); + attrStr.addAttribute(OS.NSUnderlineColorAttributeName, color, range); + } + } + } + if (style.rise != 0) { + attrStr.addAttribute(OS.NSBaselineOffsetAttributeName, NSNumber.numberWithInt(style.rise), range); + } + if (style.metrics != null) { + //TODO implement metrics + } + } + attrStr.endEditing(); + textStorage.setAttributedString(attrStr); + attrStr.release(); + + textContainer.setLineFragmentPadding(0); + layoutManager.glyphRangeForTextContainer(textContainer); + + int numberOfLines; + int /*long*/ numberOfGlyphs = layoutManager.numberOfGlyphs(), index; + int /*long*/ rangePtr = OS.malloc(NSRange.sizeof); + NSRange lineRange = new NSRange(); + for (numberOfLines = 0, index = 0; index < numberOfGlyphs; numberOfLines++){ + layoutManager.lineFragmentUsedRectForGlyphAtIndex(index, rangePtr, true); + OS.memmove(lineRange, rangePtr, NSRange.sizeof); + index = lineRange.location + lineRange.length; + } + if (numberOfLines == 0) numberOfLines++; + int[] offsets = new int[numberOfLines + 1]; + NSRect[] bounds = new NSRect[numberOfLines]; + for (numberOfLines = 0, index = 0; index < numberOfGlyphs; numberOfLines++){ + bounds[numberOfLines] = layoutManager.lineFragmentUsedRectForGlyphAtIndex(index, rangePtr, true); + if (numberOfLines < bounds.length - 1) bounds[numberOfLines].height -= spacing; + OS.memmove(lineRange, rangePtr, NSRange.sizeof); + offsets[numberOfLines] = (int)/*64*/lineRange.location; + index = lineRange.location + lineRange.length; + } + if (numberOfLines == 0) { + Font font = this.font != null ? this.font : device.systemFont; + NSFont nsFont = font.handle; + bounds[0] = new NSRect(); + bounds[0].height = Math.max(layoutManager.defaultLineHeightForFont(nsFont), ascent + descent); + } + OS.free(rangePtr); + offsets[numberOfLines] = (int)/*64*/textStorage.length(); + this.lineOffsets = offsets; + this.lineBounds = bounds; +} + +void destroy() { + freeRuns(); + font = null; + text = null; + styles = null; +} + +/** + * Draws the receiver's text using the specified GC at the specified + * point. + * + * @param gc the GC to draw + * @param x the x coordinate of the top left corner of the rectangular area where the text is to be drawn + * @param y the y coordinate of the top left corner of the rectangular area where the text is to be drawn + * + * @exception SWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the gc is null</li> + * </ul> + */ +public void draw(GC gc, int x, int y) { + draw(gc, x, y, -1, -1, null, null); +} + +/** + * Draws the receiver's text using the specified GC at the specified + * point. + * + * @param gc the GC to draw + * @param x the x coordinate of the top left corner of the rectangular area where the text is to be drawn + * @param y the y coordinate of the top left corner of the rectangular area where the text is to be drawn + * @param selectionStart the offset where the selections starts, or -1 indicating no selection + * @param selectionEnd the offset where the selections ends, or -1 indicating no selection + * @param selectionForeground selection foreground, or NULL to use the system default color + * @param selectionBackground selection background, or NULL to use the system default color + * + * @exception SWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the gc is null</li> + * </ul> + */ +public void draw(GC gc, int x, int y, int selectionStart, int selectionEnd, Color selectionForeground, Color selectionBackground) { + draw(gc, x, y, selectionStart, selectionEnd, selectionForeground, selectionBackground, 0); +} + +/** + * Draws the receiver's text using the specified GC at the specified + * point. + * <p> + * The parameter <code>flags</code> can include one of <code>SWT.DELIMITER_SELECTION</code> + * or <code>SWT.FULL_SELECTION</code> to specify the selection behavior on all lines except + * for the last line, and can also include <code>SWT.LAST_LINE_SELECTION</code> to extend + * the specified selection behavior to the last line. + * </p> + * @param gc the GC to draw + * @param x the x coordinate of the top left corner of the rectangular area where the text is to be drawn + * @param y the y coordinate of the top left corner of the rectangular area where the text is to be drawn + * @param selectionStart the offset where the selections starts, or -1 indicating no selection + * @param selectionEnd the offset where the selections ends, or -1 indicating no selection + * @param selectionForeground selection foreground, or NULL to use the system default color + * @param selectionBackground selection background, or NULL to use the system default color + * @param flags drawing options + * + * @exception SWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the gc is null</li> + * </ul> + * + * @since 3.3 + */ +public void draw(GC gc, int x, int y, int selectionStart, int selectionEnd, Color selectionForeground, Color selectionBackground, int flags) { + checkLayout (); + if (gc == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + if (gc.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT); + if (selectionForeground != null && selectionForeground.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT); + if (selectionBackground != null && selectionBackground.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT); + NSAutoreleasePool pool = gc.checkGC(GC.CLIPPING | GC.TRANSFORM | GC.FOREGROUND); + try { + computeRuns(); + int length = translateOffset(text.length()); + if (length == 0 && flags == 0) return; + gc.handle.saveGraphicsState(); + NSPoint pt = new NSPoint(); + pt.x = x; + pt.y = y; + NSRange range = new NSRange(); + int /*long*/ numberOfGlyphs = layoutManager.numberOfGlyphs(); + if (numberOfGlyphs > 0) { + range.location = 0; + range.length = numberOfGlyphs; + layoutManager.drawBackgroundForGlyphRange(range, pt); + } + boolean hasSelection = selectionStart <= selectionEnd && selectionStart != -1 && selectionEnd != -1; + if (hasSelection || (flags & SWT.LAST_LINE_SELECTION) != 0) { + if (selectionBackground == null) selectionBackground = device.getSystemColor(SWT.COLOR_LIST_SELECTION); + NSColor selectionColor = NSColor.colorWithDeviceRed(selectionBackground.handle[0], selectionBackground.handle[1], selectionBackground.handle[2], selectionBackground.handle[3]); + NSBezierPath path = NSBezierPath.bezierPath(); + NSRect rect = new NSRect(); + if (hasSelection) { + int /*long*/ pRectCount = OS.malloc(C.PTR_SIZEOF); + range.location = translateOffset(selectionStart); + range.length = translateOffset(selectionEnd - selectionStart + 1); + int /*long*/ pArray = layoutManager.rectArrayForCharacterRange(range, range, textContainer, pRectCount); + int /*long*/ [] rectCount = new int /*long*/ [1]; + OS.memmove(rectCount, pRectCount, C.PTR_SIZEOF); + OS.free(pRectCount); + for (int k = 0; k < rectCount[0]; k++, pArray += NSRect.sizeof) { + OS.memmove(rect, pArray, NSRect.sizeof); + fixRect(rect); + rect.x += pt.x; + rect.y += pt.y; + rect.height = Math.max(rect.height, ascent + descent); + path.appendBezierPathWithRect(rect); + } + } + //TODO draw full selection for wrapped text + if ((flags & SWT.LAST_LINE_SELECTION) != 0) { + NSRect bounds = lineBounds[lineBounds.length - 1]; + rect.x = pt.x + bounds.x + bounds.width; + rect.y = y + bounds.y; + rect.width = (flags & SWT.FULL_SELECTION) != 0 ? 0x7fffffff : bounds.height / 3; + rect.height = Math.max(bounds.height, ascent + descent); + path.appendBezierPathWithRect(rect); + } + selectionColor.setFill(); + path.fill(); + } + if (numberOfGlyphs > 0) { + range.location = 0; + range.length = numberOfGlyphs; + float /*double*/ [] fg = gc.data.foreground; + boolean defaultFg = fg[0] == 0 && fg[1] == 0 && fg[2] == 0 && fg[3] == 1; + if (!defaultFg) { + for (int i = 0; i < styles.length - 1; i++) { + StyleItem run = styles[i]; + if (run.style != null && run.style.foreground != null) continue; + if (run.style != null && run.style.underline && run.style.underlineStyle == SWT.UNDERLINE_LINK) continue; + range.location = length != 0 ? translateOffset(run.start) : 0; + range.length = translateOffset(styles[i + 1].start) - range.location; + layoutManager.addTemporaryAttribute(OS.NSForegroundColorAttributeName, gc.data.fg, range); + } + } + range.location = 0; + range.length = numberOfGlyphs; + layoutManager.drawGlyphsForGlyphRange(range, pt); + if (!defaultFg) { + range.location = 0; + range.length = length; + layoutManager.removeTemporaryAttribute(OS.NSForegroundColorAttributeName, range); + } + NSPoint point = new NSPoint(); + for (int j = 0; j < styles.length; j++) { + StyleItem run = styles[j]; + TextStyle style = run.style; + if (style == null) continue; + boolean drawUnderline = style.underline && !isUnderlineSupported(style); + drawUnderline = drawUnderline && (j + 1 == styles.length || !style.isAdherentUnderline(styles[j + 1].style)); + boolean drawBorder = style.borderStyle != SWT.NONE; + drawBorder = drawBorder && (j + 1 == styles.length || !style.isAdherentBorder(styles[j + 1].style)); + if (!drawUnderline && !drawBorder) continue; + int end = j + 1 < styles.length ? translateOffset(styles[j + 1].start - 1) : length; + for (int i = 0; i < lineOffsets.length - 1; i++) { + int lineStart = untranslateOffset(lineOffsets[i]); + int lineEnd = untranslateOffset(lineOffsets[i + 1] - 1); + if (drawUnderline) { + int start = run.start; + for (int k = j; k > 0 && style.isAdherentUnderline(styles[k - 1].style); k--) { + start = styles[k - 1].start; + } + start = translateOffset(start); + if (!(start > lineEnd || end < lineStart)) { + range.location = Math.max(lineStart, start); + range.length = Math.min(lineEnd, end) + 1 - range.location; + if (range.length > 0) { + int /*long*/ pRectCount = OS.malloc(C.PTR_SIZEOF); + int /*long*/ pArray = layoutManager.rectArrayForCharacterRange(range, range, textContainer, pRectCount); + int /*long*/ [] rectCount = new int /*long*/ [1]; + OS.memmove(rectCount, pRectCount, C.PTR_SIZEOF); + OS.free(pRectCount); + NSRect rect = new NSRect(); + gc.handle.saveGraphicsState(); + float /*double*/ baseline = layoutManager.typesetter().baselineOffsetInLayoutManager(layoutManager, lineStart); + float /*double*/ [] color = null; + if (style.underlineColor != null) color = style.underlineColor.handle; + if (color == null && style.foreground != null) color = style.foreground.handle; + if (color != null) { + NSColor.colorWithDeviceRed(color[0], color[1], color[2], color[3]).setStroke(); + } + for (int k = 0; k < rectCount[0]; k++, pArray += NSRect.sizeof) { + OS.memmove(rect, pArray, NSRect.sizeof); + fixRect(rect); + float /*double*/ underlineX = pt.x + rect.x; + float /*double*/ underlineY = pt.y + rect.y + rect.height - baseline + 1; + NSBezierPath path = NSBezierPath.bezierPath(); + switch (style.underlineStyle) { + case SWT.UNDERLINE_ERROR: { + path.setLineWidth(2f); + path.setLineCapStyle(OS.NSRoundLineCapStyle); + path.setLineJoinStyle(OS.NSRoundLineJoinStyle); + path.setLineDash(new float /*double*/ []{1, 3f}, 2, 0); + point.x = underlineX; + point.y = underlineY + 0.5f; + path.moveToPoint(point); + point.x = underlineX + rect.width; + point.y = underlineY + 0.5f; + path.lineToPoint(point); + break; + } + case SWT.UNDERLINE_SQUIGGLE: { + gc.handle.setShouldAntialias(false); + path.setLineWidth(1.0f); + path.setLineCapStyle(OS.NSButtLineCapStyle); + path.setLineJoinStyle(OS.NSMiterLineJoinStyle); + float /*double*/ lineBottom = pt.y + rect.y + rect.height; + float squigglyThickness = 1; + float squigglyHeight = 2 * squigglyThickness; + float /*double*/ squigglyY = Math.min(underlineY - squigglyHeight / 2, lineBottom - squigglyHeight - 1); + float[] points = computePolyline((int)underlineX, (int)squigglyY, (int)(underlineX + rect.width), (int)(squigglyY + squigglyHeight)); + point.x = points[0] + 0.5f; + point.y = points[1] + 0.5f; + path.moveToPoint(point); + for (int p = 2; p < points.length; p+=2) { + point.x = points[p] + 0.5f; + point.y = points[p+1] + 0.5f; + path.lineToPoint(point); + } + break; + } + } + path.stroke(); + } + gc.handle.restoreGraphicsState(); + } + } + } + if (drawBorder) { + int start = run.start; + for (int k = j; k > 0 && style.isAdherentBorder(styles[k - 1].style); k--) { + start = styles[k - 1].start; + } + start = translateOffset(start); + if (!(start > lineEnd || end < lineStart)) { + range.location = Math.max(lineStart, start); + range.length = Math.min(lineEnd, end) + 1 - range.location; + if (range.length > 0) { + int /*long*/ pRectCount = OS.malloc(C.PTR_SIZEOF); + int /*long*/ pArray = layoutManager.rectArrayForCharacterRange(range, range, textContainer, pRectCount); + int /*long*/ [] rectCount = new int /*long*/ [1]; + OS.memmove(rectCount, pRectCount, C.PTR_SIZEOF); + OS.free(pRectCount); + NSRect rect = new NSRect(); + gc.handle.saveGraphicsState(); + float /*double*/ [] color = null; + if (style.borderColor != null) color = style.borderColor.handle; + if (color == null && style.foreground != null) color = style.foreground.handle; + if (color != null) { + NSColor.colorWithDeviceRed(color[0], color[1], color[2], color[3]).setStroke(); + } + int width = 1; + float[] dashes = null; + switch (style.borderStyle) { + case SWT.BORDER_SOLID: break; + case SWT.BORDER_DASH: dashes = width != 0 ? GC.LINE_DASH : GC.LINE_DASH_ZERO; break; + case SWT.BORDER_DOT: dashes = width != 0 ? GC.LINE_DOT : GC.LINE_DOT_ZERO; break; + } + float /*double*/ [] lengths = null; + if (dashes != null) { + lengths = new float /*double*/[dashes.length]; + for (int k = 0; k < lengths.length; k++) { + lengths[k] = width == 0 ? dashes[k] : dashes[k] * width; + } + } + for (int k = 0; k < rectCount[0]; k++, pArray += NSRect.sizeof) { + OS.memmove(rect, pArray, NSRect.sizeof); + fixRect(rect); + rect.x += pt.x + 0.5f; + rect.y += pt.y + 0.5f; + rect.width -= 0.5f; + rect.height -= 0.5f; + NSBezierPath path = NSBezierPath.bezierPath(); + path.setLineDash(lengths, lengths != null ? lengths.length : 0, 0); + path.appendBezierPathWithRect(rect); + path.stroke(); + } + gc.handle.restoreGraphicsState(); + } + } + } + } + } + } + gc.handle.restoreGraphicsState(); + } finally { + gc.uncheckGC(pool); + } +} + +void fixRect(NSRect rect) { + for (int j = 0; j < lineBounds.length; j++) { + NSRect line = lineBounds[j]; + if (line.y <= rect.y && rect.y < line.y + line.height) { + if (rect.x + rect.width > line.x + line.width) { + rect.width = line.x + line.width - rect.x; + } + } + } +} + +void freeRuns() { + if (textStorage == null) return; + if (textStorage != null) { + textStorage.release(); + } + if (layoutManager != null) { + layoutManager.release(); + } + if (textContainer != null) { + textContainer.release(); + } + textStorage = null; + layoutManager = null; + textContainer = null; +} + +/** + * Returns the receiver's horizontal text alignment, which will be one + * of <code>SWT.LEFT</code>, <code>SWT.CENTER</code> or + * <code>SWT.RIGHT</code>. + * + * @return the alignment used to positioned text horizontally + * + * @exception SWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public int getAlignment() { + checkLayout(); + return alignment; +} + +/** + * Returns the ascent of the receiver. + * + * @return the ascent + * + * @exception SWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + * + * @see #getDescent() + * @see #setDescent(int) + * @see #setAscent(int) + * @see #getLineMetrics(int) + */ +public int getAscent () { + checkLayout(); + return ascent; +} + +/** + * Returns the bounds of the receiver. The width returned is either the + * width of the longest line or the width set using {@link TextLayout#setWidth(int)}. + * To obtain the text bounds of a line use {@link TextLayout#getLineBounds(int)}. + * + * @return the bounds of the receiver + * + * @exception SWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + * + * @see #setWidth(int) + * @see #getLineBounds(int) + */ +public Rectangle getBounds() { + checkLayout(); + NSAutoreleasePool pool = null; + if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init(); + try { + computeRuns(); + NSRect rect = layoutManager.usedRectForTextContainer(textContainer); + if (wrapWidth != -1) rect.width = wrapWidth; + if (text.length() == 0) { + Font font = this.font != null ? this.font : device.systemFont; + NSFont nsFont = font.handle; + rect.height = layoutManager.defaultLineHeightForFont(nsFont); + } + rect.height = Math.max(rect.height, ascent + descent) + spacing; + return new Rectangle(0, 0, (int)Math.ceil(rect.width), (int)Math.ceil(rect.height)); + } finally { + if (pool != null) pool.release(); + } +} + +/** + * Returns the bounds for the specified range of characters. The + * bounds is the smallest rectangle that encompasses all characters + * in the range. The start and end offsets are inclusive and will be + * clamped if out of range. + * + * @param start the start offset + * @param end the end offset + * @return the bounds of the character range + * + * @exception SWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public Rectangle getBounds(int start, int end) { + checkLayout(); + NSAutoreleasePool pool = null; + if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init(); + try { + computeRuns(); + int length = text.length(); + if (length == 0) return new Rectangle(0, 0, 0, 0); + if (start > end) return new Rectangle(0, 0, 0, 0); + start = Math.min(Math.max(0, start), length - 1); + end = Math.min(Math.max(0, end), length - 1); + start = translateOffset(start); + end = translateOffset(end); + NSRange range = new NSRange(); + range.location = start; + range.length = end - start + 1; + int /*long*/ pRectCount = OS.malloc(C.PTR_SIZEOF); + int /*long*/ pArray = layoutManager.rectArrayForCharacterRange(range, range, textContainer, pRectCount); + int /*long*/ [] rectCount = new int /*long*/ [1]; + OS.memmove(rectCount, pRectCount, C.PTR_SIZEOF); + OS.free(pRectCount); + NSRect rect = new NSRect(); + int left = 0x7FFFFFFF, right = 0; + int top = 0x7FFFFFFF, bottom = 0; + for (int i = 0; i < rectCount[0]; i++, pArray += NSRect.sizeof) { + OS.memmove(rect, pArray, NSRect.sizeof); + fixRect(rect); + left = Math.min(left, (int)rect.x); + right = Math.max(right, (int)Math.ceil(rect.x + rect.width)); + top = Math.min(top, (int)rect.y); + bottom = Math.max(bottom, (int)Math.ceil(rect.y + rect.height)); + } + return new Rectangle(left, top, right - left, bottom - top); + } finally { + if (pool != null) pool.release(); + } +} + +/** + * Returns the descent of the receiver. + * + * @return the descent + * + * @exception SWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + * + * @see #getAscent() + * @see #setAscent(int) + * @see #setDescent(int) + * @see #getLineMetrics(int) + */ +public int getDescent () { + checkLayout(); + return descent; +} + +/** + * Returns the default font currently being used by the receiver + * to draw and measure text. + * + * @return the receiver's font + * + * @exception SWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public Font getFont () { + checkLayout(); + return font; +} + +/** +* Returns the receiver's indent. +* +* @return the receiver's indent +* +* @exception SWTException <ul> +* <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> +* </ul> +* +* @since 3.2 +*/ +public int getIndent () { + checkLayout(); + return indent; +} + +/** +* Returns the receiver's justification. +* +* @return the receiver's justification +* +* @exception SWTException <ul> +* <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> +* </ul> +* +* @since 3.2 +*/ +public boolean getJustify () { + checkLayout(); + return justify; +} + +/** + * Returns the embedding level for the specified character offset. The + * embedding level is usually used to determine the directionality of a + * character in bidirectional text. + * + * @param offset the character offset + * @return the embedding level + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if the character offset is out of range</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + */ +public int getLevel(int offset) { + checkLayout(); + NSAutoreleasePool pool = null; + if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init(); + try { + computeRuns(); + int length = text.length(); + if (!(0 <= offset && offset <= length)) SWT.error(SWT.ERROR_INVALID_RANGE); + offset = translateOffset(offset); + int /*long*/ glyphOffset = layoutManager.glyphIndexForCharacterAtIndex(offset); + NSRange range = new NSRange(); + range.location = glyphOffset; + range.length = 1; + int /*long*/ pBidiLevels = OS.malloc(1); + byte[] bidiLevels = new byte[1]; + int /*long*/ result = layoutManager.getGlyphsInRange(range, 0, 0, 0, 0, pBidiLevels); + if (result > 0) { + OS.memmove(bidiLevels, pBidiLevels, 1); + } + OS.free(pBidiLevels); + return bidiLevels[0]; + } finally { + if (pool != null) pool.release(); + } +} + +/** + * Returns the line offsets. Each value in the array is the + * offset for the first character in a line except for the last + * value, which contains the length of the text. + * + * @return the line offsets + * + * @exception SWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public int[] getLineOffsets() { + checkLayout (); + NSAutoreleasePool pool = null; + if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init(); + try { + computeRuns(); + int[] offsets = new int[lineOffsets.length]; + for (int i = 0; i < offsets.length; i++) { + offsets[i] = untranslateOffset(lineOffsets[i]); + } + return offsets; + } finally { + if (pool != null) pool.release(); + } +} + +/** + * Returns the index of the line that contains the specified + * character offset. + * + * @param offset the character offset + * @return the line index + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if the character offset is out of range</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public int getLineIndex(int offset) { + checkLayout (); + NSAutoreleasePool pool = null; + if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init(); + try { + computeRuns(); + int length = text.length(); + if (!(0 <= offset && offset <= length)) SWT.error(SWT.ERROR_INVALID_RANGE); + offset = translateOffset(offset); + for (int line=0; line<lineOffsets.length - 1; line++) { + if (lineOffsets[line + 1] > offset) { + return line; + } + } + return lineBounds.length - 1; + } finally { + if (pool != null) pool.release(); + } +} + +/** + * Returns the bounds of the line for the specified line index. + * + * @param lineIndex the line index + * @return the line bounds + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if the line index is out of range</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public Rectangle getLineBounds(int lineIndex) { + checkLayout(); + NSAutoreleasePool pool = null; + if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init(); + try { + computeRuns(); + if (!(0 <= lineIndex && lineIndex < lineBounds.length)) SWT.error(SWT.ERROR_INVALID_RANGE); + NSRect rect = lineBounds[lineIndex]; + int height = Math.max((int)Math.ceil(rect.height), ascent + descent); + return new Rectangle((int)rect.x, (int)rect.y, (int)Math.ceil(rect.width), height); + } finally { + if (pool != null) pool.release(); + } +} + +/** + * Returns the receiver's line count. This includes lines caused + * by wrapping. + * + * @return the line count + * + * @exception SWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public int getLineCount() { + checkLayout (); + NSAutoreleasePool pool = null; + if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init(); + try { + computeRuns(); + return lineOffsets.length - 1; + } finally { + if (pool != null) pool.release(); + } +} + +/** + * Returns the font metrics for the specified line index. + * + * @param lineIndex the line index + * @return the font metrics + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if the line index is out of range</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public FontMetrics getLineMetrics (int lineIndex) { + checkLayout (); + NSAutoreleasePool pool = null; + if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init(); + try { + computeRuns(); + int lineCount = getLineCount(); + if (!(0 <= lineIndex && lineIndex < lineCount)) SWT.error(SWT.ERROR_INVALID_RANGE); + int length = text.length(); + if (length == 0) { + Font font = this.font != null ? this.font : device.systemFont; + NSFont nsFont = font.handle; + int ascent = (int)(0.5f + nsFont.ascender()); + int descent = (int)(0.5f + (-nsFont.descender() + nsFont.leading())); + ascent = Math.max(ascent, this.ascent); + descent = Math.max(descent, this.descent); + return FontMetrics.cocoa_new(ascent, descent, 0, 0, ascent + descent); + } + Rectangle rect = getLineBounds(lineIndex); + int baseline = (int)layoutManager.typesetter().baselineOffsetInLayoutManager(layoutManager, getLineOffsets()[lineIndex]); + return FontMetrics.cocoa_new(rect.height - baseline, baseline, 0, 0, rect.height); + } finally { + if (pool != null) pool.release(); + } +} + +/** + * Returns the location for the specified character offset. The + * <code>trailing</code> argument indicates whether the offset + * corresponds to the leading or trailing edge of the cluster. + * + * @param offset the character offset + * @param trailing the trailing flag + * @return the location of the character offset + * + * @exception SWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + * + * @see #getOffset(Point, int[]) + * @see #getOffset(int, int, int[]) + */ +public Point getLocation(int offset, boolean trailing) { + checkLayout(); + NSAutoreleasePool pool = null; + if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init(); + try { + computeRuns(); + int length = text.length(); + if (!(0 <= offset && offset <= length)) SWT.error(SWT.ERROR_INVALID_RANGE); + if (length == 0) return new Point(0, 0); + offset = translateOffset(offset); + int /*long*/ glyphIndex = layoutManager.glyphIndexForCharacterAtIndex(offset); + NSRect rect = layoutManager.lineFragmentUsedRectForGlyphAtIndex(glyphIndex, 0); + NSPoint point = layoutManager.locationForGlyphAtIndex(glyphIndex); + if (trailing) { + NSRange range = new NSRange(); + range.location = offset; + range.length = 1; + int /*long*/ pRectCount = OS.malloc(C.PTR_SIZEOF); + int /*long*/ pArray = layoutManager.rectArrayForCharacterRange(range, range, textContainer, pRectCount); + int /*long*/ [] rectCount = new int /*long*/ [1]; + OS.memmove(rectCount, pRectCount, C.PTR_SIZEOF); + OS.free(pRectCount); + if (rectCount[0] > 0) { + NSRect bounds = new NSRect(); + OS.memmove(bounds, pArray, NSRect.sizeof); + fixRect(bounds); + point.x += bounds.width; + } + } + return new Point((int)point.x, (int)rect.y); + } finally { + if (pool != null) pool.release(); + } +} + +/** + * Returns the next offset for the specified offset and movement + * type. The movement is one of <code>SWT.MOVEMENT_CHAR</code>, + * <code>SWT.MOVEMENT_CLUSTER</code>, <code>SWT.MOVEMENT_WORD</code>, + * <code>SWT.MOVEMENT_WORD_END</code> or <code>SWT.MOVEMENT_WORD_START</code>. + * + * @param offset the start offset + * @param movement the movement type + * @return the next offset + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if the offset is out of range</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + * + * @see #getPreviousOffset(int, int) + */ +public int getNextOffset (int offset, int movement) { + NSAutoreleasePool pool = null; + if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init(); + try { + return _getOffset(offset, movement, true); + } finally { + if (pool != null) pool.release(); + } +} + +int _getOffset (int offset, int movement, boolean forward) { + checkLayout(); + computeRuns(); + int length = text.length(); + if (!(0 <= offset && offset <= length)) SWT.error(SWT.ERROR_INVALID_RANGE); + if (length == 0) return 0; + offset = translateOffset(offset); + length = translateOffset(length); + switch (movement) { + case SWT.MOVEMENT_CLUSTER://TODO cluster + case SWT.MOVEMENT_CHAR: { + boolean invalid = false; + do { + int newOffset = offset; + if (forward) { + if (newOffset < length) newOffset++; + } else { + if (newOffset > 0) newOffset--; + } + if (newOffset == offset) break; + offset = newOffset; + invalid = false; + if (invalidOffsets != null) { + for (int i = 0; i < invalidOffsets.length; i++) { + if (offset == invalidOffsets[i]) { + invalid = true; + break; + } + } + } + } while (invalid); + return untranslateOffset(offset); + } + case SWT.MOVEMENT_WORD: { + return untranslateOffset((int)/*64*/textStorage.nextWordFromIndex(offset, forward)); + } + case SWT.MOVEMENT_WORD_END: { + NSRange range = textStorage.doubleClickAtIndex(length == offset ? length - 1 : offset); + return untranslateOffset((int)/*64*/(range.location + range.length)); + } + case SWT.MOVEMENT_WORD_START: { + NSRange range = textStorage.doubleClickAtIndex(length == offset ? length - 1 : offset); + return untranslateOffset((int)/*64*/range.location); + } + } + return untranslateOffset(offset); +} + +/** + * Returns the character offset for the specified point. + * For a typical character, the trailing argument will be filled in to + * indicate whether the point is closer to the leading edge (0) or + * the trailing edge (1). When the point is over a cluster composed + * of multiple characters, the trailing argument will be filled with the + * position of the character in the cluster that is closest to + * the point. + * + * @param point the point + * @param trailing the trailing buffer + * @return the character offset + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if the trailing length is less than <code>1</code></li> + * <li>ERROR_NULL_ARGUMENT - if the point is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + * + * @see #getLocation(int, boolean) + */ +public int getOffset(Point point, int[] trailing) { + checkLayout(); + NSAutoreleasePool pool = null; + if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init(); + try { + computeRuns(); + if (point == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + return getOffset(point.x, point.y, trailing); + } finally { + if (pool != null) pool.release(); + } +} + +/** + * Returns the character offset for the specified point. + * For a typical character, the trailing argument will be filled in to + * indicate whether the point is closer to the leading edge (0) or + * the trailing edge (1). When the point is over a cluster composed + * of multiple characters, the trailing argument will be filled with the + * position of the character in the cluster that is closest to + * the point. + * + * @param x the x coordinate of the point + * @param y the y coordinate of the point + * @param trailing the trailing buffer + * @return the character offset + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if the trailing length is less than <code>1</code></li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + * + * @see #getLocation(int, boolean) + */ +public int getOffset(int x, int y, int[] trailing) { + checkLayout(); + NSAutoreleasePool pool = null; + if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init(); + try { + computeRuns(); + if (trailing != null && trailing.length < 1) SWT.error(SWT.ERROR_INVALID_ARGUMENT); + int length = text.length(); + if (length == 0) return 0; + NSPoint pt = new NSPoint(); + pt.x = x; + pt.y = y; + float /*double*/[] partialFration = new float /*double*/[1]; + int /*long*/ glyphIndex = layoutManager.glyphIndexForPoint(pt, textContainer, partialFration); + int /*long*/ offset = layoutManager.characterIndexForGlyphAtIndex(glyphIndex); + if (trailing != null) trailing[0] = Math.round((float)/*64*/partialFration[0]); + return Math.min(untranslateOffset((int)/*64*/offset), length - 1); + } finally { + if (pool != null) pool.release(); + } +} + +/** + * Returns the orientation of the receiver. + * + * @return the orientation style + * + * @exception SWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public int getOrientation() { + checkLayout(); + return orientation; +} + +/** + * Returns the previous offset for the specified offset and movement + * type. The movement is one of <code>SWT.MOVEMENT_CHAR</code>, + * <code>SWT.MOVEMENT_CLUSTER</code> or <code>SWT.MOVEMENT_WORD</code>, + * <code>SWT.MOVEMENT_WORD_END</code> or <code>SWT.MOVEMENT_WORD_START</code>. + * + * @param offset the start offset + * @param movement the movement type + * @return the previous offset + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if the offset is out of range</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + * + * @see #getNextOffset(int, int) + */ +public int getPreviousOffset (int index, int movement) { + NSAutoreleasePool pool = null; + if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init(); + try { + return _getOffset(index, movement, false); + } finally { + if (pool != null) pool.release(); + } +} + +/** + * Gets the ranges of text that are associated with a <code>TextStyle</code>. + * + * @return the ranges, an array of offsets representing the start and end of each + * text style. + * + * @exception SWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + * + * @see #getStyles() + * + * @since 3.2 + */ +public int[] getRanges () { + checkLayout(); + int[] result = new int[styles.length * 2]; + int count = 0; + for (int i=0; i<styles.length - 1; i++) { + if (styles[i].style != null) { + result[count++] = styles[i].start; + result[count++] = styles[i + 1].start - 1; + } + } + if (count != result.length) { + int[] newResult = new int[count]; + System.arraycopy(result, 0, newResult, 0, count); + result = newResult; + } + return result; +} + +/** + * Returns the text segments offsets of the receiver. + * + * @return the text segments offsets + * + * @exception SWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public int[] getSegments() { + checkLayout(); + return segments; +} + +String getSegmentsText() { + if (segments == null) return text; + int nSegments = segments.length; + if (nSegments <= 1) return text; + int length = text.length(); + if (length == 0) return text; + if (nSegments == 2) { + if (segments[0] == 0 && segments[1] == length) return text; + } + invalidOffsets = new int[nSegments]; + char[] oldChars = new char[length]; + text.getChars(0, length, oldChars, 0); + char[] newChars = new char[length + nSegments]; + int charCount = 0, segmentCount = 0; + char separator = getOrientation() == SWT.RIGHT_TO_LEFT ? RTL_MARK : LTR_MARK; + while (charCount < length) { + if (segmentCount < nSegments && charCount == segments[segmentCount]) { + invalidOffsets[segmentCount] = charCount + segmentCount; + newChars[charCount + segmentCount++] = separator; + } else { + newChars[charCount + segmentCount] = oldChars[charCount++]; + } + } + if (segmentCount < nSegments) { + invalidOffsets[segmentCount] = charCount + segmentCount; + segments[segmentCount] = charCount; + newChars[charCount + segmentCount++] = separator; + } + if (segmentCount != nSegments) { + int[] tmp = new int [segmentCount]; + System.arraycopy(invalidOffsets, 0, tmp, 0, segmentCount); + invalidOffsets = tmp; + } + return new String(newChars, 0, Math.min(charCount + segmentCount, newChars.length)); +} + +/** + * Returns the line spacing of the receiver. + * + * @return the line spacing + * + * @exception SWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public int getSpacing () { + checkLayout(); + return spacing; +} + +/** + * Gets the style of the receiver at the specified character offset. + * + * @param offset the text offset + * @return the style or <code>null</code> if not set + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if the character offset is out of range</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public TextStyle getStyle (int offset) { + checkLayout(); + int length = text.length(); + if (!(0 <= offset && offset < length)) SWT.error(SWT.ERROR_INVALID_RANGE); + for (int i=1; i<styles.length; i++) { + StyleItem item = styles[i]; + if (item.start > offset) { + return styles[i - 1].style; + } + } + return null; +} + +/** + * Gets all styles of the receiver. + * + * @return the styles + * + * @exception SWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + * + * @see #getRanges() + * + * @since 3.2 + */ +public TextStyle[] getStyles () { + checkLayout(); + TextStyle[] result = new TextStyle[styles.length]; + int count = 0; + for (int i=0; i<styles.length; i++) { + if (styles[i].style != null) { + result[count++] = styles[i].style; + } + } + if (count != result.length) { + TextStyle[] newResult = new TextStyle[count]; + System.arraycopy(result, 0, newResult, 0, count); + result = newResult; + } + return result; +} + +/** + * Returns the tab list of the receiver. + * + * @return the tab list + * + * @exception SWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public int[] getTabs() { + checkLayout(); + return tabs; +} + +/** + * Gets the receiver's text, which will be an empty + * string if it has never been set. + * + * @return the receiver's text + * + * @exception SWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public String getText () { + checkLayout (); + return text; +} + +/** + * Returns the width of the receiver. + * + * @return the width + * + * @exception SWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public int getWidth () { + checkLayout(); + return wrapWidth; +} + +/** + * Returns <code>true</code> if the text layout has been disposed, + * and <code>false</code> otherwise. + * <p> + * This method gets the dispose state for the text layout. + * When a text layout has been disposed, it is an error to + * invoke any other method using the text layout. + * </p> + * + * @return <code>true</code> when the text layout is disposed and <code>false</code> otherwise + */ +public boolean isDisposed () { + return device == null; +} + +/* + * Returns true if the underline style is supported natively + */ +boolean isUnderlineSupported (TextStyle style) { + if (style != null && style.underline) { + int uStyle = style.underlineStyle; + return uStyle == SWT.UNDERLINE_SINGLE || uStyle == SWT.UNDERLINE_DOUBLE || uStyle == SWT.UNDERLINE_LINK || uStyle == UNDERLINE_THICK; + } + return false; +} + +/** + * Sets the text alignment for the receiver. The alignment controls + * how a line of text is positioned horizontally. The argument should + * be one of <code>SWT.LEFT</code>, <code>SWT.RIGHT</code> or <code>SWT.CENTER</code>. + * <p> + * The default alignment is <code>SWT.LEFT</code>. Note that the receiver's + * width must be set in order to use <code>SWT.RIGHT</code> or <code>SWT.CENTER</code> + * alignment. + * </p> + * + * @param alignment the new alignment + * + * @exception SWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + * + * @see #setWidth(int) + */ +public void setAlignment (int alignment) { + checkLayout(); + int mask = SWT.LEFT | SWT.CENTER | SWT.RIGHT; + alignment &= mask; + if (alignment == 0) return; + if ((alignment & SWT.LEFT) != 0) alignment = SWT.LEFT; + if ((alignment & SWT.RIGHT) != 0) alignment = SWT.RIGHT; + if (this.alignment == alignment) return; + NSAutoreleasePool pool = null; + if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init(); + try { + freeRuns(); + this.alignment = alignment; + } finally { + if (pool != null) pool.release(); + } +} + +/** + * Sets the ascent of the receiver. The ascent is distance in pixels + * from the baseline to the top of the line and it is applied to all + * lines. The default value is <code>-1</code> which means that the + * ascent is calculated from the line fonts. + * + * @param ascent the new ascent + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if the ascent is less than <code>-1</code></li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + * + * @see #setDescent(int) + * @see #getLineMetrics(int) + */ +public void setAscent (int ascent) { + checkLayout (); + if (ascent < -1) SWT.error(SWT.ERROR_INVALID_ARGUMENT); + if (this.ascent == ascent) return; + NSAutoreleasePool pool = null; + if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init(); + try { + freeRuns(); + this.ascent = ascent; + } finally { + if (pool != null) pool.release(); + } +} + +/** + * Sets the descent of the receiver. The descent is distance in pixels + * from the baseline to the bottom of the line and it is applied to all + * lines. The default value is <code>-1</code> which means that the + * descent is calculated from the line fonts. + * + * @param descent the new descent + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if the descent is less than <code>-1</code></li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + * + * @see #setAscent(int) + * @see #getLineMetrics(int) + */ +public void setDescent (int descent) { + checkLayout (); + if (descent < -1) SWT.error(SWT.ERROR_INVALID_ARGUMENT); + if (this.descent == descent) return; + NSAutoreleasePool pool = null; + if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init(); + try { + freeRuns(); + this.descent = descent; + } finally { + if (pool != null) pool.release(); + } +} + +/** + * Sets the default font which will be used by the receiver + * to draw and measure text. If the + * argument is null, then a default font appropriate + * for the platform will be used instead. Note that a text + * style can override the default font. + * + * @param font the new font for the receiver, or null to indicate a default font + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if the font has been disposed</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public void setFont (Font font) { + checkLayout (); + if (font != null && font.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT); + Font oldFont = this.font; + if (oldFont == font) return; + this.font = font; + if (oldFont != null && oldFont.equals(font)) return; + NSAutoreleasePool pool = null; + if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init(); + try { + freeRuns(); + } finally { + if (pool != null) pool.release(); + } +} + +/** + * Sets the indent of the receiver. This indent it applied of the first line of + * each paragraph. + * + * @param indent new indent + * + * @exception SWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + * + * @since 3.2 + */ +public void setIndent (int indent) { + checkLayout (); + if (indent < 0) return; + if (this.indent == indent) return; + NSAutoreleasePool pool = null; + if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init(); + try { + freeRuns(); + this.indent = indent; + } finally { + if (pool != null) pool.release(); + } +} + +/** + * Sets the justification of the receiver. Note that the receiver's + * width must be set in order to use justification. + * + * @param justify new justify + * + * @exception SWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + * + * @since 3.2 + */ +public void setJustify (boolean justify) { + checkLayout (); + if (justify == this.justify) return; + NSAutoreleasePool pool = null; + if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init(); + try { + freeRuns(); + this.justify = justify; + } finally { + if (pool != null) pool.release(); + } +} + +/** + * Sets the orientation of the receiver, which must be one + * of <code>SWT.LEFT_TO_RIGHT</code> or <code>SWT.RIGHT_TO_LEFT</code>. + * + * @param orientation new orientation style + * + * @exception SWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public void setOrientation(int orientation) { + checkLayout(); + int mask = SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT; + orientation &= mask; + if (orientation == 0) return; + if ((orientation & SWT.LEFT_TO_RIGHT) != 0) orientation = SWT.LEFT_TO_RIGHT; + if (this.orientation == orientation) return; + this.orientation = orientation; + NSAutoreleasePool pool = null; + if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init(); + try { + freeRuns(); + } finally { + if (pool != null) pool.release(); + } +} + +/** + * Sets the offsets of the receiver's text segments. Text segments are used to + * override the default behaviour of the bidirectional algorithm. + * Bidirectional reordering can happen within a text segment but not + * between two adjacent segments. + * <p> + * Each text segment is determined by two consecutive offsets in the + * <code>segments</code> arrays. The first element of the array should + * always be zero and the last one should always be equals to length of + * the text. + * </p> + * + * @param segments the text segments offset + * + * @exception SWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public void setSegments(int[] segments) { + checkLayout(); + if (this.segments == null && segments == null) return; + if (this.segments != null && segments !=null) { + if (this.segments.length == segments.length) { + int i; + for (i = 0; i <segments.length; i++) { + if (this.segments[i] != segments[i]) break; + } + if (i == segments.length) return; + } + } + NSAutoreleasePool pool = null; + if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init(); + try { + freeRuns(); + this.segments = segments; + } finally { + if (pool != null) pool.release(); + } +} + +/** + * Sets the line spacing of the receiver. The line spacing + * is the space left between lines. + * + * @param spacing the new line spacing + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if the spacing is negative</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public void setSpacing (int spacing) { + checkLayout(); + if (spacing < 0) SWT.error(SWT.ERROR_INVALID_ARGUMENT); + if (this.spacing == spacing) return; + NSAutoreleasePool pool = null; + if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init(); + try { + freeRuns(); + this.spacing = spacing; + } finally { + if (pool != null) pool.release(); + } +} + +/** + * Sets the style of the receiver for the specified range. Styles previously + * set for that range will be overwritten. The start and end offsets are + * inclusive and will be clamped if out of range. + * + * @param style the style + * @param start the start offset + * @param end the end offset + * + * @exception SWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public void setStyle (TextStyle style, int start, int end) { + checkLayout(); + NSAutoreleasePool pool = null; + if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init(); + try { + int length = text.length(); + if (length == 0) return; + if (start > end) return; + start = Math.min(Math.max(0, start), length - 1); + end = Math.min(Math.max(0, end), length - 1); + int low = -1; + int high = styles.length; + while (high - low > 1) { + int index = (high + low) / 2; + if (styles[index + 1].start > start) { + high = index; + } else { + low = index; + } + } + if (0 <= high && high < styles.length) { + StyleItem item = styles[high]; + if (item.start == start && styles[high + 1].start - 1 == end) { + if (style == null) { + if (item.style == null) return; + } else { + if (style.equals(item.style)) return; + } + } + } + freeRuns(); + int modifyStart = high; + int modifyEnd = modifyStart; + while (modifyEnd < styles.length) { + if (styles[modifyEnd + 1].start > end) break; + modifyEnd++; + } + if (modifyStart == modifyEnd) { + int styleStart = styles[modifyStart].start; + int styleEnd = styles[modifyEnd + 1].start - 1; + if (styleStart == start && styleEnd == end) { + styles[modifyStart].style = style; + return; + } + if (styleStart != start && styleEnd != end) { + StyleItem[] newStyles = new StyleItem[styles.length + 2]; + System.arraycopy(styles, 0, newStyles, 0, modifyStart + 1); + StyleItem item = new StyleItem(); + item.start = start; + item.style = style; + newStyles[modifyStart + 1] = item; + item = new StyleItem(); + item.start = end + 1; + item.style = styles[modifyStart].style; + newStyles[modifyStart + 2] = item; + System.arraycopy(styles, modifyEnd + 1, newStyles, modifyEnd + 3, styles.length - modifyEnd - 1); + styles = newStyles; + return; + } + } + if (start == styles[modifyStart].start) modifyStart--; + if (end == styles[modifyEnd + 1].start - 1) modifyEnd++; + int newLength = styles.length + 1 - (modifyEnd - modifyStart - 1); + StyleItem[] newStyles = new StyleItem[newLength]; + System.arraycopy(styles, 0, newStyles, 0, modifyStart + 1); + StyleItem item = new StyleItem(); + item.start = start; + item.style = style; + newStyles[modifyStart + 1] = item; + styles[modifyEnd].start = end + 1; + System.arraycopy(styles, modifyEnd, newStyles, modifyStart + 2, styles.length - modifyEnd); + styles = newStyles; + } finally { + if (pool != null) pool.release(); + } +} + +/** + * Sets the receiver's tab list. Each value in the tab list specifies + * the space in pixels from the origin of the text layout to the respective + * tab stop. The last tab stop width is repeated continuously. + * + * @param tabs the new tab list + * + * @exception SWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public void setTabs(int[] tabs) { + checkLayout(); + if (this.tabs == null && tabs == null) return; + if (this.tabs != null && tabs !=null) { + if (this.tabs.length == tabs.length) { + int i; + for (i = 0; i < tabs.length; i++) { + if (this.tabs[i] != tabs[i]) break; + } + if (i == tabs.length) return; + } + } + NSAutoreleasePool pool = null; + if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init(); + try { + freeRuns(); + this.tabs = tabs; + } finally { + if (pool != null) pool.release(); + } +} + +/** + * Sets the receiver's text. + *<p> + * Note: Setting the text also clears all the styles. This method + * returns without doing anything if the new text is the same as + * the current text. + * </p> + * + * @param text the new text + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the text is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + */ +public void setText (String text) { + checkLayout (); + if (text == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + if (text.equals(this.text)) return; + NSAutoreleasePool pool = null; + if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init(); + try { + freeRuns(); + this.text = text; + styles = new StyleItem[2]; + styles[0] = new StyleItem(); + styles[1] = new StyleItem(); + styles[styles.length - 1].start = text.length(); + } finally { + if (pool != null) pool.release(); + } +} + +/** + * Sets the line width of the receiver, which determines how + * text should be wrapped and aligned. The default value is + * <code>-1</code> which means wrapping is disabled. + * + * @param width the new width + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if the width is <code>0</code> or less than <code>-1</code></li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_GRAPHIC_DISPOSED - if the receiver has been disposed</li> + * </ul> + * + * @see #setAlignment(int) + */ +public void setWidth (int width) { + checkLayout(); + if (width < -1 || width == 0) SWT.error(SWT.ERROR_INVALID_ARGUMENT); + if (this.wrapWidth == width) return; + NSAutoreleasePool pool = null; + if (!NSThread.isMainThread()) pool = (NSAutoreleasePool) new NSAutoreleasePool().alloc().init(); + try { + freeRuns(); + this.wrapWidth = width; + } finally { + if (pool != null) pool.release(); + } +} + +/** + * Returns a string containing a concise, human-readable + * description of the receiver. + * + * @return a string representation of the receiver + */ +public String toString () { + if (isDisposed()) return "TextLayout {*DISPOSED*}"; + return "TextLayout {" + text + "}"; +} + +/* + * Translate a client offset to an internal offset + */ +int translateOffset (int offset) { + int length = text.length(); + if (length == 0) return offset; + if (invalidOffsets == null) return offset; + for (int i = 0; i < invalidOffsets.length; i++) { + if (offset < invalidOffsets[i]) break; + offset++; + } + return offset; +} + +/* + * Translate an internal offset to a client offset + */ +int untranslateOffset (int offset) { + int length = text.length(); + if (length == 0) return offset; + if (invalidOffsets == null) return offset; + for (int i = 0; i < invalidOffsets.length; i++) { + if (offset == invalidOffsets[i]) { + offset++; + continue; + } + if (offset < invalidOffsets[i]) { + return offset - i; + } + } + return offset - invalidOffsets.length; +} + +} |