diff options
author | Silenio Quarti <silenio> | 2009-07-01 14:50:54 +0000 |
---|---|---|
committer | Silenio Quarti <silenio> | 2009-07-01 14:50:54 +0000 |
commit | 093c579a4ffd9551acb901bba9617e7aa776989d (patch) | |
tree | 71cf23798b651ef92f188390841a8d130908fb11 /bundles/org.eclipse.swt/Eclipse SWT Custom Widgets | |
parent | f664d297f7bb009784868bf3fcf0b3e3bb9a646b (diff) | |
download | eclipse.platform.swt-093c579a4ffd9551acb901bba9617e7aa776989d.tar.gz eclipse.platform.swt-093c579a4ffd9551acb901bba9617e7aa776989d.tar.xz eclipse.platform.swt-093c579a4ffd9551acb901bba9617e7aa776989d.zip |
restore HEAD after accidental deletion by error in automated build script
Diffstat (limited to 'bundles/org.eclipse.swt/Eclipse SWT Custom Widgets')
61 files changed, 29526 insertions, 0 deletions
diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/AnimatedProgress.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/AnimatedProgress.java new file mode 100755 index 0000000000..41b34f7907 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/AnimatedProgress.java @@ -0,0 +1,221 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 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.custom; + +import org.eclipse.swt.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.widgets.*; +import org.eclipse.swt.events.*; + +/** + * A control for showing progress feedback for a long running operation. + * + * @deprecated As of Eclipse 2.1, use ProgressBar with the style SWT.INDETERMINATE + * + * <dl> + * <dt><b>Styles:</b><dd>VERTICAL, HORIZONTAL, BORDER + * </dl> + * + * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> + */ +public class AnimatedProgress extends Canvas { + + static final int SLEEP = 70; + static final int DEFAULT_WIDTH = 160; + static final int DEFAULT_HEIGHT = 18; + boolean active = false; + boolean showStripes = false; + int value; + int orientation = SWT.HORIZONTAL; + boolean showBorder = false; + +/** + * Constructs a new instance of this class given its parent + * and a style value describing its behavior and appearance. + * <p> + * The style value is either one of the style constants defined in + * class <code>SWT</code> which is applicable to instances of this + * class, or must be built by <em>bitwise OR</em>'ing together + * (that is, using the <code>int</code> "|" operator) two or more + * of those <code>SWT</code> style constants. The class description + * lists the style constants that are applicable to the class. + * Style bits are also inherited from superclasses. + * </p> + * + * @param parent a widget which will be the parent of the new instance (cannot be null) + * @param style the style of widget to construct + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the parent is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li> + * </ul> + * + * @see SWT#VERTICAL + * @see SWT#HORIZONTAL + * @see SWT#BORDER + * @see #getStyle() + */ +public AnimatedProgress(Composite parent, int style) { + super(parent, checkStyle(style)); + + if ((style & SWT.VERTICAL) != 0) { + orientation = SWT.VERTICAL; + } + showBorder = (style & SWT.BORDER) != 0; + + addControlListener(new ControlAdapter() { + public void controlResized(ControlEvent e) { + redraw(); + } + }); + addPaintListener(new PaintListener() { + public void paintControl(PaintEvent e) { + paint(e); + } + }); + addDisposeListener(new DisposeListener() { + public void widgetDisposed(DisposeEvent e){ + stop(); + } + }); +} +private static int checkStyle (int style) { + int mask = SWT.NONE; + return style & mask; +} +/** + * Stop the animation if it is not already stopped and + * reset the presentation to a blank appearance. + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public synchronized void clear(){ + checkWidget(); + if (active) stop(); + showStripes = false; + redraw(); +} +public Point computeSize(int wHint, int hHint, boolean changed) { + checkWidget(); + Point size = null; + if (orientation == SWT.HORIZONTAL) { + size = new Point(DEFAULT_WIDTH, DEFAULT_HEIGHT); + } else { + size = new Point(DEFAULT_HEIGHT, DEFAULT_WIDTH); + } + if (wHint != SWT.DEFAULT) size.x = wHint; + if (hHint != SWT.DEFAULT) size.y = hHint; + + return size; +} +private void drawBevelRect(GC gc, int x, int y, int w, int h, Color topleft, Color bottomright) { + gc.setForeground(topleft); + gc.drawLine(x, y, x+w-1, y); + gc.drawLine(x, y, x, y+h-1); + + gc.setForeground(bottomright); + gc.drawLine(x+w, y, x+w, y+h); + gc.drawLine(x, y+h, x+w, y+h); +} +void paint(PaintEvent event) { + GC gc = event.gc; + Display disp= getDisplay(); + + Rectangle rect= getClientArea(); + gc.fillRectangle(rect); + if (showBorder) { + drawBevelRect(gc, rect.x, rect.y, rect.width-1, rect.height-1, + disp.getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW), + disp.getSystemColor(SWT.COLOR_WIDGET_HIGHLIGHT_SHADOW)); + } + + paintStripes(gc); +} +void paintStripes(GC gc) { + + if (!showStripes) return; + + Rectangle rect= getClientArea(); + // Subtracted border painted by paint. + rect = new Rectangle(rect.x+2, rect.y+2, rect.width-4, rect.height-4); + + gc.setLineWidth(2); + gc.setClipping(rect); + Color color = getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION); + gc.setBackground(color); + gc.fillRectangle(rect); + gc.setForeground(this.getBackground()); + int step = 12; + int foregroundValue = value == 0 ? step - 2 : value - 2; + if (orientation == SWT.HORIZONTAL) { + int y = rect.y - 1; + int w = rect.width; + int h = rect.height + 2; + for (int i= 0; i < w; i+= step) { + int x = i + foregroundValue; + gc.drawLine(x, y, x, h); + } + } else { + int x = rect.x - 1; + int w = rect.width + 2; + int h = rect.height; + + for (int i= 0; i < h; i+= step) { + int y = i + foregroundValue; + gc.drawLine(x, y, w, y); + } + } + + if (active) { + value = (value + 2) % step; + } +} +/** +* Start the animation. +* +* @exception SWTException <ul> +* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> +* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> +* </ul> +*/ +public synchronized void start() { + checkWidget(); + if (active) return; + + active = true; + showStripes = true; + + final Display display = getDisplay(); + final Runnable [] timer = new Runnable [1]; + timer [0] = new Runnable () { + public void run () { + if (!active) return; + GC gc = new GC(AnimatedProgress.this); + paintStripes(gc); + gc.dispose(); + display.timerExec (SLEEP, timer [0]); + } + }; + display.timerExec (SLEEP, timer [0]); +} +/** +* Stop the animation. Freeze the presentation at its current appearance. +*/ +public synchronized void stop() { + //checkWidget(); + active = false; +} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/BidiSegmentEvent.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/BidiSegmentEvent.java new file mode 100644 index 0000000000..cafd31fcd6 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/BidiSegmentEvent.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 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.custom; + + +import org.eclipse.swt.events.*; + +/** + * This event is sent to BidiSegmentListeners when a line is to + * be measured or rendered in a bidi locale. The segments field is + * used to specify text ranges in the line that should be treated as + * separate segments for bidi reordering. Each segment will be reordered + * and rendered separately. + * <p> + * The elements in the segments field specify the start offset of + * a segment relative to the start of the line. They must follow + * the following rules: + * <ul> + * <li>first element must be 0 + * <li>elements must be in ascending order and must not have duplicates + * <li>elements must not exceed the line length + * </ul> + * In addition, the last element may be set to the end of the line + * but this is not required. + * + * The segments field may be left null if the entire line should + * be reordered as is. + * </p> + * A BidiSegmentListener may be used when adjacent segments of + * right-to-left text should not be reordered relative to each other. + * For example, within a Java editor, you may wish multiple + * right-to-left string literals to be reordered differently than the + * bidi algorithm specifies. + * + * Example: + * <pre> + * stored line = "R1R2R3" + "R4R5R6" + * R1 to R6 are right-to-left characters. The quotation marks + * are part of the line text. The line is 13 characters long. + * + * segments = null: + * entire line will be reordered and thus the two R2L segments + * swapped (as per the bidi algorithm). + * visual line (rendered on screen) = "R6R5R4" + "R3R2R1" + * + * segments = [0, 5, 8] + * "R1R2R3" will be reordered, followed by [blank]+[blank] and + * "R4R5R6". + * visual line = "R3R2R1" + "R6R5R4" + * </pre> + * + * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> + */ +public class BidiSegmentEvent extends TypedEvent { + + /** + * line start offset + */ + public int lineOffset; + + /** + * line text + */ + public String lineText; + + /** + * bidi segments, see above + */ + public int[] segments; + + static final long serialVersionUID = 3257846571587547957L; + +BidiSegmentEvent(StyledTextEvent e) { + super(e); + lineOffset = e.detail; + lineText = e.text; +} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/BidiSegmentListener.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/BidiSegmentListener.java new file mode 100644 index 0000000000..e1d9770623 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/BidiSegmentListener.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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.custom; + +import org.eclipse.swt.internal.SWTEventListener; + +/** + * This listener interface may be implemented in order to receive + * BidiSegmentEvents. + * @see BidiSegmentEvent + */ +public interface BidiSegmentListener extends SWTEventListener { + +/** + * This method is called when a line needs to be reordered for + * measuring or rendering in a bidi locale. + * <p> + * The following event fields are used:<ul> + * <li>event.lineOffset line start offset (input)</li> + * <li>event.lineText line text (input)</li> + * <li>event.segments text segments that should be reordered + * separately. (output)</li> + * </ul> + * + * @param event the given event + * separately. (output) + * @see BidiSegmentEvent + */ +public void lineGetSegments(BidiSegmentEvent event); + +} + diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/Bullet.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/Bullet.java new file mode 100644 index 0000000000..69c4694d3f --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/Bullet.java @@ -0,0 +1,156 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 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.custom; + +import org.eclipse.swt.*; + +/** + * Instances of this class represent bullets in the <code>StyledText</code>. + * <p> + * The hashCode() method in this class uses the values of the public + * fields to compute the hash value. When storing instances of the + * class in hashed collections, do not modify these fields after the + * object has been inserted. + * </p> + * <p> + * Application code does <em>not</em> need to explicitly release the + * resources managed by each instance when those instances are no longer + * required, and thus no <code>dispose()</code> method is provided. + * </p> + * + * @see StyledText#setLineBullet(int, int, Bullet) + * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> + * + * @since 3.2 + */ +public class Bullet { + /** + * The bullet type. Possible values are: + * <ul> + * <li><code>ST.BULLET_DOT</code></li> + * <li><code>ST.BULLET_NUMBER</code></li> + * <li><code>ST.BULLET_LETTER_LOWER</code></li> + * <li><code>ST.BULLET_LETTER_UPPER</code></li> + * <li><code>ST.BULLET_TEXT</code></li> + * <li><code>ST.BULLET_CUSTOM</code></li> + * </ul> + */ + public int type; + + /** + * The bullet style. + */ + public StyleRange style; + + /** + * The bullet text. + */ + public String text; + + int[] linesIndices; + int count; + +/** + * Create a new bullet with the specified style, and type <code>ST.BULLET_DOT</code>. + * The style must have a glyph metrics set. + * + * @param style the style + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT when the style or the glyph metrics are null</li> + * </ul> + */ +public Bullet(StyleRange style) { + this(ST.BULLET_DOT, style); +} +/** + * Create a new bullet the specified style and type. + * The style must have a glyph metrics set. + * + * @param type the bullet type + * @param style the style + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT when the style or the glyph metrics are null</li> + * </ul> + */ +public Bullet(int type, StyleRange style) { + if (style == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + if (style.metrics == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + this.type = type; + this.style = style; +} +void addIndices (int startLine, int lineCount) { + if (linesIndices == null) { + linesIndices = new int[lineCount]; + count = lineCount; + for (int i = 0; i < lineCount; i++) linesIndices[i] = startLine + i; + } else { + int modifyStart = 0; + while (modifyStart < count) { + if (startLine <= linesIndices[modifyStart]) break; + modifyStart++; + } + int modifyEnd = modifyStart; + while (modifyEnd < count) { + if (startLine + lineCount <= linesIndices[modifyEnd]) break; + modifyEnd++; + } + int newSize = modifyStart + lineCount + count - modifyEnd; + if (newSize > linesIndices.length) { + int[] newLinesIndices = new int[newSize]; + System.arraycopy(linesIndices, 0, newLinesIndices, 0, count); + linesIndices = newLinesIndices; + } + System.arraycopy(linesIndices, modifyEnd, linesIndices, modifyStart + lineCount, count - modifyEnd); + for (int i = 0; i < lineCount; i++) linesIndices[modifyStart + i] = startLine + i; + count = newSize; + } +} +int indexOf (int lineIndex) { + for (int i = 0; i < count; i++) { + if (linesIndices[i] == lineIndex) return i; + } + return -1; +} +public int hashCode() { + return style.hashCode() ^ type; +} +int[] removeIndices (int startLine, int replaceLineCount, int newLineCount, boolean update) { + if (count == 0) return null; + if (startLine > linesIndices[count - 1]) return null; + int endLine = startLine + replaceLineCount; + int delta = newLineCount - replaceLineCount; + for (int i = 0; i < count; i++) { + int index = linesIndices[i]; + if (startLine <= index) { + int j = i; + while (j < count) { + if (linesIndices[j] >= endLine) break; + j++; + } + if (update) { + for (int k = j; k < count; k++) linesIndices[k] += delta; + } + int[] redrawLines = new int[count - j]; + System.arraycopy(linesIndices, j, redrawLines, 0, count - j); + System.arraycopy(linesIndices, j, linesIndices, i, count - j); + count -= (j - i); + return redrawLines; + } + } + for (int i = 0; i < count; i++) linesIndices[i] += delta; + return null; +} +int size() { + return count; +} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/BusyIndicator.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/BusyIndicator.java new file mode 100755 index 0000000000..7f2f39deeb --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/BusyIndicator.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 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.custom; + + +import org.eclipse.swt.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.widgets.*; + +/** + * Support for showing a Busy Cursor during a long running process. + * + * @see <a href="http://www.eclipse.org/swt/snippets/#busyindicator">BusyIndicator snippets</a> + * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> + */ +public class BusyIndicator { + + static int nextBusyId = 1; + static final String BUSYID_NAME = "SWT BusyIndicator"; //$NON-NLS-1$ + static final String BUSY_CURSOR = "SWT BusyIndicator Cursor"; //$NON-NLS-1$ + +/** + * Runs the given <code>Runnable</code> while providing + * busy feedback using this busy indicator. + * + * @param display the display on which the busy feedback should be + * displayed. If the display is null, the Display for the current + * thread will be used. If there is no Display for the current thread, + * the runnable code will be executed and no busy feedback will be displayed. + * @param runnable the runnable for which busy feedback is to be shown. + * Must not be null. + * +* @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the runnable is null</li> + * </ul> + */ + +public static void showWhile(Display display, Runnable runnable) { + if (runnable == null) + SWT.error(SWT.ERROR_NULL_ARGUMENT); + if (display == null) { + display = Display.getCurrent(); + if (display == null) { + runnable.run(); + return; + } + } + + Integer busyId = new Integer(nextBusyId); + nextBusyId++; + Cursor cursor = display.getSystemCursor(SWT.CURSOR_WAIT); + Shell[] shells = display.getShells(); + for (int i = 0; i < shells.length; i++) { + Integer id = (Integer)shells[i].getData(BUSYID_NAME); + if (id == null) { + shells[i].setCursor(cursor); + shells[i].setData(BUSYID_NAME, busyId); + } + } + + try { + runnable.run(); + } finally { + shells = display.getShells(); + for (int i = 0; i < shells.length; i++) { + Integer id = (Integer)shells[i].getData(BUSYID_NAME); + if (id == busyId) { + shells[i].setCursor(null); + shells[i].setData(BUSYID_NAME, null); + } + } + } +} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CBanner.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CBanner.java new file mode 100644 index 0000000000..0a20c6b2ee --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CBanner.java @@ -0,0 +1,558 @@ +/******************************************************************************* + * 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.custom; + + +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.widgets.*; +import org.eclipse.swt.*; + +/** + * Instances of this class implement a Composite that lays out its + * children and allows programmatic control of the layout. It draws + * a separator between the left and right children which can be dragged + * to resize the right control. + * CBanner is used in the workbench to layout the toolbar area and + * perspective switching toolbar. + * <p> + * Note that although this class is a subclass of <code>Composite</code>, + * it does not make sense to set a layout on it. + * </p><p> + * <dl> + * <dt><b>Styles:</b></dt> + * <dd>NONE</dd> + * <dt><b>Events:</b></dt> + * <dd>(None)</dd> + * </dl> + * <p> + * IMPORTANT: This class is <em>not</em> intended to be subclassed. + * </p> + * + * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> + * + * @since 3.0 + * @noextend This class is not intended to be subclassed by clients. + */ + +public class CBanner extends Composite { + + Control left; + Control right; + Control bottom; + + boolean simple = true; + + int[] curve = new int[0]; + int curveStart = 0; + Rectangle curveRect = new Rectangle(0, 0, 0, 0); + int curve_width = 5; + int curve_indent = -2; + + int rightWidth = SWT.DEFAULT; + int rightMinWidth = 0; + int rightMinHeight = 0; + Cursor resizeCursor; + boolean dragging = false; + int rightDragDisplacement = 0; + Listener listener; + + static final int OFFSCREEN = -200; + static final int BORDER_BOTTOM = 2; + static final int BORDER_TOP = 3; + static final int BORDER_STRIPE = 1; + static final int CURVE_TAIL = 200; + static final int BEZIER_RIGHT = 30; + static final int BEZIER_LEFT = 30; + static final int MIN_LEFT = 10; + static int BORDER1 = SWT.COLOR_WIDGET_HIGHLIGHT_SHADOW; + + +/** + * Constructs a new instance of this class given its parent + * and a style value describing its behavior and appearance. + * <p> + * The style value is either one of the style constants defined in + * class <code>SWT</code> which is applicable to instances of this + * class, or must be built by <em>bitwise OR</em>'ing together + * (that is, using the <code>int</code> "|" operator) two or more + * of those <code>SWT</code> style constants. The class description + * lists the style constants that are applicable to the class. + * Style bits are also inherited from superclasses. + * </p> + * + * @param parent a widget which will be the parent of the new instance (cannot be null) + * @param style the style of widget to construct + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the parent is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li> + * </ul> + * + */ +public CBanner(Composite parent, int style) { + super(parent, checkStyle(style)); + super.setLayout(new CBannerLayout()); + resizeCursor = new Cursor(getDisplay(), SWT.CURSOR_SIZEWE); + + listener = new Listener() { + public void handleEvent(Event e) { + switch (e.type) { + case SWT.Dispose: + onDispose(e); break; + case SWT.MouseDown: + onMouseDown (e.x, e.y); break; + case SWT.MouseExit: + onMouseExit(); break; + case SWT.MouseMove: + onMouseMove(e.x, e.y); break; + case SWT.MouseUp: + onMouseUp(); break; + case SWT.Paint: + onPaint(e.gc); break; + case SWT.Resize: + onResize(); break; + } + } + }; + int[] events = new int[] {SWT.Dispose, SWT.MouseDown, SWT.MouseExit, SWT.MouseMove, SWT.MouseUp, SWT.Paint, SWT.Resize}; + for (int i = 0; i < events.length; i++) { + addListener(events[i], listener); + } +} +static int[] bezier(int x0, int y0, int x1, int y1, int x2, int y2, int x3, int y3, int count) { + // The parametric equations for a Bezier curve for x[t] and y[t] where 0 <= t <=1 are: + // x[t] = x0+3(x1-x0)t+3(x0+x2-2x1)t^2+(x3-x0+3x1-3x2)t^3 + // y[t] = y0+3(y1-y0)t+3(y0+y2-2y1)t^2+(y3-y0+3y1-3y2)t^3 + double a0 = x0; + double a1 = 3*(x1 - x0); + double a2 = 3*(x0 + x2 - 2*x1); + double a3 = x3 - x0 + 3*x1 - 3*x2; + double b0 = y0; + double b1 = 3*(y1 - y0); + double b2 = 3*(y0 + y2 - 2*y1); + double b3 = y3 - y0 + 3*y1 - 3*y2; + + int[] polygon = new int[2*count + 2]; + for (int i = 0; i <= count; i++) { + double t = (double)i / (double)count; + polygon[2*i] = (int)(a0 + a1*t + a2*t*t + a3*t*t*t); + polygon[2*i + 1] = (int)(b0 + b1*t + b2*t*t + b3*t*t*t); + } + return polygon; +} +static int checkStyle (int style) { + return SWT.NONE; +} +/* +* This class was not intended to be subclassed but this restriction +* cannot be enforced without breaking backward compatibility. +*/ +//protected void checkSubclass () { +// String name = getClass ().getName (); +// int index = name.lastIndexOf ('.'); +// if (!name.substring (0, index + 1).equals ("org.eclipse.swt.custom.")) { +// SWT.error (SWT.ERROR_INVALID_SUBCLASS); +// } +//} +/** +* Returns the Control that appears on the bottom side of the banner. +* +* @return the control that appears on the bottom side of the banner or null +* +* @exception SWTException <ul> +* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> +* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> +* </ul> +* +* @since 3.0 +*/ +public Control getBottom() { + checkWidget(); + return bottom; +} +public Rectangle getClientArea() { + return new Rectangle(0, 0, 0, 0); +} + +/** +* Returns the Control that appears on the left side of the banner. +* +* @return the control that appears on the left side of the banner or null +* +* @exception SWTException <ul> +* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> +* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> +* </ul> +* +* @since 3.0 +*/ +public Control getLeft() { + checkWidget(); + return left; +} + +/** +* Returns the Control that appears on the right side of the banner. +* +* @return the control that appears on the right side of the banner or null +* +* @exception SWTException <ul> +* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> +* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> +* </ul> +* +* @since 3.0 +*/ +public Control getRight() { + checkWidget(); + return right; +} +/** + * Returns the minimum size of the control that appears on the right of the banner. + * + * @return the minimum size of the control that appears on the right of the banner + * + * @since 3.1 + */ +public Point getRightMinimumSize() { + checkWidget(); + return new Point(rightMinWidth, rightMinHeight); +} +/** + * Returns the width of the control that appears on the right of the banner. + * + * @return the width of the control that appears on the right of the banner + * + * @since 3.0 + */ +public int getRightWidth() { + checkWidget(); + if (right == null) return 0; + if (rightWidth == SWT.DEFAULT) { + Point size = right.computeSize(SWT.DEFAULT, SWT.DEFAULT, false); + return size.x; + } + return rightWidth; +} +/** + * Returns <code>true</code> if the CBanner is rendered + * with a simple, traditional shape. + * + * @return <code>true</code> if the CBanner is rendered with a simple shape + * + * @since 3.0 + */ +public boolean getSimple() { + checkWidget(); + return simple; +} +void onDispose(Event event) { + removeListener(SWT.Dispose, listener); + notifyListeners(SWT.Dispose, event); + event.type = SWT.None; + + if (resizeCursor != null) resizeCursor.dispose(); + resizeCursor = null; + left = null; + right = null; + bottom = null; +} +void onMouseDown (int x, int y) { + if (curveRect.contains(x, y)) { + dragging = true; + rightDragDisplacement = curveStart - x + curve_width - curve_indent; + } +} +void onMouseExit() { + if (!dragging) setCursor(null); +} +void onMouseMove(int x, int y) { + if (dragging) { + Point size = getSize(); + if (!(0 < x && x < size.x)) return; + rightWidth = Math.max(0, size.x - x - rightDragDisplacement); + if (rightMinWidth == SWT.DEFAULT) { + Point minSize = right.computeSize(rightMinWidth, rightMinHeight); + rightWidth = Math.max(minSize.x, rightWidth); + } else { + rightWidth = Math.max(rightMinWidth, rightWidth); + } + layout(false); + return; + } + if (curveRect.contains(x, y)) { + setCursor(resizeCursor); + } else { + setCursor(null); + } +} +void onMouseUp () { + dragging = false; +} +void onPaint(GC gc) { +// Useful for debugging paint problems +// { +// Point size = getSize(); +// gc.setBackground(getDisplay().getSystemColor(SWT.COLOR_GREEN)); +// gc.fillRectangle(-10, -10, size.x+20, size.y+20); +// } + if (left == null && right == null) return; + Point size = getSize(); + Color border1 = getDisplay().getSystemColor(BORDER1); + if (bottom != null) { + int y = bottom.getBounds().y - BORDER_STRIPE - 1; + gc.setForeground(border1); + gc.drawLine(0, y, size.x, y); + } + if (left == null || right == null) return; + int[] line1 = new int[curve.length+6]; + int index = 0; + int x = curveStart; + line1[index++] = x + 1; + line1[index++] = size.y - BORDER_STRIPE; + for (int i = 0; i < curve.length/2; i++) { + line1[index++]=x+curve[2*i]; + line1[index++]=curve[2*i+1]; + } + line1[index++] = x + curve_width; + line1[index++] = 0; + line1[index++] = size.x; + line1[index++] = 0; + + Color background = getBackground(); + + if (getDisplay().getDepth() >= 15) { + // Anti- aliasing + int[] line2 = new int[line1.length]; + index = 0; + for (int i = 0; i < line1.length/2; i++) { + line2[index] = line1[index++] - 1; + line2[index] = line1[index++]; + } + int[] line3 = new int[line1.length]; + index = 0; + for (int i = 0; i < line1.length/2; i++) { + line3[index] = line1[index++] + 1; + line3[index] = line1[index++]; + } + RGB from = border1.getRGB(); + RGB to = background.getRGB(); + int red = from.red + 3*(to.red - from.red)/4; + int green = from.green + 3*(to.green - from.green)/4; + int blue = from.blue + 3*(to.blue - from.blue)/4; + Color color = new Color(getDisplay(), red, green, blue); + gc.setForeground(color); + gc.drawPolyline(line2); + gc.drawPolyline(line3); + color.dispose(); + + // draw tail fading to background + int x1 = Math.max(0, curveStart - CURVE_TAIL); + gc.setForeground(background); + gc.setBackground(border1); + gc.fillGradientRectangle(x1, size.y - BORDER_STRIPE, curveStart-x1+1, 1, false); + } else { + // draw solid tail + int x1 = Math.max(0, curveStart - CURVE_TAIL); + gc.setForeground(border1); + gc.drawLine(x1, size.y - BORDER_STRIPE, curveStart+1, size.y - BORDER_STRIPE); + } + + // draw border + gc.setForeground(border1); + gc.drawPolyline(line1); +} + +void onResize() { + updateCurve(getSize().y); +} +/** +* Set the control that appears on the bottom side of the banner. +* The bottom control is optional. Setting the bottom control to null will remove it from +* the banner - however, the creator of the control must dispose of the control. +* +* @param control the control to be displayed on the bottom or null +* +* @exception SWTException <ul> +* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> +* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> +* <li>ERROR_INVALID_ARGUMENT - if the bottom control was not created as a child of the receiver</li> +* </ul> +* +* @since 3.0 +*/ +public void setBottom(Control control) { + checkWidget(); + if (control != null && control.getParent() != this) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + if (bottom != null && !bottom.isDisposed()) { + Point size = bottom.getSize(); + bottom.setLocation(OFFSCREEN - size.x, OFFSCREEN - size.y); + } + bottom = control; + layout(false); +} +/** + * Sets the layout which is associated with the receiver to be + * the argument which may be null. + * <p> + * Note: No Layout can be set on this Control because it already + * manages the size and position of its children. + * </p> + * + * @param layout the receiver's new layout or null + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void setLayout (Layout layout) { + checkWidget(); + return; +} + +/** +* Set the control that appears on the left side of the banner. +* The left control is optional. Setting the left control to null will remove it from +* the banner - however, the creator of the control must dispose of the control. +* +* @param control the control to be displayed on the left or null +* +* @exception SWTException <ul> +* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> +* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> +* <li>ERROR_INVALID_ARGUMENT - if the left control was not created as a child of the receiver</li> +* </ul> +* +* @since 3.0 +*/ +public void setLeft(Control control) { + checkWidget(); + if (control != null && control.getParent() != this) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + if (left != null && !left.isDisposed()) { + Point size = left.getSize(); + left.setLocation(OFFSCREEN - size.x, OFFSCREEN - size.y); + } + left = control; + layout(false); +} +/** +* Set the control that appears on the right side of the banner. +* The right control is optional. Setting the right control to null will remove it from +* the banner - however, the creator of the control must dispose of the control. +* +* @param control the control to be displayed on the right or null +* +* @exception SWTException <ul> +* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> +* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> +* <li>ERROR_INVALID_ARGUMENT - if the right control was not created as a child of the receiver</li> +* </ul> +* +* @since 3.0 +*/ +public void setRight(Control control) { + checkWidget(); + if (control != null && control.getParent() != this) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + if (right != null && !right.isDisposed()) { + Point size = right.getSize(); + right.setLocation(OFFSCREEN - size.x, OFFSCREEN - size.y); + } + right = control; + layout(false); +} +/** + * Set the minimum height of the control that appears on the right side of the banner. + * + * @param size the minimum size of the control on the right + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * <li>ERROR_INVALID_ARGUMENT - if the size is null or the values of size are less than SWT.DEFAULT</li> + * </ul> + * + * @since 3.1 + */ +public void setRightMinimumSize(Point size) { + checkWidget(); + if (size == null || size.x < SWT.DEFAULT || size.y < SWT.DEFAULT) SWT.error(SWT.ERROR_INVALID_ARGUMENT); + rightMinWidth = size.x; + rightMinHeight = size.y; + layout(false); +} +/** + * Set the width of the control that appears on the right side of the banner. + * + * @param width the width of the control on the right + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * <li>ERROR_INVALID_ARGUMENT - if width is less than SWT.DEFAULT</li> + * </ul> + * + * @since 3.0 + */ +public void setRightWidth(int width) { + checkWidget(); + if (width < SWT.DEFAULT) SWT.error(SWT.ERROR_INVALID_ARGUMENT); + rightWidth = width; + layout(false); +} +/** + * Sets the shape that the CBanner will use to render itself. + * + * @param simple <code>true</code> if the CBanner should render itself in a simple, traditional style + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.0 + */ +public void setSimple(boolean simple) { + checkWidget(); + if (this.simple != simple) { + this.simple = simple; + if (simple) { + curve_width = 5; + curve_indent = -2; + } else { + curve_width = 50; + curve_indent = 5; + } + updateCurve(getSize().y); + layout(false); + redraw(); + } +} +void updateCurve(int height) { + int h = height - BORDER_STRIPE; + if (simple) { + curve = new int[] {0,h, 1,h, 2,h-1, 3,h-2, + 3,2, 4,1, 5,0,}; + } else { + curve = bezier(0, h+1, BEZIER_LEFT, h+1, + curve_width-BEZIER_RIGHT, 0, curve_width, 0, + curve_width); + } +} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CBannerLayout.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CBannerLayout.java new file mode 100644 index 0000000000..2b0b5f19af --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CBannerLayout.java @@ -0,0 +1,188 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.custom; + +import org.eclipse.swt.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.widgets.*; + +/** + * This class provides the layout for CBanner + * + * @see CBanner + */ +class CBannerLayout extends Layout { + +protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) { + CBanner banner = (CBanner)composite; + Control left = banner.left; + Control right = banner.right; + Control bottom = banner.bottom; + boolean showCurve = left != null && right != null; + int height = hHint; + int width = wHint; + + // Calculate component sizes + Point bottomSize = new Point(0, 0); + if (bottom != null) { + int trim = computeTrim(bottom); + int w = wHint == SWT.DEFAULT ? SWT.DEFAULT : Math.max(0, width - trim); + bottomSize = computeChildSize(bottom, w, SWT.DEFAULT, flushCache); + } + Point rightSize = new Point(0, 0); + if (right != null) { + int trim = computeTrim(right); + int w = SWT.DEFAULT; + if (banner.rightWidth != SWT.DEFAULT) { + w = banner.rightWidth - trim; + if (left != null) { + w = Math.min(w, width - banner.curve_width + 2* banner.curve_indent - CBanner.MIN_LEFT - trim); + } + w = Math.max(0, w); + } + rightSize = computeChildSize(right, w, SWT.DEFAULT, flushCache); + if (wHint != SWT.DEFAULT) { + width -= rightSize.x + banner.curve_width - 2* banner.curve_indent; + } + } + Point leftSize = new Point(0, 0); + if (left != null) { + int trim = computeTrim(left); + int w = wHint == SWT.DEFAULT ? SWT.DEFAULT : Math.max(0, width - trim); + leftSize = computeChildSize(left, w, SWT.DEFAULT, flushCache); + } + + // Add up sizes + width = leftSize.x + rightSize.x; + height = bottomSize.y; + if (bottom != null && (left != null || right != null)) { + height += CBanner.BORDER_STRIPE + 2; + } + if (left != null) { + if (right == null) { + height += leftSize.y; + } else { + height += Math.max(leftSize.y, banner.rightMinHeight == SWT.DEFAULT ? rightSize.y : banner.rightMinHeight); + } + } else { + height += rightSize.y; + } + if (showCurve) { + width += banner.curve_width - 2*banner.curve_indent; + height += CBanner.BORDER_TOP + CBanner.BORDER_BOTTOM + 2*CBanner.BORDER_STRIPE; + } + + if (wHint != SWT.DEFAULT) width = wHint; + if (hHint != SWT.DEFAULT) height = hHint; + + return new Point(width, height); +} +Point computeChildSize(Control control, int wHint, int hHint, boolean flushCache) { + Object data = control.getLayoutData(); + if (data == null || !(data instanceof CLayoutData)) { + data = new CLayoutData(); + control.setLayoutData(data); + } + return ((CLayoutData)data).computeSize(control, wHint, hHint, flushCache); +} +int computeTrim(Control c) { + if (c instanceof Scrollable) { + Rectangle rect = ((Scrollable) c).computeTrim (0, 0, 0, 0); + return rect.width; + } + return c.getBorderWidth () * 2; +} +protected boolean flushCache(Control control) { + Object data = control.getLayoutData(); + if (data != null && data instanceof CLayoutData) ((CLayoutData)data).flushCache(); + return true; +} +protected void layout(Composite composite, boolean flushCache) { + CBanner banner = (CBanner)composite; + Control left = banner.left; + Control right = banner.right; + Control bottom = banner.bottom; + + Point size = banner.getSize(); + boolean showCurve = left != null && right != null; + int width = size.x - 2*banner.getBorderWidth(); + int height = size.y - 2*banner.getBorderWidth(); + + Point bottomSize = new Point(0, 0); + if (bottom != null) { + int trim = computeTrim(bottom); + int w = Math.max(0, width - trim); + bottomSize = computeChildSize(bottom, w, SWT.DEFAULT, flushCache); + height -= bottomSize.y + CBanner.BORDER_STRIPE + 2; + } + if (showCurve) height -= CBanner.BORDER_TOP + CBanner.BORDER_BOTTOM + 2*CBanner.BORDER_STRIPE; + height = Math.max(0, height); + Point rightSize = new Point(0,0); + if (right != null) { + int trim = computeTrim(right); + int w = SWT.DEFAULT; + if (banner.rightWidth != SWT.DEFAULT) { + w = banner.rightWidth - trim; + if (left != null) { + w = Math.min(w, width - banner.curve_width + 2* banner.curve_indent - CBanner.MIN_LEFT - trim); + } + w = Math.max(0, w); + } + rightSize = computeChildSize(right, w, SWT.DEFAULT, flushCache); + width = width - (rightSize.x - banner.curve_indent + banner.curve_width - banner.curve_indent); + } + Point leftSize = new Point(0, 0); + if (left != null) { + int trim = computeTrim(left); + int w = Math.max(0, width - trim); + leftSize = computeChildSize(left, w, SWT.DEFAULT, flushCache); + } + + int x = 0; + int y = 0; + int oldStart = banner.curveStart; + Rectangle leftRect = null; + Rectangle rightRect = null; + Rectangle bottomRect = null; + if (bottom != null) { + bottomRect = new Rectangle(x, y+size.y-bottomSize.y, bottomSize.x, bottomSize.y); + } + if (showCurve) y += CBanner.BORDER_TOP + CBanner.BORDER_STRIPE; + if(left != null) { + leftRect = new Rectangle(x, y, leftSize.x, leftSize.y); + banner.curveStart = x + leftSize.x - banner.curve_indent; + x += leftSize.x - banner.curve_indent + banner.curve_width - banner.curve_indent; + } + if (right != null) { + if (left != null) { + rightSize.y = Math.max(leftSize.y, banner.rightMinHeight == SWT.DEFAULT ? rightSize.y : banner.rightMinHeight); + } + rightRect = new Rectangle(x, y, rightSize.x, rightSize.y); + } + if (banner.curveStart < oldStart) { + banner.redraw(banner.curveStart - CBanner.CURVE_TAIL, 0, oldStart + banner.curve_width - banner.curveStart + CBanner.CURVE_TAIL + 5, size.y, false); + } + if (banner.curveStart > oldStart) { + banner.redraw(oldStart - CBanner.CURVE_TAIL, 0, banner.curveStart + banner.curve_width - oldStart + CBanner.CURVE_TAIL + 5, size.y, false); + } + /* + * The paint events must be flushed in order to make the curve draw smoothly + * while the user drags the divider. + * On Windows, it is necessary to flush the paints before the children are + * resized because otherwise the children (particularly toolbars) will flash. + */ + banner.update(); + banner.curveRect = new Rectangle(banner.curveStart, 0, banner.curve_width, size.y); + if (bottomRect != null) bottom.setBounds(bottomRect); + if (rightRect != null) right.setBounds(rightRect); + if (leftRect != null) left.setBounds(leftRect); +} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CCombo.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CCombo.java new file mode 100755 index 0000000000..f07a52ab39 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CCombo.java @@ -0,0 +1,1793 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 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.custom; + + +import org.eclipse.swt.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.events.*; +import org.eclipse.swt.widgets.*; +import org.eclipse.swt.accessibility.*; + +/** + * The CCombo class represents a selectable user interface object + * that combines a text field and a list and issues notification + * when an item is selected from the list. + * <p> + * CCombo was written to work around certain limitations in the native + * combo box. Specifically, on win32, the height of a CCombo can be set; + * attempts to set the height of a Combo are ignored. CCombo can be used + * anywhere that having the increased flexibility is more important than + * getting native L&F, but the decision should not be taken lightly. + * There is no is no strict requirement that CCombo look or behave + * the same as the native combo box. + * </p> + * <p> + * Note that although this class is a subclass of <code>Composite</code>, + * it does not make sense to add children to it, or set a layout on it. + * </p> + * <dl> + * <dt><b>Styles:</b> + * <dd>BORDER, READ_ONLY, FLAT</dd> + * <dt><b>Events:</b> + * <dd>DefaultSelection, Modify, Selection, Verify</dd> + * </dl> + * + * @see <a href="http://www.eclipse.org/swt/snippets/#ccombo">CCombo snippets</a> + * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: CustomControlExample</a> + * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> + */ +public class CCombo extends Composite { + + Text text; + List list; + int visibleItemCount = 5; + Shell popup; + Button arrow; + boolean hasFocus; + Listener listener, filter; + Color foreground, background; + Font font; + Shell _shell; + + static final String PACKAGE_PREFIX = "org.eclipse.swt.custom."; //$NON-NLS-1$ + +/** + * Constructs a new instance of this class given its parent + * and a style value describing its behavior and appearance. + * <p> + * The style value is either one of the style constants defined in + * class <code>SWT</code> which is applicable to instances of this + * class, or must be built by <em>bitwise OR</em>'ing together + * (that is, using the <code>int</code> "|" operator) two or more + * of those <code>SWT</code> style constants. The class description + * lists the style constants that are applicable to the class. + * Style bits are also inherited from superclasses. + * </p> + * + * @param parent a widget which will be the parent of the new instance (cannot be null) + * @param style the style of widget to construct + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the parent is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li> + * </ul> + * + * @see SWT#BORDER + * @see SWT#READ_ONLY + * @see SWT#FLAT + * @see Widget#getStyle() + */ +public CCombo (Composite parent, int style) { + super (parent, style = checkStyle (style)); + _shell = super.getShell (); + + int textStyle = SWT.SINGLE; + if ((style & SWT.READ_ONLY) != 0) textStyle |= SWT.READ_ONLY; + if ((style & SWT.FLAT) != 0) textStyle |= SWT.FLAT; + text = new Text (this, textStyle); + int arrowStyle = SWT.ARROW | SWT.DOWN; + if ((style & SWT.FLAT) != 0) arrowStyle |= SWT.FLAT; + arrow = new Button (this, arrowStyle); + + listener = new Listener () { + public void handleEvent (Event event) { + if (isDisposed ()) return; + if (popup == event.widget) { + popupEvent (event); + return; + } + if (text == event.widget) { + textEvent (event); + return; + } + if (list == event.widget) { + listEvent (event); + return; + } + if (arrow == event.widget) { + arrowEvent (event); + return; + } + if (CCombo.this == event.widget) { + comboEvent (event); + return; + } + if (getShell () == event.widget) { + getDisplay().asyncExec(new Runnable() { + public void run() { + if (isDisposed ()) return; + handleFocus (SWT.FocusOut); + } + }); + } + } + }; + filter = new Listener() { + public void handleEvent(Event event) { + if (isDisposed ()) return; + Shell shell = ((Control)event.widget).getShell (); + if (shell == CCombo.this.getShell ()) { + handleFocus (SWT.FocusOut); + } + } + }; + + int [] comboEvents = {SWT.Dispose, SWT.FocusIn, SWT.Move, SWT.Resize}; + for (int i=0; i<comboEvents.length; i++) this.addListener (comboEvents [i], listener); + + int [] textEvents = {SWT.DefaultSelection, SWT.KeyDown, SWT.KeyUp, SWT.MenuDetect, SWT.Modify, SWT.MouseDown, SWT.MouseUp, SWT.MouseDoubleClick, SWT.MouseWheel, SWT.Traverse, SWT.FocusIn, SWT.Verify}; + for (int i=0; i<textEvents.length; i++) text.addListener (textEvents [i], listener); + + int [] arrowEvents = {SWT.MouseDown, SWT.MouseUp, SWT.Selection, SWT.FocusIn}; + for (int i=0; i<arrowEvents.length; i++) arrow.addListener (arrowEvents [i], listener); + + createPopup(null, -1); + initAccessible(); +} +static int checkStyle (int style) { + int mask = SWT.BORDER | SWT.READ_ONLY | SWT.FLAT | SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT; + return SWT.NO_FOCUS | (style & mask); +} +/** + * Adds the argument to the end of the receiver's list. + * + * @param string the new item + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the string is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see #add(String,int) + */ +public void add (String string) { + checkWidget(); + if (string == null) SWT.error (SWT.ERROR_NULL_ARGUMENT); + list.add (string); +} +/** + * Adds the argument to the receiver's list at the given + * zero-relative index. + * <p> + * Note: To add an item at the end of the list, use the + * result of calling <code>getItemCount()</code> as the + * index or use <code>add(String)</code>. + * </p> + * + * @param string the new item + * @param index the index for the item + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the string is null</li> + * <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list (inclusive)</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see #add(String) + */ +public void add (String string, int index) { + checkWidget(); + if (string == null) SWT.error (SWT.ERROR_NULL_ARGUMENT); + list.add (string, index); +} +/** + * Adds the listener to the collection of listeners who will + * be notified when the receiver's text is modified, by sending + * it one of the messages defined in the <code>ModifyListener</code> + * interface. + * + * @param listener the listener which should be notified + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the listener is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see ModifyListener + * @see #removeModifyListener + */ +public void addModifyListener (ModifyListener listener) { + checkWidget(); + if (listener == null) SWT.error (SWT.ERROR_NULL_ARGUMENT); + TypedListener typedListener = new TypedListener (listener); + addListener (SWT.Modify, typedListener); +} +/** + * Adds the listener to the collection of listeners who will + * be notified when the user changes the receiver's selection, by sending + * it one of the messages defined in the <code>SelectionListener</code> + * interface. + * <p> + * <code>widgetSelected</code> is called when the combo's list selection changes. + * <code>widgetDefaultSelected</code> is typically called when ENTER is pressed the combo's text area. + * </p> + * + * @param listener the listener which should be notified when the user changes the receiver's selection + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the listener is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see SelectionListener + * @see #removeSelectionListener + * @see SelectionEvent + */ +public void addSelectionListener(SelectionListener listener) { + checkWidget(); + if (listener == null) SWT.error (SWT.ERROR_NULL_ARGUMENT); + TypedListener typedListener = new TypedListener (listener); + addListener (SWT.Selection,typedListener); + addListener (SWT.DefaultSelection,typedListener); +} +/** + * Adds the listener to the collection of listeners who will + * be notified when the receiver's text is verified, by sending + * it one of the messages defined in the <code>VerifyListener</code> + * interface. + * + * @param listener the listener which should be notified + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the listener is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see VerifyListener + * @see #removeVerifyListener + * + * @since 3.3 + */ +public void addVerifyListener (VerifyListener listener) { + checkWidget(); + if (listener == null) SWT.error (SWT.ERROR_NULL_ARGUMENT); + TypedListener typedListener = new TypedListener (listener); + addListener (SWT.Verify,typedListener); +} +void arrowEvent (Event event) { + switch (event.type) { + case SWT.FocusIn: { + handleFocus (SWT.FocusIn); + break; + } + case SWT.MouseDown: { + Event mouseEvent = new Event (); + mouseEvent.button = event.button; + mouseEvent.count = event.count; + mouseEvent.stateMask = event.stateMask; + mouseEvent.time = event.time; + mouseEvent.x = event.x; mouseEvent.y = event.y; + notifyListeners (SWT.MouseDown, mouseEvent); + event.doit = mouseEvent.doit; + break; + } + case SWT.MouseUp: { + Event mouseEvent = new Event (); + mouseEvent.button = event.button; + mouseEvent.count = event.count; + mouseEvent.stateMask = event.stateMask; + mouseEvent.time = event.time; + mouseEvent.x = event.x; mouseEvent.y = event.y; + notifyListeners (SWT.MouseUp, mouseEvent); + event.doit = mouseEvent.doit; + break; + } + case SWT.Selection: { + text.setFocus(); + dropDown (!isDropped ()); + break; + } + } +} +protected void checkSubclass () { + String name = getClass ().getName (); + int index = name.lastIndexOf ('.'); + if (!name.substring (0, index + 1).equals (PACKAGE_PREFIX)) { + SWT.error (SWT.ERROR_INVALID_SUBCLASS); + } +} +/** + * Sets the selection in the receiver's text field to an empty + * selection starting just before the first character. If the + * text field is editable, this has the effect of placing the + * i-beam at the start of the text. + * <p> + * Note: To clear the selected items in the receiver's list, + * use <code>deselectAll()</code>. + * </p> + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see #deselectAll + */ +public void clearSelection () { + checkWidget (); + text.clearSelection (); + list.deselectAll (); +} +void comboEvent (Event event) { + switch (event.type) { + case SWT.Dispose: + removeListener(SWT.Dispose, listener); + notifyListeners(SWT.Dispose, event); + event.type = SWT.None; + + if (popup != null && !popup.isDisposed ()) { + list.removeListener (SWT.Dispose, listener); + popup.dispose (); + } + Shell shell = getShell (); + shell.removeListener (SWT.Deactivate, listener); + Display display = getDisplay (); + display.removeFilter (SWT.FocusIn, filter); + popup = null; + text = null; + list = null; + arrow = null; + _shell = null; + break; + case SWT.FocusIn: + Control focusControl = getDisplay ().getFocusControl (); + if (focusControl == arrow || focusControl == list) return; + if (isDropped()) { + list.setFocus(); + } else { + text.setFocus(); + } + break; + case SWT.Move: + dropDown (false); + break; + case SWT.Resize: + internalLayout (false); + break; + } +} + +public Point computeSize (int wHint, int hHint, boolean changed) { + checkWidget (); + int width = 0, height = 0; + String[] items = list.getItems (); + GC gc = new GC (text); + int spacer = gc.stringExtent (" ").x; //$NON-NLS-1$ + int textWidth = gc.stringExtent (text.getText ()).x; + for (int i = 0; i < items.length; i++) { + textWidth = Math.max (gc.stringExtent (items[i]).x, textWidth); + } + gc.dispose (); + Point textSize = text.computeSize (SWT.DEFAULT, SWT.DEFAULT, changed); + Point arrowSize = arrow.computeSize (SWT.DEFAULT, SWT.DEFAULT, changed); + Point listSize = list.computeSize (SWT.DEFAULT, SWT.DEFAULT, changed); + int borderWidth = getBorderWidth (); + + height = Math.max (textSize.y, arrowSize.y); + width = Math.max (textWidth + 2*spacer + arrowSize.x + 2*borderWidth, listSize.x); + if (wHint != SWT.DEFAULT) width = wHint; + if (hHint != SWT.DEFAULT) height = hHint; + return new Point (width + 2*borderWidth, height + 2*borderWidth); +} +/** + * Copies the selected text. + * <p> + * The current selection is copied to the clipboard. + * </p> + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.3 + */ +public void copy () { + checkWidget (); + text.copy (); +} +void createPopup(String[] items, int selectionIndex) { + // create shell and list + popup = new Shell (getShell (), SWT.NO_TRIM | SWT.ON_TOP); + int style = getStyle (); + int listStyle = SWT.SINGLE | SWT.V_SCROLL; + if ((style & SWT.FLAT) != 0) listStyle |= SWT.FLAT; + if ((style & SWT.RIGHT_TO_LEFT) != 0) listStyle |= SWT.RIGHT_TO_LEFT; + if ((style & SWT.LEFT_TO_RIGHT) != 0) listStyle |= SWT.LEFT_TO_RIGHT; + list = new List (popup, listStyle); + if (font != null) list.setFont (font); + if (foreground != null) list.setForeground (foreground); + if (background != null) list.setBackground (background); + + int [] popupEvents = {SWT.Close, SWT.Paint, SWT.Deactivate}; + for (int i=0; i<popupEvents.length; i++) popup.addListener (popupEvents [i], listener); + int [] listEvents = {SWT.MouseUp, SWT.Selection, SWT.Traverse, SWT.KeyDown, SWT.KeyUp, SWT.FocusIn, SWT.Dispose}; + for (int i=0; i<listEvents.length; i++) list.addListener (listEvents [i], listener); + + if (items != null) list.setItems (items); + if (selectionIndex != -1) list.setSelection (selectionIndex); +} +/** + * Cuts the selected text. + * <p> + * The current selection is first copied to the + * clipboard and then deleted from the widget. + * </p> + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.3 + */ +public void cut () { + checkWidget (); + text.cut (); +} +/** + * Deselects the item at the given zero-relative index in the receiver's + * list. If the item at the index was already deselected, it remains + * deselected. Indices that are out of range are ignored. + * + * @param index the index of the item to deselect + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void deselect (int index) { + checkWidget (); + if (0 <= index && index < list.getItemCount () && + index == list.getSelectionIndex() && + text.getText().equals(list.getItem(index))) { + text.setText(""); //$NON-NLS-1$ + list.deselect (index); + } +} +/** + * Deselects all selected items in the receiver's list. + * <p> + * Note: To clear the selection in the receiver's text field, + * use <code>clearSelection()</code>. + * </p> + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see #clearSelection + */ +public void deselectAll () { + checkWidget (); + text.setText(""); //$NON-NLS-1$ + list.deselectAll (); +} +void dropDown (boolean drop) { + if (drop == isDropped ()) return; + if (!drop) { + popup.setVisible (false); + if (!isDisposed () && isFocusControl()) { + text.setFocus(); + } + return; + } + if (!isVisible()) return; + if (getShell() != popup.getParent ()) { + String[] items = list.getItems (); + int selectionIndex = list.getSelectionIndex (); + list.removeListener (SWT.Dispose, listener); + popup.dispose(); + popup = null; + list = null; + createPopup (items, selectionIndex); + } + + Point size = getSize (); + int itemCount = list.getItemCount (); + itemCount = (itemCount == 0) ? visibleItemCount : Math.min(visibleItemCount, itemCount); + int itemHeight = list.getItemHeight () * itemCount; + Point listSize = list.computeSize (SWT.DEFAULT, itemHeight, false); + list.setBounds (1, 1, Math.max (size.x - 2, listSize.x), listSize.y); + + int index = list.getSelectionIndex (); + if (index != -1) list.setTopIndex (index); + Display display = getDisplay (); + Rectangle listRect = list.getBounds (); + Rectangle parentRect = display.map (getParent (), null, getBounds ()); + Point comboSize = getSize (); + Rectangle displayRect = getMonitor ().getClientArea (); + int width = Math.max (comboSize.x, listRect.width + 2); + int height = listRect.height + 2; + int x = parentRect.x; + int y = parentRect.y + comboSize.y; + if (y + height > displayRect.y + displayRect.height) y = parentRect.y - height; + if (x + width > displayRect.x + displayRect.width) x = displayRect.x + displayRect.width - listRect.width; + popup.setBounds (x, y, width, height); + popup.setVisible (true); + if (isFocusControl()) list.setFocus (); +} +/* + * Return the lowercase of the first non-'&' character following + * an '&' character in the given string. If there are no '&' + * characters in the given string, return '\0'. + */ +char _findMnemonic (String string) { + if (string == null) return '\0'; + int index = 0; + int length = string.length (); + do { + while (index < length && string.charAt (index) != '&') index++; + if (++index >= length) return '\0'; + if (string.charAt (index) != '&') return Character.toLowerCase (string.charAt (index)); + index++; + } while (index < length); + return '\0'; +} +/* + * Return the Label immediately preceding the receiver in the z-order, + * or null if none. + */ +Label getAssociatedLabel () { + Control[] siblings = getParent ().getChildren (); + for (int i = 0; i < siblings.length; i++) { + if (siblings [i] == this) { + if (i > 0 && siblings [i-1] instanceof Label) { + return (Label) siblings [i-1]; + } + } + } + return null; +} +public Control [] getChildren () { + checkWidget(); + return new Control [0]; +} +/** + * Gets the editable state. + * + * @return whether or not the receiver is editable + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.0 + */ +public boolean getEditable () { + checkWidget (); + return text.getEditable(); +} +/** + * Returns the item at the given, zero-relative index in the + * receiver's list. Throws an exception if the index is out + * of range. + * + * @param index the index of the item to return + * @return the item at the given index + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list minus 1 (inclusive)</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public String getItem (int index) { + checkWidget(); + return list.getItem (index); +} +/** + * Returns the number of items contained in the receiver's list. + * + * @return the number of items + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public int getItemCount () { + checkWidget (); + return list.getItemCount (); +} +/** + * Returns the height of the area which would be used to + * display <em>one</em> of the items in the receiver's list. + * + * @return the height of one item + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public int getItemHeight () { + checkWidget (); + return list.getItemHeight (); +} +/** + * Returns an array of <code>String</code>s which are the items + * in the receiver's list. + * <p> + * Note: This is not the actual structure used by the receiver + * to maintain its list of items, so modifying the array will + * not affect the receiver. + * </p> + * + * @return the items in the receiver's list + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public String [] getItems () { + checkWidget (); + return list.getItems (); +} +/** + * Returns <code>true</code> if the receiver's list is visible, + * and <code>false</code> otherwise. + * <p> + * If one of the receiver's ancestors is not visible or some + * other condition makes the receiver not visible, this method + * may still indicate that it is considered visible even though + * it may not actually be showing. + * </p> + * + * @return the receiver's list's visibility state + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.4 + */ +public boolean getListVisible () { + checkWidget (); + return isDropped(); +} +public Menu getMenu() { + return text.getMenu(); +} +/** + * Returns a <code>Point</code> whose x coordinate is the start + * of the selection in the receiver's text field, and whose y + * coordinate is the end of the selection. The returned values + * are zero-relative. An "empty" selection as indicated by + * the the x and y coordinates having the same value. + * + * @return a point representing the selection start and end + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public Point getSelection () { + checkWidget (); + return text.getSelection (); +} +/** + * Returns the zero-relative index of the item which is currently + * selected in the receiver's list, or -1 if no item is selected. + * + * @return the index of the selected item + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public int getSelectionIndex () { + checkWidget (); + return list.getSelectionIndex (); +} +public Shell getShell () { + checkWidget (); + Shell shell = super.getShell (); + if (shell != _shell) { + if (_shell != null && !_shell.isDisposed ()) { + _shell.removeListener (SWT.Deactivate, listener); + } + _shell = shell; + } + return _shell; +} +public int getStyle () { + int style = super.getStyle (); + style &= ~SWT.READ_ONLY; + if (!text.getEditable()) style |= SWT.READ_ONLY; + return style; +} +/** + * Returns a string containing a copy of the contents of the + * receiver's text field. + * + * @return the receiver's text + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public String getText () { + checkWidget (); + return text.getText (); +} +/** + * Returns the height of the receivers's text field. + * + * @return the text height + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public int getTextHeight () { + checkWidget (); + return text.getLineHeight (); +} +/** + * Returns the maximum number of characters that the receiver's + * text field is capable of holding. If this has not been changed + * by <code>setTextLimit()</code>, it will be the constant + * <code>Combo.LIMIT</code>. + * + * @return the text limit + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public int getTextLimit () { + checkWidget (); + return text.getTextLimit (); +} +/** + * Gets the number of items that are visible in the drop + * down portion of the receiver's list. + * + * @return the number of items that are visible + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.0 + */ +public int getVisibleItemCount () { + checkWidget (); + return visibleItemCount; +} +void handleFocus (int type) { + switch (type) { + case SWT.FocusIn: { + if (hasFocus) return; + if (getEditable ()) text.selectAll (); + hasFocus = true; + Shell shell = getShell (); + shell.removeListener (SWT.Deactivate, listener); + shell.addListener (SWT.Deactivate, listener); + Display display = getDisplay (); + display.removeFilter (SWT.FocusIn, filter); + display.addFilter (SWT.FocusIn, filter); + Event e = new Event (); + notifyListeners (SWT.FocusIn, e); + break; + } + case SWT.FocusOut: { + if (!hasFocus) return; + Control focusControl = getDisplay ().getFocusControl (); + if (focusControl == arrow || focusControl == list || focusControl == text) return; + hasFocus = false; + Shell shell = getShell (); + shell.removeListener(SWT.Deactivate, listener); + Display display = getDisplay (); + display.removeFilter (SWT.FocusIn, filter); + Event e = new Event (); + notifyListeners (SWT.FocusOut, e); + break; + } + } +} +/** + * Searches the receiver's list starting at the first item + * (index 0) until an item is found that is equal to the + * argument, and returns the index of that item. If no item + * is found, returns -1. + * + * @param string the search item + * @return the index of the item + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the string is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public int indexOf (String string) { + checkWidget (); + if (string == null) SWT.error (SWT.ERROR_NULL_ARGUMENT); + return list.indexOf (string); +} +/** + * Searches the receiver's list starting at the given, + * zero-relative index until an item is found that is equal + * to the argument, and returns the index of that item. If + * no item is found or the starting index is out of range, + * returns -1. + * + * @param string the search item + * @param start the zero-relative index at which to begin the search + * @return the index of the item + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the string is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public int indexOf (String string, int start) { + checkWidget (); + if (string == null) SWT.error (SWT.ERROR_NULL_ARGUMENT); + return list.indexOf (string, start); +} + +void initAccessible() { + AccessibleAdapter accessibleAdapter = new AccessibleAdapter () { + public void getName (AccessibleEvent e) { + String name = null; + Label label = getAssociatedLabel (); + if (label != null) { + name = stripMnemonic (label.getText()); + } + e.result = name; + } + public void getKeyboardShortcut(AccessibleEvent e) { + String shortcut = null; + Label label = getAssociatedLabel (); + if (label != null) { + String text = label.getText (); + if (text != null) { + char mnemonic = _findMnemonic (text); + if (mnemonic != '\0') { + shortcut = "Alt+"+mnemonic; //$NON-NLS-1$ + } + } + } + e.result = shortcut; + } + public void getHelp (AccessibleEvent e) { + e.result = getToolTipText (); + } + }; + getAccessible ().addAccessibleListener (accessibleAdapter); + text.getAccessible ().addAccessibleListener (accessibleAdapter); + list.getAccessible ().addAccessibleListener (accessibleAdapter); + + arrow.getAccessible ().addAccessibleListener (new AccessibleAdapter() { + public void getName (AccessibleEvent e) { + e.result = isDropped () ? SWT.getMessage ("SWT_Close") : SWT.getMessage ("SWT_Open"); //$NON-NLS-1$ //$NON-NLS-2$ + } + public void getKeyboardShortcut (AccessibleEvent e) { + e.result = "Alt+Down Arrow"; //$NON-NLS-1$ + } + public void getHelp (AccessibleEvent e) { + e.result = getToolTipText (); + } + }); + + getAccessible().addAccessibleTextListener (new AccessibleTextAdapter() { + public void getCaretOffset (AccessibleTextEvent e) { + e.offset = text.getCaretPosition (); + } + public void getSelectionRange(AccessibleTextEvent e) { + Point sel = text.getSelection(); + e.offset = sel.x; + e.length = sel.y - sel.x; + } + }); + + getAccessible().addAccessibleControlListener (new AccessibleControlAdapter() { + public void getChildAtPoint (AccessibleControlEvent e) { + Point testPoint = toControl (e.x, e.y); + if (getBounds ().contains (testPoint)) { + e.childID = ACC.CHILDID_SELF; + } + } + + public void getLocation (AccessibleControlEvent e) { + Rectangle location = getBounds (); + Point pt = getParent().toDisplay (location.x, location.y); + e.x = pt.x; + e.y = pt.y; + e.width = location.width; + e.height = location.height; + } + + public void getChildCount (AccessibleControlEvent e) { + e.detail = 0; + } + + public void getRole (AccessibleControlEvent e) { + e.detail = ACC.ROLE_COMBOBOX; + } + + public void getState (AccessibleControlEvent e) { + e.detail = ACC.STATE_NORMAL; + } + + public void getValue (AccessibleControlEvent e) { + e.result = getText (); + } + }); + + text.getAccessible ().addAccessibleControlListener (new AccessibleControlAdapter () { + public void getRole (AccessibleControlEvent e) { + e.detail = text.getEditable () ? ACC.ROLE_TEXT : ACC.ROLE_LABEL; + } + }); + + arrow.getAccessible ().addAccessibleControlListener (new AccessibleControlAdapter() { + public void getDefaultAction (AccessibleControlEvent e) { + e.result = isDropped () ? SWT.getMessage ("SWT_Close") : SWT.getMessage ("SWT_Open"); //$NON-NLS-1$ //$NON-NLS-2$ + } + }); +} +boolean isDropped () { + return popup.getVisible (); +} +public boolean isFocusControl () { + checkWidget(); + if (text.isFocusControl () || arrow.isFocusControl () || list.isFocusControl () || popup.isFocusControl ()) { + return true; + } + return super.isFocusControl (); +} +void internalLayout (boolean changed) { + if (isDropped ()) dropDown (false); + Rectangle rect = getClientArea (); + int width = rect.width; + int height = rect.height; + Point arrowSize = arrow.computeSize (SWT.DEFAULT, height, changed); + text.setBounds (0, 0, width - arrowSize.x, height); + arrow.setBounds (width - arrowSize.x, 0, arrowSize.x, arrowSize.y); +} +void listEvent (Event event) { + switch (event.type) { + case SWT.Dispose: + if (getShell () != popup.getParent ()) { + String[] items = list.getItems (); + int selectionIndex = list.getSelectionIndex (); + popup = null; + list = null; + createPopup (items, selectionIndex); + } + break; + case SWT.FocusIn: { + handleFocus (SWT.FocusIn); + break; + } + case SWT.MouseUp: { + if (event.button != 1) return; + dropDown (false); + break; + } + case SWT.Selection: { + int index = list.getSelectionIndex (); + if (index == -1) return; + text.setText (list.getItem (index)); + text.selectAll (); + list.setSelection (index); + Event e = new Event (); + e.time = event.time; + e.stateMask = event.stateMask; + e.doit = event.doit; + notifyListeners (SWT.Selection, e); + event.doit = e.doit; + break; + } + case SWT.Traverse: { + switch (event.detail) { + case SWT.TRAVERSE_RETURN: + case SWT.TRAVERSE_ESCAPE: + case SWT.TRAVERSE_ARROW_PREVIOUS: + case SWT.TRAVERSE_ARROW_NEXT: + event.doit = false; + break; + case SWT.TRAVERSE_TAB_NEXT: + case SWT.TRAVERSE_TAB_PREVIOUS: + event.doit = text.traverse(event.detail); + event.detail = SWT.TRAVERSE_NONE; + if (event.doit) dropDown(false); + return; + } + Event e = new Event (); + e.time = event.time; + e.detail = event.detail; + e.doit = event.doit; + e.character = event.character; + e.keyCode = event.keyCode; + notifyListeners (SWT.Traverse, e); + event.doit = e.doit; + event.detail = e.detail; + break; + } + case SWT.KeyUp: { + Event e = new Event (); + e.time = event.time; + e.character = event.character; + e.keyCode = event.keyCode; + e.stateMask = event.stateMask; + notifyListeners (SWT.KeyUp, e); + break; + } + case SWT.KeyDown: { + if (event.character == SWT.ESC) { + // Escape key cancels popup list + dropDown (false); + } + if ((event.stateMask & SWT.ALT) != 0 && (event.keyCode == SWT.ARROW_UP || event.keyCode == SWT.ARROW_DOWN)) { + dropDown (false); + } + if (event.character == SWT.CR) { + // Enter causes default selection + dropDown (false); + Event e = new Event (); + e.time = event.time; + e.stateMask = event.stateMask; + notifyListeners (SWT.DefaultSelection, e); + } + // At this point the widget may have been disposed. + // If so, do not continue. + if (isDisposed ()) break; + Event e = new Event(); + e.time = event.time; + e.character = event.character; + e.keyCode = event.keyCode; + e.stateMask = event.stateMask; + notifyListeners(SWT.KeyDown, e); + break; + + } + } +} +/** + * Pastes text from clipboard. + * <p> + * The selected text is deleted from the widget + * and new text inserted from the clipboard. + * </p> + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.3 + */ +public void paste () { + checkWidget (); + text.paste (); +} +void popupEvent(Event event) { + switch (event.type) { + case SWT.Paint: + // draw black rectangle around list + Rectangle listRect = list.getBounds(); + Color black = getDisplay().getSystemColor(SWT.COLOR_BLACK); + event.gc.setForeground(black); + event.gc.drawRectangle(0, 0, listRect.width + 1, listRect.height + 1); + break; + case SWT.Close: + event.doit = false; + dropDown (false); + break; + case SWT.Deactivate: + /* + * Bug in GTK. When the arrow button is pressed the popup control receives a + * deactivate event and then the arrow button receives a selection event. If + * we hide the popup in the deactivate event, the selection event will show + * it again. To prevent the popup from showing again, we will let the selection + * event of the arrow button hide the popup. + * In Windows, hiding the popup during the deactivate causes the deactivate + * to be called twice and the selection event to be disappear. + */ + if (!"carbon".equals(SWT.getPlatform())) { + Point point = arrow.toControl(getDisplay().getCursorLocation()); + Point size = arrow.getSize(); + Rectangle rect = new Rectangle(0, 0, size.x, size.y); + if (!rect.contains(point)) dropDown (false); + } else { + dropDown(false); + } + break; + } +} +public void redraw () { + super.redraw(); + text.redraw(); + arrow.redraw(); + if (popup.isVisible()) list.redraw(); +} +public void redraw (int x, int y, int width, int height, boolean all) { + super.redraw(x, y, width, height, true); +} + +/** + * Removes the item from the receiver's list at the given + * zero-relative index. + * + * @param index the index for the item + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list minus 1 (inclusive)</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void remove (int index) { + checkWidget(); + list.remove (index); +} +/** + * Removes the items from the receiver's list which are + * between the given zero-relative start and end + * indices (inclusive). + * + * @param start the start of the range + * @param end the end of the range + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_RANGE - if either the start or end are not between 0 and the number of elements in the list minus 1 (inclusive)</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void remove (int start, int end) { + checkWidget(); + list.remove (start, end); +} +/** + * Searches the receiver's list starting at the first item + * until an item is found that is equal to the argument, + * and removes that item from the list. + * + * @param string the item to remove + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the string is null</li> + * <li>ERROR_INVALID_ARGUMENT - if the string is not found in the list</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void remove (String string) { + checkWidget(); + if (string == null) SWT.error (SWT.ERROR_NULL_ARGUMENT); + list.remove (string); +} +/** + * Removes all of the items from the receiver's list and clear the + * contents of receiver's text field. + * <p> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void removeAll () { + checkWidget(); + text.setText (""); //$NON-NLS-1$ + list.removeAll (); +} +/** + * Removes the listener from the collection of listeners who will + * be notified when the receiver's text is modified. + * + * @param listener the listener which should no longer be notified + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the listener is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see ModifyListener + * @see #addModifyListener + */ +public void removeModifyListener (ModifyListener listener) { + checkWidget(); + if (listener == null) SWT.error (SWT.ERROR_NULL_ARGUMENT); + removeListener(SWT.Modify, listener); +} +/** + * Removes the listener from the collection of listeners who will + * be notified when the user changes the receiver's selection. + * + * @param listener the listener which should no longer be notified + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the listener is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see SelectionListener + * @see #addSelectionListener + */ +public void removeSelectionListener (SelectionListener listener) { + checkWidget(); + if (listener == null) SWT.error (SWT.ERROR_NULL_ARGUMENT); + removeListener(SWT.Selection, listener); + removeListener(SWT.DefaultSelection,listener); +} +/** + * Removes the listener from the collection of listeners who will + * be notified when the control is verified. + * + * @param listener the listener which should no longer be notified + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the listener is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see VerifyListener + * @see #addVerifyListener + * + * @since 3.3 + */ +public void removeVerifyListener (VerifyListener listener) { + checkWidget(); + if (listener == null) SWT.error (SWT.ERROR_NULL_ARGUMENT); + removeListener(SWT.Verify, listener); +} +/** + * Selects the item at the given zero-relative index in the receiver's + * list. If the item at the index was already selected, it remains + * selected. Indices that are out of range are ignored. + * + * @param index the index of the item to select + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void select (int index) { + checkWidget(); + if (index == -1) { + list.deselectAll (); + text.setText (""); //$NON-NLS-1$ + return; + } + if (0 <= index && index < list.getItemCount()) { + if (index != getSelectionIndex()) { + text.setText (list.getItem (index)); + text.selectAll (); + list.select (index); + list.showSelection (); + } + } +} +public void setBackground (Color color) { + super.setBackground(color); + background = color; + if (text != null) text.setBackground(color); + if (list != null) list.setBackground(color); + if (arrow != null) arrow.setBackground(color); +} +/** + * Sets the editable state. + * + * @param editable the new editable state + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.0 + */ +public void setEditable (boolean editable) { + checkWidget (); + text.setEditable(editable); +} +public void setEnabled (boolean enabled) { + super.setEnabled(enabled); + if (popup != null) popup.setVisible (false); + if (text != null) text.setEnabled(enabled); + if (arrow != null) arrow.setEnabled(enabled); +} +public boolean setFocus () { + checkWidget(); + if (!isEnabled () || !isVisible ()) return false; + if (isFocusControl ()) return true; + return text.setFocus (); +} +public void setFont (Font font) { + super.setFont (font); + this.font = font; + text.setFont (font); + list.setFont (font); + internalLayout (true); +} +public void setForeground (Color color) { + super.setForeground(color); + foreground = color; + if (text != null) text.setForeground(color); + if (list != null) list.setForeground(color); + if (arrow != null) arrow.setForeground(color); +} +/** + * Sets the text of the item in the receiver's list at the given + * zero-relative index to the string argument. This is equivalent + * to <code>remove</code>'ing the old item at the index, and then + * <code>add</code>'ing the new item at that index. + * + * @param index the index for the item + * @param string the new text for the item + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list minus 1 (inclusive)</li> + * <li>ERROR_NULL_ARGUMENT - if the string is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void setItem (int index, String string) { + checkWidget(); + list.setItem (index, string); +} +/** + * Sets the receiver's list to be the given array of items. + * + * @param items the array of items + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the items array is null</li> + * <li>ERROR_INVALID_ARGUMENT - if an item in the items array is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void setItems (String [] items) { + checkWidget (); + list.setItems (items); + if (!text.getEditable ()) text.setText (""); //$NON-NLS-1$ +} +/** + * Sets the layout which is associated with the receiver to be + * the argument which may be null. + * <p> + * Note: No Layout can be set on this Control because it already + * manages the size and position of its children. + * </p> + * + * @param layout the receiver's new layout or null + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void setLayout (Layout layout) { + checkWidget (); + return; +} +/** + * Marks the receiver's list as visible if the argument is <code>true</code>, + * and marks it invisible otherwise. + * <p> + * If one of the receiver's ancestors is not visible or some + * other condition makes the receiver not visible, marking + * it visible may not actually cause it to be displayed. + * </p> + * + * @param visible the new visibility state + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.4 + */ +public void setListVisible (boolean visible) { + checkWidget (); + dropDown(visible); +} +public void setMenu(Menu menu) { + text.setMenu(menu); +} +/** + * Sets the selection in the receiver's text field to the + * range specified by the argument whose x coordinate is the + * start of the selection and whose y coordinate is the end + * of the selection. + * + * @param selection a point representing the new selection start and end + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the point is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void setSelection (Point selection) { + checkWidget(); + if (selection == null) SWT.error (SWT.ERROR_NULL_ARGUMENT); + text.setSelection (selection.x, selection.y); +} + +/** + * Sets the contents of the receiver's text field to the + * given string. + * <p> + * Note: The text field in a <code>Combo</code> is typically + * only capable of displaying a single line of text. Thus, + * setting the text to a string containing line breaks or + * other special characters will probably cause it to + * display incorrectly. + * </p> + * + * @param string the new text + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the string is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void setText (String string) { + checkWidget(); + if (string == null) SWT.error (SWT.ERROR_NULL_ARGUMENT); + int index = list.indexOf (string); + if (index == -1) { + list.deselectAll (); + text.setText (string); + return; + } + text.setText (string); + text.selectAll (); + list.setSelection (index); + list.showSelection (); +} +/** + * Sets the maximum number of characters that the receiver's + * text field is capable of holding to be the argument. + * + * @param limit new text limit + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_CANNOT_BE_ZERO - if the limit is zero</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void setTextLimit (int limit) { + checkWidget(); + text.setTextLimit (limit); +} + +public void setToolTipText (String string) { + checkWidget(); + super.setToolTipText(string); + arrow.setToolTipText (string); + text.setToolTipText (string); +} + +public void setVisible (boolean visible) { + super.setVisible(visible); + /* + * At this point the widget may have been disposed in a FocusOut event. + * If so then do not continue. + */ + if (isDisposed ()) return; + // TEMPORARY CODE + if (popup == null || popup.isDisposed ()) return; + if (!visible) popup.setVisible (false); +} +/** + * Sets the number of items that are visible in the drop + * down portion of the receiver's list. + * + * @param count the new number of items to be visible + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.0 + */ +public void setVisibleItemCount (int count) { + checkWidget (); + if (count < 0) return; + visibleItemCount = count; +} +String stripMnemonic (String string) { + int index = 0; + int length = string.length (); + do { + while ((index < length) && (string.charAt (index) != '&')) index++; + if (++index >= length) return string; + if (string.charAt (index) != '&') { + return string.substring(0, index-1) + string.substring(index, length); + } + index++; + } while (index < length); + return string; +} +void textEvent (Event event) { + switch (event.type) { + case SWT.FocusIn: { + handleFocus (SWT.FocusIn); + break; + } + case SWT.DefaultSelection: { + dropDown (false); + Event e = new Event (); + e.time = event.time; + e.stateMask = event.stateMask; + notifyListeners (SWT.DefaultSelection, e); + break; + } + case SWT.KeyDown: { + Event keyEvent = new Event (); + keyEvent.time = event.time; + keyEvent.character = event.character; + keyEvent.keyCode = event.keyCode; + keyEvent.stateMask = event.stateMask; + notifyListeners (SWT.KeyDown, keyEvent); + if (isDisposed ()) break; + event.doit = keyEvent.doit; + if (!event.doit) break; + if (event.keyCode == SWT.ARROW_UP || event.keyCode == SWT.ARROW_DOWN) { + event.doit = false; + if ((event.stateMask & SWT.ALT) != 0) { + boolean dropped = isDropped (); + text.selectAll (); + if (!dropped) setFocus (); + dropDown (!dropped); + break; + } + + int oldIndex = getSelectionIndex (); + if (event.keyCode == SWT.ARROW_UP) { + select (Math.max (oldIndex - 1, 0)); + } else { + select (Math.min (oldIndex + 1, getItemCount () - 1)); + } + if (oldIndex != getSelectionIndex ()) { + Event e = new Event(); + e.time = event.time; + e.stateMask = event.stateMask; + notifyListeners (SWT.Selection, e); + } + if (isDisposed ()) break; + } + + // Further work : Need to add support for incremental search in + // pop up list as characters typed in text widget + break; + } + case SWT.KeyUp: { + Event e = new Event (); + e.time = event.time; + e.character = event.character; + e.keyCode = event.keyCode; + e.stateMask = event.stateMask; + notifyListeners (SWT.KeyUp, e); + event.doit = e.doit; + break; + } + case SWT.MenuDetect: { + Event e = new Event (); + e.time = event.time; + notifyListeners (SWT.MenuDetect, e); + break; + } + case SWT.Modify: { + list.deselectAll (); + Event e = new Event (); + e.time = event.time; + notifyListeners (SWT.Modify, e); + break; + } + case SWT.MouseDown: { + Event mouseEvent = new Event (); + mouseEvent.button = event.button; + mouseEvent.count = event.count; + mouseEvent.stateMask = event.stateMask; + mouseEvent.time = event.time; + mouseEvent.x = event.x; mouseEvent.y = event.y; + notifyListeners (SWT.MouseDown, mouseEvent); + if (isDisposed ()) break; + event.doit = mouseEvent.doit; + if (!event.doit) break; + if (event.button != 1) return; + if (text.getEditable ()) return; + boolean dropped = isDropped (); + text.selectAll (); + if (!dropped) setFocus (); + dropDown (!dropped); + break; + } + case SWT.MouseUp: { + Event mouseEvent = new Event (); + mouseEvent.button = event.button; + mouseEvent.count = event.count; + mouseEvent.stateMask = event.stateMask; + mouseEvent.time = event.time; + mouseEvent.x = event.x; mouseEvent.y = event.y; + notifyListeners (SWT.MouseUp, mouseEvent); + if (isDisposed ()) break; + event.doit = mouseEvent.doit; + if (!event.doit) break; + if (event.button != 1) return; + if (text.getEditable ()) return; + text.selectAll (); + break; + } + case SWT.MouseDoubleClick: { + Event mouseEvent = new Event (); + mouseEvent.button = event.button; + mouseEvent.count = event.count; + mouseEvent.stateMask = event.stateMask; + mouseEvent.time = event.time; + mouseEvent.x = event.x; mouseEvent.y = event.y; + notifyListeners (SWT.MouseDoubleClick, mouseEvent); + break; + } + case SWT.MouseWheel: { + Event keyEvent = new Event (); + keyEvent.time = event.time; + keyEvent.keyCode = event.count > 0 ? SWT.ARROW_UP : SWT.ARROW_DOWN; + keyEvent.stateMask = event.stateMask; + notifyListeners (SWT.KeyDown, keyEvent); + if (isDisposed ()) break; + event.doit = keyEvent.doit; + if (!event.doit) break; + if (event.count != 0) { + event.doit = false; + int oldIndex = getSelectionIndex (); + if (event.count > 0) { + select (Math.max (oldIndex - 1, 0)); + } else { + select (Math.min (oldIndex + 1, getItemCount () - 1)); + } + if (oldIndex != getSelectionIndex ()) { + Event e = new Event(); + e.time = event.time; + e.stateMask = event.stateMask; + notifyListeners (SWT.Selection, e); + } + if (isDisposed ()) break; + } + break; + } + case SWT.Traverse: { + switch (event.detail) { + case SWT.TRAVERSE_ARROW_PREVIOUS: + case SWT.TRAVERSE_ARROW_NEXT: + // The enter causes default selection and + // the arrow keys are used to manipulate the list contents so + // do not use them for traversal. + event.doit = false; + break; + case SWT.TRAVERSE_TAB_PREVIOUS: + event.doit = traverse(SWT.TRAVERSE_TAB_PREVIOUS); + event.detail = SWT.TRAVERSE_NONE; + return; + } + Event e = new Event (); + e.time = event.time; + e.detail = event.detail; + e.doit = event.doit; + e.character = event.character; + e.keyCode = event.keyCode; + notifyListeners (SWT.Traverse, e); + event.doit = e.doit; + event.detail = e.detail; + break; + } + case SWT.Verify: { + Event e = new Event (); + e.text = event.text; + e.start = event.start; + e.end = event.end; + e.character = event.character; + e.keyCode = event.keyCode; + e.stateMask = event.stateMask; + notifyListeners (SWT.Verify, e); + event.doit = e.doit; + break; + } + } +} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CLabel.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CLabel.java new file mode 100755 index 0000000000..ddd3c6a529 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CLabel.java @@ -0,0 +1,826 @@ +/******************************************************************************* + * 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.custom; + +import org.eclipse.swt.*; +import org.eclipse.swt.widgets.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.events.*; +import org.eclipse.swt.accessibility.*; + +/** + * A Label which supports aligned text and/or an image and different border styles. + * <p> + * If there is not enough space a CLabel uses the following strategy to fit the + * information into the available space: + * <pre> + * ignores the indent in left align mode + * ignores the image and the gap + * shortens the text by replacing the center portion of the label with an ellipsis + * shortens the text by removing the center portion of the label + * </pre> + * <p> + * <dl> + * <dt><b>Styles:</b> + * <dd>LEFT, RIGHT, CENTER, SHADOW_IN, SHADOW_OUT, SHADOW_NONE</dd> + * <dt><b>Events:</b> + * <dd></dd> + * </dl> + * + * </p><p> + * IMPORTANT: This class is <em>not</em> intended to be subclassed. + * </p> + * + * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: CustomControlExample</a> + * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> + * @noextend This class is not intended to be subclassed by clients. + */ +public class CLabel extends Canvas { + + /** Gap between icon and text */ + private static final int GAP = 5; + /** Left and right margins */ + private static final int INDENT = 3; + /** a string inserted in the middle of text that has been shortened */ + private static final String ELLIPSIS = "..."; //$NON-NLS-1$ // could use the ellipsis glyph on some platforms "\u2026" + /** the alignment. Either CENTER, RIGHT, LEFT. Default is LEFT*/ + private int align = SWT.LEFT; + private int hIndent = INDENT; + private int vIndent = INDENT; + /** the current text */ + private String text; + /** the current icon */ + private Image image; + // The tooltip is used for two purposes - the application can set + // a tooltip or the tooltip can be used to display the full text when the + // the text has been truncated due to the label being too short. + // The appToolTip stores the tooltip set by the application. Control.tooltiptext + // contains whatever tooltip is currently being displayed. + private String appToolTipText; + + private Image backgroundImage; + private Color[] gradientColors; + private int[] gradientPercents; + private boolean gradientVertical; + private Color background; + private Listener disposeListener; + + private static int DRAW_FLAGS = SWT.DRAW_MNEMONIC | SWT.DRAW_TAB | SWT.DRAW_TRANSPARENT | SWT.DRAW_DELIMITER; + +/** + * Constructs a new instance of this class given its parent + * and a style value describing its behavior and appearance. + * <p> + * The style value is either one of the style constants defined in + * class <code>SWT</code> which is applicable to instances of this + * class, or must be built by <em>bitwise OR</em>'ing together + * (that is, using the <code>int</code> "|" operator) two or more + * of those <code>SWT</code> style constants. The class description + * lists the style constants that are applicable to the class. + * Style bits are also inherited from superclasses. + * </p> + * + * @param parent a widget which will be the parent of the new instance (cannot be null) + * @param style the style of widget to construct + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the parent is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li> + * </ul> + * + * @see SWT#LEFT + * @see SWT#RIGHT + * @see SWT#CENTER + * @see SWT#SHADOW_IN + * @see SWT#SHADOW_OUT + * @see SWT#SHADOW_NONE + * @see #getStyle() + */ +public CLabel(Composite parent, int style) { + super(parent, checkStyle(style)); + if ((style & (SWT.CENTER | SWT.RIGHT)) == 0) style |= SWT.LEFT; + if ((style & SWT.CENTER) != 0) align = SWT.CENTER; + if ((style & SWT.RIGHT) != 0) align = SWT.RIGHT; + if ((style & SWT.LEFT) != 0) align = SWT.LEFT; + + addPaintListener(new PaintListener(){ + public void paintControl(PaintEvent event) { + onPaint(event); + } + }); + + addTraverseListener(new TraverseListener() { + public void keyTraversed(TraverseEvent event) { + if (event.detail == SWT.TRAVERSE_MNEMONIC) { + onMnemonic(event); + } + } + }); + + disposeListener = new Listener() { + public void handleEvent(Event event) { + onDispose(event); + } + }; + + initAccessible(); + +} +/** + * Check the style bits to ensure that no invalid styles are applied. + */ +private static int checkStyle (int style) { + if ((style & SWT.BORDER) != 0) style |= SWT.SHADOW_IN; + int mask = SWT.SHADOW_IN | SWT.SHADOW_OUT | SWT.SHADOW_NONE | SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT; + style = style & mask; + return style |= SWT.NO_FOCUS | SWT.DOUBLE_BUFFERED; +} + +//protected void checkSubclass () { +// String name = getClass().getName (); +// String validName = CLabel.class.getName(); +// if (!validName.equals(name)) { +// SWT.error (SWT.ERROR_INVALID_SUBCLASS); +// } +//} + +public Point computeSize(int wHint, int hHint, boolean changed) { + checkWidget(); + Point e = getTotalSize(image, text); + if (wHint == SWT.DEFAULT){ + e.x += 2*hIndent; + } else { + e.x = wHint; + } + if (hHint == SWT.DEFAULT) { + e.y += 2*vIndent; + } else { + e.y = hHint; + } + return e; +} +/** + * Draw a rectangle in the given colors. + */ +private void drawBevelRect(GC gc, int x, int y, int w, int h, Color topleft, Color bottomright) { + gc.setForeground(bottomright); + gc.drawLine(x+w, y, x+w, y+h); + gc.drawLine(x, y+h, x+w, y+h); + + gc.setForeground(topleft); + gc.drawLine(x, y, x+w-1, y); + gc.drawLine(x, y, x, y+h-1); +} +/* + * Return the lowercase of the first non-'&' character following + * an '&' character in the given string. If there are no '&' + * characters in the given string, return '\0'. + */ +char _findMnemonic (String string) { + if (string == null) return '\0'; + int index = 0; + int length = string.length (); + do { + while (index < length && string.charAt (index) != '&') index++; + if (++index >= length) return '\0'; + if (string.charAt (index) != '&') return Character.toLowerCase (string.charAt (index)); + index++; + } while (index < length); + return '\0'; +} +/** + * Returns the alignment. + * The alignment style (LEFT, CENTER or RIGHT) is returned. + * + * @return SWT.LEFT, SWT.RIGHT or SWT.CENTER + */ +public int getAlignment() { + //checkWidget(); + return align; +} +/** + * Return the CLabel's image or <code>null</code>. + * + * @return the image of the label or null + */ +public Image getImage() { + //checkWidget(); + return image; +} +/** + * Compute the minimum size. + */ +private Point getTotalSize(Image image, String text) { + Point size = new Point(0, 0); + + if (image != null) { + Rectangle r = image.getBounds(); + size.x += r.width; + size.y += r.height; + } + + GC gc = new GC(this); + if (text != null && text.length() > 0) { + Point e = gc.textExtent(text, DRAW_FLAGS); + size.x += e.x; + size.y = Math.max(size.y, e.y); + if (image != null) size.x += GAP; + } else { + size.y = Math.max(size.y, gc.getFontMetrics().getHeight()); + } + gc.dispose(); + + return size; +} +public int getStyle () { + int style = super.getStyle(); + switch (align) { + case SWT.RIGHT: style |= SWT.RIGHT; break; + case SWT.CENTER: style |= SWT.CENTER; break; + case SWT.LEFT: style |= SWT.LEFT; break; + } + return style; +} + +/** + * Return the Label's text. + * + * @return the text of the label or null + */ +public String getText() { + //checkWidget(); + return text; +} +public String getToolTipText () { + checkWidget(); + return appToolTipText; +} +private void initAccessible() { + Accessible accessible = getAccessible(); + accessible.addAccessibleListener(new AccessibleAdapter() { + public void getName(AccessibleEvent e) { + e.result = getText(); + } + + public void getHelp(AccessibleEvent e) { + e.result = getToolTipText(); + } + + public void getKeyboardShortcut(AccessibleEvent e) { + char mnemonic = _findMnemonic(CLabel.this.text); + if (mnemonic != '\0') { + e.result = "Alt+"+mnemonic; //$NON-NLS-1$ + } + } + }); + + accessible.addAccessibleControlListener(new AccessibleControlAdapter() { + public void getChildAtPoint(AccessibleControlEvent e) { + e.childID = ACC.CHILDID_SELF; + } + + public void getLocation(AccessibleControlEvent e) { + Rectangle rect = getDisplay().map(getParent(), null, getBounds()); + e.x = rect.x; + e.y = rect.y; + e.width = rect.width; + e.height = rect.height; + } + + public void getChildCount(AccessibleControlEvent e) { + e.detail = 0; + } + + public void getRole(AccessibleControlEvent e) { + e.detail = ACC.ROLE_LABEL; + } + + public void getState(AccessibleControlEvent e) { + e.detail = ACC.STATE_READONLY; + } + }); +} +void onDispose(Event event) { + removeListener(SWT.Dispose, disposeListener); + notifyListeners(SWT.Dispose, event); + event.type = SWT.None; + + gradientColors = null; + gradientPercents = null; + backgroundImage = null; + text = null; + image = null; + appToolTipText = null; +} +void onMnemonic(TraverseEvent event) { + char mnemonic = _findMnemonic(text); + if (mnemonic == '\0') return; + if (Character.toLowerCase(event.character) != mnemonic) return; + Composite control = this.getParent(); + while (control != null) { + Control [] children = control.getChildren(); + int index = 0; + while (index < children.length) { + if (children [index] == this) break; + index++; + } + index++; + if (index < children.length) { + if (children [index].setFocus ()) { + event.doit = true; + event.detail = SWT.TRAVERSE_NONE; + } + } + control = control.getParent(); + } +} + +void onPaint(PaintEvent event) { + Rectangle rect = getClientArea(); + if (rect.width == 0 || rect.height == 0) return; + + boolean shortenText = false; + String t = text; + Image img = image; + int availableWidth = Math.max(0, rect.width - 2*hIndent); + Point extent = getTotalSize(img, t); + if (extent.x > availableWidth) { + img = null; + extent = getTotalSize(img, t); + if (extent.x > availableWidth) { + shortenText = true; + } + } + + GC gc = event.gc; + String[] lines = text == null ? null : splitString(text); + + // shorten the text + if (shortenText) { + extent.x = 0; + for(int i = 0; i < lines.length; i++) { + Point e = gc.textExtent(lines[i], DRAW_FLAGS); + if (e.x > availableWidth) { + lines[i] = shortenText(gc, lines[i], availableWidth); + extent.x = Math.max(extent.x, getTotalSize(null, lines[i]).x); + } else { + extent.x = Math.max(extent.x, e.x); + } + } + if (appToolTipText == null) { + super.setToolTipText(text); + } + } else { + super.setToolTipText(appToolTipText); + } + + // determine horizontal position + int x = rect.x + hIndent; + if (align == SWT.CENTER) { + x = (rect.width - extent.x)/2; + } + if (align == SWT.RIGHT) { + x = rect.width - hIndent - extent.x; + } + + // draw a background image behind the text + try { + if (backgroundImage != null) { + // draw a background image behind the text + Rectangle imageRect = backgroundImage.getBounds(); + // tile image to fill space + gc.setBackground(getBackground()); + gc.fillRectangle(rect); + int xPos = 0; + while (xPos < rect.width) { + int yPos = 0; + while (yPos < rect.height) { + gc.drawImage(backgroundImage, xPos, yPos); + yPos += imageRect.height; + } + xPos += imageRect.width; + } + } else if (gradientColors != null) { + // draw a gradient behind the text + final Color oldBackground = gc.getBackground(); + if (gradientColors.length == 1) { + if (gradientColors[0] != null) gc.setBackground(gradientColors[0]); + gc.fillRectangle(0, 0, rect.width, rect.height); + } else { + final Color oldForeground = gc.getForeground(); + Color lastColor = gradientColors[0]; + if (lastColor == null) lastColor = oldBackground; + int pos = 0; + for (int i = 0; i < gradientPercents.length; ++i) { + gc.setForeground(lastColor); + lastColor = gradientColors[i + 1]; + if (lastColor == null) lastColor = oldBackground; + gc.setBackground(lastColor); + if (gradientVertical) { + final int gradientHeight = (gradientPercents[i] * rect.height / 100) - pos; + gc.fillGradientRectangle(0, pos, rect.width, gradientHeight, true); + pos += gradientHeight; + } else { + final int gradientWidth = (gradientPercents[i] * rect.width / 100) - pos; + gc.fillGradientRectangle(pos, 0, gradientWidth, rect.height, false); + pos += gradientWidth; + } + } + if (gradientVertical && pos < rect.height) { + gc.setBackground(getBackground()); + gc.fillRectangle(0, pos, rect.width, rect.height - pos); + } + if (!gradientVertical && pos < rect.width) { + gc.setBackground(getBackground()); + gc.fillRectangle(pos, 0, rect.width - pos, rect.height); + } + gc.setForeground(oldForeground); + } + gc.setBackground(oldBackground); + } else { + if (background != null || (getStyle() & SWT.DOUBLE_BUFFERED) == 0) { + gc.setBackground(getBackground()); + gc.fillRectangle(rect); + } + } + } catch (SWTException e) { + if ((getStyle() & SWT.DOUBLE_BUFFERED) == 0) { + gc.setBackground(getBackground()); + gc.fillRectangle(rect); + } + } + + // draw border + int style = getStyle(); + if ((style & SWT.SHADOW_IN) != 0 || (style & SWT.SHADOW_OUT) != 0) { + paintBorder(gc, rect); + } + + // draw the image + if (img != null) { + Rectangle imageRect = img.getBounds(); + gc.drawImage(img, 0, 0, imageRect.width, imageRect.height, + x, (rect.height-imageRect.height)/2, imageRect.width, imageRect.height); + x += imageRect.width + GAP; + extent.x -= imageRect.width + GAP; + } + // draw the text + if (lines != null) { + int lineHeight = gc.getFontMetrics().getHeight(); + int textHeight = lines.length * lineHeight; + int lineY = Math.max(vIndent, rect.y + (rect.height - textHeight) / 2); + gc.setForeground(getForeground()); + for (int i = 0; i < lines.length; i++) { + int lineX = x; + if (lines.length > 1) { + if (align == SWT.CENTER) { + int lineWidth = gc.textExtent(lines[i], DRAW_FLAGS).x; + lineX = x + Math.max(0, (extent.x - lineWidth) / 2); + } + if (align == SWT.RIGHT) { + int lineWidth = gc.textExtent(lines[i], DRAW_FLAGS).x; + lineX = Math.max(x, rect.x + rect.width - hIndent - lineWidth); + } + } + gc.drawText(lines[i], lineX, lineY, DRAW_FLAGS); + lineY += lineHeight; + } + } +} +/** + * Paint the Label's border. + */ +private void paintBorder(GC gc, Rectangle r) { + Display disp= getDisplay(); + + Color c1 = null; + Color c2 = null; + + int style = getStyle(); + if ((style & SWT.SHADOW_IN) != 0) { + c1 = disp.getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW); + c2 = disp.getSystemColor(SWT.COLOR_WIDGET_HIGHLIGHT_SHADOW); + } + if ((style & SWT.SHADOW_OUT) != 0) { + c1 = disp.getSystemColor(SWT.COLOR_WIDGET_LIGHT_SHADOW); + c2 = disp.getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW); + } + + if (c1 != null && c2 != null) { + gc.setLineWidth(1); + drawBevelRect(gc, r.x, r.y, r.width-1, r.height-1, c1, c2); + } +} +/** + * Set the alignment of the CLabel. + * Use the values LEFT, CENTER and RIGHT to align image and text within the available space. + * + * @param align the alignment style of LEFT, RIGHT or CENTER + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * <li>ERROR_INVALID_ARGUMENT - if the value of align is not one of SWT.LEFT, SWT.RIGHT or SWT.CENTER</li> + * </ul> + */ +public void setAlignment(int align) { + checkWidget(); + if (align != SWT.LEFT && align != SWT.RIGHT && align != SWT.CENTER) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + if (this.align != align) { + this.align = align; + redraw(); + } +} + +public void setBackground (Color color) { + super.setBackground (color); + // Are these settings the same as before? + if (backgroundImage == null && + gradientColors == null && + gradientPercents == null) { + if (color == null) { + if (background == null) return; + } else { + if (color.equals(background)) return; + } + } + background = color; + backgroundImage = null; + gradientColors = null; + gradientPercents = null; + redraw (); +} + +/** + * Specify a gradient of colours to be drawn in the background of the CLabel. + * <p>For example, to draw a gradient that varies from dark blue to blue and then to + * white and stays white for the right half of the label, use the following call + * to setBackground:</p> + * <pre> + * clabel.setBackground(new Color[]{display.getSystemColor(SWT.COLOR_DARK_BLUE), + * display.getSystemColor(SWT.COLOR_BLUE), + * display.getSystemColor(SWT.COLOR_WHITE), + * display.getSystemColor(SWT.COLOR_WHITE)}, + * new int[] {25, 50, 100}); + * </pre> + * + * @param colors an array of Color that specifies the colors to appear in the gradient + * in order of appearance from left to right; The value <code>null</code> + * clears the background gradient; the value <code>null</code> can be used + * inside the array of Color to specify the background color. + * @param percents an array of integers between 0 and 100 specifying the percent of the width + * of the widget at which the color should change; the size of the percents + * array must be one less than the size of the colors array. + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * <li>ERROR_INVALID_ARGUMENT - if the values of colors and percents are not consistent</li> + * </ul> + */ +public void setBackground(Color[] colors, int[] percents) { + setBackground(colors, percents, false); +} +/** + * Specify a gradient of colours to be drawn in the background of the CLabel. + * <p>For example, to draw a gradient that varies from dark blue to white in the vertical, + * direction use the following call + * to setBackground:</p> + * <pre> + * clabel.setBackground(new Color[]{display.getSystemColor(SWT.COLOR_DARK_BLUE), + * display.getSystemColor(SWT.COLOR_WHITE)}, + * new int[] {100}, true); + * </pre> + * + * @param colors an array of Color that specifies the colors to appear in the gradient + * in order of appearance from left/top to right/bottom; The value <code>null</code> + * clears the background gradient; the value <code>null</code> can be used + * inside the array of Color to specify the background color. + * @param percents an array of integers between 0 and 100 specifying the percent of the width/height + * of the widget at which the color should change; the size of the percents + * array must be one less than the size of the colors array. + * @param vertical indicate the direction of the gradient. True is vertical and false is horizontal. + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * <li>ERROR_INVALID_ARGUMENT - if the values of colors and percents are not consistent</li> + * </ul> + * + * @since 3.0 + */ +public void setBackground(Color[] colors, int[] percents, boolean vertical) { + checkWidget(); + if (colors != null) { + if (percents == null || percents.length != colors.length - 1) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + if (getDisplay().getDepth() < 15) { + // Don't use gradients on low color displays + colors = new Color[] {colors[colors.length - 1]}; + percents = new int[] { }; + } + for (int i = 0; i < percents.length; i++) { + if (percents[i] < 0 || percents[i] > 100) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + if (i > 0 && percents[i] < percents[i-1]) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + } + } + + // Are these settings the same as before? + final Color background = getBackground(); + if (backgroundImage == null) { + if ((gradientColors != null) && (colors != null) && + (gradientColors.length == colors.length)) { + boolean same = false; + for (int i = 0; i < gradientColors.length; i++) { + same = (gradientColors[i] == colors[i]) || + ((gradientColors[i] == null) && (colors[i] == background)) || + ((gradientColors[i] == background) && (colors[i] == null)); + if (!same) break; + } + if (same) { + for (int i = 0; i < gradientPercents.length; i++) { + same = gradientPercents[i] == percents[i]; + if (!same) break; + } + } + if (same && this.gradientVertical == vertical) return; + } + } else { + backgroundImage = null; + } + // Store the new settings + if (colors == null) { + gradientColors = null; + gradientPercents = null; + gradientVertical = false; + } else { + gradientColors = new Color[colors.length]; + for (int i = 0; i < colors.length; ++i) + gradientColors[i] = (colors[i] != null) ? colors[i] : background; + gradientPercents = new int[percents.length]; + for (int i = 0; i < percents.length; ++i) + gradientPercents[i] = percents[i]; + gradientVertical = vertical; + } + // Refresh with the new settings + redraw(); +} +/** + * Set the image to be drawn in the background of the label. + * + * @param image the image to be drawn in the background + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void setBackground(Image image) { + checkWidget(); + if (image == backgroundImage) return; + if (image != null) { + gradientColors = null; + gradientPercents = null; + } + backgroundImage = image; + redraw(); + +} +public void setFont(Font font) { + super.setFont(font); + redraw(); +} +/** + * Set the label's Image. + * The value <code>null</code> clears it. + * + * @param image the image to be displayed in the label or null + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void setImage(Image image) { + checkWidget(); + if (image != this.image) { + this.image = image; + redraw(); + } +} +/** + * Set the label's text. + * The value <code>null</code> clears it. + * <p> + * Mnemonics are indicated by an '&' that causes the next + * character to be the mnemonic. When the user presses a + * key sequence that matches the mnemonic, focus is assigned + * to the control that follows the label. On most platforms, + * the mnemonic appears underlined but may be emphasised in a + * platform specific manner. The mnemonic indicator character + * '&' can be escaped by doubling it in the string, causing + * a single '&' to be displayed. + * </p> + * + * @param text the text to be displayed in the label or null + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void setText(String text) { + checkWidget(); + if (text == null) text = ""; //$NON-NLS-1$ + if (! text.equals(this.text)) { + this.text = text; + redraw(); + } +} +public void setToolTipText (String string) { + super.setToolTipText (string); + appToolTipText = super.getToolTipText(); +} +/** + * Shorten the given text <code>t</code> so that its length doesn't exceed + * the given width. The default implementation replaces characters in the + * center of the original string with an ellipsis ("..."). + * Override if you need a different strategy. + * + * @param gc the gc to use for text measurement + * @param t the text to shorten + * @param width the width to shorten the text to, in pixels + * @return the shortened text + */ +protected String shortenText(GC gc, String t, int width) { + if (t == null) return null; + int w = gc.textExtent(ELLIPSIS, DRAW_FLAGS).x; + if (width<=w) return t; + int l = t.length(); + int max = l/2; + int min = 0; + int mid = (max+min)/2 - 1; + if (mid <= 0) return t; + TextLayout layout = new TextLayout (getDisplay()); + layout.setText(t); + mid = validateOffset(layout, mid); + while (min < mid && mid < max) { + String s1 = t.substring(0, mid); + String s2 = t.substring(validateOffset(layout, l-mid), l); + int l1 = gc.textExtent(s1, DRAW_FLAGS).x; + int l2 = gc.textExtent(s2, DRAW_FLAGS).x; + if (l1+w+l2 > width) { + max = mid; + mid = validateOffset(layout, (max+min)/2); + } else if (l1+w+l2 < width) { + min = mid; + mid = validateOffset(layout, (max+min)/2); + } else { + min = max; + } + } + String result = mid == 0 ? t : t.substring(0, mid) + ELLIPSIS + t.substring(validateOffset(layout, l-mid), l); + layout.dispose(); + return result; +} +int validateOffset(TextLayout layout, int offset) { + int nextOffset = layout.getNextOffset(offset, SWT.MOVEMENT_CLUSTER); + if (nextOffset != offset) return layout.getPreviousOffset(nextOffset, SWT.MOVEMENT_CLUSTER); + return offset; +} +private String[] splitString(String text) { + String[] lines = new String[1]; + int start = 0, pos; + do { + pos = text.indexOf('\n', start); + if (pos == -1) { + lines[lines.length - 1] = text.substring(start); + } else { + boolean crlf = (pos > 0) && (text.charAt(pos - 1) == '\r'); + lines[lines.length - 1] = text.substring(start, pos - (crlf ? 1 : 0)); + start = pos + 1; + String[] newLines = new String[lines.length+1]; + System.arraycopy(lines, 0, newLines, 0, lines.length); + lines = newLines; + } + } while (pos != -1); + return lines; +} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CLayoutData.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CLayoutData.java new file mode 100644 index 0000000000..7af979e690 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CLayoutData.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (c) 2005 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.custom; + +import org.eclipse.swt.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.widgets.*; + +class CLayoutData { + + int defaultWidth = -1, defaultHeight = -1; + int currentWhint, currentHhint, currentWidth = -1, currentHeight = -1; + +Point computeSize (Control control, int wHint, int hHint, boolean flushCache) { + if (flushCache) flushCache(); + if (wHint == SWT.DEFAULT && hHint == SWT.DEFAULT) { + if (defaultWidth == -1 || defaultHeight == -1) { + Point size = control.computeSize (wHint, hHint, flushCache); + defaultWidth = size.x; + defaultHeight = size.y; + } + return new Point(defaultWidth, defaultHeight); + } + if (currentWidth == -1 || currentHeight == -1 || wHint != currentWhint || hHint != currentHhint) { + Point size = control.computeSize (wHint, hHint, flushCache); + currentWhint = wHint; + currentHhint = hHint; + currentWidth = size.x; + currentHeight = size.y; + } + return new Point(currentWidth, currentHeight); +} +void flushCache () { + defaultWidth = defaultHeight = -1; + currentWidth = currentHeight = -1; +} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabFolder.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabFolder.java new file mode 100755 index 0000000000..9d26184c96 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabFolder.java @@ -0,0 +1,4031 @@ +/******************************************************************************* + * 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.custom; + +import org.eclipse.swt.*; +import org.eclipse.swt.accessibility.*; +import org.eclipse.swt.events.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.widgets.*; + +/** + * + * Instances of this class implement the notebook user interface + * metaphor. It allows the user to select a notebook page from + * set of pages. + * <p> + * The item children that may be added to instances of this class + * must be of type <code>CTabItem</code>. + * <code>Control</code> children are created and then set into a + * tab item using <code>CTabItem#setControl</code>. + * </p><p> + * Note that although this class is a subclass of <code>Composite</code>, + * it does not make sense to set a layout on it. + * </p><p> + * <dl> + * <dt><b>Styles:</b></dt> + * <dd>CLOSE, TOP, BOTTOM, FLAT, BORDER, SINGLE, MULTI</dd> + * <dt><b>Events:</b></dt> + * <dd>Selection</dd> + * <dd>"CTabFolder2"</dd> + * </dl> + * <p> + * Note: Only one of the styles TOP and BOTTOM + * may be specified. + * </p><p> + * IMPORTANT: This class is <em>not</em> intended to be subclassed. + * </p> + * + * @see <a href="http://www.eclipse.org/swt/snippets/#ctabfolder">CTabFolder, CTabItem snippets</a> + * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: CustomControlExample</a> + * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> + * @noextend This class is not intended to be subclassed by clients. + */ + +public class CTabFolder extends Composite { + + /** + * marginWidth specifies the number of pixels of horizontal margin + * that will be placed along the left and right edges of the form. + * + * The default value is 0. + */ + public int marginWidth = 0; + /** + * marginHeight specifies the number of pixels of vertical margin + * that will be placed along the top and bottom edges of the form. + * + * The default value is 0. + */ + public int marginHeight = 0; + + /** + * A multiple of the tab height that specifies the minimum width to which a tab + * will be compressed before scrolling arrows are used to navigate the tabs. + * + * NOTE This field is badly named and can not be fixed for backwards compatibility. + * It should not be capitalized. + * + * @deprecated This field is no longer used. See setMinimumCharacters(int) + */ + public int MIN_TAB_WIDTH = 4; + + /** + * Color of innermost line of drop shadow border. + * + * NOTE This field is badly named and can not be fixed for backwards compatibility. + * It should be capitalized. + * + * @deprecated drop shadow border is no longer drawn in 3.0 + */ + public static RGB borderInsideRGB = new RGB (132, 130, 132); + /** + * Color of middle line of drop shadow border. + * + * NOTE This field is badly named and can not be fixed for backwards compatibility. + * It should be capitalized. + * + * @deprecated drop shadow border is no longer drawn in 3.0 + */ + public static RGB borderMiddleRGB = new RGB (143, 141, 138); + /** + * Color of outermost line of drop shadow border. + * + * NOTE This field is badly named and can not be fixed for backwards compatibility. + * It should be capitalized. + * + * @deprecated drop shadow border is no longer drawn in 3.0 + */ + public static RGB borderOutsideRGB = new RGB (171, 168, 165); + + /* sizing, positioning */ + int xClient, yClient; + boolean onBottom = false; + boolean single = false; + boolean simple = true; + int fixedTabHeight = SWT.DEFAULT; + int tabHeight; + int minChars = 20; + + /* item management */ + CTabItem items[] = new CTabItem[0]; + int firstIndex = -1; // index of the left most visible tab. + int selectedIndex = -1; + int[] priority = new int[0]; + boolean mru = false; + Listener listener; + boolean ignoreTraverse; + + /* External Listener management */ + CTabFolder2Listener[] folderListeners = new CTabFolder2Listener[0]; + // support for deprecated listener mechanism + CTabFolderListener[] tabListeners = new CTabFolderListener[0]; + + /* Selected item appearance */ + Image selectionBgImage; + Color[] selectionGradientColors; + int[] selectionGradientPercents; + boolean selectionGradientVertical; + Color selectionForeground; + Color selectionBackground; //selection fade end + Color selectionFadeStart; + + Color selectionHighlightGradientBegin = null; //null == no highlight + //Although we are given new colours all the time to show different states (active, etc), + //some of which may have a highlight and some not, we'd like to retain the highlight colours + //as a cache so that we can reuse them if we're again told to show the highlight. + //We are relying on the fact that only one tab state usually gets a highlight, so only + //a single cache is required. If that happens to not be true, cache simply becomes less effective, + //but we don't leak colours. + Color[] selectionHighlightGradientColorsCache = null; //null is a legal value, check on access + + /* Unselected item appearance */ + Color[] gradientColors; + int[] gradientPercents; + boolean gradientVertical; + boolean showUnselectedImage = true; + + // close, min/max and chevron buttons + Color fillColor; + boolean showClose = false; + boolean showUnselectedClose = true; + + Rectangle chevronRect = new Rectangle(0, 0, 0, 0); + int chevronImageState = NORMAL; + boolean showChevron = false; + Menu showMenu; + + boolean showMin = false; + Rectangle minRect = new Rectangle(0, 0, 0, 0); + boolean minimized = false; + int minImageState = NORMAL; + + boolean showMax = false; + Rectangle maxRect = new Rectangle(0, 0, 0, 0); + boolean maximized = false; + int maxImageState = NORMAL; + + Control topRight; + Rectangle topRightRect = new Rectangle(0, 0, 0, 0); + int topRightAlignment = SWT.RIGHT; + + // borders and shapes + int borderLeft = 0; + int borderRight = 0; + int borderTop = 0; + int borderBottom = 0; + + int highlight_margin = 0; + int highlight_header = 0; + + int[] curve; + int[] topCurveHighlightStart; + int[] topCurveHighlightEnd; + int curveWidth = 0; + int curveIndent = 0; + + // when disposing CTabFolder, don't try to layout the items or + // change the selection as each child is destroyed. + boolean inDispose = false; + + // keep track of size changes in order to redraw only affected area + // on Resize + Point oldSize; + Font oldFont; + + // internal constants + static final int DEFAULT_WIDTH = 64; + static final int DEFAULT_HEIGHT = 64; + static final int BUTTON_SIZE = 18; + + static final int[] TOP_LEFT_CORNER = new int[] {0,6, 1,5, 1,4, 4,1, 5,1, 6,0}; + + //TOP_LEFT_CORNER_HILITE is laid out in reverse (ie. top to bottom) + //so can fade in same direction as right swoop curve + static final int[] TOP_LEFT_CORNER_HILITE = new int[] {5,2, 4,2, 3,3, 2,4, 2,5, 1,6}; + + static final int[] TOP_RIGHT_CORNER = new int[] {-6,0, -5,1, -4,1, -1,4, -1,5, 0,6}; + static final int[] BOTTOM_LEFT_CORNER = new int[] {0,-6, 1,-5, 1,-4, 4,-1, 5,-1, 6,0}; + static final int[] BOTTOM_RIGHT_CORNER = new int[] {-6,0, -5,-1, -4,-1, -1,-4, -1,-5, 0,-6}; + + static final int[] SIMPLE_TOP_LEFT_CORNER = new int[] {0,2, 1,1, 2,0}; + static final int[] SIMPLE_TOP_RIGHT_CORNER = new int[] {-2,0, -1,1, 0,2}; + static final int[] SIMPLE_BOTTOM_LEFT_CORNER = new int[] {0,-2, 1,-1, 2,0}; + static final int[] SIMPLE_BOTTOM_RIGHT_CORNER = new int[] {-2,0, -1,-1, 0,-2}; + static final int[] SIMPLE_UNSELECTED_INNER_CORNER = new int[] {0,0}; + + static final int[] TOP_LEFT_CORNER_BORDERLESS = new int[] {0,6, 1,5, 1,4, 4,1, 5,1, 6,0}; + static final int[] TOP_RIGHT_CORNER_BORDERLESS = new int[] {-7,0, -6,1, -5,1, -2,4, -2,5, -1,6}; + static final int[] BOTTOM_LEFT_CORNER_BORDERLESS = new int[] {0,-6, 1,-6, 1,-5, 2,-4, 4,-2, 5,-1, 6,-1, 6,0}; + static final int[] BOTTOM_RIGHT_CORNER_BORDERLESS = new int[] {-7,0, -7,-1, -6,-1, -5,-2, -3,-4, -2,-5, -2,-6, -1,-6}; + + static final int[] SIMPLE_TOP_LEFT_CORNER_BORDERLESS = new int[] {0,2, 1,1, 2,0}; + static final int[] SIMPLE_TOP_RIGHT_CORNER_BORDERLESS= new int[] {-3,0, -2,1, -1,2}; + static final int[] SIMPLE_BOTTOM_LEFT_CORNER_BORDERLESS = new int[] {0,-3, 1,-2, 2,-1, 3,0}; + static final int[] SIMPLE_BOTTOM_RIGHT_CORNER_BORDERLESS = new int[] {-4,0, -3,-1, -2,-2, -1,-3}; + + static final int SELECTION_FOREGROUND = SWT.COLOR_LIST_FOREGROUND; + static final int SELECTION_BACKGROUND = SWT.COLOR_LIST_BACKGROUND; + static final int BORDER1_COLOR = SWT.COLOR_WIDGET_NORMAL_SHADOW; + static final int FOREGROUND = SWT.COLOR_WIDGET_FOREGROUND; + static final int BACKGROUND = SWT.COLOR_WIDGET_BACKGROUND; + static final int BUTTON_BORDER = SWT.COLOR_WIDGET_DARK_SHADOW; + static final int BUTTON_FILL = SWT.COLOR_LIST_BACKGROUND; + + static final int NONE = 0; + static final int NORMAL = 1; + static final int HOT = 2; + static final int SELECTED = 3; + static final RGB CLOSE_FILL = new RGB(252, 160, 160); + + static final int CHEVRON_CHILD_ID = 0; + static final int MINIMIZE_CHILD_ID = 1; + static final int MAXIMIZE_CHILD_ID = 2; + static final int EXTRA_CHILD_ID_COUNT = 3; + + +/** + * Constructs a new instance of this class given its parent + * and a style value describing its behavior and appearance. + * <p> + * The style value is either one of the style constants defined in + * class <code>SWT</code> which is applicable to instances of this + * class, or must be built by <em>bitwise OR</em>'ing together + * (that is, using the <code>int</code> "|" operator) two or more + * of those <code>SWT</code> style constants. The class description + * lists the style constants that are applicable to the class. + * Style bits are also inherited from superclasses. + * </p> + * + * @param parent a widget which will be the parent of the new instance (cannot be null) + * @param style the style of widget to construct + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the parent is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li> + * </ul> + * + * @see SWT#TOP + * @see SWT#BOTTOM + * @see SWT#FLAT + * @see SWT#BORDER + * @see SWT#SINGLE + * @see SWT#MULTI + * @see #getStyle() + */ +public CTabFolder(Composite parent, int style) { + super(parent, checkStyle (parent, style)); + init(style); +} + +void init(int style) { + super.setLayout(new CTabFolderLayout()); + int style2 = super.getStyle(); + oldFont = getFont(); + onBottom = (style2 & SWT.BOTTOM) != 0; + showClose = (style2 & SWT.CLOSE) != 0; +// showMin = (style2 & SWT.MIN) != 0; - conflicts with SWT.TOP +// showMax = (style2 & SWT.MAX) != 0; - conflicts with SWT.BOTTOM + single = (style2 & SWT.SINGLE) != 0; + borderLeft = borderRight = (style & SWT.BORDER) != 0 ? 1 : 0; + borderTop = onBottom ? borderLeft : 0; + borderBottom = onBottom ? 0 : borderLeft; + highlight_header = (style & SWT.FLAT) != 0 ? 1 : 3; + highlight_margin = (style & SWT.FLAT) != 0 ? 0 : 2; + //set up default colors + Display display = getDisplay(); + selectionForeground = display.getSystemColor(SELECTION_FOREGROUND); + selectionBackground = display.getSystemColor(SELECTION_BACKGROUND); + updateTabHeight(false); + + initAccessible(); + + // Add all listeners + listener = new Listener() { + public void handleEvent(Event event) { + switch (event.type) { + case SWT.Dispose: onDispose(event); break; + case SWT.DragDetect: onDragDetect(event); break; + case SWT.FocusIn: onFocus(event); break; + case SWT.FocusOut: onFocus(event); break; + case SWT.KeyDown: onKeyDown(event); break; + case SWT.MouseDoubleClick: onMouseDoubleClick(event); break; + case SWT.MouseDown: onMouse(event); break; + case SWT.MouseEnter: onMouse(event); break; + case SWT.MouseExit: onMouse(event); break; + case SWT.MouseMove: onMouse(event); break; + case SWT.MouseUp: onMouse(event); break; + case SWT.Paint: onPaint(event); break; + case SWT.Resize: onResize(); break; + case SWT.Traverse: onTraverse(event); break; + } + } + }; + + int[] folderEvents = new int[]{ + SWT.Dispose, + SWT.DragDetect, + SWT.FocusIn, + SWT.FocusOut, + SWT.KeyDown, + SWT.MouseDoubleClick, + SWT.MouseDown, + SWT.MouseEnter, + SWT.MouseExit, + SWT.MouseMove, + SWT.MouseUp, + SWT.Paint, + SWT.Resize, + SWT.Traverse, + }; + for (int i = 0; i < folderEvents.length; i++) { + addListener(folderEvents[i], listener); + } +} +static int checkStyle (Composite parent, int style) { + int mask = SWT.CLOSE | SWT.TOP | SWT.BOTTOM | SWT.FLAT | SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT | SWT.SINGLE | SWT.MULTI; + style = style & mask; + // TOP and BOTTOM are mutually exclusive. + // TOP is the default + if ((style & SWT.TOP) != 0) style = style & ~SWT.BOTTOM; + // SINGLE and MULTI are mutually exclusive. + // MULTI is the default + if ((style & SWT.MULTI) != 0) style = style & ~SWT.SINGLE; + // reduce the flash by not redrawing the entire area on a Resize event + style |= SWT.NO_REDRAW_RESIZE; + //TEMPORARY CODE + /* + * The default background on carbon and some GTK themes is not a solid color + * but a texture. To show the correct default background, we must allow + * the operating system to draw it and therefore, we can not use the + * NO_BACKGROUND style. The NO_BACKGROUND style is not required on platforms + * that use double buffering which is true in both of these cases. + */ + String platform = SWT.getPlatform(); + if ("cocoa".equals(platform) || "carbon".equals(platform) || "gtk".equals(platform)) return style; //$NON-NLS-1$ //$NON-NLS-2$ + + //TEMPORARY CODE + /* + * In Right To Left orientation on Windows, all GC calls that use a brush are drawing + * offset by one pixel. This results in some parts of the CTabFolder not drawing correctly. + * To alleviate some of the appearance problems, allow the OS to draw the background. + * This does not draw correctly but the result is less obviously wrong. + */ + if ((style & SWT.RIGHT_TO_LEFT) != 0) return style; + if ((parent.getStyle() & SWT.MIRRORED) != 0 && (style & SWT.LEFT_TO_RIGHT) == 0) return style; + + return style | SWT.NO_BACKGROUND; +} +static void fillRegion(GC gc, Region region) { + // NOTE: region passed in to this function will be modified + Region clipping = new Region(); + gc.getClipping(clipping); + region.intersect(clipping); + gc.setClipping(region); + gc.fillRectangle(region.getBounds()); + gc.setClipping(clipping); + clipping.dispose(); +} +/** + * + * Adds the listener to the collection of listeners who will + * be notified when a tab item is closed, minimized, maximized, + * restored, or to show the list of items that are not + * currently visible. + * + * @param listener the listener which should be notified + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the listener is null</li> + * </ul> + * + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li> + * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li> + * </ul> + * + * @see CTabFolder2Listener + * @see #removeCTabFolder2Listener(CTabFolder2Listener) + * + * @since 3.0 + */ +public void addCTabFolder2Listener(CTabFolder2Listener listener) { + checkWidget(); + if (listener == null) SWT.error (SWT.ERROR_NULL_ARGUMENT); + // add to array + CTabFolder2Listener[] newListeners = new CTabFolder2Listener[folderListeners.length + 1]; + System.arraycopy(folderListeners, 0, newListeners, 0, folderListeners.length); + folderListeners = newListeners; + folderListeners[folderListeners.length - 1] = listener; +} +/** + * Adds the listener to the collection of listeners who will + * be notified when a tab item is closed. + * + * @param listener the listener which should be notified + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the listener is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li> + * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li> + * </ul> + * + * @see CTabFolderListener + * @see #removeCTabFolderListener(CTabFolderListener) + * + * @deprecated use addCTabFolder2Listener(CTabFolder2Listener) + */ +public void addCTabFolderListener(CTabFolderListener listener) { + checkWidget(); + if (listener == null) SWT.error (SWT.ERROR_NULL_ARGUMENT); + // add to array + CTabFolderListener[] newTabListeners = new CTabFolderListener[tabListeners.length + 1]; + System.arraycopy(tabListeners, 0, newTabListeners, 0, tabListeners.length); + tabListeners = newTabListeners; + tabListeners[tabListeners.length - 1] = listener; + // display close button to be backwards compatible + if (!showClose) { + showClose = true; + updateItems(); + redraw(); + } +} +/** + * Adds the listener to the collection of listeners who will + * be notified when the user changes the receiver's selection, by sending + * it one of the messages defined in the <code>SelectionListener</code> + * interface. + * <p> + * <code>widgetSelected</code> is called when the user changes the selected tab. + * <code>widgetDefaultSelected</code> is not called. + * </p> + * + * @param listener the listener which should be notified when the user changes the receiver's selection + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the listener is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see SelectionListener + * @see #removeSelectionListener + * @see SelectionEvent + */ +public void addSelectionListener(SelectionListener listener) { + checkWidget(); + if (listener == null) { + SWT.error(SWT.ERROR_NULL_ARGUMENT); + } + TypedListener typedListener = new TypedListener(listener); + addListener(SWT.Selection, typedListener); + addListener(SWT.DefaultSelection, typedListener); +} +void antialias (int[] shape, RGB lineRGB, RGB innerRGB, RGB outerRGB, GC gc){ + // Don't perform anti-aliasing on Mac and WPF because the platform + // already does it. The simple style also does not require anti-aliasing. + if (simple) return; + String platform = SWT.getPlatform(); + if ("cocoa".equals(platform)) return; //$NON-NLS-1$ + if ("carbon".equals(platform)) return; //$NON-NLS-1$ + if ("wpf".equals(platform)) return; //$NON-NLS-1$ + // Don't perform anti-aliasing on low resolution displays + if (getDisplay().getDepth() < 15) return; + if (outerRGB != null) { + int index = 0; + boolean left = true; + int oldY = onBottom ? 0 : getSize().y; + int[] outer = new int[shape.length]; + for (int i = 0; i < shape.length/2; i++) { + if (left && (index + 3 < shape.length)) { + left = onBottom ? oldY <= shape[index+3] : oldY >= shape[index+3]; + oldY = shape[index+1]; + } + outer[index] = shape[index++] + (left ? -1 : +1); + outer[index] = shape[index++]; + } + RGB from = lineRGB; + RGB to = outerRGB; + int red = from.red + 2*(to.red - from.red)/3; + int green = from.green + 2*(to.green - from.green)/3; + int blue = from.blue + 2*(to.blue - from.blue)/3; + Color color = new Color(getDisplay(), red, green, blue); + gc.setForeground(color); + gc.drawPolyline(outer); + color.dispose(); + } + if (innerRGB != null) { + int[] inner = new int[shape.length]; + int index = 0; + boolean left = true; + int oldY = onBottom ? 0 : getSize().y; + for (int i = 0; i < shape.length/2; i++) { + if (left && (index + 3 < shape.length)) { + left = onBottom ? oldY <= shape[index+3] : oldY >= shape[index+3]; + oldY = shape[index+1]; + } + inner[index] = shape[index++] + (left ? +1 : -1); + inner[index] = shape[index++]; + } + RGB from = lineRGB; + RGB to = innerRGB; + int red = from.red + 2*(to.red - from.red)/3; + int green = from.green + 2*(to.green - from.green)/3; + int blue = from.blue + 2*(to.blue - from.blue)/3; + Color color = new Color(getDisplay(), red, green, blue); + gc.setForeground(color); + gc.drawPolyline(inner); + color.dispose(); + } +} +/* +* This class was not intended to be subclassed but this restriction +* cannot be enforced without breaking backward compatibility. +*/ +//protected void checkSubclass () { +// String name = getClass ().getName (); +// int index = name.lastIndexOf ('.'); +// if (!name.substring (0, index + 1).equals ("org.eclipse.swt.custom.")) { +// SWT.error (SWT.ERROR_INVALID_SUBCLASS); +// } +//} +public Rectangle computeTrim (int x, int y, int width, int height) { + checkWidget(); + int trimX = x - marginWidth - highlight_margin - borderLeft; + int trimWidth = width + borderLeft + borderRight + 2*marginWidth + 2*highlight_margin; + if (minimized) { + int trimY = onBottom ? y - borderTop : y - highlight_header - tabHeight - borderTop; + int trimHeight = borderTop + borderBottom + tabHeight + highlight_header; + return new Rectangle (trimX, trimY, trimWidth, trimHeight); + } else { + int trimY = onBottom ? y - marginHeight - highlight_margin - borderTop: y - marginHeight - highlight_header - tabHeight - borderTop; + int trimHeight = height + borderTop + borderBottom + 2*marginHeight + tabHeight + highlight_header + highlight_margin; + return new Rectangle (trimX, trimY, trimWidth, trimHeight); + } +} +void createItem (CTabItem item, int index) { + if (0 > index || index > getItemCount ())SWT.error (SWT.ERROR_INVALID_RANGE); + item.parent = this; + CTabItem[] newItems = new CTabItem [items.length + 1]; + System.arraycopy(items, 0, newItems, 0, index); + newItems[index] = item; + System.arraycopy(items, index, newItems, index + 1, items.length - index); + items = newItems; + if (selectedIndex >= index) selectedIndex ++; + int[] newPriority = new int[priority.length + 1]; + int next = 0, priorityIndex = priority.length; + for (int i = 0; i < priority.length; i++) { + if (!mru && priority[i] == index) { + priorityIndex = next++; + } + newPriority[next++] = priority[i] >= index ? priority[i] + 1 : priority[i]; + } + newPriority[priorityIndex] = index; + priority = newPriority; + + if (items.length == 1) { + if (!updateTabHeight(false)) updateItems(); + redraw(); + } else { + updateItems(); + redrawTabs(); + } +} +void destroyItem (CTabItem item) { + if (inDispose) return; + int index = indexOf(item); + if (index == -1) return; + + if (items.length == 1) { + items = new CTabItem[0]; + priority = new int[0]; + firstIndex = -1; + selectedIndex = -1; + + Control control = item.getControl(); + if (control != null && !control.isDisposed()) { + control.setVisible(false); + } + setToolTipText(null); + setButtonBounds(); + redraw(); + return; + } + + CTabItem[] newItems = new CTabItem [items.length - 1]; + System.arraycopy(items, 0, newItems, 0, index); + System.arraycopy(items, index + 1, newItems, index, items.length - index - 1); + items = newItems; + + int[] newPriority = new int[priority.length - 1]; + int next = 0; + for (int i = 0; i < priority.length; i++) { + if (priority [i] == index) continue; + newPriority[next++] = priority[i] > index ? priority[i] - 1 : priority [i]; + } + priority = newPriority; + + // move the selection if this item is selected + if (selectedIndex == index) { + Control control = item.getControl(); + selectedIndex = -1; + int nextSelection = mru ? priority[0] : Math.max(0, index - 1); + setSelection(nextSelection, true); + if (control != null && !control.isDisposed()) { + control.setVisible(false); + } + } else if (selectedIndex > index) { + selectedIndex --; + } + + updateItems(); + redrawTabs(); +} +void drawBackground(GC gc, int[] shape, boolean selected) { + Color defaultBackground = selected ? selectionBackground : getBackground(); + Image image = selected ? selectionBgImage : null; + Color[] colors = selected ? selectionGradientColors : gradientColors; + int[] percents = selected ? selectionGradientPercents : gradientPercents; + boolean vertical = selected ? selectionGradientVertical : gradientVertical; + Point size = getSize(); + int width = size.x; + int height = tabHeight + highlight_header; + int x = 0; + if (borderLeft > 0) { + x += 1; width -= 2; + } + int y = onBottom ? size.y - borderBottom - height : borderTop; + drawBackground(gc, shape, x, y, width, height, defaultBackground, image, colors, percents, vertical); +} +void drawBackground(GC gc, int[] shape, int x, int y, int width, int height, Color defaultBackground, Image image, Color[] colors, int[] percents, boolean vertical) { + Region clipping = new Region(); + gc.getClipping(clipping); + Region region = new Region(); + region.add(shape); + region.intersect(clipping); + gc.setClipping(region); + + if (image != null) { + // draw the background image in shape + gc.setBackground(defaultBackground); + gc.fillRectangle(x, y, width, height); + Rectangle imageRect = image.getBounds(); + gc.drawImage(image, imageRect.x, imageRect.y, imageRect.width, imageRect.height, x, y, width, height); + } else if (colors != null) { + // draw gradient + if (colors.length == 1) { + Color background = colors[0] != null ? colors[0] : defaultBackground; + gc.setBackground(background); + gc.fillRectangle(x, y, width, height); + } else { + if (vertical) { + if (onBottom) { + int pos = 0; + if (percents[percents.length - 1] < 100) { + pos = (100 - percents[percents.length - 1]) * height / 100; + gc.setBackground(defaultBackground); + gc.fillRectangle(x, y, width, pos); + } + Color lastColor = colors[colors.length-1]; + if (lastColor == null) lastColor = defaultBackground; + for (int i = percents.length-1; i >= 0; i--) { + gc.setForeground(lastColor); + lastColor = colors[i]; + if (lastColor == null) lastColor = defaultBackground; + gc.setBackground(lastColor); + int percentage = i > 0 ? percents[i] - percents[i-1] : percents[i]; + int gradientHeight = percentage * height / 100; + gc.fillGradientRectangle(x, y+pos, width, gradientHeight, true); + pos += gradientHeight; + } + } else { + Color lastColor = colors[0]; + if (lastColor == null) lastColor = defaultBackground; + int pos = 0; + for (int i = 0; i < percents.length; i++) { + gc.setForeground(lastColor); + lastColor = colors[i + 1]; + if (lastColor == null) lastColor = defaultBackground; + gc.setBackground(lastColor); + int percentage = i > 0 ? percents[i] - percents[i-1] : percents[i]; + int gradientHeight = percentage * height / 100; + gc.fillGradientRectangle(x, y+pos, width, gradientHeight, true); + pos += gradientHeight; + } + if (pos < height) { + gc.setBackground(defaultBackground); + gc.fillRectangle(x, pos, width, height-pos+1); + } + } + } else { //horizontal gradient + y = 0; + height = getSize().y; + Color lastColor = colors[0]; + if (lastColor == null) lastColor = defaultBackground; + int pos = 0; + for (int i = 0; i < percents.length; ++i) { + gc.setForeground(lastColor); + lastColor = colors[i + 1]; + if (lastColor == null) lastColor = defaultBackground; + gc.setBackground(lastColor); + int gradientWidth = (percents[i] * width / 100) - pos; + gc.fillGradientRectangle(x+pos, y, gradientWidth, height, false); + pos += gradientWidth; + } + if (pos < width) { + gc.setBackground(defaultBackground); + gc.fillRectangle(x+pos, y, width-pos, height); + } + } + } + } else { + // draw a solid background using default background in shape + if ((getStyle() & SWT.NO_BACKGROUND) != 0 || !defaultBackground.equals(getBackground())) { + gc.setBackground(defaultBackground); + gc.fillRectangle(x, y, width, height); + } + } + gc.setClipping(clipping); + clipping.dispose(); + region.dispose(); +} +void drawBody(Event event) { + GC gc = event.gc; + Point size = getSize(); + + // fill in body + if (!minimized){ + int width = size.x - borderLeft - borderRight - 2*highlight_margin; + int height = size.y - borderTop - borderBottom - tabHeight - highlight_header - highlight_margin; + // Draw highlight margin + if (highlight_margin > 0) { + int[] shape = null; + if (onBottom) { + int x1 = borderLeft; + int y1 = borderTop; + int x2 = size.x - borderRight; + int y2 = size.y - borderBottom - tabHeight - highlight_header; + shape = new int[] {x1,y1, x2,y1, x2,y2, x2-highlight_margin,y2, + x2-highlight_margin, y1+highlight_margin, x1+highlight_margin,y1+highlight_margin, + x1+highlight_margin,y2, x1,y2}; + } else { + int x1 = borderLeft; + int y1 = borderTop + tabHeight + highlight_header; + int x2 = size.x - borderRight; + int y2 = size.y - borderBottom; + shape = new int[] {x1,y1, x1+highlight_margin,y1, x1+highlight_margin,y2-highlight_margin, + x2-highlight_margin,y2-highlight_margin, x2-highlight_margin,y1, + x2,y1, x2,y2, x1,y2}; + } + // If horizontal gradient, show gradient across the whole area + if (selectedIndex != -1 && selectionGradientColors != null && selectionGradientColors.length > 1 && !selectionGradientVertical) { + drawBackground(gc, shape, true); + } else if (selectedIndex == -1 && gradientColors != null && gradientColors.length > 1 && !gradientVertical) { + drawBackground(gc, shape, false); + } else { + gc.setBackground(selectedIndex == -1 ? getBackground() : selectionBackground); + gc.fillPolygon(shape); + } + } + //Draw client area + if ((getStyle() & SWT.NO_BACKGROUND) != 0) { + gc.setBackground(getBackground()); + gc.fillRectangle(xClient - marginWidth, yClient - marginHeight, width, height); + } + } else { + if ((getStyle() & SWT.NO_BACKGROUND) != 0) { + int height = borderTop + tabHeight + highlight_header + borderBottom; + if (size.y > height) { + gc.setBackground(getParent().getBackground()); + gc.fillRectangle(0, height, size.x, size.y - height); + } + } + } + + //draw 1 pixel border around outside + if (borderLeft > 0) { + gc.setForeground(getDisplay().getSystemColor(BORDER1_COLOR)); + int x1 = borderLeft - 1; + int x2 = size.x - borderRight; + int y1 = onBottom ? borderTop - 1 : borderTop + tabHeight; + int y2 = onBottom ? size.y - tabHeight - borderBottom - 1 : size.y - borderBottom; + gc.drawLine(x1, y1, x1, y2); // left + gc.drawLine(x2, y1, x2, y2); // right + if (onBottom) { + gc.drawLine(x1, y1, x2, y1); // top + } else { + gc.drawLine(x1, y2, x2, y2); // bottom + } + } +} + +void drawChevron(GC gc) { + if (chevronRect.width == 0 || chevronRect.height == 0) return; + // draw chevron (10x7) + Display display = getDisplay(); + Point dpi = display.getDPI(); + int fontHeight = 72 * 10 / dpi.y; + FontData fd = getFont().getFontData()[0]; + fd.setHeight(fontHeight); + Font f = new Font(display, fd); + int fHeight = f.getFontData()[0].getHeight() * dpi.y / 72; + int indent = Math.max(2, (chevronRect.height - fHeight - 4) /2); + int x = chevronRect.x + 2; + int y = chevronRect.y + indent; + int count; + if (single) { + count = selectedIndex == -1 ? items.length : items.length - 1; + } else { + int showCount = 0; + while (showCount < priority.length && items[priority[showCount]].showing) { + showCount++; + } + count = items.length - showCount; + } + String chevronString = count > 99 ? "99+" : String.valueOf(count); //$NON-NLS-1$ + switch (chevronImageState) { + case NORMAL: { + Color chevronBorder = single ? getSelectionForeground() : getForeground(); + gc.setForeground(chevronBorder); + gc.setFont(f); + gc.drawLine(x,y, x+2,y+2); + gc.drawLine(x+2,y+2, x,y+4); + gc.drawLine(x+1,y, x+3,y+2); + gc.drawLine(x+3,y+2, x+1,y+4); + gc.drawLine(x+4,y, x+6,y+2); + gc.drawLine(x+6,y+2, x+5,y+4); + gc.drawLine(x+5,y, x+7,y+2); + gc.drawLine(x+7,y+2, x+4,y+4); + gc.drawString(chevronString, x+7, y+3, true); + break; + } + case HOT: { + gc.setForeground(display.getSystemColor(BUTTON_BORDER)); + gc.setBackground(display.getSystemColor(BUTTON_FILL)); + gc.setFont(f); + gc.fillRoundRectangle(chevronRect.x, chevronRect.y, chevronRect.width, chevronRect.height, 6, 6); + gc.drawRoundRectangle(chevronRect.x, chevronRect.y, chevronRect.width - 1, chevronRect.height - 1, 6, 6); + gc.drawLine(x,y, x+2,y+2); + gc.drawLine(x+2,y+2, x,y+4); + gc.drawLine(x+1,y, x+3,y+2); + gc.drawLine(x+3,y+2, x+1,y+4); + gc.drawLine(x+4,y, x+6,y+2); + gc.drawLine(x+6,y+2, x+5,y+4); + gc.drawLine(x+5,y, x+7,y+2); + gc.drawLine(x+7,y+2, x+4,y+4); + gc.drawString(chevronString, x+7, y+3, true); + break; + } + case SELECTED: { + gc.setForeground(display.getSystemColor(BUTTON_BORDER)); + gc.setBackground(display.getSystemColor(BUTTON_FILL)); + gc.setFont(f); + gc.fillRoundRectangle(chevronRect.x, chevronRect.y, chevronRect.width, chevronRect.height, 6, 6); + gc.drawRoundRectangle(chevronRect.x, chevronRect.y, chevronRect.width - 1, chevronRect.height - 1, 6, 6); + gc.drawLine(x+1,y+1, x+3,y+3); + gc.drawLine(x+3,y+3, x+1,y+5); + gc.drawLine(x+2,y+1, x+4,y+3); + gc.drawLine(x+4,y+3, x+2,y+5); + gc.drawLine(x+5,y+1, x+7,y+3); + gc.drawLine(x+7,y+3, x+6,y+5); + gc.drawLine(x+6,y+1, x+8,y+3); + gc.drawLine(x+8,y+3, x+5,y+5); + gc.drawString(chevronString, x+8, y+4, true); + break; + } + } + f.dispose(); +} +void drawMaximize(GC gc) { + if (maxRect.width == 0 || maxRect.height == 0) return; + Display display = getDisplay(); + // 5x4 or 7x9 + int x = maxRect.x + (CTabFolder.BUTTON_SIZE - 10)/2; + int y = maxRect.y + 3; + + gc.setForeground(display.getSystemColor(BUTTON_BORDER)); + gc.setBackground(display.getSystemColor(BUTTON_FILL)); + + switch (maxImageState) { + case NORMAL: { + if (!maximized) { + gc.fillRectangle(x, y, 9, 9); + gc.drawRectangle(x, y, 9, 9); + gc.drawLine(x+1, y+2, x+8, y+2); + } else { + gc.fillRectangle(x, y+3, 5, 4); + gc.fillRectangle(x+2, y, 5, 4); + gc.drawRectangle(x, y+3, 5, 4); + gc.drawRectangle(x+2, y, 5, 4); + gc.drawLine(x+3, y+1, x+6, y+1); + gc.drawLine(x+1, y+4, x+4, y+4); + } + break; + } + case HOT: { + gc.fillRoundRectangle(maxRect.x, maxRect.y, maxRect.width, maxRect.height, 6, 6); + gc.drawRoundRectangle(maxRect.x, maxRect.y, maxRect.width - 1, maxRect.height - 1, 6, 6); + if (!maximized) { + gc.fillRectangle(x, y, 9, 9); + gc.drawRectangle(x, y, 9, 9); + gc.drawLine(x+1, y+2, x+8, y+2); + } else { + gc.fillRectangle(x, y+3, 5, 4); + gc.fillRectangle(x+2, y, 5, 4); + gc.drawRectangle(x, y+3, 5, 4); + gc.drawRectangle(x+2, y, 5, 4); + gc.drawLine(x+3, y+1, x+6, y+1); + gc.drawLine(x+1, y+4, x+4, y+4); + } + break; + } + case SELECTED: { + gc.fillRoundRectangle(maxRect.x, maxRect.y, maxRect.width, maxRect.height, 6, 6); + gc.drawRoundRectangle(maxRect.x, maxRect.y, maxRect.width - 1, maxRect.height - 1, 6, 6); + if (!maximized) { + gc.fillRectangle(x+1, y+1, 9, 9); + gc.drawRectangle(x+1, y+1, 9, 9); + gc.drawLine(x+2, y+3, x+9, y+3); + } else { + gc.fillRectangle(x+1, y+4, 5, 4); + gc.fillRectangle(x+3, y+1, 5, 4); + gc.drawRectangle(x+1, y+4, 5, 4); + gc.drawRectangle(x+3, y+1, 5, 4); + gc.drawLine(x+4, y+2, x+7, y+2); + gc.drawLine(x+2, y+5, x+5, y+5); + } + break; + } + } +} +void drawMinimize(GC gc) { + if (minRect.width == 0 || minRect.height == 0) return; + Display display = getDisplay(); + // 5x4 or 9x3 + int x = minRect.x + (BUTTON_SIZE - 10)/2; + int y = minRect.y + 3; + + gc.setForeground(display.getSystemColor(BUTTON_BORDER)); + gc.setBackground(display.getSystemColor(BUTTON_FILL)); + + switch (minImageState) { + case NORMAL: { + if (!minimized) { + gc.fillRectangle(x, y, 9, 3); + gc.drawRectangle(x, y, 9, 3); + } else { + gc.fillRectangle(x, y+3, 5, 4); + gc.fillRectangle(x+2, y, 5, 4); + gc.drawRectangle(x, y+3, 5, 4); + gc.drawRectangle(x+2, y, 5, 4); + gc.drawLine(x+3, y+1, x+6, y+1); + gc.drawLine(x+1, y+4, x+4, y+4); + } + break; + } + case HOT: { + gc.fillRoundRectangle(minRect.x, minRect.y, minRect.width, minRect.height, 6, 6); + gc.drawRoundRectangle(minRect.x, minRect.y, minRect.width - 1, minRect.height - 1, 6, 6); + if (!minimized) { + gc.fillRectangle(x, y, 9, 3); + gc.drawRectangle(x, y, 9, 3); + } else { + gc.fillRectangle(x, y+3, 5, 4); + gc.fillRectangle(x+2, y, 5, 4); + gc.drawRectangle(x, y+3, 5, 4); + gc.drawRectangle(x+2, y, 5, 4); + gc.drawLine(x+3, y+1, x+6, y+1); + gc.drawLine(x+1, y+4, x+4, y+4); + } + break; + } + case SELECTED: { + gc.fillRoundRectangle(minRect.x, minRect.y, minRect.width, minRect.height, 6, 6); + gc.drawRoundRectangle(minRect.x, minRect.y, minRect.width - 1, minRect.height - 1, 6, 6); + if (!minimized) { + gc.fillRectangle(x+1, y+1, 9, 3); + gc.drawRectangle(x+1, y+1, 9, 3); + } else { + gc.fillRectangle(x+1, y+4, 5, 4); + gc.fillRectangle(x+3, y+1, 5, 4); + gc.drawRectangle(x+1, y+4, 5, 4); + gc.drawRectangle(x+3, y+1, 5, 4); + gc.drawLine(x+4, y+2, x+7, y+2); + gc.drawLine(x+2, y+5, x+5, y+5); + } + break; + } + } +} +void drawTabArea(Event event) { + GC gc = event.gc; + Point size = getSize(); + int[] shape = null; + Color borderColor = getDisplay().getSystemColor(BORDER1_COLOR); + + if (tabHeight == 0) { + int style = getStyle(); + if ((style & SWT.FLAT) != 0 && (style & SWT.BORDER) == 0) return; + int x1 = borderLeft - 1; + int x2 = size.x - borderRight; + int y1 = onBottom ? size.y - borderBottom - highlight_header - 1 : borderTop + highlight_header; + int y2 = onBottom ? size.y - borderBottom : borderTop; + if (borderLeft > 0 && onBottom) y2 -= 1; + + shape = new int[] {x1, y1, x1,y2, x2,y2, x2,y1}; + + // If horizontal gradient, show gradient across the whole area + if (selectedIndex != -1 && selectionGradientColors != null && selectionGradientColors.length > 1 && !selectionGradientVertical) { + drawBackground(gc, shape, true); + } else if (selectedIndex == -1 && gradientColors != null && gradientColors.length > 1 && !gradientVertical) { + drawBackground(gc, shape, false); + } else { + gc.setBackground(selectedIndex == -1 ? getBackground() : selectionBackground); + gc.fillPolygon(shape); + } + + //draw 1 pixel border + if (borderLeft > 0) { + gc.setForeground(borderColor); + gc.drawPolyline(shape); + } + return; + } + + int x = Math.max(0, borderLeft - 1); + int y = onBottom ? size.y - borderBottom - tabHeight : borderTop; + int width = size.x - borderLeft - borderRight + 1; + int height = tabHeight - 1; + + // Draw Tab Header + if (onBottom) { + int[] left, right; + if ((getStyle() & SWT.BORDER) != 0) { + left = simple ? SIMPLE_BOTTOM_LEFT_CORNER : BOTTOM_LEFT_CORNER; + right = simple ? SIMPLE_BOTTOM_RIGHT_CORNER : BOTTOM_RIGHT_CORNER; + } else { + left = simple ? SIMPLE_BOTTOM_LEFT_CORNER_BORDERLESS : BOTTOM_LEFT_CORNER_BORDERLESS; + right = simple ? SIMPLE_BOTTOM_RIGHT_CORNER_BORDERLESS : BOTTOM_RIGHT_CORNER_BORDERLESS; + } + shape = new int[left.length + right.length + 4]; + int index = 0; + shape[index++] = x; + shape[index++] = y-highlight_header; + for (int i = 0; i < left.length/2; i++) { + shape[index++] = x+left[2*i]; + shape[index++] = y+height+left[2*i+1]; + if (borderLeft == 0) shape[index-1] += 1; + } + for (int i = 0; i < right.length/2; i++) { + shape[index++] = x+width+right[2*i]; + shape[index++] = y+height+right[2*i+1]; + if (borderLeft == 0) shape[index-1] += 1; + } + shape[index++] = x+width; + shape[index++] = y-highlight_header; + } else { + int[] left, right; + if ((getStyle() & SWT.BORDER) != 0) { + left = simple ? SIMPLE_TOP_LEFT_CORNER : TOP_LEFT_CORNER; + right = simple ? SIMPLE_TOP_RIGHT_CORNER : TOP_RIGHT_CORNER; + } else { + left = simple ? SIMPLE_TOP_LEFT_CORNER_BORDERLESS : TOP_LEFT_CORNER_BORDERLESS; + right = simple ? SIMPLE_TOP_RIGHT_CORNER_BORDERLESS : TOP_RIGHT_CORNER_BORDERLESS; + } + shape = new int[left.length + right.length + 4]; + int index = 0; + shape[index++] = x; + shape[index++] = y+height+highlight_header + 1; + for (int i = 0; i < left.length/2; i++) { + shape[index++] = x+left[2*i]; + shape[index++] = y+left[2*i+1]; + } + for (int i = 0; i < right.length/2; i++) { + shape[index++] = x+width+right[2*i]; + shape[index++] = y+right[2*i+1]; + } + shape[index++] = x+width; + shape[index++] = y+height+highlight_header + 1; + } + // Fill in background + boolean bkSelected = single && selectedIndex != -1; + drawBackground(gc, shape, bkSelected); + // Fill in parent background for non-rectangular shape + Region r = new Region(); + r.add(new Rectangle(x, y, width + 1, height + 1)); + r.subtract(shape); + gc.setBackground(getParent().getBackground()); + fillRegion(gc, r); + r.dispose(); + + // Draw the unselected tabs. + if (!single) { + for (int i=0; i < items.length; i++) { + if (i != selectedIndex && event.getBounds().intersects(items[i].getBounds())) { + items[i].onPaint(gc, false); + } + } + } + + // Draw selected tab + if (selectedIndex != -1) { + CTabItem item = items[selectedIndex]; + item.onPaint(gc, true); + } else { + // if no selected tab - draw line across bottom of all tabs + int x1 = borderLeft; + int y1 = (onBottom) ? size.y - borderBottom - tabHeight - 1 : borderTop + tabHeight; + int x2 = size.x - borderRight; + gc.setForeground(borderColor); + gc.drawLine(x1, y1, x2, y1); + } + + // Draw Buttons + drawChevron(gc); + drawMinimize(gc); + drawMaximize(gc); + + // Draw border line + if (borderLeft > 0) { + RGB outside = getParent().getBackground().getRGB(); + antialias(shape, borderColor.getRGB(), null, outside, gc); + gc.setForeground(borderColor); + gc.drawPolyline(shape); + } +} +/** + * Returns <code>true</code> if the receiver's border is visible. + * + * @return the receiver's border visibility state + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.0 + */ +public boolean getBorderVisible() { + checkWidget(); + return borderLeft == 1; +} +public Rectangle getClientArea() { + checkWidget(); + if (minimized) return new Rectangle(xClient, yClient, 0, 0); + Point size = getSize(); + int width = size.x - borderLeft - borderRight - 2*marginWidth - 2*highlight_margin; + int height = size.y - borderTop - borderBottom - 2*marginHeight - highlight_margin - highlight_header; + height -= tabHeight; + return new Rectangle(xClient, yClient, width, height); +} +Color getFillColor() { + if (fillColor == null) { + fillColor = new Color(getDisplay(), CTabFolder.CLOSE_FILL); + } + return fillColor; +} +/** + * Return the tab that is located at the specified index. + * + * @param index the index of the tab item + * @return the item at the specified index + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_RANGE - if the index is out of range</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li> + * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li> + * </ul> + */ +public CTabItem getItem (int index) { + //checkWidget(); + if (index < 0 || index >= items.length) + SWT.error(SWT.ERROR_INVALID_RANGE); + return items [index]; +} +/** + * Gets the item at a point in the widget. + * + * @param pt the point in coordinates relative to the CTabFolder + * @return the item at a point or null + * + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li> + * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li> + * </ul> + */ +public CTabItem getItem (Point pt) { + //checkWidget(); + if (items.length == 0) return null; + Point size = getSize(); + if (size.x <= borderLeft + borderRight) return null; + if (showChevron && chevronRect.contains(pt)) return null; + for (int i = 0; i < priority.length; i++) { + CTabItem item = items[priority[i]]; + Rectangle rect = item.getBounds(); + if (rect.contains(pt)) return item; + } + return null; +} +/** + * Return the number of tabs in the folder. + * + * @return the number of tabs in the folder + * + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li> + * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li> + * </ul> + */ +public int getItemCount(){ + //checkWidget(); + return items.length; +} +/** + * Return the tab items. + * + * @return the tab items + * + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li> + * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li> + * </ul> + */ +public CTabItem [] getItems() { + //checkWidget(); + CTabItem[] tabItems = new CTabItem [items.length]; + System.arraycopy(items, 0, tabItems, 0, items.length); + return tabItems; +} +/* + * Return the lowercase of the first non-'&' character following + * an '&' character in the given string. If there are no '&' + * characters in the given string, return '\0'. + */ +char _findMnemonic (String string) { + if (string == null) return '\0'; + int index = 0; + int length = string.length (); + do { + while (index < length && string.charAt (index) != '&') index++; + if (++index >= length) return '\0'; + if (string.charAt (index) != '&') return Character.toLowerCase (string.charAt (index)); + index++; + } while (index < length); + return '\0'; +} +String stripMnemonic (String string) { + int index = 0; + int length = string.length (); + do { + while ((index < length) && (string.charAt (index) != '&')) index++; + if (++index >= length) return string; + if (string.charAt (index) != '&') { + return string.substring(0, index-1) + string.substring(index, length); + } + index++; + } while (index < length); + return string; +} +/** + * Returns <code>true</code> if the receiver is minimized. + * + * @return the receiver's minimized state + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.0 + */ +public boolean getMinimized() { + checkWidget(); + return minimized; +} +/** + * Returns <code>true</code> if the minimize button + * is visible. + * + * @return the visibility of the minimized button + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.0 + */ +public boolean getMinimizeVisible() { + checkWidget(); + return showMin; +} +/** + * Returns the number of characters that will + * appear in a fully compressed tab. + * + * @return number of characters that will appear in a fully compressed tab + * + * @since 3.0 + */ +public int getMinimumCharacters() { + checkWidget(); + return minChars; +} +/** + * Returns <code>true</code> if the receiver is maximized. + * <p> + * + * @return the receiver's maximized state + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.0 + */ +public boolean getMaximized() { + checkWidget(); + return maximized; +} +/** + * Returns <code>true</code> if the maximize button + * is visible. + * + * @return the visibility of the maximized button + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.0 + */ +public boolean getMaximizeVisible() { + checkWidget(); + return showMax; +} +/** + * Returns <code>true</code> if the receiver displays most + * recently used tabs and <code>false</code> otherwise. + * <p> + * When there is not enough horizontal space to show all the tabs, + * by default, tabs are shown sequentially from left to right in + * order of their index. When the MRU visibility is turned on, + * the tabs that are visible will be the tabs most recently selected. + * Tabs will still maintain their left to right order based on index + * but only the most recently selected tabs are visible. + * <p> + * For example, consider a CTabFolder that contains "Tab 1", "Tab 2", + * "Tab 3" and "Tab 4" (in order by index). The user selects + * "Tab 1" and then "Tab 3". If the CTabFolder is now + * compressed so that only two tabs are visible, by default, + * "Tab 2" and "Tab 3" will be shown ("Tab 3" since it is currently + * selected and "Tab 2" because it is the previous item in index order). + * If MRU visibility is enabled, the two visible tabs will be "Tab 1" + * and "Tab 3" (in that order from left to right).</p> + * + * @return the receiver's header's visibility state + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.1 + */ +public boolean getMRUVisible() { + checkWidget(); + return mru; +} +int getRightItemEdge (){ + int x = getSize().x - borderRight - 3; + if (showMin) x -= BUTTON_SIZE; + if (showMax) x -= BUTTON_SIZE; + if (showChevron) x -= 3*BUTTON_SIZE/2; + if (topRight != null && topRightAlignment != SWT.FILL) { + Point rightSize = topRight.computeSize(SWT.DEFAULT, SWT.DEFAULT); + x -= rightSize.x + 3; + } + return Math.max(0, x); +} +/** + * Return the selected tab item, or null if there is no selection. + * + * @return the selected tab item, or null if none has been selected + * + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li> + * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li> + * </ul> + */ +public CTabItem getSelection() { + //checkWidget(); + if (selectedIndex == -1) return null; + return items[selectedIndex]; +} +/** + * Returns the receiver's selection background color. + * + * @return the selection background color of the receiver + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.0 + */ +public Color getSelectionBackground() { + checkWidget(); + return selectionBackground; +} +/** + * Returns the receiver's selection foreground color. + * + * @return the selection foreground color of the receiver + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.0 + */ +public Color getSelectionForeground() { + checkWidget(); + return selectionForeground; +} +/** + * Return the index of the selected tab item, or -1 if there + * is no selection. + * + * @return the index of the selected tab item or -1 + * + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li> + * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li> + * </ul> + */ +public int getSelectionIndex() { + //checkWidget(); + return selectedIndex; +} +/** + * Returns <code>true</code> if the CTabFolder is rendered + * with a simple, traditional shape. + * + * @return <code>true</code> if the CTabFolder is rendered with a simple shape + * + * @since 3.0 + */ +public boolean getSimple() { + checkWidget(); + return simple; +} +/** + * Returns <code>true</code> if the CTabFolder only displays the selected tab + * and <code>false</code> if the CTabFolder displays multiple tabs. + * + * @return <code>true</code> if the CTabFolder only displays the selected tab and <code>false</code> if the CTabFolder displays multiple tabs + * + * @since 3.0 + */ +public boolean getSingle() { + checkWidget(); + return single; +} + +public int getStyle() { + int style = super.getStyle(); + style &= ~(SWT.TOP | SWT.BOTTOM); + style |= onBottom ? SWT.BOTTOM : SWT.TOP; + style &= ~(SWT.SINGLE | SWT.MULTI); + style |= single ? SWT.SINGLE : SWT.MULTI; + if (borderLeft != 0) style |= SWT.BORDER; + style &= ~SWT.CLOSE; + if (showClose) style |= SWT.CLOSE; + return style; +} +/** + * Returns the height of the tab + * + * @return the height of the tab + * + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li> + * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li> + * </ul> + */ +public int getTabHeight(){ + checkWidget(); + if (fixedTabHeight != SWT.DEFAULT) return fixedTabHeight; + return tabHeight - 1; // -1 for line drawn across top of tab +} +/** + * Returns the position of the tab. Possible values are SWT.TOP or SWT.BOTTOM. + * + * @return the position of the tab + * + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li> + * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li> + * </ul> + */ +public int getTabPosition(){ + checkWidget(); + return onBottom ? SWT.BOTTOM : SWT.TOP; +} +/** + * Returns the control in the top right corner of the tab folder. + * Typically this is a close button or a composite with a menu and close button. + * + * @return the control in the top right corner of the tab folder or null + * + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li> + * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li> + * </ul> + * + * @since 2.1 + */ +public Control getTopRight() { + checkWidget(); + return topRight; +} +/** + * Returns <code>true</code> if the close button appears + * when the user hovers over an unselected tabs. + * + * @return <code>true</code> if the close button appears on unselected tabs + * + * @since 3.0 + */ +public boolean getUnselectedCloseVisible() { + checkWidget(); + return showUnselectedClose; +} +/** + * Returns <code>true</code> if an image appears + * in unselected tabs. + * + * @return <code>true</code> if an image appears in unselected tabs + * + * @since 3.0 + */ +public boolean getUnselectedImageVisible() { + checkWidget(); + return showUnselectedImage; +} +/** + * Return the index of the specified tab or -1 if the tab is not + * in the receiver. + * + * @param item the tab item for which the index is required + * + * @return the index of the specified tab item or -1 + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the listener is null</li> + * </ul> + * + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li> + * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li> + * </ul> + */ +public int indexOf(CTabItem item) { + checkWidget(); + if (item == null) { + SWT.error(SWT.ERROR_NULL_ARGUMENT); + } + for (int i = 0; i < items.length; i++) { + if (items[i] == item) return i; + } + return -1; +} +void initAccessible() { + final Accessible accessible = getAccessible(); + accessible.addAccessibleListener(new AccessibleAdapter() { + public void getName(AccessibleEvent e) { + String name = null; + int childID = e.childID; + if (childID >= 0 && childID < items.length) { + name = stripMnemonic(items[childID].getText()); + } else if (childID == items.length + CHEVRON_CHILD_ID) { + name = SWT.getMessage("SWT_ShowList"); //$NON-NLS-1$ + } else if (childID == items.length + MINIMIZE_CHILD_ID) { + name = minimized ? SWT.getMessage("SWT_Restore") : SWT.getMessage("SWT_Minimize"); //$NON-NLS-1$ //$NON-NLS-2$ + } else if (childID == items.length + MAXIMIZE_CHILD_ID) { + name = maximized ? SWT.getMessage("SWT_Restore") : SWT.getMessage("SWT_Maximize"); //$NON-NLS-1$ //$NON-NLS-2$ + } + e.result = name; + } + + public void getHelp(AccessibleEvent e) { + String help = null; + int childID = e.childID; + if (childID == ACC.CHILDID_SELF) { + help = getToolTipText(); + } else if (childID >= 0 && childID < items.length) { + help = items[childID].getToolTipText(); + } + e.result = help; + } + + public void getKeyboardShortcut(AccessibleEvent e) { + String shortcut = null; + int childID = e.childID; + if (childID >= 0 && childID < items.length) { + String text = items[childID].getText(); + if (text != null) { + char mnemonic = _findMnemonic(text); + if (mnemonic != '\0') { + shortcut = "Alt+"+mnemonic; //$NON-NLS-1$ + } + } + } + e.result = shortcut; + } + }); + + accessible.addAccessibleControlListener(new AccessibleControlAdapter() { + public void getChildAtPoint(AccessibleControlEvent e) { + Point testPoint = toControl(e.x, e.y); + int childID = ACC.CHILDID_NONE; + for (int i = 0; i < items.length; i++) { + if (items[i].getBounds().contains(testPoint)) { + childID = i; + break; + } + } + if (childID == ACC.CHILDID_NONE) { + if (showChevron && chevronRect.contains(testPoint)) { + childID = items.length + CHEVRON_CHILD_ID; + } else if (showMin && minRect.contains(testPoint)) { + childID = items.length + MINIMIZE_CHILD_ID; + } else if (showMax && maxRect.contains(testPoint)) { + childID = items.length + MAXIMIZE_CHILD_ID; + } else { + Rectangle location = getBounds(); + location.height = location.height - getClientArea().height; + if (location.contains(testPoint)) { + childID = ACC.CHILDID_SELF; + } + } + } + e.childID = childID; + } + + public void getLocation(AccessibleControlEvent e) { + Rectangle location = null; + Point pt = null; + int childID = e.childID; + if (childID == ACC.CHILDID_SELF) { + location = getBounds(); + pt = getParent().toDisplay(location.x, location.y); + } else { + if (childID >= 0 && childID < items.length && items[childID].isShowing()) { + location = items[childID].getBounds(); + } else if (showChevron && childID == items.length + CHEVRON_CHILD_ID) { + location = chevronRect; + } else if (showMin && childID == items.length + MINIMIZE_CHILD_ID) { + location = minRect; + } else if (showMax && childID == items.length + MAXIMIZE_CHILD_ID) { + location = maxRect; + } + if (location != null) { + pt = toDisplay(location.x, location.y); + } + } + if (location != null && pt != null) { + e.x = pt.x; + e.y = pt.y; + e.width = location.width; + e.height = location.height; + } + } + + public void getChildCount(AccessibleControlEvent e) { + e.detail = items.length + EXTRA_CHILD_ID_COUNT; + } + + public void getDefaultAction(AccessibleControlEvent e) { + String action = null; + int childID = e.childID; + if (childID >= 0 && childID < items.length) { + action = SWT.getMessage ("SWT_Switch"); //$NON-NLS-1$ + } + if (childID >= items.length && childID < items.length + EXTRA_CHILD_ID_COUNT) { + action = SWT.getMessage ("SWT_Press"); //$NON-NLS-1$ + } + e.result = action; + } + + public void getFocus(AccessibleControlEvent e) { + int childID = ACC.CHILDID_NONE; + if (isFocusControl()) { + if (selectedIndex == -1) { + childID = ACC.CHILDID_SELF; + } else { + childID = selectedIndex; + } + } + e.childID = childID; + } + + public void getRole(AccessibleControlEvent e) { + int role = 0; + int childID = e.childID; + if (childID == ACC.CHILDID_SELF) { + role = ACC.ROLE_TABFOLDER; + } else if (childID >= 0 && childID < items.length) { + role = ACC.ROLE_TABITEM; + } else if (childID >= items.length && childID < items.length + EXTRA_CHILD_ID_COUNT) { + role = ACC.ROLE_PUSHBUTTON; + } + e.detail = role; + } + + public void getSelection(AccessibleControlEvent e) { + e.childID = (selectedIndex == -1) ? ACC.CHILDID_NONE : selectedIndex; + } + + public void getState(AccessibleControlEvent e) { + int state = 0; + int childID = e.childID; + if (childID == ACC.CHILDID_SELF) { + state = ACC.STATE_NORMAL; + } else if (childID >= 0 && childID < items.length) { + state = ACC.STATE_SELECTABLE; + if (isFocusControl()) { + state |= ACC.STATE_FOCUSABLE; + } + if (selectedIndex == childID) { + state |= ACC.STATE_SELECTED; + if (isFocusControl()) { + state |= ACC.STATE_FOCUSED; + } + } + } else if (childID == items.length + CHEVRON_CHILD_ID) { + state = showChevron ? ACC.STATE_NORMAL : ACC.STATE_INVISIBLE; + } else if (childID == items.length + MINIMIZE_CHILD_ID) { + state = showMin ? ACC.STATE_NORMAL : ACC.STATE_INVISIBLE; + } else if (childID == items.length + MAXIMIZE_CHILD_ID) { + state = showMax ? ACC.STATE_NORMAL : ACC.STATE_INVISIBLE; + } + e.detail = state; + } + + public void getChildren(AccessibleControlEvent e) { + int childIdCount = items.length + EXTRA_CHILD_ID_COUNT; + Object[] children = new Object[childIdCount]; + for (int i = 0; i < childIdCount; i++) { + children[i] = new Integer(i); + } + e.children = children; + } + }); + + addListener(SWT.Selection, new Listener() { + public void handleEvent(Event event) { + if (isFocusControl()) { + if (selectedIndex == -1) { + accessible.setFocus(ACC.CHILDID_SELF); + } else { + accessible.setFocus(selectedIndex); + } + } + } + }); + + addListener(SWT.FocusIn, new Listener() { + public void handleEvent(Event event) { + if (selectedIndex == -1) { + accessible.setFocus(ACC.CHILDID_SELF); + } else { + accessible.setFocus(selectedIndex); + } + } + }); +} +void onKeyDown (Event event) { + switch (event.keyCode) { + case SWT.ARROW_LEFT: + case SWT.ARROW_RIGHT: + int count = items.length; + if (count == 0) return; + if (selectedIndex == -1) return; + int leadKey = (getStyle() & SWT.RIGHT_TO_LEFT) != 0 ? SWT.ARROW_RIGHT : SWT.ARROW_LEFT; + int offset = event.keyCode == leadKey ? -1 : 1; + int index; + if (!mru) { + index = selectedIndex + offset; + } else { + int[] visible = new int[items.length]; + int idx = 0; + int current = -1; + for (int i = 0; i < items.length; i++) { + if (items[i].showing) { + if (i == selectedIndex) current = idx; + visible [idx++] = i; + } + } + if (current + offset >= 0 && current + offset < idx){ + index = visible [current + offset]; + } else { + if (showChevron) { + CTabFolderEvent e = new CTabFolderEvent(this); + e.widget = this; + e.time = event.time; + e.x = chevronRect.x; + e.y = chevronRect.y; + e.width = chevronRect.width; + e.height = chevronRect.height; + e.doit = true; + for (int i = 0; i < folderListeners.length; i++) { + folderListeners[i].showList(e); + } + if (e.doit && !isDisposed()) { + showList(chevronRect); + } + } + return; + } + } + if (index < 0 || index >= count) return; + setSelection (index, true); + forceFocus(); + } +} +void onDispose(Event event) { + removeListener(SWT.Dispose, listener); + notifyListeners(SWT.Dispose, event); + event.type = SWT.None; + /* + * Usually when an item is disposed, destroyItem will change the size of the items array, + * reset the bounds of all the tabs and manage the widget associated with the tab. + * Since the whole folder is being disposed, this is not necessary. For speed + * the inDispose flag is used to skip over this part of the item dispose. + */ + inDispose = true; + + if (showMenu != null && !showMenu.isDisposed()) { + showMenu.dispose(); + showMenu = null; + } + int length = items.length; + for (int i = 0; i < length; i++) { + if (items[i] != null) { + items[i].dispose(); + } + } + if (fillColor != null) { + fillColor.dispose(); + fillColor = null; + } + + selectionGradientColors = null; + selectionGradientPercents = null; + selectionBgImage = null; + + selectionBackground = null; + selectionForeground = null; + disposeSelectionHighlightGradientColors(); +} +void onDragDetect(Event event) { + boolean consume = false; + if (chevronRect.contains(event.x, event.y) || + minRect.contains(event.x, event.y) || + maxRect.contains(event.x, event.y)){ + consume = true; + } else { + for (int i = 0; i < items.length; i++) { + if (items[i].closeRect.contains(event.x, event.y)) { + consume = true; + break; + } + } + } + if (consume) { + event.type = SWT.None; + } +} +void onFocus(Event event) { + checkWidget(); + if (selectedIndex >= 0) { + redraw(); + } else { + setSelection(0, true); + } +} +boolean onMnemonic (Event event, boolean doit) { + char key = event.character; + for (int i = 0; i < items.length; i++) { + if (items[i] != null) { + char mnemonic = _findMnemonic (items[i].getText ()); + if (mnemonic != '\0') { + if (Character.toLowerCase (key) == mnemonic) { + if (doit) setSelection(i, true); + return true; + } + } + } + } + return false; +} +void onMouseDoubleClick(Event event) { + if (event.button != 1 || + (event.stateMask & SWT.BUTTON2) != 0 || + (event.stateMask & SWT.BUTTON3) != 0) return; + Event e = new Event(); + e.item = getItem(new Point(event.x, event.y)); + if (e.item != null) { + notifyListeners(SWT.DefaultSelection, e); + } +} +void onMouse(Event event) { + int x = event.x, y = event.y; + switch (event.type) { + case SWT.MouseEnter: { + setToolTipText(null); + break; + } + case SWT.MouseExit: { + if (minImageState != NORMAL) { + minImageState = NORMAL; + redraw(minRect.x, minRect.y, minRect.width, minRect.height, false); + } + if (maxImageState != NORMAL) { + maxImageState = NORMAL; + redraw(maxRect.x, maxRect.y, maxRect.width, maxRect.height, false); + } + if (chevronImageState != NORMAL) { + chevronImageState = NORMAL; + redraw(chevronRect.x, chevronRect.y, chevronRect.width, chevronRect.height, false); + } + for (int i=0; i<items.length; i++) { + CTabItem item = items[i]; + if (i != selectedIndex && item.closeImageState != NONE) { + item.closeImageState = NONE; + redraw(item.closeRect.x, item.closeRect.y, item.closeRect.width, item.closeRect.height, false); + } + if (i == selectedIndex && item.closeImageState != NORMAL) { + item.closeImageState = NORMAL; + redraw(item.closeRect.x, item.closeRect.y, item.closeRect.width, item.closeRect.height, false); + } + } + break; + } + case SWT.MouseDown: { + if (event.button != 1) return; + if (minRect.contains(x, y)) { + minImageState = SELECTED; + redraw(minRect.x, minRect.y, minRect.width, minRect.height, false); + update(); + return; + } + if (maxRect.contains(x, y)) { + maxImageState = SELECTED; + redraw(maxRect.x, maxRect.y, maxRect.width, maxRect.height, false); + update(); + return; + } + if (chevronRect.contains(x, y)) { + if (chevronImageState != HOT) { + chevronImageState = HOT; + } else { + chevronImageState = SELECTED; + } + redraw(chevronRect.x, chevronRect.y, chevronRect.width, chevronRect.height, false); + update(); + return; + } + CTabItem item = null; + if (single) { + if (selectedIndex != -1) { + Rectangle bounds = items[selectedIndex].getBounds(); + if (bounds.contains(x, y)){ + item = items[selectedIndex]; + } + } + } else { + for (int i=0; i<items.length; i++) { + Rectangle bounds = items[i].getBounds(); + if (bounds.contains(x, y)){ + item = items[i]; + } + } + } + if (item != null) { + if (item.closeRect.contains(x,y)){ + item.closeImageState = SELECTED; + redraw(item.closeRect.x, item.closeRect.y, item.closeRect.width, item.closeRect.height, false); + update(); + return; + } + int index = indexOf(item); + if (item.showing){ + setSelection(index, true); + } + return; + } + break; + } + case SWT.MouseMove: { + _setToolTipText(event.x, event.y); + boolean close = false, minimize = false, maximize = false, chevron = false; + if (minRect.contains(x, y)) { + minimize = true; + if (minImageState != SELECTED && minImageState != HOT) { + minImageState = HOT; + redraw(minRect.x, minRect.y, minRect.width, minRect.height, false); + } + } + if (maxRect.contains(x, y)) { + maximize = true; + if (maxImageState != SELECTED && maxImageState != HOT) { + maxImageState = HOT; + redraw(maxRect.x, maxRect.y, maxRect.width, maxRect.height, false); + } + } + if (chevronRect.contains(x, y)) { + chevron = true; + if (chevronImageState != SELECTED && chevronImageState != HOT) { + chevronImageState = HOT; + redraw(chevronRect.x, chevronRect.y, chevronRect.width, chevronRect.height, false); + } + } + if (minImageState != NORMAL && !minimize) { + minImageState = NORMAL; + redraw(minRect.x, minRect.y, minRect.width, minRect.height, false); + } + if (maxImageState != NORMAL && !maximize) { + maxImageState = NORMAL; + redraw(maxRect.x, maxRect.y, maxRect.width, maxRect.height, false); + } + if (chevronImageState != NORMAL && !chevron) { + chevronImageState = NORMAL; + redraw(chevronRect.x, chevronRect.y, chevronRect.width, chevronRect.height, false); + } + for (int i=0; i<items.length; i++) { + CTabItem item = items[i]; + close = false; + if (item.getBounds().contains(x, y)) { + close = true; + if (item.closeRect.contains(x, y)) { + if (item.closeImageState != SELECTED && item.closeImageState != HOT) { + item.closeImageState = HOT; + redraw(item.closeRect.x, item.closeRect.y, item.closeRect.width, item.closeRect.height, false); + } + } else { + if (item.closeImageState != NORMAL) { + item.closeImageState = NORMAL; + redraw(item.closeRect.x, item.closeRect.y, item.closeRect.width, item.closeRect.height, false); + } + } + } + if (i != selectedIndex && item.closeImageState != NONE && !close) { + item.closeImageState = NONE; + redraw(item.closeRect.x, item.closeRect.y, item.closeRect.width, item.closeRect.height, false); + } + if (i == selectedIndex && item.closeImageState != NORMAL && !close) { + item.closeImageState = NORMAL; + redraw(item.closeRect.x, item.closeRect.y, item.closeRect.width, item.closeRect.height, false); + } + } + break; + } + case SWT.MouseUp: { + if (event.button != 1) return; + if (chevronRect.contains(x, y)) { + boolean selected = chevronImageState == SELECTED; + if (!selected) return; + CTabFolderEvent e = new CTabFolderEvent(this); + e.widget = this; + e.time = event.time; + e.x = chevronRect.x; + e.y = chevronRect.y; + e.width = chevronRect.width; + e.height = chevronRect.height; + e.doit = true; + for (int i = 0; i < folderListeners.length; i++) { + folderListeners[i].showList(e); + } + if (e.doit && !isDisposed()) { + showList(chevronRect); + } + return; + } + if (minRect.contains(x, y)) { + boolean selected = minImageState == SELECTED; + minImageState = HOT; + redraw(minRect.x, minRect.y, minRect.width, minRect.height, false); + if (!selected) return; + CTabFolderEvent e = new CTabFolderEvent(this); + e.widget = this; + e.time = event.time; + for (int i = 0; i < folderListeners.length; i++) { + if (minimized) { + folderListeners[i].restore(e); + } else { + folderListeners[i].minimize(e); + } + } + return; + } + if (maxRect.contains(x, y)) { + boolean selected = maxImageState == SELECTED; + maxImageState = HOT; + redraw(maxRect.x, maxRect.y, maxRect.width, maxRect.height, false); + if (!selected) return; + CTabFolderEvent e = new CTabFolderEvent(this); + e.widget = this; + e.time = event.time; + for (int i = 0; i < folderListeners.length; i++) { + if (maximized) { + folderListeners[i].restore(e); + } else { + folderListeners[i].maximize(e); + } + } + return; + } + CTabItem item = null; + if (single) { + if (selectedIndex != -1) { + Rectangle bounds = items[selectedIndex].getBounds(); + if (bounds.contains(x, y)){ + item = items[selectedIndex]; + } + } + } else { + for (int i=0; i<items.length; i++) { + Rectangle bounds = items[i].getBounds(); + if (bounds.contains(x, y)){ + item = items[i]; + } + } + } + if (item != null) { + if (item.closeRect.contains(x,y)) { + boolean selected = item.closeImageState == SELECTED; + item.closeImageState = HOT; + redraw(item.closeRect.x, item.closeRect.y, item.closeRect.width, item.closeRect.height, false); + if (!selected) return; + CTabFolderEvent e = new CTabFolderEvent(this); + e.widget = this; + e.time = event.time; + e.item = item; + e.doit = true; + for (int j = 0; j < folderListeners.length; j++) { + CTabFolder2Listener listener = folderListeners[j]; + listener.close(e); + } + for (int j = 0; j < tabListeners.length; j++) { + CTabFolderListener listener = tabListeners[j]; + listener.itemClosed(e); + } + if (e.doit) item.dispose(); + if (!isDisposed() && item.isDisposed()) { + Display display = getDisplay(); + Point pt = display.getCursorLocation(); + pt = display.map(null, this, pt.x, pt.y); + CTabItem nextItem = getItem(pt); + if (nextItem != null) { + if (nextItem.closeRect.contains(pt)) { + if (nextItem.closeImageState != SELECTED && nextItem.closeImageState != HOT) { + nextItem.closeImageState = HOT; + redraw(nextItem.closeRect.x, nextItem.closeRect.y, nextItem.closeRect.width, nextItem.closeRect.height, false); + } + } else { + if (nextItem.closeImageState != NORMAL) { + nextItem.closeImageState = NORMAL; + redraw(nextItem.closeRect.x, nextItem.closeRect.y, nextItem.closeRect.width, nextItem.closeRect.height, false); + } + } + } + } + return; + } + } + } + } +} +void onPageTraversal(Event event) { + int count = items.length; + if (count == 0) return; + int index = selectedIndex; + if (index == -1) { + index = 0; + } else { + int offset = (event.detail == SWT.TRAVERSE_PAGE_NEXT) ? 1 : -1; + if (!mru) { + index = (selectedIndex + offset + count) % count; + } else { + int[] visible = new int[items.length]; + int idx = 0; + int current = -1; + for (int i = 0; i < items.length; i++) { + if (items[i].showing) { + if (i == selectedIndex) current = idx; + visible [idx++] = i; + } + } + if (current + offset >= 0 && current + offset < idx){ + index = visible [current + offset]; + } else { + if (showChevron) { + CTabFolderEvent e = new CTabFolderEvent(this); + e.widget = this; + e.time = event.time; + e.x = chevronRect.x; + e.y = chevronRect.y; + e.width = chevronRect.width; + e.height = chevronRect.height; + e.doit = true; + for (int i = 0; i < folderListeners.length; i++) { + folderListeners[i].showList(e); + } + if (e.doit && !isDisposed()) { + showList(chevronRect); + } + } + } + } + } + setSelection (index, true); +} +void onPaint(Event event) { + if (inDispose) return; + Font font = getFont(); + if (oldFont == null || !oldFont.equals(font)) { + // handle case where default font changes + oldFont = font; + if (!updateTabHeight(false)) { + updateItems(); + redraw(); + return; + } + } + + GC gc = event.gc; + Font gcFont = gc.getFont(); + Color gcBackground = gc.getBackground(); + Color gcForeground = gc.getForeground(); + +// Useful for debugging paint problems +//{ +//Point size = getSize(); +//gc.setBackground(getDisplay().getSystemColor(SWT.COLOR_GREEN)); +//gc.fillRectangle(-10, -10, size.x + 20, size.y+20); +//} + + drawBody(event); + + gc.setFont(gcFont); + gc.setForeground(gcForeground); + gc.setBackground(gcBackground); + + drawTabArea(event); + + gc.setFont(gcFont); + gc.setForeground(gcForeground); + gc.setBackground(gcBackground); +} + +void onResize() { + if (updateItems()) redrawTabs(); + + Point size = getSize(); + if (oldSize == null) { + redraw(); + } else { + if (onBottom && size.y != oldSize.y) { + redraw(); + } else { + int x1 = Math.min(size.x, oldSize.x); + if (size.x != oldSize.x) x1 -= borderRight + highlight_margin + 2; + if (!simple) x1 -= 5; // rounded top right corner + int y1 = Math.min(size.y, oldSize.y); + if (size.y != oldSize.y) y1 -= borderBottom + highlight_margin; + int x2 = Math.max(size.x, oldSize.x); + int y2 = Math.max(size.y, oldSize.y); + redraw(0, y1, x2, y2 - y1, false); + redraw(x1, 0, x2 - x1, y2, false); + } + } + oldSize = size; +} +void onTraverse (Event event) { + if (ignoreTraverse) return; + switch (event.detail) { + case SWT.TRAVERSE_ESCAPE: + case SWT.TRAVERSE_RETURN: + case SWT.TRAVERSE_TAB_NEXT: + case SWT.TRAVERSE_TAB_PREVIOUS: + Control focusControl = getDisplay().getFocusControl(); + if (focusControl == this) event.doit = true; + break; + case SWT.TRAVERSE_MNEMONIC: + event.doit = onMnemonic(event, false); + break; + case SWT.TRAVERSE_PAGE_NEXT: + case SWT.TRAVERSE_PAGE_PREVIOUS: + event.doit = items.length > 0; + break; + } + ignoreTraverse = true; + notifyListeners(SWT.Traverse, event); + ignoreTraverse = false; + event.type = SWT.None; + if (isDisposed()) return; + if (!event.doit) return; + switch (event.detail) { + case SWT.TRAVERSE_MNEMONIC: + onMnemonic(event, true); + event.detail = SWT.TRAVERSE_NONE; + break; + case SWT.TRAVERSE_PAGE_NEXT: + case SWT.TRAVERSE_PAGE_PREVIOUS: + onPageTraversal(event); + event.detail = SWT.TRAVERSE_NONE; + break; + } +} +void redrawTabs() { + Point size = getSize(); + if (onBottom) { + redraw(0, size.y - borderBottom - tabHeight - highlight_header - 1, size.x, borderBottom + tabHeight + highlight_header + 1, false); + } else { + redraw(0, 0, size.x, borderTop + tabHeight + highlight_header + 1, false); + } +} +/** + * Removes the listener. + * + * @param listener the listener which should no longer be notified + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the listener is null</li> + * </ul> + * + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li> + * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li> + * </ul> + * + * @see #addCTabFolder2Listener(CTabFolder2Listener) + * + * @since 3.0 + */ +public void removeCTabFolder2Listener(CTabFolder2Listener listener) { + checkWidget(); + if (listener == null) SWT.error (SWT.ERROR_NULL_ARGUMENT); + if (folderListeners.length == 0) return; + int index = -1; + for (int i = 0; i < folderListeners.length; i++) { + if (listener == folderListeners[i]){ + index = i; + break; + } + } + if (index == -1) return; + if (folderListeners.length == 1) { + folderListeners = new CTabFolder2Listener[0]; + return; + } + CTabFolder2Listener[] newTabListeners = new CTabFolder2Listener[folderListeners.length - 1]; + System.arraycopy(folderListeners, 0, newTabListeners, 0, index); + System.arraycopy(folderListeners, index + 1, newTabListeners, index, folderListeners.length - index - 1); + folderListeners = newTabListeners; +} +/** + * Removes the listener. + * + * @param listener the listener which should no longer be notified + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the listener is null</li> + * </ul> + * + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li> + * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li> + * </ul> + * + * @deprecated see removeCTabFolderCloseListener(CTabFolderListener) + */ +public void removeCTabFolderListener(CTabFolderListener listener) { + checkWidget(); + if (listener == null) SWT.error (SWT.ERROR_NULL_ARGUMENT); + if (tabListeners.length == 0) return; + int index = -1; + for (int i = 0; i < tabListeners.length; i++) { + if (listener == tabListeners[i]){ + index = i; + break; + } + } + if (index == -1) return; + if (tabListeners.length == 1) { + tabListeners = new CTabFolderListener[0]; + return; + } + CTabFolderListener[] newTabListeners = new CTabFolderListener[tabListeners.length - 1]; + System.arraycopy(tabListeners, 0, newTabListeners, 0, index); + System.arraycopy(tabListeners, index + 1, newTabListeners, index, tabListeners.length - index - 1); + tabListeners = newTabListeners; +} +/** + * Removes the listener from the collection of listeners who will + * be notified when the user changes the receiver's selection. + * + * @param listener the listener which should no longer be notified + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the listener is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see SelectionListener + * @see #addSelectionListener + */ +public void removeSelectionListener(SelectionListener listener) { + checkWidget(); + if (listener == null) { + SWT.error(SWT.ERROR_NULL_ARGUMENT); + } + removeListener(SWT.Selection, listener); + removeListener(SWT.DefaultSelection, listener); +} +public void setBackground (Color color) { + super.setBackground(color); + redraw(); +} +/** + * Specify a gradient of colours to be drawn in the background of the unselected tabs. + * For example to draw a gradient that varies from dark blue to blue and then to + * white, use the following call to setBackground: + * <pre> + * cfolder.setBackground(new Color[]{display.getSystemColor(SWT.COLOR_DARK_BLUE), + * display.getSystemColor(SWT.COLOR_BLUE), + * display.getSystemColor(SWT.COLOR_WHITE), + * display.getSystemColor(SWT.COLOR_WHITE)}, + * new int[] {25, 50, 100}); + * </pre> + * + * @param colors an array of Color that specifies the colors to appear in the gradient + * in order of appearance left to right. The value <code>null</code> clears the + * background gradient. The value <code>null</code> can be used inside the array of + * Color to specify the background color. + * @param percents an array of integers between 0 and 100 specifying the percent of the width + * of the widget at which the color should change. The size of the percents array must be one + * less than the size of the colors array. + * + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li> + * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li> + * </ul> + * + * @since 3.0 + */ +void setBackground(Color[] colors, int[] percents) { + setBackground(colors, percents, false); +} +/** + * Specify a gradient of colours to be drawn in the background of the unselected tab. + * For example to draw a vertical gradient that varies from dark blue to blue and then to + * white, use the following call to setBackground: + * <pre> + * cfolder.setBackground(new Color[]{display.getSystemColor(SWT.COLOR_DARK_BLUE), + * display.getSystemColor(SWT.COLOR_BLUE), + * display.getSystemColor(SWT.COLOR_WHITE), + * display.getSystemColor(SWT.COLOR_WHITE)}, + * new int[] {25, 50, 100}, true); + * </pre> + * + * @param colors an array of Color that specifies the colors to appear in the gradient + * in order of appearance left to right. The value <code>null</code> clears the + * background gradient. The value <code>null</code> can be used inside the array of + * Color to specify the background color. + * @param percents an array of integers between 0 and 100 specifying the percent of the width + * of the widget at which the color should change. The size of the percents array must be one + * less than the size of the colors array. + * + * @param vertical indicate the direction of the gradient. True is vertical and false is horizontal. + * + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li> + * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li> + * </ul> + * + * @since 3.0 + */ +void setBackground(Color[] colors, int[] percents, boolean vertical) { + checkWidget(); + if (colors != null) { + if (percents == null || percents.length != colors.length - 1) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + for (int i = 0; i < percents.length; i++) { + if (percents[i] < 0 || percents[i] > 100) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + if (i > 0 && percents[i] < percents[i-1]) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + } + if (getDisplay().getDepth() < 15) { + // Don't use gradients on low color displays + colors = new Color[] {colors[colors.length - 1]}; + percents = new int[] {}; + } + } + + // Are these settings the same as before? + if ((gradientColors != null) && (colors != null) && + (gradientColors.length == colors.length)) { + boolean same = false; + for (int i = 0; i < gradientColors.length; i++) { + if (gradientColors[i] == null) { + same = colors[i] == null; + } else { + same = gradientColors[i].equals(colors[i]); + } + if (!same) break; + } + if (same) { + for (int i = 0; i < gradientPercents.length; i++) { + same = gradientPercents[i] == percents[i]; + if (!same) break; + } + } + if (same && this.gradientVertical == vertical) return; + } + // Store the new settings + if (colors == null) { + gradientColors = null; + gradientPercents = null; + gradientVertical = false; + setBackground((Color)null); + } else { + gradientColors = new Color[colors.length]; + for (int i = 0; i < colors.length; ++i) { + gradientColors[i] = colors[i]; + } + gradientPercents = new int[percents.length]; + for (int i = 0; i < percents.length; ++i) { + gradientPercents[i] = percents[i]; + } + gradientVertical = vertical; + setBackground(gradientColors[gradientColors.length-1]); + } + + // Refresh with the new settings + redraw(); +} +/** + * Toggle the visibility of the border + * + * @param show true if the border should be displayed + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void setBorderVisible(boolean show) { + checkWidget(); + if ((borderLeft == 1) == show) return; + borderLeft = borderRight = show ? 1 : 0; + borderTop = onBottom ? borderLeft : 0; + borderBottom = onBottom ? 0 : borderLeft; + Rectangle rectBefore = getClientArea(); + updateItems(); + Rectangle rectAfter = getClientArea(); + if (!rectBefore.equals(rectAfter)) { + notifyListeners(SWT.Resize, new Event()); + } + redraw(); +} +void setButtonBounds() { + Point size = getSize(); + int oldX, oldY, oldWidth, oldHeight; + // max button + oldX = maxRect.x; + oldY = maxRect.y; + oldWidth = maxRect.width; + oldHeight = maxRect.height; + maxRect.x = maxRect.y = maxRect.width = maxRect.height = 0; + if (showMax) { + maxRect.x = size.x - borderRight - BUTTON_SIZE - 3; + if (borderRight > 0) maxRect.x += 1; + maxRect.y = onBottom ? size.y - borderBottom - tabHeight + (tabHeight - BUTTON_SIZE)/2: borderTop + (tabHeight - BUTTON_SIZE)/2; + maxRect.width = BUTTON_SIZE; + maxRect.height = BUTTON_SIZE; + } + if (oldX != maxRect.x || oldWidth != maxRect.width || + oldY != maxRect.y || oldHeight != maxRect.height) { + int left = Math.min(oldX, maxRect.x); + int right = Math.max(oldX + oldWidth, maxRect.x + maxRect.width); + int top = onBottom ? size.y - borderBottom - tabHeight: borderTop + 1; + redraw(left, top, right - left, tabHeight, false); + } + + // min button + oldX = minRect.x; + oldY = minRect.y; + oldWidth = minRect.width; + oldHeight = minRect.height; + minRect.x = minRect.y = minRect.width = minRect.height = 0; + if (showMin) { + minRect.x = size.x - borderRight - maxRect.width - BUTTON_SIZE - 3; + if (borderRight > 0) minRect.x += 1; + minRect.y = onBottom ? size.y - borderBottom - tabHeight + (tabHeight - BUTTON_SIZE)/2: borderTop + (tabHeight - BUTTON_SIZE)/2; + minRect.width = BUTTON_SIZE; + minRect.height = BUTTON_SIZE; + } + if (oldX != minRect.x || oldWidth != minRect.width || + oldY != minRect.y || oldHeight != minRect.height) { + int left = Math.min(oldX, minRect.x); + int right = Math.max(oldX + oldWidth, minRect.x + minRect.width); + int top = onBottom ? size.y - borderBottom - tabHeight: borderTop + 1; + redraw(left, top, right - left, tabHeight, false); + } + + // top right control + oldX = topRightRect.x; + oldY = topRightRect.y; + oldWidth = topRightRect.width; + oldHeight = topRightRect.height; + topRightRect.x = topRightRect.y = topRightRect.width = topRightRect.height = 0; + if (topRight != null) { + switch (topRightAlignment) { + case SWT.FILL: { + int rightEdge = size.x - borderRight - 3 - maxRect.width - minRect.width; + if (!simple && borderRight > 0 && !showMax && !showMin) rightEdge -= 2; + if (single) { + if (items.length == 0 || selectedIndex == -1) { + topRightRect.x = borderLeft + 3; + topRightRect.width = rightEdge - topRightRect.x; + } else { + // fill size is 0 if item compressed + CTabItem item = items[selectedIndex]; + if (item.x + item.width + 7 + 3*BUTTON_SIZE/2 >= rightEdge) break; + topRightRect.x = item.x + item.width + 7 + 3*BUTTON_SIZE/2; + topRightRect.width = rightEdge - topRightRect.x; + } + } else { + // fill size is 0 if chevron showing + if (showChevron) break; + if (items.length == 0) { + topRightRect.x = borderLeft + 3; + } else { + CTabItem item = items[items.length - 1]; + topRightRect.x = item.x + item.width; + if (!simple && items.length - 1 == selectedIndex) topRightRect.x += curveWidth - curveIndent; + } + topRightRect.width = Math.max(0, rightEdge - topRightRect.x); + } + topRightRect.y = onBottom ? size.y - borderBottom - tabHeight: borderTop + 1; + topRightRect.height = tabHeight - 1; + break; + } + case SWT.RIGHT: { + Point topRightSize = topRight.computeSize(SWT.DEFAULT, tabHeight, false); + int rightEdge = size.x - borderRight - 3 - maxRect.width - minRect.width; + if (!simple && borderRight > 0 && !showMax && !showMin) rightEdge -= 2; + topRightRect.x = rightEdge - topRightSize.x; + topRightRect.width = topRightSize.x; + topRightRect.y = onBottom ? size.y - borderBottom - tabHeight: borderTop + 1; + topRightRect.height = tabHeight - 1; + } + } + topRight.setBounds(topRightRect); + } + if (oldX != topRightRect.x || oldWidth != topRightRect.width || + oldY != topRightRect.y || oldHeight != topRightRect.height) { + int left = Math.min(oldX, topRightRect.x); + int right = Math.max(oldX + oldWidth, topRightRect.x + topRightRect.width); + int top = onBottom ? size.y - borderBottom - tabHeight : borderTop + 1; + redraw(left, top, right - left, tabHeight, false); + } + + // chevron button + oldX = chevronRect.x; + oldY = chevronRect.y; + oldWidth = chevronRect.width; + oldHeight = chevronRect.height; + chevronRect.x = chevronRect.y = chevronRect.height = chevronRect.width = 0; + if (single) { + if (selectedIndex == -1 || items.length > 1) { + chevronRect.width = 3*BUTTON_SIZE/2; + chevronRect.height = BUTTON_SIZE; + chevronRect.y = onBottom ? size.y - borderBottom - tabHeight + (tabHeight - chevronRect.height)/2 : borderTop + (tabHeight - chevronRect.height)/2; + if (selectedIndex == -1) { + chevronRect.x = size.x - borderRight - 3 - minRect.width - maxRect.width - topRightRect.width - chevronRect.width; + } else { + CTabItem item = items[selectedIndex]; + int w = size.x - borderRight - 3 - minRect.width - maxRect.width - chevronRect.width; + if (topRightRect.width > 0) w -= topRightRect.width + 3; + chevronRect.x = Math.min(item.x + item.width + 3, w); + } + if (borderRight > 0) chevronRect.x += 1; + } + } else { + if (showChevron) { + chevronRect.width = 3*BUTTON_SIZE/2; + chevronRect.height = BUTTON_SIZE; + int i = 0, lastIndex = -1; + while (i < priority.length && items[priority[i]].showing) { + lastIndex = Math.max(lastIndex, priority[i++]); + } + if (lastIndex == -1) lastIndex = firstIndex; + CTabItem lastItem = items[lastIndex]; + int w = lastItem.x + lastItem.width + 3; + if (!simple && lastIndex == selectedIndex) w += curveWidth - 2*curveIndent; + chevronRect.x = Math.min(w, getRightItemEdge()); + chevronRect.y = onBottom ? size.y - borderBottom - tabHeight + (tabHeight - chevronRect.height)/2 : borderTop + (tabHeight - chevronRect.height)/2; + } + } + if (oldX != chevronRect.x || oldWidth != chevronRect.width || + oldY != chevronRect.y || oldHeight != chevronRect.height) { + int left = Math.min(oldX, chevronRect.x); + int right = Math.max(oldX + oldWidth, chevronRect.x + chevronRect.width); + int top = onBottom ? size.y - borderBottom - tabHeight: borderTop + 1; + redraw(left, top, right - left, tabHeight, false); + } +} +public void setFont(Font font) { + checkWidget(); + if (font != null && font.equals(getFont())) return; + super.setFont(font); + oldFont = getFont(); + if (!updateTabHeight(false)) { + updateItems(); + redraw(); + } +} +public void setForeground (Color color) { + super.setForeground(color); + redraw(); +} +/** + * Display an insert marker before or after the specified tab item. + * + * A value of null will clear the mark. + * + * @param item the item with which the mark is associated or null + * + * @param after true if the mark should be displayed after the specified item + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void setInsertMark(CTabItem item, boolean after) { + checkWidget(); +} +/** + * Display an insert marker before or after the specified tab item. + * + * A value of -1 will clear the mark. + * + * @param index the index of the item with which the mark is associated or null + * + * @param after true if the mark should be displayed after the specified item + * + * @exception IllegalArgumentException<ul> + * </ul> + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void setInsertMark(int index, boolean after) { + checkWidget(); + if (index < -1 || index >= getItemCount()) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } +} +boolean setItemLocation() { + boolean changed = false; + if (items.length == 0) return false; + Point size = getSize(); + int y = onBottom ? Math.max(borderBottom, size.y - borderBottom - tabHeight) : borderTop; + if (single) { + int defaultX = getDisplay().getBounds().width + 10; // off screen + for (int i = 0; i < items.length; i++) { + CTabItem item = items[i]; + if (i == selectedIndex) { + firstIndex = selectedIndex; + int oldX = item.x, oldY = item.y; + item.x = borderLeft; + item.y = y; + item.showing = true; + if (showClose || item.showClose) { + item.closeRect.x = borderLeft + CTabItem.LEFT_MARGIN; + item.closeRect.y = onBottom ? size.y - borderBottom - tabHeight + (tabHeight - BUTTON_SIZE)/2: borderTop + (tabHeight - BUTTON_SIZE)/2; + } + if (item.x != oldX || item.y != oldY) changed = true; + } else { + item.x = defaultX; + item.showing = false; + } + } + } else { + int rightItemEdge = getRightItemEdge(); + int maxWidth = rightItemEdge - borderLeft; + int width = 0; + for (int i = 0; i < priority.length; i++) { + CTabItem item = items[priority[i]]; + width += item.width; + item.showing = i == 0 ? true : item.width > 0 && width <= maxWidth; + if (!simple && priority[i] == selectedIndex) width += curveWidth - 2*curveIndent; + } + int x = 0; + int defaultX = getDisplay().getBounds().width + 10; // off screen + firstIndex = items.length - 1; + for (int i = 0; i < items.length; i++) { + CTabItem item = items[i]; + if (!item.showing) { + if (item.x != defaultX) changed = true; + item.x = defaultX; + } else { + firstIndex = Math.min(firstIndex, i); + if (item.x != x || item.y != y) changed = true; + item.x = x; + item.y = y; + if (i == selectedIndex) { + int edge = Math.min(item.x + item.width, rightItemEdge); + item.closeRect.x = edge - CTabItem.RIGHT_MARGIN - BUTTON_SIZE; + } else { + item.closeRect.x = item.x + item.width - CTabItem.RIGHT_MARGIN - BUTTON_SIZE; + } + item.closeRect.y = onBottom ? size.y - borderBottom - tabHeight + (tabHeight - BUTTON_SIZE)/2: borderTop + (tabHeight - BUTTON_SIZE)/2; + x = x + item.width; + if (!simple && i == selectedIndex) x += curveWidth - 2*curveIndent; + } + } + } + return changed; +} +boolean setItemSize() { + boolean changed = false; + if (isDisposed()) return changed; + Point size = getSize(); + if (size.x <= 0 || size.y <= 0) return changed; + xClient = borderLeft + marginWidth + highlight_margin; + if (onBottom) { + yClient = borderTop + highlight_margin + marginHeight; + } else { + yClient = borderTop + tabHeight + highlight_header + marginHeight; + } + showChevron = false; + if (single) { + showChevron = true; + if (selectedIndex != -1) { + CTabItem tab = items[selectedIndex]; + GC gc = new GC(this); + int width = tab.preferredWidth(gc, true, false); + gc.dispose(); + width = Math.min(width, getRightItemEdge() - borderLeft); + if (tab.height != tabHeight || tab.width != width) { + changed = true; + tab.shortenedText = null; + tab.shortenedTextWidth = 0; + tab.height = tabHeight; + tab.width = width; + tab.closeRect.width = tab.closeRect.height = 0; + if (showClose || tab.showClose) { + tab.closeRect.width = BUTTON_SIZE; + tab.closeRect.height = BUTTON_SIZE; + } + } + } + return changed; + } + + if (items.length == 0) return changed; + + int[] widths; + GC gc = new GC(this); + int tabAreaWidth = size.x - borderLeft - borderRight - 3; + if (showMin) tabAreaWidth -= BUTTON_SIZE; + if (showMax) tabAreaWidth -= BUTTON_SIZE; + if (topRightAlignment == SWT.RIGHT && topRight != null) { + Point rightSize = topRight.computeSize(SWT.DEFAULT, SWT.DEFAULT, false); + tabAreaWidth -= rightSize.x + 3; + } + if (!simple) tabAreaWidth -= curveWidth - 2*curveIndent; + tabAreaWidth = Math.max(0, tabAreaWidth); + + // First, try the minimum tab size at full compression. + int minWidth = 0; + int[] minWidths = new int[items.length]; + for (int i = 0; i < priority.length; i++) { + int index = priority[i]; + minWidths[index] = items[index].preferredWidth(gc, index == selectedIndex, true); + minWidth += minWidths[index]; + if (minWidth > tabAreaWidth) break; + } + if (minWidth > tabAreaWidth) { + // full compression required and a chevron + showChevron = items.length > 1; + if (showChevron) tabAreaWidth -= 3*BUTTON_SIZE/2; + widths = minWidths; + int index = selectedIndex != -1 ? selectedIndex : 0; + if (tabAreaWidth < widths[index]) { + widths[index] = Math.max(0, tabAreaWidth); + } + } else { + int maxWidth = 0; + int[] maxWidths = new int[items.length]; + for (int i = 0; i < items.length; i++) { + maxWidths[i] = items[i].preferredWidth(gc, i == selectedIndex, false); + maxWidth += maxWidths[i]; + } + if (maxWidth <= tabAreaWidth) { + // no compression required + widths = maxWidths; + } else { + // determine compression for each item + int extra = (tabAreaWidth - minWidth) / items.length; + while (true) { + int large = 0, totalWidth = 0; + for (int i = 0 ; i < items.length; i++) { + if (maxWidths[i] > minWidths[i] + extra) { + totalWidth += minWidths[i] + extra; + large++; + } else { + totalWidth += maxWidths[i]; + } + } + if (totalWidth >= tabAreaWidth) { + extra--; + break; + } + if (large == 0 || tabAreaWidth - totalWidth < large) break; + extra++; + } + widths = new int[items.length]; + for (int i = 0; i < items.length; i++) { + widths[i] = Math.min(maxWidths[i], minWidths[i] + extra); + } + } + } + gc.dispose(); + + for (int i = 0; i < items.length; i++) { + CTabItem tab = items[i]; + int width = widths[i]; + if (tab.height != tabHeight || tab.width != width) { + changed = true; + tab.shortenedText = null; + tab.shortenedTextWidth = 0; + tab.height = tabHeight; + tab.width = width; + tab.closeRect.width = tab.closeRect.height = 0; + if (showClose || tab.showClose) { + if (i == selectedIndex || showUnselectedClose) { + tab.closeRect.width = BUTTON_SIZE; + tab.closeRect.height = BUTTON_SIZE; + } + } + } + } + return changed; +} +/** + * Marks the receiver's maximize button as visible if the argument is <code>true</code>, + * and marks it invisible otherwise. + * + * @param visible the new visibility state + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.0 + */ +public void setMaximizeVisible(boolean visible) { + checkWidget(); + if (showMax == visible) return; + // display maximize button + showMax = visible; + updateItems(); + redraw(); +} +/** + * Sets the layout which is associated with the receiver to be + * the argument which may be null. + * <p> + * Note: No Layout can be set on this Control because it already + * manages the size and position of its children. + * </p> + * + * @param layout the receiver's new layout or null + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void setLayout (Layout layout) { + checkWidget(); + return; +} +/** + * Sets the maximized state of the receiver. + * + * @param maximize the new maximized state + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.0 + */ +public void setMaximized(boolean maximize) { + checkWidget (); + if (this.maximized == maximize) return; + if (maximize && this.minimized) setMinimized(false); + this.maximized = maximize; + redraw(maxRect.x, maxRect.y, maxRect.width, maxRect.height, false); +} +/** + * Marks the receiver's minimize button as visible if the argument is <code>true</code>, + * and marks it invisible otherwise. + * + * @param visible the new visibility state + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.0 + */ +public void setMinimizeVisible(boolean visible) { + checkWidget(); + if (showMin == visible) return; + // display minimize button + showMin = visible; + updateItems(); + redraw(); +} +/** + * Sets the minimized state of the receiver. + * + * @param minimize the new minimized state + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.0 + */ +public void setMinimized(boolean minimize) { + checkWidget (); + if (this.minimized == minimize) return; + if (minimize && this.maximized) setMaximized(false); + this.minimized = minimize; + redraw(minRect.x, minRect.y, minRect.width, minRect.height, false); +} + +/** + * Sets the minimum number of characters that will + * be displayed in a fully compressed tab. + * + * @param count the minimum number of characters that will be displayed in a fully compressed tab + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * <li>ERROR_INVALID_RANGE - if the count is less than zero</li> + * </ul> + * + * @since 3.0 + */ +public void setMinimumCharacters(int count) { + checkWidget (); + if (count < 0) SWT.error(SWT.ERROR_INVALID_RANGE); + if (minChars == count) return; + minChars = count; + if (updateItems()) redrawTabs(); +} + +/** + * When there is not enough horizontal space to show all the tabs, + * by default, tabs are shown sequentially from left to right in + * order of their index. When the MRU visibility is turned on, + * the tabs that are visible will be the tabs most recently selected. + * Tabs will still maintain their left to right order based on index + * but only the most recently selected tabs are visible. + * <p> + * For example, consider a CTabFolder that contains "Tab 1", "Tab 2", + * "Tab 3" and "Tab 4" (in order by index). The user selects + * "Tab 1" and then "Tab 3". If the CTabFolder is now + * compressed so that only two tabs are visible, by default, + * "Tab 2" and "Tab 3" will be shown ("Tab 3" since it is currently + * selected and "Tab 2" because it is the previous item in index order). + * If MRU visibility is enabled, the two visible tabs will be "Tab 1" + * and "Tab 3" (in that order from left to right).</p> + * + * @param show the new visibility state + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.1 + */ +public void setMRUVisible(boolean show) { + checkWidget(); + if (mru == show) return; + mru = show; + if (!mru) { + int idx = firstIndex; + int next = 0; + for (int i = firstIndex; i < items.length; i++) { + priority[next++] = i; + } + for (int i = 0; i < idx; i++) { + priority[next++] = i; + } + if (updateItems()) redrawTabs(); + } +} +/** + * Set the selection to the tab at the specified item. + * + * @param item the tab item to be selected + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the item is null</li> + * </ul> + * + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li> + * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li> + * </ul> + */ +public void setSelection(CTabItem item) { + checkWidget(); + if (item == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + int index = indexOf(item); + setSelection(index); +} +/** + * Set the selection to the tab at the specified index. + * + * @param index the index of the tab item to be selected + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void setSelection(int index) { + checkWidget(); + if (index < 0 || index >= items.length) return; + CTabItem selection = items[index]; + if (selectedIndex == index) { + showItem(selection); + return; + } + + int oldIndex = selectedIndex; + selectedIndex = index; + if (oldIndex != -1) { + items[oldIndex].closeImageState = NONE; + } + selection.closeImageState = NORMAL; + selection.showing = false; + + Control newControl = selection.control; + Control oldControl = null; + if (oldIndex != -1) { + oldControl = items[oldIndex].control; + } + + if (newControl != oldControl) { + if (newControl != null && !newControl.isDisposed()) { + newControl.setBounds(getClientArea()); + newControl.setVisible(true); + } + if (oldControl != null && !oldControl.isDisposed()) { + oldControl.setVisible(false); + } + } + showItem(selection); + redraw(); +} +void setSelection(int index, boolean notify) { + int oldSelectedIndex = selectedIndex; + setSelection(index); + if (notify && selectedIndex != oldSelectedIndex && selectedIndex != -1) { + Event event = new Event(); + event.item = getItem(selectedIndex); + notifyListeners(SWT.Selection, event); + } +} +/** + * Sets the receiver's selection background color to the color specified + * by the argument, or to the default system color for the control + * if the argument is null. + * + * @param color the new color (or null) + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.0 + */ +public void setSelectionBackground (Color color) { + checkWidget(); + setSelectionHighlightGradientColor(null); + if (selectionBackground == color) return; + if (color == null) color = getDisplay().getSystemColor(SELECTION_BACKGROUND); + selectionBackground = color; + if (selectedIndex > -1) redraw(); +} +/** + * Specify a gradient of colours to be draw in the background of the selected tab. + * For example to draw a gradient that varies from dark blue to blue and then to + * white, use the following call to setBackground: + * <pre> + * cfolder.setBackground(new Color[]{display.getSystemColor(SWT.COLOR_DARK_BLUE), + * display.getSystemColor(SWT.COLOR_BLUE), + * display.getSystemColor(SWT.COLOR_WHITE), + * display.getSystemColor(SWT.COLOR_WHITE)}, + * new int[] {25, 50, 100}); + * </pre> + * + * @param colors an array of Color that specifies the colors to appear in the gradient + * in order of appearance left to right. The value <code>null</code> clears the + * background gradient. The value <code>null</code> can be used inside the array of + * Color to specify the background color. + * @param percents an array of integers between 0 and 100 specifying the percent of the width + * of the widget at which the color should change. The size of the percents array must be one + * less than the size of the colors array. + * + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li> + * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li> + * </ul> + */ +public void setSelectionBackground(Color[] colors, int[] percents) { + setSelectionBackground(colors, percents, false); +} +/** + * Specify a gradient of colours to be draw in the background of the selected tab. + * For example to draw a vertical gradient that varies from dark blue to blue and then to + * white, use the following call to setBackground: + * <pre> + * cfolder.setBackground(new Color[]{display.getSystemColor(SWT.COLOR_DARK_BLUE), + * display.getSystemColor(SWT.COLOR_BLUE), + * display.getSystemColor(SWT.COLOR_WHITE), + * display.getSystemColor(SWT.COLOR_WHITE)}, + * new int[] {25, 50, 100}, true); + * </pre> + * + * @param colors an array of Color that specifies the colors to appear in the gradient + * in order of appearance left to right. The value <code>null</code> clears the + * background gradient. The value <code>null</code> can be used inside the array of + * Color to specify the background color. + * @param percents an array of integers between 0 and 100 specifying the percent of the width + * of the widget at which the color should change. The size of the percents array must be one + * less than the size of the colors array. + * + * @param vertical indicate the direction of the gradient. True is vertical and false is horizontal. + * + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li> + * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li> + * </ul> + * + * @since 3.0 + */ +public void setSelectionBackground(Color[] colors, int[] percents, boolean vertical) { + checkWidget(); + int colorsLength; + Color highlightBeginColor = null; //null == no highlight + + if (colors != null) { + //The colors array can optionally have an extra entry which describes the highlight top color + //Thus its either one or two larger than the percents array + if (percents == null || + ! ((percents.length == colors.length - 1) || (percents.length == colors.length - 2))){ + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + for (int i = 0; i < percents.length; i++) { + if (percents[i] < 0 || percents[i] > 100) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + if (i > 0 && percents[i] < percents[i-1]) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + } + //If the colors is exactly two more than percents then last is highlight + //Keep track of *real* colorsLength (minus the highlight) + if(percents.length == colors.length - 2) { + highlightBeginColor = colors[colors.length - 1]; + colorsLength = colors.length - 1; + } else { + colorsLength = colors.length; + } + if (getDisplay().getDepth() < 15) { + // Don't use gradients on low color displays + colors = new Color[] {colors[colorsLength - 1]}; + colorsLength = colors.length; + percents = new int[] {}; + } + } else { + colorsLength = 0; + } + + // Are these settings the same as before? + if (selectionBgImage == null) { + if ((selectionGradientColors != null) && (colors != null) && + (selectionGradientColors.length == colorsLength)) { + boolean same = false; + for (int i = 0; i < selectionGradientColors.length; i++) { + if (selectionGradientColors[i] == null) { + same = colors[i] == null; + } else { + same = selectionGradientColors[i].equals(colors[i]); + } + if (!same) break; + } + if (same) { + for (int i = 0; i < selectionGradientPercents.length; i++) { + same = selectionGradientPercents[i] == percents[i]; + if (!same) break; + } + } + if (same && this.selectionGradientVertical == vertical) return; + } + } else { + selectionBgImage = null; + } + // Store the new settings + if (colors == null) { + selectionGradientColors = null; + selectionGradientPercents = null; + selectionGradientVertical = false; + setSelectionBackground((Color)null); + setSelectionHighlightGradientColor(null); + } else { + selectionGradientColors = new Color[colorsLength]; + for (int i = 0; i < colorsLength; ++i) { + selectionGradientColors[i] = colors[i]; + } + selectionGradientPercents = new int[percents.length]; + for (int i = 0; i < percents.length; ++i) { + selectionGradientPercents[i] = percents[i]; + } + selectionGradientVertical = vertical; + setSelectionBackground(selectionGradientColors[selectionGradientColors.length-1]); + setSelectionHighlightGradientColor(highlightBeginColor); + } + + // Refresh with the new settings + if (selectedIndex > -1) redraw(); +} + +/* + * Set the color for the highlight start for selected tabs. + * Update the cache of highlight gradient colors if required. + */ + +void setSelectionHighlightGradientColor(Color start) { + //Set to null to match all the early return cases. + //For early returns, don't realloc the cache, we may get a cache hit next time we're given the highlight + selectionHighlightGradientBegin = null; + + if(start == null) + return; + + //don't bother on low colour + if (getDisplay().getDepth() < 15) + return; + + //don't bother if we don't have a background gradient + if(selectionGradientColors.length < 2) + return; + + //OK we know its a valid gradient now + selectionHighlightGradientBegin = start; + + if(! isSelectionHighlightColorsCacheHit(start)) + createSelectionHighlightGradientColors(start); //if no cache hit then compute new ones +} + +/* + * Return true if given start color, the cache of highlight colors we have + * would match the highlight colors we'd compute. + */ +boolean isSelectionHighlightColorsCacheHit(Color start) { + + if(selectionHighlightGradientColorsCache == null) + return false; + + //this case should never happen but check to be safe before accessing array indexes + if(selectionHighlightGradientColorsCache.length < 2) + return false; + + Color highlightBegin = selectionHighlightGradientColorsCache[0]; + Color highlightEnd = selectionHighlightGradientColorsCache[selectionHighlightGradientColorsCache.length - 1]; + + if(! highlightBegin.equals(start)) + return false; + + //Compare number of colours we have vs. we'd compute + if(selectionHighlightGradientColorsCache.length != tabHeight) + return false; + + //Compare existing highlight end to what it would be (selectionBackground) + if(! highlightEnd.equals(selectionBackground)) + return false; + + return true; +} + +/** + * Set the image to be drawn in the background of the selected tab. Image + * is stretched or compressed to cover entire selection tab area. + * + * @param image the image to be drawn in the background + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void setSelectionBackground(Image image) { + checkWidget(); + setSelectionHighlightGradientColor(null); + if (image == selectionBgImage) return; + if (image != null) { + selectionGradientColors = null; + selectionGradientPercents = null; + disposeSelectionHighlightGradientColors(); + } + selectionBgImage = image; + if (selectedIndex > -1) redraw(); +} +/** + * Set the foreground color of the selected tab. + * + * @param color the color of the text displayed in the selected tab + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void setSelectionForeground (Color color) { + checkWidget(); + if (selectionForeground == color) return; + if (color == null) color = getDisplay().getSystemColor(SELECTION_FOREGROUND); + selectionForeground = color; + if (selectedIndex > -1) redraw(); +} + +/* + * Allocate colors for the highlight line. + * Colours will be a gradual blend ranging from to. + * Blend length will be tab height. + * Recompute this if tab height changes. + * Could remain null if there'd be no gradient (start=end or low colour display) + */ +void createSelectionHighlightGradientColors(Color start) { + disposeSelectionHighlightGradientColors(); //dispose if existing + + if(start == null) //shouldn't happen but just to be safe + return; + + //alloc colours for entire height to ensure it matches wherever we stop drawing + int fadeGradientSize = tabHeight; + + RGB from = start.getRGB(); + RGB to = selectionBackground.getRGB(); + + selectionHighlightGradientColorsCache = new Color[fadeGradientSize]; + int denom = fadeGradientSize - 1; + + for (int i = 0; i < fadeGradientSize; i++) { + int propFrom = denom - i; + int propTo = i; + int red = (to.red * propTo + from.red * propFrom) / denom; + int green = (to.green * propTo + from.green * propFrom) / denom; + int blue = (to.blue * propTo + from.blue * propFrom) / denom; + selectionHighlightGradientColorsCache[i] = new Color(getDisplay(), red, green, blue); + } +} + +void disposeSelectionHighlightGradientColors() { + if(selectionHighlightGradientColorsCache == null) + return; + for (int i = 0; i < selectionHighlightGradientColorsCache.length; i++) { + selectionHighlightGradientColorsCache[i].dispose(); + } + selectionHighlightGradientColorsCache = null; +} + +/* + * Return the gradient start color for selected tabs, which is the start of the tab fade + * (end is selectionBackground). + */ +Color getSelectionBackgroundGradientBegin() { + if (selectionGradientColors == null) + return getSelectionBackground(); + if (selectionGradientColors.length == 0) + return getSelectionBackground(); + return selectionGradientColors[0]; +} + +/** + * Sets the shape that the CTabFolder will use to render itself. + * + * @param simple <code>true</code> if the CTabFolder should render itself in a simple, traditional style + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.0 + */ +public void setSimple(boolean simple) { + checkWidget(); + if (this.simple != simple) { + this.simple = simple; + Rectangle rectBefore = getClientArea(); + updateItems(); + Rectangle rectAfter = getClientArea(); + if (!rectBefore.equals(rectAfter)) { + notifyListeners(SWT.Resize, new Event()); + } + redraw(); + } +} +/** + * Sets the number of tabs that the CTabFolder should display + * + * @param single <code>true</code> if only the selected tab should be displayed otherwise, multiple tabs will be shown. + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.0 + */ +public void setSingle(boolean single) { + checkWidget(); + if (this.single != single) { + this.single = single; + if (!single) { + for (int i = 0; i < items.length; i++) { + if (i != selectedIndex && items[i].closeImageState == NORMAL) { + items[i].closeImageState = NONE; + } + } + } + Rectangle rectBefore = getClientArea(); + updateItems(); + Rectangle rectAfter = getClientArea(); + if (!rectBefore.equals(rectAfter)) { + notifyListeners(SWT.Resize, new Event()); + } + redraw(); + } +} +/** + * Specify a fixed height for the tab items. If no height is specified, + * the default height is the height of the text or the image, whichever + * is greater. Specifying a height of -1 will revert to the default height. + * + * @param height the pixel value of the height or -1 + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * <li>ERROR_INVALID_ARGUMENT - if called with a height of less than 0</li> + * </ul> + */ +public void setTabHeight(int height) { + checkWidget(); + if (height < -1) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + fixedTabHeight = height; + updateTabHeight(false); +} +/** + * Specify whether the tabs should appear along the top of the folder + * or along the bottom of the folder. + * + * @param position <code>SWT.TOP</code> for tabs along the top or <code>SWT.BOTTOM</code> for tabs along the bottom + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * <li>ERROR_INVALID_ARGUMENT - if the position value is not either SWT.TOP or SWT.BOTTOM</li> + * </ul> + * + * @since 3.0 + */ +public void setTabPosition(int position) { + checkWidget(); + if (position != SWT.TOP && position != SWT.BOTTOM) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + if (onBottom != (position == SWT.BOTTOM)) { + onBottom = position == SWT.BOTTOM; + borderTop = onBottom ? borderLeft : 0; + borderBottom = onBottom ? 0 : borderRight; + updateTabHeight(true); + Rectangle rectBefore = getClientArea(); + updateItems(); + Rectangle rectAfter = getClientArea(); + if (!rectBefore.equals(rectAfter)) { + notifyListeners(SWT.Resize, new Event()); + } + redraw(); + } +} +/** + * Set the control that appears in the top right corner of the tab folder. + * Typically this is a close button or a composite with a Menu and close button. + * The topRight control is optional. Setting the top right control to null will + * remove it from the tab folder. + * + * @param control the control to be displayed in the top right corner or null + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * <li>ERROR_INVALID_ARGUMENT - if the control is not a child of this CTabFolder</li> + * </ul> + * + * @since 2.1 + */ +public void setTopRight(Control control) { + setTopRight(control, SWT.RIGHT); +} +/** + * Set the control that appears in the top right corner of the tab folder. + * Typically this is a close button or a composite with a Menu and close button. + * The topRight control is optional. Setting the top right control to null + * will remove it from the tab folder. + * <p> + * The alignment parameter sets the layout of the control in the tab area. + * <code>SWT.RIGHT</code> will cause the control to be positioned on the far + * right of the folder and it will have its default size. <code>SWT.FILL</code> + * will size the control to fill all the available space to the right of the + * last tab. If there is no available space, the control will not be visible. + * </p> + * + * @param control the control to be displayed in the top right corner or null + * @param alignment <code>SWT.RIGHT</code> or <code>SWT.FILL</code> + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * <li>ERROR_INVALID_ARGUMENT - if the control is not a child of this CTabFolder</li> + * </ul> + * + * @since 3.0 + */ +public void setTopRight(Control control, int alignment) { + checkWidget(); + if (alignment != SWT.RIGHT && alignment != SWT.FILL) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + if (control != null && control.getParent() != this) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + topRight = control; + topRightAlignment = alignment; + if (updateItems()) redraw(); +} +/** + * Specify whether the close button appears + * when the user hovers over an unselected tabs. + * + * @param visible <code>true</code> makes the close button appear + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.0 + */ +public void setUnselectedCloseVisible(boolean visible) { + checkWidget(); + if (showUnselectedClose == visible) return; + // display close button when mouse hovers + showUnselectedClose = visible; + updateItems(); + redraw(); +} +/** + * Specify whether the image appears on unselected tabs. + * + * @param visible <code>true</code> makes the image appear + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.0 + */ +public void setUnselectedImageVisible(boolean visible) { + checkWidget(); + if (showUnselectedImage == visible) return; + // display image on unselected items + showUnselectedImage = visible; + updateItems(); + redraw(); +} +/** + * Shows the item. If the item is already showing in the receiver, + * this method simply returns. Otherwise, the items are scrolled until + * the item is visible. + * + * @param item the item to be shown + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the item is null</li> + * <li>ERROR_INVALID_ARGUMENT - if the item has been disposed</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see CTabFolder#showSelection() + * + * @since 2.0 + */ +public void showItem (CTabItem item) { + checkWidget(); + if (item == null) SWT.error (SWT.ERROR_NULL_ARGUMENT); + if (item.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT); + int index = indexOf(item); + if (index == -1) SWT.error(SWT.ERROR_INVALID_ARGUMENT); + int idx = -1; + for (int i = 0; i < priority.length; i++) { + if (priority[i] == index) { + idx = i; + break; + } + } + if (mru) { + // move to front of mru order + int[] newPriority = new int[priority.length]; + System.arraycopy(priority, 0, newPriority, 1, idx); + System.arraycopy(priority, idx+1, newPriority, idx+1, priority.length - idx - 1); + newPriority[0] = index; + priority = newPriority; + } + if (item.isShowing()) return; + updateItems(index); + redrawTabs(); +} +void showList (Rectangle rect) { + if (items.length == 0 || !showChevron) return; + if (showMenu == null || showMenu.isDisposed()) { + showMenu = new Menu(this); + } else { + MenuItem[] items = showMenu.getItems(); + for (int i = 0; i < items.length; i++) { + items[i].dispose(); + } + } + final String id = "CTabFolder_showList_Index"; //$NON-NLS-1$ + for (int i = 0; i < items.length; i++) { + CTabItem tab = items[i]; + if (tab.showing) continue; + MenuItem item = new MenuItem(showMenu, SWT.NONE); + item.setText(tab.getText()); + item.setImage(tab.getImage()); + item.setData(id, tab); + item.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent e) { + MenuItem menuItem = (MenuItem)e.widget; + int index = indexOf((CTabItem)menuItem.getData(id)); + CTabFolder.this.setSelection(index, true); + } + }); + } + int x = rect.x; + int y = rect.y + rect.height; + Point location = getDisplay().map(this, null, x, y); + showMenu.setLocation(location.x, location.y); + showMenu.setVisible(true); +} +/** + * Shows the selection. If the selection is already showing in the receiver, + * this method simply returns. Otherwise, the items are scrolled until + * the selection is visible. + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see CTabFolder#showItem(CTabItem) + * + * @since 2.0 + */ +public void showSelection () { + checkWidget (); + if (selectedIndex != -1) { + showItem(getSelection()); + } +} + +void _setToolTipText (int x, int y) { + String oldTip = getToolTipText(); + String newTip = _getToolTip(x, y); + if (newTip == null || !newTip.equals(oldTip)) { + setToolTipText(newTip); + } +} + +boolean updateItems() { + return updateItems(selectedIndex); +} + +boolean updateItems(int showIndex) { + if (!single && !mru && showIndex != -1) { + // make sure selected item will be showing + int firstIndex = showIndex; + if (priority[0] < showIndex) { + int maxWidth = getRightItemEdge() - borderLeft; + if (!simple) maxWidth -= curveWidth - 2*curveIndent; + int width = 0; + int[] widths = new int[items.length]; + GC gc = new GC(this); + for (int i = priority[0]; i <= showIndex; i++) { + widths[i] = items[i].preferredWidth(gc, i == selectedIndex, true); + width += widths[i]; + if (width > maxWidth) break; + } + if (width > maxWidth) { + width = 0; + for (int i = showIndex; i >= 0; i--) { + if (widths[i] == 0) widths[i] = items[i].preferredWidth(gc, i == selectedIndex, true); + width += widths[i]; + if (width > maxWidth) break; + firstIndex = i; + } + } else { + firstIndex = priority[0]; + for (int i = showIndex + 1; i < items.length; i++) { + widths[i] = items[i].preferredWidth(gc, i == selectedIndex, true); + width += widths[i]; + if (width >= maxWidth) break; + } + if (width < maxWidth) { + for (int i = priority[0] - 1; i >= 0; i--) { + if (widths[i] == 0) widths[i] = items[i].preferredWidth(gc, i == selectedIndex, true); + width += widths[i]; + if (width > maxWidth) break; + firstIndex = i; + } + } + } + gc.dispose(); + } + if (firstIndex != priority[0]) { + int index = 0; + for (int i = firstIndex; i < items.length; i++) { + priority[index++] = i; + } + for (int i = 0; i < firstIndex; i++) { + priority[index++] = i; + } + } + } + + boolean oldShowChevron = showChevron; + boolean changed = setItemSize(); + changed |= setItemLocation(); + setButtonBounds(); + changed |= showChevron != oldShowChevron; + if (changed && getToolTipText() != null) { + Point pt = getDisplay().getCursorLocation(); + pt = toControl(pt); + _setToolTipText(pt.x, pt.y); + } + return changed; +} +boolean updateTabHeight(boolean force){ + int style = getStyle(); + if (fixedTabHeight == 0 && (style & SWT.FLAT) != 0 && (style & SWT.BORDER) == 0) highlight_header = 0; + int oldHeight = tabHeight; + if (fixedTabHeight != SWT.DEFAULT) { + tabHeight = fixedTabHeight == 0 ? 0 : fixedTabHeight + 1; // +1 for line drawn across top of tab + } else { + int tempHeight = 0; + GC gc = new GC(this); + if (items.length == 0) { + tempHeight = gc.textExtent("Default", CTabItem.FLAGS).y + CTabItem.TOP_MARGIN + CTabItem.BOTTOM_MARGIN; //$NON-NLS-1$ + } else { + for (int i=0; i < items.length; i++) { + tempHeight = Math.max(tempHeight, items[i].preferredHeight(gc)); + } + } + gc.dispose(); + tabHeight = tempHeight; + } + if (!force && tabHeight == oldHeight) return false; + + oldSize = null; + if (onBottom) { + int d = tabHeight - 12; + curve = new int[]{0,13+d, 0,12+d, 2,12+d, 3,11+d, 5,11+d, 6,10+d, 7,10+d, 9,8+d, 10,8+d, + 11,7+d, 11+d,7, + 12+d,6, 13+d,6, 15+d,4, 16+d,4, 17+d,3, 19+d,3, 20+d,2, 22+d,2, 23+d,1}; + curveWidth = 26+d; + curveIndent = curveWidth/3; + } else { + int d = tabHeight - 12; + curve = new int[]{0,0, 0,1, 2,1, 3,2, 5,2, 6,3, 7,3, 9,5, 10,5, + 11,6, 11+d,6+d, + 12+d,7+d, 13+d,7+d, 15+d,9+d, 16+d,9+d, 17+d,10+d, 19+d,10+d, 20+d,11+d, 22+d,11+d, 23+d,12+d}; + curveWidth = 26+d; + curveIndent = curveWidth/3; + + //this could be static but since values depend on curve, better to keep in one place + topCurveHighlightStart = new int[] { + 0, 2, 1, 2, 2, 2, + 3, 3, 4, 3, 5, 3, + 6, 4, 7, 4, + 8, 5, + 9, 6, 10, 6}; + + //also, by adding in 'd' here we save some math cost when drawing the curve + topCurveHighlightEnd = new int[] { + 10+d, 6+d, + 11+d, 7+d, + 12+d, 8+d, 13+d, 8+d, + 14+d, 9+d, + 15+d, 10+d, 16+d, 10+d, + 17+d, 11+d, 18+d, 11+d, 19+d, 11+d, + 20+d, 12+d, 21+d, 12+d, 22+d, 12+d }; + } + notifyListeners(SWT.Resize, new Event()); + return true; +} +String _getToolTip(int x, int y) { + if (showMin && minRect.contains(x, y)) return minimized ? SWT.getMessage("SWT_Restore") : SWT.getMessage("SWT_Minimize"); //$NON-NLS-1$ //$NON-NLS-2$ + if (showMax && maxRect.contains(x, y)) return maximized ? SWT.getMessage("SWT_Restore") : SWT.getMessage("SWT_Maximize"); //$NON-NLS-1$ //$NON-NLS-2$ + if (showChevron && chevronRect.contains(x, y)) return SWT.getMessage("SWT_ShowList"); //$NON-NLS-1$ + CTabItem item = getItem(new Point (x, y)); + if (item == null) return null; + if (!item.showing) return null; + if ((showClose || item.showClose) && item.closeRect.contains(x, y)) { + return SWT.getMessage("SWT_Close"); //$NON-NLS-1$ + } + return item.getToolTipText(); +} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabFolder2Adapter.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabFolder2Adapter.java new file mode 100644 index 0000000000..82c0212709 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabFolder2Adapter.java @@ -0,0 +1,83 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 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.custom; + +/** + * This adapter class provides default implementations for the + * methods described by the <code>CTabFolder2Listener</code> interface. + * <p> + * Classes that wish to deal with <code>CTabFolderEvent</code>s can + * extend this class and override only the methods which they are + * interested in. + * </p> + * + * @see CTabFolder2Listener + * @see CTabFolderEvent + * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> + * + * @since 3.0 + */ +public class CTabFolder2Adapter implements CTabFolder2Listener { + +/** + * Sent when the user clicks on the close button of an item in the CTabFolder. The item being closed is specified + * in the event.item field. Setting the event.doit field to false will stop the CTabItem from closing. + * When the CTabItem is closed, it is disposed. The contents of the CTabItem (see CTabItem#setControl) will be + * made not visible when the CTabItem is closed. + * <p> + * The default behaviour is to close the CTabItem. + * </p> + * + * @param event an event indicating the item being closed + */ +public void close(CTabFolderEvent event){} + +/** + * Sent when the user clicks on the minimize button of a CTabFolder. + * <p> + * The default behaviour is to do nothing. + * </p> + * + * @param event an event containing information about the minimize + */ +public void minimize(CTabFolderEvent event){} + +/** + * Sent when the user clicks on the maximize button of a CTabFolder. + * <p> + * The default behaviour is to do nothing. + * </p> + * + * @param event an event containing information about the maximize + */ +public void maximize(CTabFolderEvent event){} + +/** + * Sent when the user clicks on the restore button of a CTabFolder. + * <p> + * The default behaviour is to do nothing. + * </p> + * + * @param event an event containing information about the restore + */ +public void restore(CTabFolderEvent event){} + +/** + * Sent when the user clicks on the chevron button of a CTabFolder. + * <p> + * The default behaviour is to show a list of items that are not currently + * visible and to change the selection based on the item selected from the list. + * </p> + * + * @param event an event containing information about the show list + */ +public void showList(CTabFolderEvent event){} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabFolder2Listener.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabFolder2Listener.java new file mode 100644 index 0000000000..7e2e17862f --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabFolder2Listener.java @@ -0,0 +1,112 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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.custom; + +import org.eclipse.swt.internal.*; + +/** + * Classes which implement this interface provide methods + * that deal with the events that are generated by the CTabFolder + * control. + * <p> + * After creating an instance of a class that implements + * this interface it can be added to a CTabFolder using the + * <code>addCTabFolder2Listener</code> method and removed using + * the <code>removeCTabFolder2Listener</code> method. When + * events occurs in a CTabFolder the appropriate method + * will be invoked. + * </p> + * + * @see CTabFolder2Adapter + * @see CTabFolderEvent + * + * @since 3.0 + */ +public interface CTabFolder2Listener extends SWTEventListener { + +/** + * Sent when the user clicks on the close button of an item in the CTabFolder. + * The item being closed is specified in the event.item field. + * Setting the event.doit field to false will stop the CTabItem from closing. + * When the CTabItem is closed, it is disposed. The contents of the + * CTabItem (see CTabItem.setControl) will be made not visible when + * the CTabItem is closed. + * + * @param event an event indicating the item being closed + */ +public void close(CTabFolderEvent event); + +/** + * Sent when the user clicks on the minimize button of a CTabFolder. + * The state of the CTabFolder does not change automatically - it + * is up to the application to change the state of the CTabFolder + * in response to this event using CTabFolder.setMinimized(true). + * + * @param event an event containing information about the minimize + * + * @see CTabFolder#getMinimized() + * @see CTabFolder#setMinimized(boolean) + * @see CTabFolder#setMinimizeVisible(boolean) + */ +public void minimize(CTabFolderEvent event); + +/** + * Sent when the user clicks on the maximize button of a CTabFolder. + * The state of the CTabFolder does not change automatically - it + * is up to the application to change the state of the CTabFolder + * in response to this event using CTabFolder.setMaximized(true). + * + * @param event an event containing information about the maximize + * + * @see CTabFolder#getMaximized() + * @see CTabFolder#setMaximized(boolean) + * @see CTabFolder#setMaximizeVisible(boolean) + */ +public void maximize(CTabFolderEvent event); + +/** + * Sent when the user clicks on the restore button of a CTabFolder. + * This event is sent either to restore the CTabFolder from the + * minimized state or from the maximized state. To determine + * which restore is requested, use CTabFolder.getMinimized() or + * CTabFolder.getMaximized() to determine the current state. + * The state of the CTabFolder does not change automatically - it + * is up to the application to change the state of the CTabFolder + * in response to this event using CTabFolder.setMaximized(false) + * or CTabFolder.setMinimized(false). + * + * @param event an event containing information about the restore + * + * @see CTabFolder#getMinimized() + * @see CTabFolder#getMaximized() + * @see CTabFolder#setMinimized(boolean) + * @see CTabFolder#setMinimizeVisible(boolean) + * @see CTabFolder#setMaximized(boolean) + * @see CTabFolder#setMaximizeVisible(boolean) + */ +public void restore(CTabFolderEvent event); + +/** + * Sent when the user clicks on the chevron button of the CTabFolder. + * A chevron appears in the CTabFolder when there are more tabs + * than can be displayed at the current widget size. To select a + * tab that is not currently visible, the user clicks on the + * chevron and selects a tab item from a list. By default, the + * CTabFolder provides a list of all the items that are not currently + * visible, however, the application can provide its own list by setting + * the event.doit field to <code>false</code> and displaying a selection list. + * + * @param event an event containing information about the show list + * + * @see CTabFolder#setSelection(CTabItem) + */ +public void showList(CTabFolderEvent event); +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabFolderAdapter.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabFolderAdapter.java new file mode 100755 index 0000000000..85f210a938 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabFolderAdapter.java @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 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.custom; + + +/** + * This adapter class provides a default implementation for the + * method described by the <code>CTabFolderListener</code> interface. + * + * @see CTabFolderListener + * @see CTabFolderEvent + * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> + */ +public class CTabFolderAdapter implements CTabFolderListener { + public void itemClosed(CTabFolderEvent event){} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabFolderEvent.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabFolderEvent.java new file mode 100755 index 0000000000..99394bf37a --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabFolderEvent.java @@ -0,0 +1,92 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 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.custom; + + +import org.eclipse.swt.events.*; +import org.eclipse.swt.widgets.*; + +/** + * This event is sent when an event is generated in the CTabFolder. + * + * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> + */ +public class CTabFolderEvent extends TypedEvent { + /** + * The tab item for the operation. + */ + public Widget item; + + /** + * A flag indicating whether the operation should be allowed. + * Setting this field to <code>false</code> will cancel the operation. + * Applies to the close and showList events. + */ + public boolean doit; + + /** + * The widget-relative, x coordinate of the chevron button + * at the time of the event. Applies to the showList event. + * + * @since 3.0 + */ + public int x; + /** + * The widget-relative, y coordinate of the chevron button + * at the time of the event. Applies to the showList event. + * + * @since 3.0 + */ + public int y; + /** + * The width of the chevron button at the time of the event. + * Applies to the showList event. + * + * @since 3.0 + */ + public int width; + /** + * The height of the chevron button at the time of the event. + * Applies to the showList event. + * + * @since 3.0 + */ + public int height; + + static final long serialVersionUID = 3760566386225066807L; + +/** + * Constructs a new instance of this class. + * + * @param w the widget that fired the event + */ +CTabFolderEvent(Widget w) { + super(w); +} + +/** + * Returns a string containing a concise, human-readable + * description of the receiver. + * + * @return a string representation of the event + */ +public String toString() { + String string = super.toString (); + return string.substring (0, string.length() - 1) // remove trailing '}' + + " item=" + item + + " doit=" + doit + + " x=" + x + + " y=" + y + + " width=" + width + + " height=" + height + + "}"; +} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabFolderLayout.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabFolderLayout.java new file mode 100644 index 0000000000..e01fac6e55 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabFolderLayout.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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.custom; + +import org.eclipse.swt.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.widgets.*; + +/** + * This class provides the layout for CTabFolder + * + * @see CTabFolder + */ +class CTabFolderLayout extends Layout { +protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) { + CTabFolder folder = (CTabFolder)composite; + CTabItem[] items = folder.items; + // preferred width of tab area to show all tabs + int tabW = 0; + GC gc = new GC(folder); + for (int i = 0; i < items.length; i++) { + if (folder.single) { + tabW = Math.max(tabW, items[i].preferredWidth(gc, true, false)); + } else { + tabW += items[i].preferredWidth(gc, i == folder.selectedIndex, false); + } + } + gc.dispose(); + tabW += 3; + if (folder.showMax) tabW += CTabFolder.BUTTON_SIZE; + if (folder.showMin) tabW += CTabFolder.BUTTON_SIZE; + if (folder.single) tabW += 3*CTabFolder.BUTTON_SIZE/2; //chevron + if (folder.topRight != null) { + Point pt = folder.topRight.computeSize(SWT.DEFAULT, folder.tabHeight, flushCache); + tabW += 3 + pt.x; + } + if (!folder.single && !folder.simple) tabW += folder.curveWidth - 2*folder.curveIndent; + + int controlW = 0; + int controlH = 0; + // preferred size of controls in tab items + for (int i = 0; i < items.length; i++) { + Control control = items[i].getControl(); + if (control != null && !control.isDisposed()){ + Point size = control.computeSize (wHint, hHint, flushCache); + controlW = Math.max (controlW, size.x); + controlH = Math.max (controlH, size.y); + } + } + + int minWidth = Math.max(tabW, controlW); + int minHeight = (folder.minimized) ? 0 : controlH; + if (minWidth == 0) minWidth = CTabFolder.DEFAULT_WIDTH; + if (minHeight == 0) minHeight = CTabFolder.DEFAULT_HEIGHT; + + if (wHint != SWT.DEFAULT) minWidth = wHint; + if (hHint != SWT.DEFAULT) minHeight = hHint; + + return new Point (minWidth, minHeight); +} +protected boolean flushCache(Control control) { + return true; +} +protected void layout(Composite composite, boolean flushCache) { + CTabFolder folder = (CTabFolder)composite; + // resize content + if (folder.selectedIndex != -1) { + Control control = folder.items[folder.selectedIndex].getControl(); + if (control != null && !control.isDisposed()) { + control.setBounds(folder.getClientArea()); + } + } +} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabFolderListener.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabFolderListener.java new file mode 100755 index 0000000000..4fdeab51fe --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabFolderListener.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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.custom; + +import org.eclipse.swt.internal.*; + +/** + * Classes which implement this interface provide a method + * that deals with events generated in the CTabFolder. + * <p> + * After creating an instance of a class that implements + * this interface it can be added to a CTabFolder using the + * <code>addCTabFolderListener</code> method and removed using + * the <code>removeCTabFolderListener</code> method. When a + * tab item is closed, the itemClosed method will be invoked. + * </p> + * + * @see CTabFolderEvent + */ +public interface CTabFolderListener extends SWTEventListener { + +/** + * Sent when the user clicks on the close button of an item in the CTabFolder. The item being closed is specified + * in the event.item field. Setting the event.doit field to false will stop the CTabItem from closing. + * When the CTabItem is closed, it is disposed. The contents of the CTabItem (see CTabItem.setControl) will be + * made not visible when the CTabItem is closed. + * + * @param event an event indicating the item being closed + * + * @see CTabItem#setControl + */ +public void itemClosed(CTabFolderEvent event); +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabItem.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabItem.java new file mode 100755 index 0000000000..ffe19d20e0 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabItem.java @@ -0,0 +1,1053 @@ +/******************************************************************************* + * 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.custom; + + +import org.eclipse.swt.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.widgets.*; + +/** + * Instances of this class represent a selectable user interface object + * that represent a page in a notebook widget. + * + * <dl> + * <dt><b>Styles:</b></dt> + * <dd>SWT.CLOSE</dd> + * <dt><b>Events:</b></dt> + * <dd>(none)</dd> + * </dl> + * <p> + * IMPORTANT: This class is <em>not</em> intended to be subclassed. + * </p> + * + * @see <a href="http://www.eclipse.org/swt/snippets/#ctabfolder">CTabFolder, CTabItem snippets</a> + * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> + * @noextend This class is not intended to be subclassed by clients. + */ +public class CTabItem extends Item { + CTabFolder parent; + int x,y,width,height = 0; + Control control; // the tab page + + String toolTipText; + String shortenedText; + int shortenedTextWidth; + + // Appearance + Font font; + Image disabledImage; + + Rectangle closeRect = new Rectangle(0, 0, 0, 0); + int closeImageState = CTabFolder.NONE; + boolean showClose = false; + boolean showing = false; + + // internal constants + static final int TOP_MARGIN = 2; + static final int BOTTOM_MARGIN = 2; + static final int LEFT_MARGIN = 4; + static final int RIGHT_MARGIN = 4; + static final int INTERNAL_SPACING = 4; + static final int FLAGS = SWT.DRAW_TRANSPARENT | SWT.DRAW_MNEMONIC; + static final String ELLIPSIS = "..."; //$NON-NLS-1$ // could use the ellipsis glyph on some platforms "\u2026" + +/** + * Constructs a new instance of this class given its parent + * (which must be a <code>CTabFolder</code>) and a style value + * describing its behavior and appearance. The item is added + * to the end of the items maintained by its parent. + * <p> + * The style value is either one of the style constants defined in + * class <code>SWT</code> which is applicable to instances of this + * class, or must be built by <em>bitwise OR</em>'ing together + * (that is, using the <code>int</code> "|" operator) two or more + * of those <code>SWT</code> style constants. The class description + * lists the style constants that are applicable to the class. + * Style bits are also inherited from superclasses. + * </p> + * + * @param parent a CTabFolder which will be the parent of the new instance (cannot be null) + * @param style the style of control to construct + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the parent is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li> + * </ul> + * + * @see SWT + * @see Widget#getStyle() + */ +public CTabItem (CTabFolder parent, int style) { + this(parent, style, parent.getItemCount()); +} +/** + * Constructs a new instance of this class given its parent + * (which must be a <code>CTabFolder</code>), a style value + * describing its behavior and appearance, and the index + * at which to place it in the items maintained by its parent. + * <p> + * The style value is either one of the style constants defined in + * class <code>SWT</code> which is applicable to instances of this + * class, or must be built by <em>bitwise OR</em>'ing together + * (that is, using the <code>int</code> "|" operator) two or more + * of those <code>SWT</code> style constants. The class description + * lists the style constants that are applicable to the class. + * Style bits are also inherited from superclasses. + * </p> + * + * @param parent a CTabFolder which will be the parent of the new instance (cannot be null) + * @param style the style of control to construct + * @param index the zero-relative index to store the receiver in its parent + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the parent is null</li> + * <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the parent (inclusive)</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li> + * </ul> + * + * @see SWT + * @see Widget#getStyle() + */ +public CTabItem (CTabFolder parent, int style, int index) { + super (parent, style); + showClose = (style & SWT.CLOSE) != 0; + parent.createItem (this, index); +} + +/* + * Return whether to use ellipses or just truncate labels + */ +boolean useEllipses() { + return parent.simple; +} + +String shortenText(GC gc, String text, int width) { + return useEllipses() + ? shortenText(gc, text, width, ELLIPSIS) + : shortenText(gc, text, width, ""); //$NON-NLS-1$ +} + +String shortenText(GC gc, String text, int width, String ellipses) { + if (gc.textExtent(text, FLAGS).x <= width) return text; + int ellipseWidth = gc.textExtent(ellipses, FLAGS).x; + int length = text.length(); + TextLayout layout = new TextLayout(getDisplay()); + layout.setText(text); + int end = layout.getPreviousOffset(length, SWT.MOVEMENT_CLUSTER); + while (end > 0) { + text = text.substring(0, end); + int l = gc.textExtent(text, FLAGS).x; + if (l + ellipseWidth <= width) { + break; + } + end = layout.getPreviousOffset(end, SWT.MOVEMENT_CLUSTER); + } + layout.dispose(); + return end == 0 ? text.substring(0, 1) : text + ellipses; +} + +public void dispose() { + if (isDisposed ()) return; + //if (!isValidThread ()) error (SWT.ERROR_THREAD_INVALID_ACCESS); + parent.destroyItem(this); + super.dispose(); + parent = null; + control = null; + toolTipText = null; + shortenedText = null; + font = null; +} +void drawClose(GC gc) { + if (closeRect.width == 0 || closeRect.height == 0) return; + Display display = getDisplay(); + + // draw X 9x9 + int indent = Math.max(1, (CTabFolder.BUTTON_SIZE-9)/2); + int x = closeRect.x + indent; + int y = closeRect.y + indent; + y += parent.onBottom ? -1 : 1; + + Color closeBorder = display.getSystemColor(CTabFolder.BUTTON_BORDER); + switch (closeImageState) { + case CTabFolder.NORMAL: { + int[] shape = new int[] {x,y, x+2,y, x+4,y+2, x+5,y+2, x+7,y, x+9,y, + x+9,y+2, x+7,y+4, x+7,y+5, x+9,y+7, x+9,y+9, + x+7,y+9, x+5,y+7, x+4,y+7, x+2,y+9, x,y+9, + x,y+7, x+2,y+5, x+2,y+4, x,y+2}; + gc.setBackground(display.getSystemColor(CTabFolder.BUTTON_FILL)); + gc.fillPolygon(shape); + gc.setForeground(closeBorder); + gc.drawPolygon(shape); + break; + } + case CTabFolder.HOT: { + int[] shape = new int[] {x,y, x+2,y, x+4,y+2, x+5,y+2, x+7,y, x+9,y, + x+9,y+2, x+7,y+4, x+7,y+5, x+9,y+7, x+9,y+9, + x+7,y+9, x+5,y+7, x+4,y+7, x+2,y+9, x,y+9, + x,y+7, x+2,y+5, x+2,y+4, x,y+2}; + gc.setBackground(parent.getFillColor()); + gc.fillPolygon(shape); + gc.setForeground(closeBorder); + gc.drawPolygon(shape); + break; + } + case CTabFolder.SELECTED: { + int[] shape = new int[] {x+1,y+1, x+3,y+1, x+5,y+3, x+6,y+3, x+8,y+1, x+10,y+1, + x+10,y+3, x+8,y+5, x+8,y+6, x+10,y+8, x+10,y+10, + x+8,y+10, x+6,y+8, x+5,y+8, x+3,y+10, x+1,y+10, + x+1,y+8, x+3,y+6, x+3,y+5, x+1,y+3}; + gc.setBackground(parent.getFillColor()); + gc.fillPolygon(shape); + gc.setForeground(closeBorder); + gc.drawPolygon(shape); + break; + } + case CTabFolder.NONE: { + int[] shape = new int[] {x,y, x+10,y, x+10,y+10, x,y+10}; + if (parent.gradientColors != null && !parent.gradientVertical) { + parent.drawBackground(gc, shape, false); + } else { + Color defaultBackground = parent.getBackground(); + Color[] colors = parent.gradientColors; + int[] percents = parent.gradientPercents; + boolean vertical = parent.gradientVertical; + parent.drawBackground(gc, shape, x, y, 10, 10, defaultBackground, null, colors, percents, vertical); + } + break; + } + } +} +void drawSelected(GC gc ) { + Point size = parent.getSize(); + int rightEdge = Math.min (x + width, parent.getRightItemEdge()); + + // Draw selection border across all tabs + int xx = parent.borderLeft; + int yy = parent.onBottom ? size.y - parent.borderBottom - parent.tabHeight - parent.highlight_header : parent.borderTop + parent.tabHeight + 1; + int ww = size.x - parent.borderLeft - parent.borderRight; + int hh = parent.highlight_header - 1; + int[] shape = new int[] {xx,yy, xx+ww,yy, xx+ww,yy+hh, xx,yy+hh}; + if (parent.selectionGradientColors != null && !parent.selectionGradientVertical) { + parent.drawBackground(gc, shape, true); + } else { + gc.setBackground(parent.selectionBackground); + gc.fillRectangle(xx, yy, ww, hh); + } + + if (parent.single) { + if (!showing) return; + } else { + // if selected tab scrolled out of view or partially out of view + // just draw bottom line + if (!showing){ + int x1 = Math.max(0, parent.borderLeft - 1); + int y1 = (parent.onBottom) ? y - 1 : y + height; + int x2 = size.x - parent.borderRight; + gc.setForeground(getDisplay().getSystemColor(CTabFolder.BORDER1_COLOR)); + gc.drawLine(x1, y1, x2, y1); + return; + } + + // draw selected tab background and outline + shape = null; + if (this.parent.onBottom) { + int[] left = parent.simple ? CTabFolder.SIMPLE_BOTTOM_LEFT_CORNER : CTabFolder.BOTTOM_LEFT_CORNER; + int[] right = parent.simple ? CTabFolder.SIMPLE_BOTTOM_RIGHT_CORNER : parent.curve; + if (parent.borderLeft == 0 && parent.indexOf(this) == parent.firstIndex) { + left = new int[]{x, y+height}; + } + shape = new int[left.length+right.length+8]; + int index = 0; + shape[index++] = x; // first point repeated here because below we reuse shape to draw outline + shape[index++] = y - 1; + shape[index++] = x; + shape[index++] = y - 1; + for (int i = 0; i < left.length/2; i++) { + shape[index++] = x + left[2*i]; + shape[index++] = y + height + left[2*i+1] - 1; + } + for (int i = 0; i < right.length/2; i++) { + shape[index++] = parent.simple ? rightEdge - 1 + right[2*i] : rightEdge - parent.curveIndent + right[2*i]; + shape[index++] = parent.simple ? y + height + right[2*i+1] - 1 : y + right[2*i+1] - 2; + } + shape[index++] = parent.simple ? rightEdge - 1 : rightEdge + parent.curveWidth - parent.curveIndent; + shape[index++] = y - 1; + shape[index++] = parent.simple ? rightEdge - 1 : rightEdge + parent.curveWidth - parent.curveIndent; + shape[index++] = y - 1; + } else { + int[] left = parent.simple ? CTabFolder.SIMPLE_TOP_LEFT_CORNER : CTabFolder.TOP_LEFT_CORNER; + int[] right = parent.simple ? CTabFolder.SIMPLE_TOP_RIGHT_CORNER : parent.curve; + if (parent.borderLeft == 0 && parent.indexOf(this) == parent.firstIndex) { + left = new int[]{x, y}; + } + shape = new int[left.length+right.length+8]; + int index = 0; + shape[index++] = x; // first point repeated here because below we reuse shape to draw outline + shape[index++] = y + height + 1; + shape[index++] = x; + shape[index++] = y + height + 1; + for (int i = 0; i < left.length/2; i++) { + shape[index++] = x + left[2*i]; + shape[index++] = y + left[2*i+1]; + } + for (int i = 0; i < right.length/2; i++) { + shape[index++] = parent.simple ? rightEdge - 1 + right[2*i] : rightEdge - parent.curveIndent + right[2*i]; + shape[index++] = y + right[2*i+1]; + } + shape[index++] = parent.simple ? rightEdge - 1 : rightEdge + parent.curveWidth - parent.curveIndent; + shape[index++] = y + height + 1; + shape[index++] = parent.simple ? rightEdge - 1 : rightEdge + parent.curveWidth - parent.curveIndent; + shape[index++] = y + height + 1; + } + + Rectangle clipping = gc.getClipping(); + Rectangle bounds = getBounds(); + bounds.height += 1; + if (parent.onBottom) bounds.y -= 1; + boolean tabInPaint = clipping.intersects(bounds); + + if (tabInPaint) { + // fill in tab background + if (parent.selectionGradientColors != null && !parent.selectionGradientVertical) { + parent.drawBackground(gc, shape, true); + } else { + Color defaultBackground = parent.selectionBackground; + Image image = parent.selectionBgImage; + Color[] colors = parent.selectionGradientColors; + int[] percents = parent.selectionGradientPercents; + boolean vertical = parent.selectionGradientVertical; + xx = x; + yy = parent.onBottom ? y -1 : y + 1; + ww = width; + hh = height; + if (!parent.single && !parent.simple) ww += parent.curveWidth - parent.curveIndent; + parent.drawBackground(gc, shape, xx, yy, ww, hh, defaultBackground, image, colors, percents, vertical); + } + } + + //Highlight MUST be drawn before the outline so that outline can cover it in the right spots (start of swoop) + //otherwise the curve looks jagged + drawHighlight(gc, rightEdge); + + // draw outline + shape[0] = Math.max(0, parent.borderLeft - 1); + if (parent.borderLeft == 0 && parent.indexOf(this) == parent.firstIndex) { + shape[1] = parent.onBottom ? y + height - 1 : y; + shape[5] = shape[3] = shape[1]; + } + shape[shape.length - 2] = size.x - parent.borderRight + 1; + for (int i = 0; i < shape.length/2; i++) { + if (shape[2*i + 1] == y + height + 1) shape[2*i + 1] -= 1; + } + RGB inside = parent.selectionBackground.getRGB(); + if (parent.selectionBgImage != null || + (parent.selectionGradientColors != null && parent.selectionGradientColors.length > 1)) { + inside = null; + } + RGB outside = parent.getBackground().getRGB(); + if (parent.gradientColors != null && parent.gradientColors.length > 1) { + outside = null; + } + Color borderColor = getDisplay().getSystemColor(CTabFolder.BORDER1_COLOR); + parent.antialias(shape, borderColor.getRGB(), inside, outside, gc); + gc.setForeground(borderColor); + gc.drawPolyline(shape); + + if (!tabInPaint) return; + } + + // draw Image + int xDraw = x + LEFT_MARGIN; + if (parent.single && (parent.showClose || showClose)) xDraw += CTabFolder.BUTTON_SIZE; + Image image = getImage(); + if (image != null) { + Rectangle imageBounds = image.getBounds(); + // only draw image if it won't overlap with close button + int maxImageWidth = rightEdge - xDraw - RIGHT_MARGIN; + if (!parent.single && closeRect.width > 0) maxImageWidth -= closeRect.width + INTERNAL_SPACING; + if (imageBounds.width < maxImageWidth) { + int imageX = xDraw; + int imageY = y + (height - imageBounds.height) / 2; + imageY += parent.onBottom ? -1 : 1; + gc.drawImage(image, imageX, imageY); + xDraw += imageBounds.width + INTERNAL_SPACING; + } + } + + // draw Text + int textWidth = rightEdge - xDraw - RIGHT_MARGIN; + if (!parent.single && closeRect.width > 0) textWidth -= closeRect.width + INTERNAL_SPACING; + if (textWidth > 0) { + Font gcFont = gc.getFont(); + gc.setFont(font == null ? parent.getFont() : font); + + if (shortenedText == null || shortenedTextWidth != textWidth) { + shortenedText = shortenText(gc, getText(), textWidth); + shortenedTextWidth = textWidth; + } + Point extent = gc.textExtent(shortenedText, FLAGS); + int textY = y + (height - extent.y) / 2; + textY += parent.onBottom ? -1 : 1; + + gc.setForeground(parent.selectionForeground); + gc.drawText(shortenedText, xDraw, textY, FLAGS); + gc.setFont(gcFont); + + // draw a Focus rectangle + if (parent.isFocusControl()) { + Display display = getDisplay(); + if (parent.simple || parent.single) { + gc.setBackground(display.getSystemColor(SWT.COLOR_BLACK)); + gc.setForeground(display.getSystemColor(SWT.COLOR_WHITE)); + gc.drawFocus(xDraw-1, textY-1, extent.x+2, extent.y+2); + } else { + gc.setForeground(display.getSystemColor(CTabFolder.BUTTON_BORDER)); + gc.drawLine(xDraw, textY+extent.y+1, xDraw+extent.x+1, textY+extent.y+1); + } + } + } + if (parent.showClose || showClose) drawClose(gc); +} + +/* + * Draw a highlight effect along the left, top, and right edges of the tab. + * Only for curved tabs, on top. + * Do not draw if insufficient colors. + */ +void drawHighlight(GC gc, int rightEdge) { + //only draw for curvy tabs and only draw for top tabs + if(parent.simple || this.parent.onBottom) + return; + + if(parent.selectionHighlightGradientBegin == null) + return; + + Color[] gradients = parent.selectionHighlightGradientColorsCache; + if(gradients == null) + return; + int gradientsSize = gradients.length; + if(gradientsSize == 0) + return; //shouldn't happen but just to be tidy + + gc.setForeground(gradients[0]); + + //draw top horizontal line + gc.drawLine( + CTabFolder.TOP_LEFT_CORNER_HILITE[0] + x + 1, //rely on fact that first pair is top/right of curve + 1 + y, + rightEdge - parent.curveIndent, + 1 + y); + + int[] leftHighlightCurve = CTabFolder.TOP_LEFT_CORNER_HILITE; + + int d = parent.tabHeight - parent.topCurveHighlightEnd.length /2; + + int lastX = 0; + int lastY = 0; + int lastColorIndex = 0; + + //draw upper left curve highlight + for (int i = 0; i < leftHighlightCurve.length /2; i++) { + int rawX = leftHighlightCurve[i * 2]; + int rawY = leftHighlightCurve[i * 2 + 1]; + lastX = rawX + x; + lastY = rawY + y; + lastColorIndex = rawY - 1; + gc.setForeground(gradients[lastColorIndex]); + gc.drawPoint(lastX, lastY); + } + //draw left vertical line highlight + for(int i = lastColorIndex; i < gradientsSize; i++) { + gc.setForeground(gradients[i]); + gc.drawPoint(lastX, 1 + lastY++); + } + + int rightEdgeOffset = rightEdge - parent.curveIndent; + + //draw right swoop highlight up to diagonal portion + for (int i = 0; i < parent.topCurveHighlightStart.length /2; i++) { + int rawX = parent.topCurveHighlightStart[i * 2]; + int rawY = parent.topCurveHighlightStart[i * 2 + 1]; + lastX = rawX + rightEdgeOffset; + lastY = rawY + y; + lastColorIndex = rawY - 1; + if(lastColorIndex >= gradientsSize) + break; //can happen if tabs are unusually short and cut off the curve + gc.setForeground(gradients[lastColorIndex]); + gc.drawPoint(lastX, lastY); + } + //draw right diagonal line highlight + for(int i = lastColorIndex; i < lastColorIndex + d; i++) { + if(i >= gradientsSize) + break; //can happen if tabs are unusually short and cut off the curve + gc.setForeground(gradients[i]); + gc.drawPoint(1 + lastX++, 1 + lastY++); + } + + //draw right swoop highlight from diagonal portion to end + for (int i = 0; i < parent.topCurveHighlightEnd.length /2; i++) { + int rawX = parent.topCurveHighlightEnd[i * 2]; //d is already encoded in this value + int rawY = parent.topCurveHighlightEnd[i * 2 + 1]; //d already encoded + lastX = rawX + rightEdgeOffset; + lastY = rawY + y; + lastColorIndex = rawY - 1; + if(lastColorIndex >= gradientsSize) + break; //can happen if tabs are unusually short and cut off the curve + gc.setForeground(gradients[lastColorIndex]); + gc.drawPoint(lastX, lastY); + } +} + +/* + * Draw the unselected border for the receiver on the right. + * + * @param gc + */ +void drawRightUnselectedBorder(GC gc) { + + int[] shape = null; + int startX = x + width - 1; + + if (this.parent.onBottom) { + int[] right = parent.simple + ? CTabFolder.SIMPLE_UNSELECTED_INNER_CORNER + : CTabFolder.BOTTOM_RIGHT_CORNER; + + shape = new int[right.length + 2]; + int index = 0; + + for (int i = 0; i < right.length / 2; i++) { + shape[index++] = startX + right[2 * i]; + shape[index++] = y + height + right[2 * i + 1] - 1; + } + shape[index++] = startX; + shape[index++] = y - 1; + } else { + int[] right = parent.simple + ? CTabFolder.SIMPLE_UNSELECTED_INNER_CORNER + : CTabFolder.TOP_RIGHT_CORNER; + + shape = new int[right.length + 2]; + int index = 0; + + for (int i = 0; i < right.length / 2; i++) { + shape[index++] = startX + right[2 * i]; + shape[index++] = y + right[2 * i + 1]; + } + + shape[index++] = startX; + shape[index++] = y + height; + + } + + drawBorder(gc, shape); + +} + +/* + * Draw the border of the tab + * + * @param gc + * @param shape + */ +void drawBorder(GC gc, int[] shape) { + + gc.setForeground(getDisplay().getSystemColor(CTabFolder.BORDER1_COLOR)); + gc.drawPolyline(shape); +} + +/* + * Draw the unselected border for the receiver on the left. + * + * @param gc + */ +void drawLeftUnselectedBorder(GC gc) { + + int[] shape = null; + if (this.parent.onBottom) { + int[] left = parent.simple + ? CTabFolder.SIMPLE_UNSELECTED_INNER_CORNER + : CTabFolder.BOTTOM_LEFT_CORNER; + + shape = new int[left.length + 2]; + int index = 0; + shape[index++] = x; + shape[index++] = y - 1; + for (int i = 0; i < left.length / 2; i++) { + shape[index++] = x + left[2 * i]; + shape[index++] = y + height + left[2 * i + 1] - 1; + } + } else { + int[] left = parent.simple + ? CTabFolder.SIMPLE_UNSELECTED_INNER_CORNER + : CTabFolder.TOP_LEFT_CORNER; + + shape = new int[left.length + 2]; + int index = 0; + shape[index++] = x; + shape[index++] = y + height; + for (int i = 0; i < left.length / 2; i++) { + shape[index++] = x + left[2 * i]; + shape[index++] = y + left[2 * i + 1]; + } + + } + + drawBorder(gc, shape); +} + +void drawUnselected(GC gc) { + // Do not draw partial items + if (!showing) return; + + Rectangle clipping = gc.getClipping(); + Rectangle bounds = getBounds(); + if (!clipping.intersects(bounds)) return; + + // draw border + int index = parent.indexOf(this); + + if (index > 0 && index < parent.selectedIndex) + drawLeftUnselectedBorder(gc); + // If it is the last one then draw a line + if (index > parent.selectedIndex) + drawRightUnselectedBorder(gc); + + // draw Image + int xDraw = x + LEFT_MARGIN; + Image image = getImage(); + if (image != null && parent.showUnselectedImage) { + Rectangle imageBounds = image.getBounds(); + // only draw image if it won't overlap with close button + int maxImageWidth = x + width - xDraw - RIGHT_MARGIN; + if (parent.showUnselectedClose && (parent.showClose || showClose)) { + maxImageWidth -= closeRect.width + INTERNAL_SPACING; + } + if (imageBounds.width < maxImageWidth) { + int imageX = xDraw; + int imageHeight = imageBounds.height; + int imageY = y + (height - imageHeight) / 2; + imageY += parent.onBottom ? -1 : 1; + int imageWidth = imageBounds.width * imageHeight / imageBounds.height; + gc.drawImage(image, + imageBounds.x, imageBounds.y, imageBounds.width, imageBounds.height, + imageX, imageY, imageWidth, imageHeight); + xDraw += imageWidth + INTERNAL_SPACING; + } + } + // draw Text + int textWidth = x + width - xDraw - RIGHT_MARGIN; + if (parent.showUnselectedClose && (parent.showClose || showClose)) { + textWidth -= closeRect.width + INTERNAL_SPACING; + } + if (textWidth > 0) { + Font gcFont = gc.getFont(); + gc.setFont(font == null ? parent.getFont() : font); + if (shortenedText == null || shortenedTextWidth != textWidth) { + shortenedText = shortenText(gc, getText(), textWidth); + shortenedTextWidth = textWidth; + } + Point extent = gc.textExtent(shortenedText, FLAGS); + int textY = y + (height - extent.y) / 2; + textY += parent.onBottom ? -1 : 1; + gc.setForeground(parent.getForeground()); + gc.drawText(shortenedText, xDraw, textY, FLAGS); + gc.setFont(gcFont); + } + // draw close + if (parent.showUnselectedClose && (parent.showClose || showClose)) drawClose(gc); +} +/** + * Returns a rectangle describing the receiver's size and location + * relative to its parent. + * + * @return the receiver's bounding column rectangle + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public Rectangle getBounds () { + //checkWidget(); + int w = width; + if (!parent.simple && !parent.single && parent.indexOf(this) == parent.selectedIndex) w += parent.curveWidth - parent.curveIndent; + return new Rectangle(x, y, w, height); +} +/** +* Gets the control that is displayed in the content area of the tab item. +* +* @return the control +* +* @exception SWTException <ul> +* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> +* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> +* </ul> +*/ +public Control getControl () { + checkWidget(); + return control; +} +/** + * Get the image displayed in the tab if the tab is disabled. + * + * @return the disabled image or null + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @deprecated the disabled image is not used + */ +public Image getDisabledImage(){ + checkWidget(); + return disabledImage; +} +/** + * Returns the font that the receiver will use to paint textual information. + * + * @return the receiver's font + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.0 + */ +public Font getFont() { + checkWidget(); + if (font != null) return font; + return parent.getFont(); +} +/** + * Returns the receiver's parent, which must be a <code>CTabFolder</code>. + * + * @return the receiver's parent + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public CTabFolder getParent () { + //checkWidget(); + return parent; +} +/** + * Returns <code>true</code> to indicate that the receiver's close button should be shown. + * Otherwise return <code>false</code>. The initial value is defined by the style (SWT.CLOSE) + * that was used to create the receiver. + * + * @return <code>true</code> if the close button should be shown + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.4 + */ +public boolean getShowClose() { + checkWidget(); + return showClose; +} +/** + * Returns the receiver's tool tip text, or null if it has + * not been set. + * + * @return the receiver's tool tip text + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public String getToolTipText () { + checkWidget(); + if (toolTipText == null && shortenedText != null) { + String text = getText(); + if (!shortenedText.equals(text)) return text; + } + return toolTipText; +} +/** +* Returns <code>true</code> if the item will be rendered in the visible area of the CTabFolder. Returns false otherwise. +* +* @return <code>true</code> if the item will be rendered in the visible area of the CTabFolder. Returns false otherwise. +* +* @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * +* @since 3.0 +*/ +public boolean isShowing () { + checkWidget(); + return showing; +} +void onPaint(GC gc, boolean isSelected) { + if (width == 0 || height == 0) return; + if (isSelected) { + drawSelected(gc); + } else { + drawUnselected(gc); + } +} +int preferredHeight(GC gc) { + Image image = getImage(); + int h = (image == null) ? 0 : image.getBounds().height; + String text = getText(); + if (font == null) { + h = Math.max(h, gc.textExtent(text, FLAGS).y); + } else { + Font gcFont = gc.getFont(); + gc.setFont(font); + h = Math.max(h, gc.textExtent(text, FLAGS).y); + gc.setFont(gcFont); + } + return h + TOP_MARGIN + BOTTOM_MARGIN; +} +int preferredWidth(GC gc, boolean isSelected, boolean minimum) { + // NOTE: preferred width does not include the "dead space" caused + // by the curve. + if (isDisposed()) return 0; + int w = 0; + Image image = getImage(); + if (image != null && (isSelected || parent.showUnselectedImage)) { + w += image.getBounds().width; + } + String text = null; + if (minimum) { + int minChars = parent.minChars; + text = minChars == 0 ? null : getText(); + if (text != null && text.length() > minChars) { + if (useEllipses()) { + int end = minChars < ELLIPSIS.length() + 1 ? minChars : minChars - ELLIPSIS.length(); + text = text.substring(0, end); + if (minChars > ELLIPSIS.length() + 1) text += ELLIPSIS; + } else { + int end = minChars; + text = text.substring(0, end); + } + } + } else { + text = getText(); + } + if (text != null) { + if (w > 0) w += INTERNAL_SPACING; + if (font == null) { + w += gc.textExtent(text, FLAGS).x; + } else { + Font gcFont = gc.getFont(); + gc.setFont(font); + w += gc.textExtent(text, FLAGS).x; + gc.setFont(gcFont); + } + } + if (parent.showClose || showClose) { + if (isSelected || parent.showUnselectedClose) { + if (w > 0) w += INTERNAL_SPACING; + w += CTabFolder.BUTTON_SIZE; + } + } + return w + LEFT_MARGIN + RIGHT_MARGIN; +} +/** + * Sets the control that is used to fill the client area of + * the tab folder when the user selects the tab item. + * + * @param control the new control (or null) + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if the control has been disposed</li> + * <li>ERROR_INVALID_PARENT - if the control is not in the same widget tree</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void setControl (Control control) { + checkWidget(); + if (control != null) { + if (control.isDisposed()) SWT.error (SWT.ERROR_INVALID_ARGUMENT); + if (control.getParent() != parent) SWT.error (SWT.ERROR_INVALID_PARENT); + } + if (this.control != null && !this.control.isDisposed()) { + this.control.setVisible(false); + } + this.control = control; + if (this.control != null) { + int index = parent.indexOf (this); + if (index == parent.getSelectionIndex ()){ + this.control.setBounds(parent.getClientArea ()); + this.control.setVisible(true); + } else { + this.control.setVisible(false); + } + } +} +/** + * Sets the image that is displayed if the tab item is disabled. + * Null will clear the image. + * + * @param image the image to be displayed when the item is disabled or null + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @deprecated This image is not used + */ +public void setDisabledImage (Image image) { + checkWidget(); + if (image != null && image.isDisposed ()) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + this.disabledImage = image; +} +/** + * Sets the font that the receiver will use to paint textual information + * for this item to the font specified by the argument, or to the default font + * for that kind of control if the argument is null. + * + * @param font the new font (or null) + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.0 + */ +public void setFont (Font font){ + checkWidget(); + if (font != null && font.isDisposed ()) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + if (font == null && this.font == null) return; + if (font != null && font.equals(this.font)) return; + this.font = font; + if (!parent.updateTabHeight(false)) { + parent.updateItems(); + parent.redrawTabs(); + } +} +public void setImage (Image image) { + checkWidget(); + if (image != null && image.isDisposed ()) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + Image oldImage = getImage(); + if (image == null && oldImage == null) return; + if (image != null && image.equals(oldImage)) return; + super.setImage(image); + if (!parent.updateTabHeight(false)) { + // If image is the same size as before, + // redraw only the image + if (oldImage != null && image != null) { + Rectangle oldBounds = oldImage.getBounds(); + Rectangle bounds = image.getBounds(); + if (bounds.width == oldBounds.width && bounds.height == oldBounds.height) { + if (showing) { + boolean selected = parent.indexOf(this) == parent.selectedIndex; + if (selected || parent.showUnselectedImage) { + int imageX = x + LEFT_MARGIN, maxImageWidth; + if (selected) { + if (parent.single && (parent.showClose || showClose)) imageX += CTabFolder.BUTTON_SIZE; + int rightEdge = Math.min (x + width, parent.getRightItemEdge()); + maxImageWidth = rightEdge - imageX - RIGHT_MARGIN; + if (!parent.single && closeRect.width > 0) maxImageWidth -= closeRect.width + INTERNAL_SPACING; + } else { + maxImageWidth = x + width - imageX - RIGHT_MARGIN; + if (parent.showUnselectedClose && (parent.showClose || showClose)) { + maxImageWidth -= closeRect.width + INTERNAL_SPACING; + } + } + if (bounds.width < maxImageWidth) { + int imageY = y + (height - bounds.height) / 2 + (parent.onBottom ? -1 : 1); + parent.redraw(imageX, imageY, bounds.width, bounds.height, false); + } + } + } + return; + } + } + parent.updateItems(); + parent.redrawTabs(); + } +} +/** + * Sets to <code>true</code> to indicate that the receiver's close button should be shown. + * If the parent (CTabFolder) was created with SWT.CLOSE style, changing this value has + * no effect. + * + * @param close the new state of the close button + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.4 + */ +public void setShowClose(boolean close) { + checkWidget(); + if (showClose == close) return; + showClose = close; + parent.updateItems(); + parent.redrawTabs(); +} +public void setText (String string) { + checkWidget(); + if (string == null) SWT.error (SWT.ERROR_NULL_ARGUMENT); + if (string.equals(getText())) return; + super.setText(string); + shortenedText = null; + shortenedTextWidth = 0; + if (!parent.updateTabHeight(false)) { + parent.updateItems(); + parent.redrawTabs(); + } +} +/** + * Sets the receiver's tool tip text to the argument, which + * may be null indicating that the default tool tip for the + * control will be shown. For a control that has a default + * tool tip, such as the Tree control on Windows, setting + * the tool tip text to an empty string replaces the default, + * causing no tool tip text to be shown. + * + * @param string the new tool tip text (or null) + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void setToolTipText (String string) { + checkWidget(); + toolTipText = string; +} + +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CaretEvent.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CaretEvent.java new file mode 100644 index 0000000000..4c1b1d6571 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CaretEvent.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2008 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.custom; + + +import org.eclipse.swt.events.*; + +/** + * This event is sent when the caret offset changes. + * + * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> + * + * @since 3.5 + */ +public class CaretEvent extends TypedEvent { + + /** + * caret offset + */ + public int caretOffset; + + static final long serialVersionUID = 3257846571587545489L; + +CaretEvent(StyledTextEvent e) { + super(e); + caretOffset = e.end; +} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CaretListener.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CaretListener.java new file mode 100644 index 0000000000..7423d26404 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CaretListener.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2008 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.custom; + +import org.eclipse.swt.internal.SWTEventListener; + +/** + * This listener interface may be implemented in order to receive + * CaretEvents. + * + * @see CaretEvent + * + * @since 3.5 + */ +public interface CaretListener extends SWTEventListener { + +/** + * This method is called after the caret offset is changed. + * + * The following event fields are used:<ul> + * <li>event.caretOffset the new caret offset (input)</li> + * </ul> + * + * @param event the given event + * + * @see CaretEvent + */ +public void caretMoved(CaretEvent event); + +} + diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/ControlEditor.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/ControlEditor.java new file mode 100755 index 0000000000..9e7de3ee9e --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/ControlEditor.java @@ -0,0 +1,255 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 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.custom; + + +import org.eclipse.swt.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.widgets.*; + +/** +* +* A ControlEditor is a manager for a Control that appears above a composite and tracks with the +* moving and resizing of that composite. It can be used to display one control above +* another control. This could be used when editing a control that does not have editing +* capabilities by using a text editor or for launching a dialog by placing a button +* above a control. +* +* <p> Here is an example of using a ControlEditor: +* +* <code><pre> +* Canvas canvas = new Canvas(shell, SWT.BORDER); +* canvas.setBounds(10, 10, 300, 300); +* Color color = new Color(null, 255, 0, 0); +* canvas.setBackground(color); +* ControlEditor editor = new ControlEditor (canvas); +* // The editor will be a button in the bottom right corner of the canvas. +* // When selected, it will launch a Color dialog that will change the background +* // of the canvas. +* Button button = new Button(canvas, SWT.PUSH); +* button.setText("Select Color..."); +* button.addSelectionListener (new SelectionAdapter() { +* public void widgetSelected(SelectionEvent e) { +* ColorDialog dialog = new ColorDialog(shell); +* dialog.open(); +* RGB rgb = dialog.getRGB(); +* if (rgb != null) { +* if (color != null) color.dispose(); +* color = new Color(null, rgb); +* canvas.setBackground(color); +* } +* +* } +* }); +* +* editor.horizontalAlignment = SWT.RIGHT; +* editor.verticalAlignment = SWT.BOTTOM; +* editor.grabHorizontal = false; +* editor.grabVertical = false; +* Point size = button.computeSize(SWT.DEFAULT, SWT.DEFAULT); +* editor.minimumWidth = size.x; +* editor.minimumHeight = size.y; +* editor.setEditor (button); +* </pre></code> +* +* @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> +*/ +public class ControlEditor { + + /** + * Specifies how the editor should be aligned relative to the control. Allowed values + * are SWT.LEFT, SWT.RIGHT and SWT.CENTER. The default value is SWT.CENTER. + */ + public int horizontalAlignment = SWT.CENTER; + + /** + * Specifies whether the editor should be sized to use the entire width of the control. + * True means resize the editor to the same width as the cell. False means do not adjust + * the width of the editor. The default value is false. + */ + public boolean grabHorizontal = false; + + /** + * Specifies the minimum width the editor can have. This is used in association with + * a true value of grabHorizontal. If the cell becomes smaller than the minimumWidth, the + * editor will not made smaller than the minimum width value. The default value is 0. + */ + public int minimumWidth = 0; + + /** + * Specifies how the editor should be aligned relative to the control. Allowed values + * are SWT.TOP, SWT.BOTTOM and SWT.CENTER. The default value is SWT.CENTER. + */ + public int verticalAlignment = SWT.CENTER; + + /** + * Specifies whether the editor should be sized to use the entire height of the control. + * True means resize the editor to the same height as the underlying control. False means do not adjust + * the height of the editor. The default value is false. + */ + public boolean grabVertical = false; + + /** + * Specifies the minimum height the editor can have. This is used in association with + * a true value of grabVertical. If the control becomes smaller than the minimumHeight, the + * editor will not made smaller than the minimum height value. The default value is 0. + */ + public int minimumHeight = 0; + + Composite parent; + Control editor; + private boolean hadFocus; + private Listener controlListener; + private Listener scrollbarListener; + + private final static int [] EVENTS = {SWT.KeyDown, SWT.KeyUp, SWT.MouseDown, SWT.MouseUp, SWT.Resize}; +/** +* Creates a ControlEditor for the specified Composite. +* +* @param parent the Composite above which this editor will be displayed +* +*/ +public ControlEditor (Composite parent) { + this.parent = parent; + + controlListener = new Listener() { + public void handleEvent(Event e) { + layout (); + } + }; + for (int i=0; i<EVENTS.length; i++) { + parent.addListener (EVENTS [i], controlListener); + } + + scrollbarListener = new Listener() { + public void handleEvent(Event e) { + scroll (e); + } + }; + ScrollBar hBar = parent.getHorizontalBar (); + if (hBar != null) hBar.addListener (SWT.Selection, scrollbarListener); + ScrollBar vBar = parent.getVerticalBar (); + if (vBar != null) vBar.addListener (SWT.Selection, scrollbarListener); +} +Rectangle computeBounds () { + Rectangle clientArea = parent.getClientArea(); + Rectangle editorRect = new Rectangle(clientArea.x, clientArea.y, minimumWidth, minimumHeight); + + if (grabHorizontal) + editorRect.width = Math.max(clientArea.width, minimumWidth); + + if (grabVertical) + editorRect.height = Math.max(clientArea.height, minimumHeight); + + switch (horizontalAlignment) { + case SWT.RIGHT: + editorRect.x += clientArea.width - editorRect.width; + break; + case SWT.LEFT: + // do nothing - clientArea.x is the right answer + break; + default: + // default is CENTER + editorRect.x += (clientArea.width - editorRect.width)/2; + } + + switch (verticalAlignment) { + case SWT.BOTTOM: + editorRect.y += clientArea.height - editorRect.height; + break; + case SWT.TOP: + // do nothing - clientArea.y is the right answer + break; + default : + // default is CENTER + editorRect.y += (clientArea.height - editorRect.height)/2; + } + + + return editorRect; + +} +/** + * Removes all associations between the Editor and the underlying composite. The + * composite and the editor Control are <b>not</b> disposed. + */ +public void dispose () { + if (parent != null && !parent.isDisposed()) { + for (int i=0; i<EVENTS.length; i++) { + parent.removeListener (EVENTS [i], controlListener); + } + ScrollBar hBar = parent.getHorizontalBar (); + if (hBar != null) hBar.removeListener (SWT.Selection, scrollbarListener); + ScrollBar vBar = parent.getVerticalBar (); + if (vBar != null) vBar.removeListener (SWT.Selection, scrollbarListener); + } + + parent = null; + editor = null; + hadFocus = false; + controlListener = null; + scrollbarListener = null; +} +/** +* Returns the Control that is displayed above the composite being edited. +* +* @return the Control that is displayed above the composite being edited +*/ +public Control getEditor () { + return editor; +} +/** + * Lays out the control within the underlying composite. This + * method should be called after changing one or more fields to + * force the Editor to resize. + * + * @since 2.1 + */ +public void layout () { + if (editor == null || editor.isDisposed()) return; + if (editor.getVisible ()) { + hadFocus = editor.isFocusControl(); + } // this doesn't work because + // resizing the column takes the focus away + // before we get here + editor.setBounds (computeBounds ()); + if (hadFocus) { + if (editor == null || editor.isDisposed()) return; + editor.setFocus (); + } +} +void scroll (Event e) { + if (editor == null || editor.isDisposed()) return; + layout(); +} +/** +* Specify the Control that is to be displayed. +* +* <p>Note: The Control provided as the editor <b>must</b> be created with its parent +* being the Composite specified in the ControlEditor constructor. +* +* @param editor the Control that is displayed above the composite being edited +*/ +public void setEditor (Control editor) { + + if (editor == null) { + // this is the case where the caller is setting the editor to be blank + // set all the values accordingly + this.editor = null; + return; + } + + this.editor = editor; + layout(); + if (this.editor == null || this.editor.isDisposed()) return; + editor.setVisible(true); +} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/DefaultContent.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/DefaultContent.java new file mode 100755 index 0000000000..cd00023a27 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/DefaultContent.java @@ -0,0 +1,881 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 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.custom; + +import org.eclipse.swt.*; +import org.eclipse.swt.internal.Compatibility; +import org.eclipse.swt.widgets.*; +import java.util.Vector; + +class DefaultContent implements StyledTextContent { + private final static String LineDelimiter = System.getProperty("line.separator"); + + Vector textListeners = new Vector(); // stores text listeners for event sending + char[] textStore = new char[0]; // stores the actual text + int gapStart = -1; // the character position start of the gap + int gapEnd = -1; // the character position after the end of the gap + int gapLine = -1; // the line on which the gap exists, the gap will always be associated with one line + int highWatermark = 300; + int lowWatermark = 50; + + int[][] lines = new int[50][2]; // array of character positions and lengths representing the lines of text + int lineCount = 0; // the number of lines of text + int expandExp = 1; // the expansion exponent, used to increase the lines array exponentially + int replaceExpandExp = 1; // the expansion exponent, used to increase the lines array exponentially + +/** + * Creates a new DefaultContent and initializes it. A <code>StyledTextContent</> will always have + * at least one empty line. + */ +DefaultContent() { + super(); + setText(""); +} +/** + * Adds a line to the end of the line indexes array. Increases the size of the array if necessary. + * <code>lineCount</code> is updated to reflect the new entry. + * <p> + * + * @param start the start of the line + * @param length the length of the line + */ +void addLineIndex(int start, int length) { + int size = lines.length; + if (lineCount == size) { + // expand the lines by powers of 2 + int[][] newLines = new int[size+Compatibility.pow2(expandExp)][2]; + System.arraycopy(lines, 0, newLines, 0, size); + lines = newLines; + expandExp++; + } + int[] range = new int[] {start, length}; + lines[lineCount] = range; + lineCount++; +} +/** + * Adds a line index to the end of <code>linesArray</code>. Increases the + * size of the array if necessary and returns a new array. + * <p> + * + * @param start the start of the line + * @param length the length of the line + * @param linesArray the array to which to add the line index + * @param count the position at which to add the line + * @return a new array of line indexes + */ +int[][] addLineIndex(int start, int length, int[][] linesArray, int count) { + int size = linesArray.length; + int[][] newLines = linesArray; + if (count == size) { + newLines = new int[size+Compatibility.pow2(replaceExpandExp)][2]; + replaceExpandExp++; + System.arraycopy(linesArray, 0, newLines, 0, size); + } + int[] range = new int[] {start, length}; + newLines[count] = range; + return newLines; +} +/** + * Adds a <code>TextChangeListener</code> listening for + * <code>TextChangingEvent</code> and <code>TextChangedEvent</code>. A + * <code>TextChangingEvent</code> is sent before changes to the text occur. + * A <code>TextChangedEvent</code> is sent after changes to the text + * occurred. + * <p> + * + * @param listener the listener + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT when listener is null</li> + * </ul> + */ +public void addTextChangeListener(TextChangeListener listener) { + if (listener == null) error(SWT.ERROR_NULL_ARGUMENT); + StyledTextListener typedListener = new StyledTextListener(listener); + textListeners.addElement(typedListener); +} +/** + * Adjusts the gap to accommodate a text change that is occurring. + * <p> + * + * @param position the position at which a change is occurring + * @param sizeHint the size of the change + * @param line the line where the gap will go + */ +void adjustGap(int position, int sizeHint, int line) { + if (position == gapStart) { + // text is being inserted at the gap position + int size = (gapEnd - gapStart) - sizeHint; + if (lowWatermark <= size && size <= highWatermark) + return; + } else if ((position + sizeHint == gapStart) && (sizeHint < 0)) { + // text is being deleted at the gap position + int size = (gapEnd - gapStart) - sizeHint; + if (lowWatermark <= size && size <= highWatermark) + return; + } + moveAndResizeGap(position, sizeHint, line); +} +/** + * Calculates the indexes of each line in the text store. Assumes no gap exists. + * Optimized to do less checking. + */ +void indexLines(){ + int start = 0; + lineCount = 0; + int textLength = textStore.length; + int i; + for (i = start; i < textLength; i++) { + char ch = textStore[i]; + if (ch == SWT.CR) { + // see if the next character is a LF + if (i + 1 < textLength) { + ch = textStore[i+1]; + if (ch == SWT.LF) { + i++; + } + } + addLineIndex(start, i - start + 1); + start = i + 1; + } else if (ch == SWT.LF) { + addLineIndex(start, i - start + 1); + start = i + 1; + } + } + addLineIndex(start, i - start); +} +/** + * Returns whether or not the given character is a line delimiter. Both CR and LF + * are valid line delimiters. + * <p> + * + * @param ch the character to test + * @return true if ch is a delimiter, false otherwise + */ +boolean isDelimiter(char ch) { + if (ch == SWT.CR) return true; + if (ch == SWT.LF) return true; + return false; +} +/** + * Determine whether or not the replace operation is valid. DefaultContent will not allow + * the /r/n line delimiter to be split or partially deleted. + * <p> + * + * @param start start offset of text to replace + * @param replaceLength start offset of text to replace + * @param newText start offset of text to replace + * @return a boolean specifying whether or not the replace operation is valid + */ +protected boolean isValidReplace(int start, int replaceLength, String newText){ + if (replaceLength == 0) { + // inserting text, see if the \r\n line delimiter is being split + if (start == 0) return true; + if (start == getCharCount()) return true; + char before = getTextRange(start - 1, 1).charAt(0); + if (before == '\r') { + char after = getTextRange(start, 1).charAt(0); + if (after == '\n') return false; + } + } else { + // deleting text, see if part of a \r\n line delimiter is being deleted + char startChar = getTextRange(start, 1).charAt(0); + if (startChar == '\n') { + // see if char before delete position is \r + if (start != 0) { + char before = getTextRange(start - 1, 1).charAt(0); + if (before == '\r') return false; + } + } + char endChar = getTextRange(start + replaceLength - 1, 1).charAt(0); + if (endChar == '\r') { + // see if char after delete position is \n + if (start + replaceLength != getCharCount()) { + char after = getTextRange(start + replaceLength, 1).charAt(0); + if (after == '\n') return false; + } + } + } + return true; +} +/** + * Calculates the indexes of each line of text in the given range. + * <p> + * + * @param offset the logical start offset of the text lineate + * @param length the length of the text to lineate, includes gap + * @param numLines the number of lines to initially allocate for the line index array, + * passed in for efficiency (the exact number of lines may be known) + * @return a line indexes array where each line is identified by a start offset and + * a length + */ +int[][] indexLines(int offset, int length, int numLines){ + int[][] indexedLines = new int[numLines][2]; + int start = 0; + int lineCount = 0; + int i; + replaceExpandExp = 1; + for (i = start; i < length; i++) { + int location = i + offset; + if ((location >= gapStart) && (location < gapEnd)) { + // ignore the gap + } else { + char ch = textStore[location]; + if (ch == SWT.CR) { + // see if the next character is a LF + if (location+1 < textStore.length) { + ch = textStore[location+1]; + if (ch == SWT.LF) { + i++; + } + } + indexedLines = addLineIndex(start, i - start + 1, indexedLines, lineCount); + lineCount++; + start = i + 1; + } else if (ch == SWT.LF) { + indexedLines = addLineIndex(start, i - start + 1, indexedLines, lineCount); + lineCount++; + start = i + 1; + } + } + } + int[][] newLines = new int[lineCount+1][2]; + System.arraycopy(indexedLines, 0, newLines, 0, lineCount); + int[] range = new int[] {start, i - start}; + newLines[lineCount] = range; + return newLines; +} +/** + * Inserts text. + * <p> + * + * @param position the position at which to insert the text + * @param text the text to insert + */ +void insert(int position, String text) { + if (text.length() == 0) return; + + int startLine = getLineAtOffset(position); + int change = text.length(); + boolean endInsert = position == getCharCount(); + adjustGap(position, change, startLine); + + // during an insert the gap will be adjusted to start at + // position and it will be associated with startline, the + // inserted text will be placed in the gap + int startLineOffset = getOffsetAtLine(startLine); + // at this point, startLineLength will include the start line + // and all of the newly inserted text + int startLineLength = getPhysicalLine(startLine).length(); + + if (change > 0) { + // shrink gap + gapStart += (change); + for (int i = 0; i < text.length(); i++) { + textStore[position + i]= text.charAt(i); + } + } + + // figure out the number of new lines that have been inserted + int [][] newLines = indexLines(startLineOffset, startLineLength, 10); + // only insert an empty line if it is the last line in the text + int numNewLines = newLines.length - 1; + if (newLines[numNewLines][1] == 0) { + // last inserted line is a new line + if (endInsert) { + // insert happening at end of the text, leave numNewLines as + // is since the last new line will not be concatenated with another + // line + numNewLines += 1; + } else { + numNewLines -= 1; + } + } + + // make room for the new lines + expandLinesBy(numNewLines); + // shift down the lines after the replace line + for (int i = lineCount - 1; i > startLine; i--) { + lines[i + numNewLines]=lines[i]; + } + // insert the new lines + for (int i = 0; i < numNewLines; i++) { + newLines[i][0] += startLineOffset; + lines[startLine + i]=newLines[i]; + } + // update the last inserted line + if (numNewLines < newLines.length) { + newLines[numNewLines][0] += startLineOffset; + lines[startLine + numNewLines] = newLines[numNewLines]; + } + + lineCount += numNewLines; + gapLine = getLineAtPhysicalOffset(gapStart); +} +/** + * Moves the gap and adjusts its size in anticipation of a text change. + * The gap is resized to actual size + the specified size and moved to the given + * position. + * <p> + * + * @param position the position at which a change is occurring + * @param size the size of the change + * @param newGapLine the line where the gap should be put + */ +void moveAndResizeGap(int position, int size, int newGapLine) { + char[] content = null; + int oldSize = gapEnd - gapStart; + int newSize; + if (size > 0) { + newSize = highWatermark + size; + } else { + newSize = lowWatermark - size; + } + // remove the old gap from the lines information + if (gapExists()) { + // adjust the line length + lines[gapLine][1] = lines[gapLine][1] - oldSize; + // adjust the offsets of the lines after the gapLine + for (int i = gapLine + 1; i < lineCount; i++) { + lines[i][0] = lines[i][0] - oldSize; + } + } + + if (newSize < 0) { + if (oldSize > 0) { + // removing the gap + content = new char[textStore.length - oldSize]; + System.arraycopy(textStore, 0, content, 0, gapStart); + System.arraycopy(textStore, gapEnd, content, gapStart, content.length - gapStart); + textStore = content; + } + gapStart = gapEnd = position; + return; + } + content = new char[textStore.length + (newSize - oldSize)]; + int newGapStart = position; + int newGapEnd = newGapStart + newSize; + if (oldSize == 0) { + System.arraycopy(textStore, 0, content, 0, newGapStart); + System.arraycopy(textStore, newGapStart, content, newGapEnd, content.length - newGapEnd); + } else if (newGapStart < gapStart) { + int delta = gapStart - newGapStart; + System.arraycopy(textStore, 0, content, 0, newGapStart); + System.arraycopy(textStore, newGapStart, content, newGapEnd, delta); + System.arraycopy(textStore, gapEnd, content, newGapEnd + delta, textStore.length - gapEnd); + } else { + int delta = newGapStart - gapStart; + System.arraycopy(textStore, 0, content, 0, gapStart); + System.arraycopy(textStore, gapEnd, content, gapStart, delta); + System.arraycopy(textStore, gapEnd + delta, content, newGapEnd, content.length - newGapEnd); + } + textStore = content; + gapStart = newGapStart; + gapEnd = newGapEnd; + + // add the new gap to the lines information + if (gapExists()) { + gapLine = newGapLine; + // adjust the line length + int gapLength = gapEnd - gapStart; + lines[gapLine][1] = lines[gapLine][1] + (gapLength); + // adjust the offsets of the lines after the gapLine + for (int i = gapLine + 1; i < lineCount; i++) { + lines[i][0] = lines[i][0] + gapLength; + } + } +} +/** + * Returns the number of lines that are in the specified text. + * <p> + * + * @param startOffset the start of the text to lineate + * @param length the length of the text to lineate + * @return number of lines + */ +int lineCount(int startOffset, int length){ + if (length == 0) { + return 0; + } + int lineCount = 0; + int count = 0; + int i = startOffset; + if (i >= gapStart) { + i += gapEnd - gapStart; + } + while (count < length) { + if ((i >= gapStart) && (i < gapEnd)) { + // ignore the gap + } else { + char ch = textStore[i]; + if (ch == SWT.CR) { + // see if the next character is a LF + if (i + 1 < textStore.length) { + ch = textStore[i+1]; + if (ch == SWT.LF) { + i++; + count++; + } + } + lineCount++; + } else if (ch == SWT.LF) { + lineCount++; + } + count++; + } + i++; + } + return lineCount; +} +/** + * Returns the number of lines that are in the specified text. + * <p> + * + * @param text the text to lineate + * @return number of lines in the text + */ +int lineCount(String text){ + int lineCount = 0; + int length = text.length(); + for (int i = 0; i < length; i++) { + char ch = text.charAt(i); + if (ch == SWT.CR) { + if (i + 1 < length && text.charAt(i + 1) == SWT.LF) { + i++; + } + lineCount++; + } else if (ch == SWT.LF) { + lineCount++; + } + } + return lineCount; +} +/** + * @return the logical length of the text store + */ +public int getCharCount() { + int length = gapEnd - gapStart; + return (textStore.length - length); +} +/** + * Returns the line at <code>index</code> without delimiters. + * <p> + * + * @param index the index of the line to return + * @return the logical line text (i.e., without the gap) + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT when index is out of range</li> + * </ul> + */ +public String getLine(int index) { + if ((index >= lineCount) || (index < 0)) error(SWT.ERROR_INVALID_ARGUMENT); + int start = lines[index][0]; + int length = lines[index][1]; + int end = start + length - 1; + if (!gapExists() || (end < gapStart) || (start >= gapEnd)) { + // line is before or after the gap + while ((length - 1 >= 0) && isDelimiter(textStore[start+length-1])) { + length--; + } + return new String(textStore, start, length); + } else { + // gap is in the specified range, strip out the gap + StringBuffer buf = new StringBuffer(); + int gapLength = gapEnd - gapStart; + buf.append(textStore, start, gapStart - start); + buf.append(textStore, gapEnd, length - gapLength - (gapStart - start)); + length = buf.length(); + while ((length - 1 >=0) && isDelimiter(buf.charAt(length - 1))) { + length--; + } + return buf.toString().substring(0, length); + } +} +/** + * Returns the line delimiter that should be used by the StyledText + * widget when inserting new lines. This delimiter may be different than the + * delimiter that is used by the <code>StyledTextContent</code> interface. + * <p> + * + * @return the platform line delimiter as specified in the line.separator + * system property. + */ +public String getLineDelimiter() { + return LineDelimiter; +} +/** + * Returns the line at the given index with delimiters. + * <p> + * @param index the index of the line to return + * @return the logical line text (i.e., without the gap) with delimiters + */ +String getFullLine(int index) { + int start = lines[index][0]; + int length = lines[index][1]; + int end = start + length - 1; + if (!gapExists() || (end < gapStart) || (start >= gapEnd)) { + // line is before or after the gap + return new String(textStore, start, length); + } else { + // gap is in the specified range, strip out the gap + StringBuffer buffer = new StringBuffer(); + int gapLength = gapEnd - gapStart; + buffer.append(textStore, start, gapStart - start); + buffer.append(textStore, gapEnd, length - gapLength - (gapStart - start)); + return buffer.toString(); + } +} +/** + * Returns the physical line at the given index (i.e., with delimiters and the gap). + * <p> + * + * @param index the line index + * @return the physical line + */ +String getPhysicalLine(int index) { + int start = lines[index][0]; + int length = lines[index][1]; + return getPhysicalText(start, length); +} +/** + * @return the number of lines in the text store + */ +public int getLineCount(){ + return lineCount; +} +/** + * Returns the line at the given offset. + * <p> + * + * @param charPosition logical character offset (i.e., does not include gap) + * @return the line index + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT when charPosition is out of range</li> + * </ul> + */ +public int getLineAtOffset(int charPosition){ + if ((charPosition > getCharCount()) || (charPosition < 0)) error(SWT.ERROR_INVALID_ARGUMENT); + int position; + if (charPosition < gapStart) { + // position is before the gap + position = charPosition; + } else { + // position includes the gap + position = charPosition + (gapEnd - gapStart); + } + + // if last line and the line is not empty you can ask for + // a position that doesn't exist (the one to the right of the + // last character) - for inserting + if (lineCount > 0) { + int lastLine = lineCount - 1; + if (position == lines[lastLine][0] + lines[lastLine][1]) + return lastLine; + } + + int high = lineCount; + int low = -1; + int index = lineCount; + while (high - low > 1) { + index = (high + low) / 2; + int lineStart = lines[index][0]; + int lineEnd = lineStart + lines[index][1] - 1; + if (position <= lineStart) { + high = index; + } else if (position <= lineEnd) { + high = index; + break; + } else { + low = index; + } + } + return high; +} +/** + * Returns the line index at the given physical offset. + * <p> + * + * @param position physical character offset (i.e., includes gap) + * @return the line index + */ +int getLineAtPhysicalOffset(int position){ + int high = lineCount; + int low = -1; + int index = lineCount; + while (high - low > 1) { + index = (high + low) / 2; + int lineStart = lines[index][0]; + int lineEnd = lineStart + lines[index][1] - 1; + if (position <= lineStart) { + high = index; + } else if (position <= lineEnd) { + high = index; + break; + } else { + low = index; + } + } + return high; +} +/** + * Returns the logical offset of the given line. + * <p> + * + * @param lineIndex index of line + * @return the logical starting offset of the line. When there are not any lines, + * getOffsetAtLine(0) is a valid call that should answer 0. + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT when lineIndex is out of range</li> + * </ul> + */ +public int getOffsetAtLine(int lineIndex) { + if (lineIndex == 0) return 0; + if ((lineIndex >= lineCount) || (lineIndex < 0)) error(SWT.ERROR_INVALID_ARGUMENT); + int start = lines[lineIndex][0]; + if (start > gapEnd) { + return start - (gapEnd - gapStart); + } else { + return start; + } +} +/** + * Increases the line indexes array to accommodate more lines. + * <p> + * + * @param numLines the number to increase the array by + */ +void expandLinesBy(int numLines) { + int size = lines.length; + if (size - lineCount >= numLines) { + return; + } + int[][] newLines = new int[size+Math.max(10, numLines)][2]; + System.arraycopy(lines, 0, newLines, 0, size); + lines = newLines; +} +/** + * Reports an SWT error. + * <p> + * + * @param code the error code + */ +void error (int code) { + SWT.error(code); +} +/** + * Returns whether or not a gap exists in the text store. + * <p> + * + * @return true if gap exists, false otherwise + */ +boolean gapExists() { + return gapStart != gapEnd; +} +/** + * Returns a string representing the continuous content of + * the text store. + * <p> + * + * @param start the physical start offset of the text to return + * @param length the physical length of the text to return + * @return the text + */ +String getPhysicalText(int start, int length) { + return new String(textStore, start, length); +} +/** + * Returns a string representing the logical content of + * the text store (i.e., gap stripped out). + * <p> + * + * @param start the logical start offset of the text to return + * @param length the logical length of the text to return + * @return the text + */ +public String getTextRange(int start, int length) { + if (textStore == null) + return ""; + if (length == 0) + return ""; + int end= start + length; + if (!gapExists() || (end < gapStart)) + return new String(textStore, start, length); + if (gapStart < start) { + int gapLength= gapEnd - gapStart; + return new String(textStore, start + gapLength , length); + } + StringBuffer buf = new StringBuffer(); + buf.append(textStore, start, gapStart - start); + buf.append(textStore, gapEnd, end - gapStart); + return buf.toString(); +} +/** + * Removes the specified <code>TextChangeListener</code>. + * <p> + * + * @param listener the listener which should no longer be notified + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT when listener is null</li> + * </ul> + */ +public void removeTextChangeListener(TextChangeListener listener){ + if (listener == null) error(SWT.ERROR_NULL_ARGUMENT); + for (int i = 0; i < textListeners.size(); i++) { + TypedListener typedListener = (TypedListener) textListeners.elementAt(i); + if (typedListener.getEventListener () == listener) { + textListeners.removeElementAt(i); + break; + } + } +} +/** + * Replaces the text with <code>newText</code> starting at position <code>start</code> + * for a length of <code>replaceLength</code>. Notifies the appropriate listeners. + * <p> + * + * When sending the TextChangingEvent, <code>newLineCount</code> is the number of + * lines that are going to be inserted and <code>replaceLineCount</code> is + * the number of lines that are going to be deleted, based on the change + * that occurs visually. For example: + * <ul> + * <li>(replaceText,newText) ==> (replaceLineCount,newLineCount) + * <li>("","\n") ==> (0,1) + * <li>("\n\n","a") ==> (2,0) + * </ul> + * </p> + * + * @param start start offset of text to replace + * @param replaceLength start offset of text to replace + * @param newText start offset of text to replace + * + * @exception SWTException <ul> + * <li>ERROR_INVALID_ARGUMENT when the text change results in a multi byte + * line delimiter being split or partially deleted. Splitting a line + * delimiter by inserting text between the CR and LF characters of the + * \r\n delimiter or deleting part of this line delimiter is not supported</li> + * </ul> + */ +public void replaceTextRange(int start, int replaceLength, String newText){ + // check for invalid replace operations + if (!isValidReplace(start, replaceLength, newText)) SWT.error(SWT.ERROR_INVALID_ARGUMENT); + + // inform listeners + StyledTextEvent event = new StyledTextEvent(this); + event.type = StyledText.TextChanging; + event.start = start; + event.replaceLineCount = lineCount(start, replaceLength); + event.text = newText; + event.newLineCount = lineCount(newText); + event.replaceCharCount = replaceLength; + event.newCharCount = newText.length(); + sendTextEvent(event); + + // first delete the text to be replaced + delete(start, replaceLength, event.replaceLineCount + 1); + // then insert the new text + insert(start, newText); + // inform listeners + event = new StyledTextEvent(this); + event.type = StyledText.TextChanged; + sendTextEvent(event); +} +/** + * Sends the text listeners the TextChanged event. + */ +void sendTextEvent(StyledTextEvent event) { + for (int i = 0; i < textListeners.size(); i++) { + ((StyledTextListener)textListeners.elementAt(i)).handleEvent(event); + } +} +/** + * Sets the content to text and removes the gap since there are no sensible predictions + * about where the next change will occur. + * <p> + * + * @param text the text + */ +public void setText (String text){ + textStore = text.toCharArray(); + gapStart = -1; + gapEnd = -1; + expandExp = 1; + indexLines(); + StyledTextEvent event = new StyledTextEvent(this); + event.type = StyledText.TextSet; + event.text = ""; + sendTextEvent(event); +} +/** + * Deletes text. + * <p> + * @param position the position at which the text to delete starts + * @param length the length of the text to delete + * @param numLines the number of lines that are being deleted + */ +void delete(int position, int length, int numLines) { + if (length == 0) return; + + int startLine = getLineAtOffset(position); + int startLineOffset = getOffsetAtLine(startLine); + int endLine = getLineAtOffset(position + length); + + String endText = ""; + boolean splittingDelimiter = false; + if (position + length < getCharCount()) { + endText = getTextRange(position + length - 1, 2); + if ((endText.charAt(0) == SWT.CR) && (endText.charAt(1) == SWT.LF)) { + splittingDelimiter = true; + } + } + + adjustGap(position + length, -length, startLine); + int [][] oldLines = indexLines(position, length + (gapEnd - gapStart), numLines); + + // enlarge the gap - the gap can be enlarged either to the + // right or left + if (position + length == gapStart) { + gapStart -= length; + } else { + gapEnd += length; + } + + // figure out the length of the new concatenated line, do so by + // finding the first line delimiter after position + int j = position; + boolean eol = false; + while (j < textStore.length && !eol) { + if (j < gapStart || j >= gapEnd) { + char ch = textStore[j]; + if (isDelimiter(ch)) { + if (j + 1 < textStore.length) { + if (ch == SWT.CR && (textStore[j+1] == SWT.LF)) { + j++; + } + } + eol = true; + } + } + j++; + } + // update the line where the deletion started + lines[startLine][1] = (position - startLineOffset) + (j - position); + // figure out the number of lines that have been deleted + int numOldLines = oldLines.length - 1; + if (splittingDelimiter) numOldLines -= 1; + // shift up the lines after the last deleted line, no need to update + // the offset or length of the lines + for (int i = endLine + 1; i < lineCount; i++) { + lines[i - numOldLines] = lines[i]; + } + lineCount -= numOldLines; + gapLine = getLineAtPhysicalOffset(gapStart); +} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/ExtendedModifyEvent.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/ExtendedModifyEvent.java new file mode 100755 index 0000000000..3d429291df --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/ExtendedModifyEvent.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 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.custom; + +import org.eclipse.swt.events.*; + +/** + * This event is sent after a text change occurs. + * + * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> + */ +public final class ExtendedModifyEvent extends TypedEvent { + /** start offset of the new text */ + public int start; + /** length of the new text */ + public int length; + /** replaced text or empty string if no text was replaced */ + public String replacedText; + + static final long serialVersionUID = 3258696507027830832L; + +/** + * Constructs a new instance of this class based on the + * information in the given event. + * + * @param e the event containing the information + */ +public ExtendedModifyEvent(StyledTextEvent e) { + super(e); + start = e.start; + length = e.end - e.start; + replacedText = e.text; +} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/ExtendedModifyListener.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/ExtendedModifyListener.java new file mode 100755 index 0000000000..86ca395bc2 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/ExtendedModifyListener.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 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.custom; + +import org.eclipse.swt.internal.SWTEventListener; + +/** + * Classes which implement this interface provide a method + * that deals with the event that is generated when text + * is modified. + * + * @see ExtendedModifyEvent + * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> + */ +public interface ExtendedModifyListener extends SWTEventListener { + +/** + * This method is called after a text change occurs. + * <p> + * The following event fields are used:<ul> + * <li>event.start the start offset of the new text (input)</li> + * <li>event.length the length of the new text (input)</li> + * <li>event.replacedText the replaced text (input)</li> + * </ul> + * + * @param event the given event + * @see ExtendedModifyEvent + */ +public void modifyText(ExtendedModifyEvent event); +} + + diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/LineBackgroundEvent.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/LineBackgroundEvent.java new file mode 100755 index 0000000000..2337737299 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/LineBackgroundEvent.java @@ -0,0 +1,54 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 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.custom; + +import org.eclipse.swt.events.*; +import org.eclipse.swt.graphics.*; + +/** + * This event is sent when a line is about to be drawn. + * + * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> + */ +public class LineBackgroundEvent extends TypedEvent { + + /** + * line start offset + */ + public int lineOffset; + + /** + * line text + */ + public String lineText; + + /** + * line background color + */ + public Color lineBackground; + + static final long serialVersionUID = 3978711687853324342L; + +/** + * Constructs a new instance of this class based on the + * information in the given event. + * + * @param e the event containing the information + */ +public LineBackgroundEvent(StyledTextEvent e) { + super(e); + lineOffset = e.detail; + lineText = e.text; + lineBackground = e.lineBackground; +} +} + + diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/LineBackgroundListener.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/LineBackgroundListener.java new file mode 100755 index 0000000000..ebfac4dd41 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/LineBackgroundListener.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 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.custom; + +import org.eclipse.swt.internal.SWTEventListener; + +/** + * Classes which implement this interface provide a method + * that can provide the background color for a line that + * is to be drawn. + * + * @see LineBackgroundEvent + * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> + */ +public interface LineBackgroundListener extends SWTEventListener { + +/** + * This method is called when a line is about to be drawn in order to get its + * background color. + * <p> + * The following event fields are used:<ul> + * <li>event.lineOffset line start offset (input)</li> + * <li>event.lineText line text (input)</li> + * <li>event.lineBackground line background color (output)</li> + * </ul> + * + * @param event the given event + * @see LineBackgroundEvent + */ +public void lineGetBackground(LineBackgroundEvent event); +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/LineStyleEvent.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/LineStyleEvent.java new file mode 100755 index 0000000000..7b5e2b3956 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/LineStyleEvent.java @@ -0,0 +1,103 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 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.custom; + +import org.eclipse.swt.events.*; + +/** + * This event is sent when a line is about to be drawn. + * + * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> + */ +public class LineStyleEvent extends TypedEvent { + + /** + * line start offset (input) + */ + public int lineOffset; + + /** + * line text (input) + */ + public String lineText; + + /** + * line ranges (output) + * + * @since 3.2 + */ + public int[] ranges; + + /** + * line styles (output) + * + * Note: Because a StyleRange includes the start and length, the + * same instance cannot occur multiple times in the array of styles. + * If the same style attributes, such as font and color, occur in + * multiple StyleRanges, <code>ranges</code> can be used to share + * styles and reduce memory usage. + */ + public StyleRange[] styles; + + /** + * line alignment (input, output) + * + * @since 3.2 + */ + public int alignment; + + /** + * line indent (input, output) + * + * @since 3.2 + */ + public int indent; + + /** + * line justification (input, output) + * + * @since 3.2 + */ + public boolean justify; + + /** + * line bullet (output) + * @since 3.2 + */ + public Bullet bullet; + + /** + * line bullet index (output) + * @since 3.2 + */ + public int bulletIndex; + + static final long serialVersionUID = 3906081274027192884L; + +/** + * Constructs a new instance of this class based on the + * information in the given event. + * + * @param e the event containing the information + */ +public LineStyleEvent(StyledTextEvent e) { + super(e); + styles = e.styles; + ranges = e.ranges; + lineOffset = e.detail; + lineText = e.text; + alignment = e.alignment; + justify = e.justify; + indent = e.indent; + bullet = e.bullet; + bulletIndex = e.bulletIndex; +} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/LineStyleListener.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/LineStyleListener.java new file mode 100755 index 0000000000..a6acfda978 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/LineStyleListener.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 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.custom; + +import org.eclipse.swt.internal.SWTEventListener; + +/** + * Classes which implement this interface provide a method + * that can provide the style information for a line that + * is to be drawn. + * + * @see LineStyleEvent + * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> + */ +public interface LineStyleListener extends SWTEventListener { + +/** + * This method is called when a line is about to be drawn in order to get the + * line's style information. + * <p> + * The following event fields are used:<ul> + * <li>event.lineOffset line start offset (input)</li> + * <li>event.lineText line text (input)</li> + * <li>event.styles array of StyleRanges, need to be in order (output)</li> + * </ul> + * + * @param event the given event + * @see LineStyleEvent + */ +public void lineGetStyle(LineStyleEvent event); +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/MovementEvent.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/MovementEvent.java new file mode 100644 index 0000000000..97d758dd2e --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/MovementEvent.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 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.custom; + +import org.eclipse.swt.events.*; + +/** + * This event is sent when a new offset is required based on the current + * offset and a movement type. + * + * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> + * + * @since 3.3 + */ +public class MovementEvent extends TypedEvent { + + /** + * line start offset (input) + */ + public int lineOffset; + + /** + * line text (input) + */ + public String lineText; + + /** + * the current offset (input) + */ + public int offset; + + /** + * the new offset (input, output) + */ + public int newOffset; + + /** + * the movement type (input) + * + * @see org.eclipse.swt.SWT#MOVEMENT_WORD + * @see org.eclipse.swt.SWT#MOVEMENT_WORD_END + * @see org.eclipse.swt.SWT#MOVEMENT_WORD_START + * @see org.eclipse.swt.SWT#MOVEMENT_CHAR + * @see org.eclipse.swt.SWT#MOVEMENT_CLUSTER + */ + public int movement; + + static final long serialVersionUID = 3978765487853324342L; + +/** + * Constructs a new instance of this class based on the + * information in the given event. + * + * @param e the event containing the information + */ +public MovementEvent(StyledTextEvent e) { + super(e); + lineOffset = e.detail; + lineText = e.text; + movement = e.count; + offset = e.start; + newOffset = e.end; +} +} + + diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/MovementListener.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/MovementListener.java new file mode 100644 index 0000000000..abc39e58d5 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/MovementListener.java @@ -0,0 +1,67 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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.custom; + +import org.eclipse.swt.internal.SWTEventListener; + +/** + * This listener is invoked when a new offset is required based on the current + * offset and a movement type. + * + * @see org.eclipse.swt.SWT#MOVEMENT_WORD + * @see org.eclipse.swt.SWT#MOVEMENT_WORD_END + * @see org.eclipse.swt.SWT#MOVEMENT_WORD_START + * @see org.eclipse.swt.SWT#MOVEMENT_CHAR + * @see org.eclipse.swt.SWT#MOVEMENT_CLUSTER + * + * @since 3.3 + */ +public interface MovementListener extends SWTEventListener { +/** + * This method is called when a new offset is required based on the current + * offset and a movement type. + * + * <p> + * The following event fields are used:<ul> + * <li>event.lineOffset line start offset (input)</li> + * <li>event.lineText line text (input)</li> + * <li>event.movement the movement type (input)</li> + * <li>event.offset the current offset (input)</li> + * <li>event.newOffset the new offset (input, output)</li> + * </ul> + * + * @param event the event + * + * @see MovementEvent + * @see StyledText#addWordMovementListener(MovementListener) + */ +public void getNextOffset (MovementEvent event); +/** + * This method is called when a new offset is required based on the current + * offset and a movement type. + * + * <p> + * The following event fields are used:<ul> + * <li>event.lineOffset line start offset (input)</li> + * <li>event.lineText line text (input)</li> + * <li>event.movement the movement type (input)</li> + * <li>event.offset the current offset (input)</li> + * <li>event.newOffset the new offset (input, output)</li> + * </ul> + * + * @param event the event + * + * @see MovementEvent + * @see StyledText#addWordMovementListener(MovementListener) + */ +public void getPreviousOffset (MovementEvent event); + +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/PaintObjectEvent.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/PaintObjectEvent.java new file mode 100644 index 0000000000..3c6a3ca545 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/PaintObjectEvent.java @@ -0,0 +1,84 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 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.custom; + +import org.eclipse.swt.events.*; +import org.eclipse.swt.graphics.*; + +/** + * This event is sent when an object needs to be drawn. + * + * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> + * + * @since 3.2 + */ +public class PaintObjectEvent extends TypedEvent { + + /** + * the GC + */ + public GC gc; + + /** + * the x location + */ + public int x; + + /** + * the y location + */ + public int y; + + /** + * the line ascent + */ + public int ascent; + + /** + * the line descent + */ + public int descent; + + /** + * the StyleRange + */ + public StyleRange style; + + /** + * the Bullet + */ + public Bullet bullet; + + /** + * the bullet index + */ + public int bulletIndex; + + static final long serialVersionUID = 3906081274027192855L; + +/** + * Constructs a new instance of this class based on the + * information in the given event. + * + * @param e the event containing the information + */ +public PaintObjectEvent(StyledTextEvent e) { + super(e); + gc = e.gc; + x = e.x; + y = e.y; + ascent = e.ascent; + descent = e.descent; + style = e.style; + bullet = e.bullet; + bulletIndex = e.bulletIndex; +} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/PaintObjectListener.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/PaintObjectListener.java new file mode 100644 index 0000000000..fc8ac01b5b --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/PaintObjectListener.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2000, 2006 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.custom; + + +import org.eclipse.swt.internal.SWTEventListener; +/** + * This listener is invoked when an object needs to be drawn. + * + * @since 3.2 + */ +public interface PaintObjectListener extends SWTEventListener { +/** + * This method is called when an object needs to be drawn. + * + * <p> + * The following event fields are used:<ul> + * <li>event.x the x location (input)</li> + * <li>event.y the y location (input)</li> + * <li>event.ascent the line ascent (input)</li> + * <li>event.descent the line descent (input)</li> + * <li>event.gc the gc (input)</li> + * <li>event.style the style (input)</li> + * </ul> + * + * @param event the event + * + * @see PaintObjectEvent + * @see StyledText#addPaintObjectListener(PaintObjectListener) + */ +public void paintObject(PaintObjectEvent event); +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/PopupList.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/PopupList.java new file mode 100755 index 0000000000..c3bb6f073d --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/PopupList.java @@ -0,0 +1,274 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 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.custom; + +import org.eclipse.swt.*; +import org.eclipse.swt.events.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.widgets.*; + +/** +* A PopupList is a list of selectable items that appears in its own shell positioned above +* its parent shell. It is used for selecting items when editing a Table cell (similar to the +* list that appears when you open a Combo box). +* +* The list will be positioned so that it does not run off the screen and the largest number of items +* are visible. It may appear above the current cursor location or below it depending how close you +* are to the edge of the screen. +* +* @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> +*/ +public class PopupList { + Shell shell; + List list; + int minimumWidth; +/** +* Creates a PopupList above the specified shell. +* +* @param parent a Shell control which will be the parent of the new instance (cannot be null) +*/ +public PopupList(Shell parent) { + this (parent, 0); +} +/** +* Creates a PopupList above the specified shell. +* +* @param parent a widget which will be the parent of the new instance (cannot be null) +* @param style the style of widget to construct +* +* @since 3.0 +*/ +public PopupList(Shell parent, int style) { + shell = new Shell(parent, checkStyle(style)); + + list = new List(shell, SWT.SINGLE | SWT.V_SCROLL); + + // close dialog if user selects outside of the shell + shell.addListener(SWT.Deactivate, new Listener() { + public void handleEvent(Event e){ + shell.setVisible (false); + } + }); + + // resize shell when list resizes + shell.addControlListener(new ControlListener() { + public void controlMoved(ControlEvent e){} + public void controlResized(ControlEvent e){ + Rectangle shellSize = shell.getClientArea(); + list.setSize(shellSize.width, shellSize.height); + } + }); + + // return list selection on Mouse Up or Carriage Return + list.addMouseListener(new MouseListener() { + public void mouseDoubleClick(MouseEvent e){} + public void mouseDown(MouseEvent e){} + public void mouseUp(MouseEvent e){ + shell.setVisible (false); + } + }); + list.addKeyListener(new KeyListener() { + public void keyReleased(KeyEvent e){} + public void keyPressed(KeyEvent e){ + if (e.character == '\r'){ + shell.setVisible (false); + } + } + }); + +} +private static int checkStyle (int style) { + int mask = SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT; + return style & mask; +} +/** +* Gets the widget font. +* <p> +* @return the widget font +* +* @exception SWTException <ul> +* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> +* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> +* </ul> +*/ +public Font getFont () { + return list.getFont(); +} +/** +* Gets the items. +* <p> +* This operation will fail if the items cannot +* be queried from the OS. +* +* @return the items in the widget +* +* @exception SWTException <ul> +* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> +* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> +* </ul> +*/ +public String[] getItems () { + return list.getItems(); +} +/** +* Gets the minimum width of the list. +* +* @return the minimum width of the list +*/ +public int getMinimumWidth () { + return minimumWidth; +} +/** +* Launches the Popup List, waits for an item to be selected and then closes the PopupList. +* +* @param rect the initial size and location of the PopupList; the dialog will be +* positioned so that it does not run off the screen and the largest number of items are visible +* +* @return the text of the selected item or null if no item is selected +*/ +public String open (Rectangle rect) { + + Point listSize = list.computeSize (rect.width, SWT.DEFAULT, false); + Rectangle screenSize = shell.getDisplay().getBounds(); + + // Position the dialog so that it does not run off the screen and the largest number of items are visible + int spaceBelow = screenSize.height - (rect.y + rect.height) - 30; + int spaceAbove = rect.y - 30; + + int y = 0; + if (spaceAbove > spaceBelow && listSize.y > spaceBelow) { + // place popup list above table cell + if (listSize.y > spaceAbove){ + listSize.y = spaceAbove; + } else { + listSize.y += 2; + } + y = rect.y - listSize.y; + + } else { + // place popup list below table cell + if (listSize.y > spaceBelow){ + listSize.y = spaceBelow; + } else { + listSize.y += 2; + } + y = rect.y + rect.height; + } + + // Make dialog as wide as the cell + listSize.x = rect.width; + // dialog width should not be less than minimumWidth + if (listSize.x < minimumWidth) + listSize.x = minimumWidth; + + // Align right side of dialog with right side of cell + int x = rect.x + rect.width - listSize.x; + + shell.setBounds(x, y, listSize.x, listSize.y); + + shell.open(); + list.setFocus(); + + Display display = shell.getDisplay(); + while (!shell.isDisposed () && shell.isVisible ()) { + if (!display.readAndDispatch()) display.sleep(); + } + + String result = null; + if (!shell.isDisposed ()) { + String [] strings = list.getSelection (); + shell.dispose(); + if (strings.length != 0) result = strings [0]; + } + return result; +} +/** +* Selects an item with text that starts with specified String. +* <p> +* If the item is not currently selected, it is selected. +* If the item at an index is selected, it remains selected. +* If the string is not matched, it is ignored. +* +* @param string the text of the item +* +* @exception SWTException <ul> +* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> +* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> +* </ul> +*/ +public void select(String string) { + String[] items = list.getItems(); + + // find the first entry in the list that starts with the + // specified string + if (string != null){ + for (int i = 0; i < items.length; i++) { + if (items[i].startsWith(string)){ + int index = list.indexOf(items[i]); + list.select(index); + break; + } + } + } +} +/** +* Sets the widget font. +* <p> +* When new font is null, the font reverts +* to the default system font for the widget. +* +* @param font the new font (or null) +* +* @exception SWTException <ul> +* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> +* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> +* </ul> +*/ +public void setFont (Font font) { + list.setFont(font); +} +/** +* Sets all items. +* <p> +* The previous selection is cleared. +* The previous items are deleted. +* The new items are added. +* The top index is set to 0. +* +* @param strings the array of items +* +* This operation will fail when an item is null +* or could not be added in the OS. +* +* @exception IllegalArgumentException <ul> +* <li>ERROR_NULL_ARGUMENT - if the items array is null</li> +* <li>ERROR_INVALID_ARGUMENT - if an item in the items array is null</li> +* </ul> +* @exception SWTException <ul> +* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> +* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> +* </ul> +*/ +public void setItems (String[] strings) { + list.setItems(strings); +} +/** +* Sets the minimum width of the list. +* +* @param width the minimum width of the list +*/ +public void setMinimumWidth (int width) { + if (width < 0) + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + + minimumWidth = width; +} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/ST.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/ST.java new file mode 100755 index 0000000000..77276975cc --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/ST.java @@ -0,0 +1,140 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 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.custom; + + +/** + * This class provides access to the public constants provided by <code>StyledText</code>. + * + * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> + */ +public class ST { + + /* + * Navigation Key Actions. Key bindings for the actions are set + * by the StyledText widget. + */ + public static final int LINE_UP = 16777217; // binding = SWT.ARROW_UP + public static final int LINE_DOWN = 16777218; // binding = SWT.ARROW_DOWN + public static final int LINE_START = 16777223; // binding = SWT.HOME + public static final int LINE_END = 16777224; // binding = SWT.END + public static final int COLUMN_PREVIOUS = 16777219; // binding = SWT.ARROW_LEFT + public static final int COLUMN_NEXT = 16777220; // binding = SWT.ARROW_RIGHT + public static final int PAGE_UP = 16777221; // binding = SWT.PAGE_UP + public static final int PAGE_DOWN = 16777222; // binding = SWT.PAGE_DOWN + public static final int WORD_PREVIOUS = 17039363; // binding = SWT.MOD1 + SWT.ARROW_LEFT + public static final int WORD_NEXT = 17039364; // binding = SWT.MOD1 + SWT.ARROW_RIGHT + public static final int TEXT_START = 17039367; // binding = SWT.MOD1 + SWT.HOME + public static final int TEXT_END = 17039368; // binding = SWT.MOD1 + SWT.END + public static final int WINDOW_START = 17039365; // binding = SWT.MOD1 + SWT.PAGE_UP + public static final int WINDOW_END = 17039366; // binding = SWT.MOD1 + SWT.PAGE_DOWN + + /* + * Selection Key Actions + */ + public static final int SELECT_ALL = 262209; // binding = SWT.MOD1 + 'A' + public static final int SELECT_LINE_UP = 16908289; // binding = SWT.MOD2 + SWT.ARROW_UP + public static final int SELECT_LINE_DOWN = 16908290; // binding = SWT.MOD2 + SWT.ARROW_DOWN + public static final int SELECT_LINE_START = 16908295; // binding = SWT.MOD2 + SWT.HOME + public static final int SELECT_LINE_END = 16908296; // binding = SWT.MOD2 + SWT.END + public static final int SELECT_COLUMN_PREVIOUS = 16908291; // binding = SWT.MOD2 + SWT.ARROW_LEFT + public static final int SELECT_COLUMN_NEXT = 16908292; // binding = SWT.MOD2 + SWT.ARROW_RIGHT + public static final int SELECT_PAGE_UP = 16908293; // binding = SWT.MOD2 + SWT.PAGE_UP + public static final int SELECT_PAGE_DOWN = 16908294; // binding = SWT.MOD2 + SWT.PAGE_DOWN + public static final int SELECT_WORD_PREVIOUS = 17170435; // binding = SWT.MOD1 + SWT.MOD2 + SWT.ARROW_LEFT + public static final int SELECT_WORD_NEXT = 17170436; // binding = SWT.MOD1 + SWT.MOD2 + SWT.ARROW_RIGHT + public static final int SELECT_TEXT_START = 17170439; // binding = SWT.MOD1 + SWT.MOD2 + SWT.HOME + public static final int SELECT_TEXT_END = 17170440; // binding = SWT.MOD1 + SWT.MOD2 + SWT.END + public static final int SELECT_WINDOW_START = 17170437; // binding = SWT.MOD1 + SWT.MOD2 + SWT.PAGE_UP + public static final int SELECT_WINDOW_END = 17170438; // binding = SWT.MOD1 + SWT.MOD2 + SWT.PAGE_DOWN + + /* + * Modification Key Actions + */ + public static final int CUT = 131199; // binding = SWT.MOD2 + SWT.DEL + public static final int COPY = 17039369; // binding = SWT.MOD1 + SWT.INSERT; + public static final int PASTE = 16908297; // binding = SWT.MOD2 + SWT.INSERT ; + public static final int DELETE_PREVIOUS = '\b'; // binding = SWT.BS; + public static final int DELETE_NEXT = 0x7F; // binding = SWT.DEL; + public static final int DELETE_WORD_PREVIOUS = 262152; // binding = SWT.BS | SWT.MOD1; + public static final int DELETE_WORD_NEXT = 262271; // binding = SWT.DEL | SWT.MOD1; + + /* + * Miscellaneous Key Actions + */ + public static final int TOGGLE_OVERWRITE = 16777225; // binding = SWT.INSERT; + + /** + * TEMPORARY CODE - API SUBJECT TO CHANGE + * + * Toggle block selection mode + * + * @since 3.5 + */ + public static final int TOGGLE_BLOCKSELECTION = 16777226; + + /** + * Bullet style dot. + * + * @see Bullet + * + * @since 3.2 + */ + public static final int BULLET_DOT = 1 << 0; + + /** + * Bullet style number. + * + * @see Bullet + * + * @since 3.2 + */ + public static final int BULLET_NUMBER = 1 << 1; + + /** + * Bullet style lower case letter. + * + * @see Bullet + * + * @since 3.2 + */ + public static final int BULLET_LETTER_LOWER = 1 << 2; + + /** + * Bullet style upper case letter. + * + * @see Bullet + * + * @since 3.2 + */ + public static final int BULLET_LETTER_UPPER = 1 << 3; + + /** + * Bullet style text. + * + * @see Bullet + * + * @since 3.2 + */ + public static final int BULLET_TEXT = 1 << 4; + + /** + * Bullet style custom draw. + * + * @see StyledText#addPaintObjectListener(PaintObjectListener) + * @see StyledText#removePaintObjectListener(PaintObjectListener) + * @see Bullet + * + * @since 3.2 + */ + public static final int BULLET_CUSTOM = 1 << 5; + +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/SashForm.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/SashForm.java new file mode 100755 index 0000000000..232652d665 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/SashForm.java @@ -0,0 +1,446 @@ +/******************************************************************************* + * 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.custom; + + +import org.eclipse.swt.*; +import org.eclipse.swt.widgets.*; +import org.eclipse.swt.graphics.*; + +/** + * The SashForm is a composite control that lays out its children in a + * row or column arrangement (as specified by the orientation) and places + * a Sash between each child. One child may be maximized to occupy the + * entire size of the SashForm. The relative sizes of the children may + * be specified using weights. + * <p> + * <dl> + * <dt><b>Styles:</b></dt> + * <dd>HORIZONTAL, VERTICAL, SMOOTH</dd> + * </dl> + * </p> + * + * @see <a href="http://www.eclipse.org/swt/snippets/#sashform">SashForm snippets</a> + * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: CustomControlExample</a> + * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> + */ +public class SashForm extends Composite { + + /** + * The width of all sashes in the form. + */ + public int SASH_WIDTH = 3; + + int sashStyle; + Sash[] sashes = new Sash[0]; + // Remember background and foreground + // colors to determine whether to set + // sashes to the default color (null) or + // a specific color + Color background = null; + Color foreground = null; + Control[] controls = new Control[0]; + Control maxControl = null; + Listener sashListener; + static final int DRAG_MINIMUM = 20; + +/** + * Constructs a new instance of this class given its parent + * and a style value describing its behavior and appearance. + * <p> + * The style value is either one of the style constants defined in + * class <code>SWT</code> which is applicable to instances of this + * class, or must be built by <em>bitwise OR</em>'ing together + * (that is, using the <code>int</code> "|" operator) two or more + * of those <code>SWT</code> style constants. The class description + * lists the style constants that are applicable to the class. + * Style bits are also inherited from superclasses. + * </p> + * + * @param parent a widget which will be the parent of the new instance (cannot be null) + * @param style the style of widget to construct + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the parent is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li> + * </ul> + * + * @see SWT#HORIZONTAL + * @see SWT#VERTICAL + * @see #getStyle() + */ +public SashForm(Composite parent, int style) { + super(parent, checkStyle(style)); + super.setLayout(new SashFormLayout()); + sashStyle = ((style & SWT.VERTICAL) != 0) ? SWT.HORIZONTAL : SWT.VERTICAL; + if ((style & SWT.BORDER) != 0) sashStyle |= SWT.BORDER; + if ((style & SWT.SMOOTH) != 0) sashStyle |= SWT.SMOOTH; + sashListener = new Listener() { + public void handleEvent(Event e) { + onDragSash(e); + } + }; +} +static int checkStyle (int style) { + int mask = SWT.BORDER | SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT; + return style & mask; +} +Sash createSash() { + Sash sash = new Sash(this, sashStyle); + sash.setBackground(background); + sash.setForeground(foreground); + sash.setToolTipText(getToolTipText()); + sash.addListener(SWT.Selection, sashListener); + return sash; +} +/** + * Returns SWT.HORIZONTAL if the controls in the SashForm are laid out side by side + * or SWT.VERTICAL if the controls in the SashForm are laid out top to bottom. + * + * @return SWT.HORIZONTAL or SWT.VERTICAL + */ +public int getOrientation() { + //checkWidget(); + return (sashStyle & SWT.VERTICAL) != 0 ? SWT.HORIZONTAL : SWT.VERTICAL; +} +/** + * Returns the width of the sashes when the controls in the SashForm are + * laid out. + * + * @return the width of the sashes + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.4 + */ +public int getSashWidth() { + checkWidget(); + return SASH_WIDTH; +} +public int getStyle() { + int style = super.getStyle(); + style |= getOrientation() == SWT.VERTICAL ? SWT.VERTICAL : SWT.HORIZONTAL; + if ((sashStyle & SWT.SMOOTH) != 0) style |= SWT.SMOOTH; + return style; +} +/** + * Answer the control that currently is maximized in the SashForm. + * This value may be null. + * + * @return the control that currently is maximized or null + */ +public Control getMaximizedControl(){ + //checkWidget(); + return this.maxControl; +} +/** + * Answer the relative weight of each child in the SashForm. The weight represents the + * percent of the total width (if SashForm has Horizontal orientation) or + * total height (if SashForm has Vertical orientation) each control occupies. + * The weights are returned in order of the creation of the widgets (weight[0] + * corresponds to the weight of the first child created). + * + * @return the relative weight of each child + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ + +public int[] getWeights() { + checkWidget(); + Control[] cArray = getControls(false); + int[] ratios = new int[cArray.length]; + for (int i = 0; i < cArray.length; i++) { + Object data = cArray[i].getLayoutData(); + if (data != null && data instanceof SashFormData) { + ratios[i] = (int)(((SashFormData)data).weight * 1000 >> 16); + } else { + ratios[i] = 200; + } + } + return ratios; +} +Control[] getControls(boolean onlyVisible) { + Control[] children = getChildren(); + Control[] result = new Control[0]; + for (int i = 0; i < children.length; i++) { + if (children[i] instanceof Sash) continue; + if (onlyVisible && !children[i].getVisible()) continue; + + Control[] newResult = new Control[result.length + 1]; + System.arraycopy(result, 0, newResult, 0, result.length); + newResult[result.length] = children[i]; + result = newResult; + } + return result; +} +void onDragSash(Event event) { + Sash sash = (Sash)event.widget; + int sashIndex = -1; + for (int i= 0; i < sashes.length; i++) { + if (sashes[i] == sash) { + sashIndex = i; + break; + } + } + if (sashIndex == -1) return; + + Control c1 = controls[sashIndex]; + Control c2 = controls[sashIndex + 1]; + Rectangle b1 = c1.getBounds(); + Rectangle b2 = c2.getBounds(); + + Rectangle sashBounds = sash.getBounds(); + Rectangle area = getClientArea(); + boolean correction = false; + if (getOrientation() == SWT.HORIZONTAL) { + correction = b1.width < DRAG_MINIMUM || b2.width < DRAG_MINIMUM; + int totalWidth = b2.x + b2.width - b1.x; + int shift = event.x - sashBounds.x; + b1.width += shift; + b2.x += shift; + b2.width -= shift; + if (b1.width < DRAG_MINIMUM) { + b1.width = DRAG_MINIMUM; + b2.x = b1.x + b1.width + sashBounds.width; + b2.width = totalWidth - b2.x; + event.x = b1.x + b1.width; + event.doit = false; + } + if (b2.width < DRAG_MINIMUM) { + b1.width = totalWidth - DRAG_MINIMUM - sashBounds.width; + b2.x = b1.x + b1.width + sashBounds.width; + b2.width = DRAG_MINIMUM; + event.x = b1.x + b1.width; + event.doit = false; + } + Object data1 = c1.getLayoutData(); + if (data1 == null || !(data1 instanceof SashFormData)) { + data1 = new SashFormData(); + c1.setLayoutData(data1); + } + Object data2 = c2.getLayoutData(); + if (data2 == null || !(data2 instanceof SashFormData)) { + data2 = new SashFormData(); + c2.setLayoutData(data2); + } + ((SashFormData)data1).weight = (((long)b1.width << 16) + area.width - 1) / area.width; + ((SashFormData)data2).weight = (((long)b2.width << 16) + area.width - 1) / area.width; + } else { + correction = b1.height < DRAG_MINIMUM || b2.height < DRAG_MINIMUM; + int totalHeight = b2.y + b2.height - b1.y; + int shift = event.y - sashBounds.y; + b1.height += shift; + b2.y += shift; + b2.height -= shift; + if (b1.height < DRAG_MINIMUM) { + b1.height = DRAG_MINIMUM; + b2.y = b1.y + b1.height + sashBounds.height; + b2.height = totalHeight - b2.y; + event.y = b1.y + b1.height; + event.doit = false; + } + if (b2.height < DRAG_MINIMUM) { + b1.height = totalHeight - DRAG_MINIMUM - sashBounds.height; + b2.y = b1.y + b1.height + sashBounds.height; + b2.height = DRAG_MINIMUM; + event.y = b1.y + b1.height; + event.doit = false; + } + Object data1 = c1.getLayoutData(); + if (data1 == null || !(data1 instanceof SashFormData)) { + data1 = new SashFormData(); + c1.setLayoutData(data1); + } + Object data2 = c2.getLayoutData(); + if (data2 == null || !(data2 instanceof SashFormData)) { + data2 = new SashFormData(); + c2.setLayoutData(data2); + } + ((SashFormData)data1).weight = (((long)b1.height << 16) + area.height - 1) / area.height; + ((SashFormData)data2).weight = (((long)b2.height << 16) + area.height - 1) / area.height; + } + if (correction || (event.doit && event.detail != SWT.DRAG)) { + c1.setBounds(b1); + sash.setBounds(event.x, event.y, event.width, event.height); + c2.setBounds(b2); + } +} +/** + * If orientation is SWT.HORIZONTAL, lay the controls in the SashForm + * out side by side. If orientation is SWT.VERTICAL, lay the + * controls in the SashForm out top to bottom. + * + * @param orientation SWT.HORIZONTAL or SWT.VERTICAL + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * <li>ERROR_INVALID_ARGUMENT - if the value of orientation is not SWT.HORIZONTAL or SWT.VERTICAL + * </ul> + */ +public void setOrientation(int orientation) { + checkWidget(); + if (getOrientation() == orientation) return; + if (orientation != SWT.HORIZONTAL && orientation != SWT.VERTICAL) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + sashStyle &= ~(SWT.HORIZONTAL | SWT.VERTICAL); + sashStyle |= orientation == SWT.VERTICAL ? SWT.HORIZONTAL : SWT.VERTICAL; + for (int i = 0; i < sashes.length; i++) { + sashes[i].dispose(); + sashes[i] = createSash(); + } + layout(false); +} +public void setBackground (Color color) { + super.setBackground(color); + background = color; + for (int i = 0; i < sashes.length; i++) { + sashes[i].setBackground(background); + } +} +public void setForeground (Color color) { + super.setForeground(color); + foreground = color; + for (int i = 0; i < sashes.length; i++) { + sashes[i].setForeground(foreground); + } +} +/** + * Sets the layout which is associated with the receiver to be + * the argument which may be null. + * <p> + * Note: No Layout can be set on this Control because it already + * manages the size and position of its children. + * </p> + * + * @param layout the receiver's new layout or null + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void setLayout (Layout layout) { + checkWidget(); + return; +} +/** + * Specify the control that should take up the entire client area of the SashForm. + * If one control has been maximized, and this method is called with a different control, + * the previous control will be minimized and the new control will be maximized. + * If the value of control is null, the SashForm will minimize all controls and return to + * the default layout where all controls are laid out separated by sashes. + * + * @param control the control to be maximized or null + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void setMaximizedControl(Control control){ + checkWidget(); + if (control == null) { + if (maxControl != null) { + this.maxControl = null; + layout(false); + for (int i= 0; i < sashes.length; i++){ + sashes[i].setVisible(true); + } + } + return; + } + + for (int i= 0; i < sashes.length; i++){ + sashes[i].setVisible(false); + } + maxControl = control; + layout(false); +} + +/** + * Specify the width of the sashes when the controls in the SashForm are + * laid out. + * + * @param width the width of the sashes + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.4 + */ +public void setSashWidth(int width) { + checkWidget(); + if (SASH_WIDTH == width) return; + SASH_WIDTH = width; + layout(false); +} +public void setToolTipText(String string) { + super.setToolTipText(string); + for (int i = 0; i < sashes.length; i++) { + sashes[i].setToolTipText(string); + } +} +/** + * Specify the relative weight of each child in the SashForm. This will determine + * what percent of the total width (if SashForm has Horizontal orientation) or + * total height (if SashForm has Vertical orientation) each control will occupy. + * The weights must be positive values and there must be an entry for each + * non-sash child of the SashForm. + * + * @param weights the relative weight of each child + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * <li>ERROR_INVALID_ARGUMENT - if the weights value is null or of incorrect length (must match the number of children)</li> + * </ul> + */ +public void setWeights(int[] weights) { + checkWidget(); + Control[] cArray = getControls(false); + if (weights == null || weights.length != cArray.length) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + + int total = 0; + for (int i = 0; i < weights.length; i++) { + if (weights[i] < 0) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + total += weights[i]; + } + if (total == 0) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + for (int i = 0; i < cArray.length; i++) { + Object data = cArray[i].getLayoutData(); + if (data == null || !(data instanceof SashFormData)) { + data = new SashFormData(); + cArray[i].setLayoutData(data); + } + ((SashFormData)data).weight = (((long)weights[i] << 16) + total - 1) / total; + } + + layout(false); +} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/SashFormData.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/SashFormData.java new file mode 100644 index 0000000000..eea3b39638 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/SashFormData.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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.custom; + +class SashFormData { + + long weight; + +String getName () { + String string = getClass ().getName (); + int index = string.lastIndexOf ('.'); + if (index == -1) return string; + return string.substring (index + 1, string.length ()); +} + +/** + * Returns a string containing a concise, human-readable + * description of the receiver. + * + * @return a string representation of the event + */ +public String toString () { + return getName()+" {weight="+weight+"}"; //$NON-NLS-2$ +} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/SashFormLayout.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/SashFormLayout.java new file mode 100644 index 0000000000..f01d0a2c16 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/SashFormLayout.java @@ -0,0 +1,192 @@ +/******************************************************************************* + * 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.custom; + +import org.eclipse.swt.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.widgets.*; + +/** + * This class provides the layout for SashForm + * + * @see SashForm + */ +class SashFormLayout extends Layout { +protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) { + SashForm sashForm = (SashForm)composite; + Control[] cArray = sashForm.getControls(true); + int width = 0; + int height = 0; + if (cArray.length == 0) { + if (wHint != SWT.DEFAULT) width = wHint; + if (hHint != SWT.DEFAULT) height = hHint; + return new Point(width, height); + } + // determine control sizes + boolean vertical = sashForm.getOrientation() == SWT.VERTICAL; + int maxIndex = 0; + int maxValue = 0; + for (int i = 0; i < cArray.length; i++) { + if (vertical) { + Point size = cArray[i].computeSize(wHint, SWT.DEFAULT, flushCache); + if (size.y > maxValue) { + maxIndex = i; + maxValue = size.y; + } + width = Math.max(width, size.x); + } else { + Point size = cArray[i].computeSize(SWT.DEFAULT, hHint, flushCache); + if (size.x > maxValue) { + maxIndex = i; + maxValue = size.x; + } + height = Math.max(height, size.y); + } + } + // get the ratios + long[] ratios = new long[cArray.length]; + long total = 0; + for (int i = 0; i < cArray.length; i++) { + Object data = cArray[i].getLayoutData(); + if (data != null && data instanceof SashFormData) { + ratios[i] = ((SashFormData)data).weight; + } else { + data = new SashFormData(); + cArray[i].setLayoutData(data); + ((SashFormData)data).weight = ratios[i] = ((200 << 16) + 999) / 1000; + + } + total += ratios[i]; + } + if (ratios[maxIndex] > 0) { + int sashwidth = sashForm.sashes.length > 0 ? sashForm.SASH_WIDTH + sashForm.sashes [0].getBorderWidth() * 2 : sashForm.SASH_WIDTH; + if (vertical) { + height += (int)(total * maxValue / ratios[maxIndex]) + (cArray.length - 1) * sashwidth; + } else { + width += (int)(total * maxValue / ratios[maxIndex]) + (cArray.length - 1) * sashwidth; + } + } + width += sashForm.getBorderWidth()*2; + height += sashForm.getBorderWidth()*2; + if (wHint != SWT.DEFAULT) width = wHint; + if (hHint != SWT.DEFAULT) height = hHint; + return new Point(width, height); +} + +protected boolean flushCache(Control control) { + return true; +} + +protected void layout(Composite composite, boolean flushCache) { + SashForm sashForm = (SashForm)composite; + Rectangle area = sashForm.getClientArea(); + if (area.width <= 1 || area.height <= 1) return; + + Control[] newControls = sashForm.getControls(true); + if (sashForm.controls.length == 0 && newControls.length == 0) return; + sashForm.controls = newControls; + + Control[] controls = sashForm.controls; + + if (sashForm.maxControl != null && !sashForm.maxControl.isDisposed()) { + for (int i= 0; i < controls.length; i++){ + if (controls[i] != sashForm.maxControl) { + controls[i].setBounds(-200, -200, 0, 0); + } else { + controls[i].setBounds(area); + } + } + return; + } + + // keep just the right number of sashes + if (sashForm.sashes.length < controls.length - 1) { + Sash[] newSashes = new Sash[controls.length - 1]; + System.arraycopy(sashForm.sashes, 0, newSashes, 0, sashForm.sashes.length); + for (int i = sashForm.sashes.length; i < newSashes.length; i++) { + newSashes[i] = sashForm.createSash(); + } + sashForm.sashes = newSashes; + } + if (sashForm.sashes.length > controls.length - 1) { + if (controls.length == 0) { + for (int i = 0; i < sashForm.sashes.length; i++) { + sashForm.sashes[i].dispose(); + } + sashForm.sashes = new Sash[0]; + } else { + Sash[] newSashes = new Sash[controls.length - 1]; + System.arraycopy(sashForm.sashes, 0, newSashes, 0, newSashes.length); + for (int i = controls.length - 1; i < sashForm.sashes.length; i++) { + sashForm.sashes[i].dispose(); + } + sashForm.sashes = newSashes; + } + } + if (controls.length == 0) return; + Sash[] sashes = sashForm.sashes; + // get the ratios + long[] ratios = new long[controls.length]; + long total = 0; + for (int i = 0; i < controls.length; i++) { + Object data = controls[i].getLayoutData(); + if (data != null && data instanceof SashFormData) { + ratios[i] = ((SashFormData)data).weight; + } else { + data = new SashFormData(); + controls[i].setLayoutData(data); + ((SashFormData)data).weight = ratios[i] = ((200 << 16) + 999) / 1000; + + } + total += ratios[i]; + } + + int sashwidth = sashes.length > 0 ? sashForm.SASH_WIDTH + sashes [0].getBorderWidth() * 2 : sashForm.SASH_WIDTH; + if (sashForm.getOrientation() == SWT.HORIZONTAL) { + int width = (int)(ratios[0] * (area.width - sashes.length * sashwidth) / total); + int x = area.x; + controls[0].setBounds(x, area.y, width, area.height); + x += width; + for (int i = 1; i < controls.length - 1; i++) { + sashes[i - 1].setBounds(x, area.y, sashwidth, area.height); + x += sashwidth; + width = (int)(ratios[i] * (area.width - sashes.length * sashwidth) / total); + controls[i].setBounds(x, area.y, width, area.height); + x += width; + } + if (controls.length > 1) { + sashes[sashes.length - 1].setBounds(x, area.y, sashwidth, area.height); + x += sashwidth; + width = area.width - x; + controls[controls.length - 1].setBounds(x, area.y, width, area.height); + } + } else { + int height = (int)(ratios[0] * (area.height - sashes.length * sashwidth) / total); + int y = area.y; + controls[0].setBounds(area.x, y, area.width, height); + y += height; + for (int i = 1; i < controls.length - 1; i++) { + sashes[i - 1].setBounds(area.x, y, area.width, sashwidth); + y += sashwidth; + height = (int)(ratios[i] * (area.height - sashes.length * sashwidth) / total); + controls[i].setBounds(area.x, y, area.width, height); + y += height; + } + if (controls.length > 1) { + sashes[sashes.length - 1].setBounds(area.x, y, area.width, sashwidth); + y += sashwidth; + height = area.height - y; + controls[controls.length - 1].setBounds(area.x, y, area.width, height); + } + + } +} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/ScrolledComposite.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/ScrolledComposite.java new file mode 100755 index 0000000000..f115784c4b --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/ScrolledComposite.java @@ -0,0 +1,700 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 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.custom; + +import org.eclipse.swt.*; +import org.eclipse.swt.events.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.widgets.*; + +/** + * A ScrolledComposite provides scrollbars and will scroll its content when the user + * uses the scrollbars. + * + * + * <p>There are two ways to use the ScrolledComposite: + * + * <p> + * 1) Set the size of the control that is being scrolled and the ScrolledComposite + * will show scrollbars when the contained control can not be fully seen. + * + * 2) The second way imitates the way a browser would work. Set the minimum size of + * the control and the ScrolledComposite will show scroll bars if the visible area is + * less than the minimum size of the control and it will expand the size of the control + * if the visible area is greater than the minimum size. This requires invoking + * both setMinWidth(), setMinHeight() and setExpandHorizontal(), setExpandVertical(). + * + * <code><pre> + * public static void main (String [] args) { + * Display display = new Display (); + * Color red = display.getSystemColor(SWT.COLOR_RED); + * Color blue = display.getSystemColor(SWT.COLOR_BLUE); + * Shell shell = new Shell (display); + * shell.setLayout(new FillLayout()); + * + * // set the size of the scrolled content - method 1 + * final ScrolledComposite sc1 = new ScrolledComposite(shell, SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER); + * final Composite c1 = new Composite(sc1, SWT.NONE); + * sc1.setContent(c1); + * c1.setBackground(red); + * GridLayout layout = new GridLayout(); + * layout.numColumns = 4; + * c1.setLayout(layout); + * Button b1 = new Button (c1, SWT.PUSH); + * b1.setText("first button"); + * c1.setSize(c1.computeSize(SWT.DEFAULT, SWT.DEFAULT)); + * + * // set the minimum width and height of the scrolled content - method 2 + * final ScrolledComposite sc2 = new ScrolledComposite(shell, SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER); + * sc2.setExpandHorizontal(true); + * sc2.setExpandVertical(true); + * final Composite c2 = new Composite(sc2, SWT.NONE); + * sc2.setContent(c2); + * c2.setBackground(blue); + * layout = new GridLayout(); + * layout.numColumns = 4; + * c2.setLayout(layout); + * Button b2 = new Button (c2, SWT.PUSH); + * b2.setText("first button"); + * sc2.setMinSize(c2.computeSize(SWT.DEFAULT, SWT.DEFAULT)); + * + * Button add = new Button (shell, SWT.PUSH); + * add.setText("add children"); + * final int[] index = new int[]{0}; + * add.addListener(SWT.Selection, new Listener() { + * public void handleEvent(Event e) { + * index[0]++; + * Button button = new Button(c1, SWT.PUSH); + * button.setText("button "+index[0]); + * // reset size of content so children can be seen - method 1 + * c1.setSize(c1.computeSize(SWT.DEFAULT, SWT.DEFAULT)); + * c1.layout(); + * + * button = new Button(c2, SWT.PUSH); + * button.setText("button "+index[0]); + * // reset the minimum width and height so children can be seen - method 2 + * sc2.setMinSize(c2.computeSize(SWT.DEFAULT, SWT.DEFAULT)); + * c2.layout(); + * } + * }); + * + * shell.open (); + * while (!shell.isDisposed ()) { + * if (!display.readAndDispatch ()) display.sleep (); + * } + * display.dispose (); + * } + * </pre></code> + * + * <dl> + * <dt><b>Styles:</b><dd>H_SCROLL, V_SCROLL + * </dl> + * + * @see <a href="http://www.eclipse.org/swt/snippets/#scrolledcomposite">ScrolledComposite snippets</a> + * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> + */ +public class ScrolledComposite extends Composite { + + Control content; + Listener contentListener; + Listener filter; + + int minHeight = 0; + int minWidth = 0; + boolean expandHorizontal = false; + boolean expandVertical = false; + boolean alwaysShowScroll = false; + boolean showFocusedControl = false; + +/** + * Constructs a new instance of this class given its parent + * and a style value describing its behavior and appearance. + * <p> + * The style value is either one of the style constants defined in + * class <code>SWT</code> which is applicable to instances of this + * class, or must be built by <em>bitwise OR</em>'ing together + * (that is, using the <code>int</code> "|" operator) two or more + * of those <code>SWT</code> style constants. The class description + * lists the style constants that are applicable to the class. + * Style bits are also inherited from superclasses. + * </p> + * + * @param parent a widget which will be the parent of the new instance (cannot be null) + * @param style the style of widget to construct + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the parent is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li> + * </ul> + * + * @see SWT#H_SCROLL + * @see SWT#V_SCROLL + * @see #getStyle() + */ +public ScrolledComposite(Composite parent, int style) { + super(parent, checkStyle(style)); + super.setLayout(new ScrolledCompositeLayout()); + ScrollBar hBar = getHorizontalBar (); + if (hBar != null) { + hBar.setVisible(false); + hBar.addListener (SWT.Selection, new Listener () { + public void handleEvent (Event e) { + hScroll(); + } + }); + } + + ScrollBar vBar = getVerticalBar (); + if (vBar != null) { + vBar.setVisible(false); + vBar.addListener (SWT.Selection, new Listener () { + public void handleEvent (Event e) { + vScroll(); + } + }); + } + + contentListener = new Listener() { + public void handleEvent(Event e) { + if (e.type != SWT.Resize) return; + layout(false); + } + }; + + filter = new Listener() { + public void handleEvent(Event event) { + if (event.widget instanceof Control) { + Control control = (Control) event.widget; + if (contains(control)) showControl(control); + } + } + }; + + addDisposeListener(new DisposeListener() { + public void widgetDisposed(DisposeEvent e) { + getDisplay().removeFilter(SWT.FocusIn, filter); + } + }); +} + +static int checkStyle (int style) { + int mask = SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER | SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT; + return style & mask; +} + +boolean contains(Control control) { + if (control == null || control.isDisposed()) return false; + + Composite parent = control.getParent(); + while (parent != null && !(parent instanceof Shell)) { + if (this == parent) return true; + parent = parent.getParent(); + } + return false; +} + +/** + * Returns the Always Show Scrollbars flag. True if the scrollbars are + * always shown even if they are not required. False if the scrollbars are only + * visible when some part of the composite needs to be scrolled to be seen. + * The H_SCROLL and V_SCROLL style bits are also required to enable scrollbars in the + * horizontal and vertical directions. + * + * @return the Always Show Scrollbars flag value + */ +public boolean getAlwaysShowScrollBars() { + //checkWidget(); + return alwaysShowScroll; +} + +/** + * Returns <code>true</code> if the content control + * will be expanded to fill available horizontal space. + * + * @return the receiver's horizontal expansion state + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.2 + */ +public boolean getExpandHorizontal() { + checkWidget(); + return expandHorizontal; +} + +/** + * Returns <code>true</code> if the content control + * will be expanded to fill available vertical space. + * + * @return the receiver's vertical expansion state + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.2 + */ +public boolean getExpandVertical() { + checkWidget(); + return expandVertical; +} + +/** + * Returns the minimum width of the content control. + * + * @return the minimum width + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.2 + */ +public int getMinWidth() { + checkWidget(); + return minWidth; +} + +/** + * Returns the minimum height of the content control. + * + * @return the minimum height + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.2 + */ +public int getMinHeight() { + checkWidget(); + return minHeight; +} + +/** + * Get the content that is being scrolled. + * + * @return the control displayed in the content area + */ +public Control getContent() { + //checkWidget(); + return content; +} + +/** + * Returns <code>true</code> if the receiver automatically scrolls to a focused child control + * to make it visible. Otherwise, returns <code>false</code>. + * + * @return a boolean indicating whether focused child controls are automatically scrolled into the viewport + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.4 + */ +public boolean getShowFocusedControl() { + checkWidget(); + return showFocusedControl; +} + +void hScroll() { + if (content == null) return; + Point location = content.getLocation (); + ScrollBar hBar = getHorizontalBar (); + int hSelection = hBar.getSelection (); + content.setLocation (-hSelection, location.y); +} +boolean needHScroll(Rectangle contentRect, boolean vVisible) { + ScrollBar hBar = getHorizontalBar(); + if (hBar == null) return false; + + Rectangle hostRect = getBounds(); + int border = getBorderWidth(); + hostRect.width -= 2*border; + ScrollBar vBar = getVerticalBar(); + if (vVisible && vBar != null) hostRect.width -= vBar.getSize().x; + + if (!expandHorizontal && contentRect.width > hostRect.width) return true; + if (expandHorizontal && minWidth > hostRect.width) return true; + return false; +} + +boolean needVScroll(Rectangle contentRect, boolean hVisible) { + ScrollBar vBar = getVerticalBar(); + if (vBar == null) return false; + + Rectangle hostRect = getBounds(); + int border = getBorderWidth(); + hostRect.height -= 2*border; + ScrollBar hBar = getHorizontalBar(); + if (hVisible && hBar != null) hostRect.height -= hBar.getSize().y; + + if (!expandVertical && contentRect.height > hostRect.height) return true; + if (expandVertical && minHeight > hostRect.height) return true; + return false; +} + +/** + * Return the point in the content that currently appears in the top left + * corner of the scrolled composite. + * + * @return the point in the content that currently appears in the top left + * corner of the scrolled composite. If no content has been set, this returns + * (0, 0). + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 2.0 + */ +public Point getOrigin() { + checkWidget(); + if (content == null) return new Point(0, 0); + Point location = content.getLocation(); + return new Point(-location.x, -location.y); +} +/** + * Scrolls the content so that the specified point in the content is in the top + * left corner. If no content has been set, nothing will occur. + * + * Negative values will be ignored. Values greater than the maximum scroll + * distance will result in scrolling to the end of the scrollbar. + * + * @param origin the point on the content to appear in the top left corner + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * <li>ERROR_INVALID_ARGUMENT - value of origin is outside of content + * </ul> + * @since 2.0 + */ +public void setOrigin(Point origin) { + setOrigin(origin.x, origin.y); +} +/** + * Scrolls the content so that the specified point in the content is in the top + * left corner. If no content has been set, nothing will occur. + * + * Negative values will be ignored. Values greater than the maximum scroll + * distance will result in scrolling to the end of the scrollbar. + * + * @param x the x coordinate of the content to appear in the top left corner + * + * @param y the y coordinate of the content to appear in the top left corner + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 2.0 + */ +public void setOrigin(int x, int y) { + checkWidget(); + if (content == null) return; + ScrollBar hBar = getHorizontalBar (); + if (hBar != null) { + hBar.setSelection(x); + x = -hBar.getSelection (); + } else { + x = 0; + } + ScrollBar vBar = getVerticalBar (); + if (vBar != null) { + vBar.setSelection(y); + y = -vBar.getSelection (); + } else { + y = 0; + } + content.setLocation(x, y); +} +/** + * Set the Always Show Scrollbars flag. True if the scrollbars are + * always shown even if they are not required. False if the scrollbars are only + * visible when some part of the composite needs to be scrolled to be seen. + * The H_SCROLL and V_SCROLL style bits are also required to enable scrollbars in the + * horizontal and vertical directions. + * + * @param show true to show the scrollbars even when not required, false to show scrollbars only when required + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void setAlwaysShowScrollBars(boolean show) { + checkWidget(); + if (show == alwaysShowScroll) return; + alwaysShowScroll = show; + ScrollBar hBar = getHorizontalBar (); + if (hBar != null && alwaysShowScroll) hBar.setVisible(true); + ScrollBar vBar = getVerticalBar (); + if (vBar != null && alwaysShowScroll) vBar.setVisible(true); + layout(false); +} + +/** + * Set the content that will be scrolled. + * + * @param content the control to be displayed in the content area + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void setContent(Control content) { + checkWidget(); + if (this.content != null && !this.content.isDisposed()) { + this.content.removeListener(SWT.Resize, contentListener); + this.content.setBounds(new Rectangle(-200, -200, 0, 0)); + } + + this.content = content; + ScrollBar vBar = getVerticalBar (); + ScrollBar hBar = getHorizontalBar (); + if (this.content != null) { + if (vBar != null) { + vBar.setMaximum (0); + vBar.setThumb (0); + vBar.setSelection(0); + } + if (hBar != null) { + hBar.setMaximum (0); + hBar.setThumb (0); + hBar.setSelection(0); + } + content.setLocation(0, 0); + layout(false); + this.content.addListener(SWT.Resize, contentListener); + } else { + if (hBar != null) hBar.setVisible(alwaysShowScroll); + if (vBar != null) vBar.setVisible(alwaysShowScroll); + } +} +/** + * Configure the ScrolledComposite to resize the content object to be as wide as the + * ScrolledComposite when the width of the ScrolledComposite is greater than the + * minimum width specified in setMinWidth. If the ScrolledComposite is less than the + * minimum width, the content will not be resized and instead the horizontal scroll bar will be + * used to view the entire width. + * If expand is false, this behaviour is turned off. By default, this behaviour is turned off. + * + * @param expand true to expand the content control to fill available horizontal space + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void setExpandHorizontal(boolean expand) { + checkWidget(); + if (expand == expandHorizontal) return; + expandHorizontal = expand; + layout(false); +} +/** + * Configure the ScrolledComposite to resize the content object to be as tall as the + * ScrolledComposite when the height of the ScrolledComposite is greater than the + * minimum height specified in setMinHeight. If the ScrolledComposite is less than the + * minimum height, the content will not be resized and instead the vertical scroll bar will be + * used to view the entire height. + * If expand is false, this behaviour is turned off. By default, this behaviour is turned off. + * + * @param expand true to expand the content control to fill available vertical space + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void setExpandVertical(boolean expand) { + checkWidget(); + if (expand == expandVertical) return; + expandVertical = expand; + layout(false); +} +/** + * Sets the layout which is associated with the receiver to be + * the argument which may be null. + * <p> + * Note: No Layout can be set on this Control because it already + * manages the size and position of its children. + * </p> + * + * @param layout the receiver's new layout or null + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void setLayout (Layout layout) { + checkWidget(); + return; +} +/** + * Specify the minimum height at which the ScrolledComposite will begin scrolling the + * content with the vertical scroll bar. This value is only relevant if + * setExpandVertical(true) has been set. + * + * @param height the minimum height or 0 for default height + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void setMinHeight(int height) { + setMinSize(minWidth, height); +} +/** + * Specify the minimum width and height at which the ScrolledComposite will begin scrolling the + * content with the horizontal scroll bar. This value is only relevant if + * setExpandHorizontal(true) and setExpandVertical(true) have been set. + * + * @param size the minimum size or null for the default size + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void setMinSize(Point size) { + if (size == null) { + setMinSize(0, 0); + } else { + setMinSize(size.x, size.y); + } +} +/** + * Specify the minimum width and height at which the ScrolledComposite will begin scrolling the + * content with the horizontal scroll bar. This value is only relevant if + * setExpandHorizontal(true) and setExpandVertical(true) have been set. + * + * @param width the minimum width or 0 for default width + * @param height the minimum height or 0 for default height + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void setMinSize(int width, int height) { + checkWidget(); + if (width == minWidth && height == minHeight) return; + minWidth = Math.max(0, width); + minHeight = Math.max(0, height); + layout(false); +} +/** + * Specify the minimum width at which the ScrolledComposite will begin scrolling the + * content with the horizontal scroll bar. This value is only relevant if + * setExpandHorizontal(true) has been set. + * + * @param width the minimum width or 0 for default width + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void setMinWidth(int width) { + setMinSize(width, minHeight); +} + +/** + * Configure the receiver to automatically scroll to a focused child control + * to make it visible. + * + * If show is <code>false</code>, show a focused control is off. + * By default, show a focused control is off. + * + * @param show <code>true</code> to show a focused control. + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.4 + */ +public void setShowFocusedControl(boolean show) { + checkWidget(); + if (showFocusedControl == show) return; + Display display = getDisplay(); + display.removeFilter(SWT.FocusIn, filter); + showFocusedControl = show; + if (!showFocusedControl) return; + display.addFilter(SWT.FocusIn, filter); + Control control = display.getFocusControl(); + if (contains(control)) showControl(control); +} + +/** + * Scrolls the content of the receiver so that the control is visible. + * + * @param control the control to be shown + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the control is null</li> + * <li>ERROR_INVALID_ARGUMENT - if the control has been disposed</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.4 + */ +public void showControl(Control control) { + checkWidget (); + if (control == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + if (control.isDisposed ()) SWT.error(SWT.ERROR_INVALID_ARGUMENT); + if (!contains(control)) SWT.error(SWT.ERROR_INVALID_ARGUMENT); + + Rectangle itemRect = getDisplay().map(control.getParent(), this, control.getBounds()); + Rectangle area = getClientArea(); + Point origin = getOrigin(); + if (itemRect.x < 0) { + origin.x = Math.max(0, origin.x + itemRect.x); + } else { + if (area.width < itemRect.x + itemRect.width) origin.x = Math.max(0, origin.x + itemRect.x + Math.min(itemRect.width, area.width) - area.width); + } + if (itemRect.y < 0) { + origin.y = Math.max(0, origin.y + itemRect.y); + } else { + if (area.height < itemRect.y + itemRect.height) origin.y = Math.max(0, origin.y + itemRect.y + Math.min(itemRect.height, area.height) - area.height); + } + setOrigin(origin); +} + +void vScroll() { + if (content == null) return; + Point location = content.getLocation (); + ScrollBar vBar = getVerticalBar (); + int vSelection = vBar.getSelection (); + content.setLocation (location.x, -vSelection); +} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/ScrolledCompositeLayout.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/ScrolledCompositeLayout.java new file mode 100644 index 0000000000..0af014860e --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/ScrolledCompositeLayout.java @@ -0,0 +1,112 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 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.custom; + +import org.eclipse.swt.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.widgets.*; + +/** + * This class provides the layout for ScrolledComposite + * + * @see ScrolledComposite + */ +class ScrolledCompositeLayout extends Layout { + + boolean inLayout = false; + static final int DEFAULT_WIDTH = 64; + static final int DEFAULT_HEIGHT = 64; + +protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) { + ScrolledComposite sc = (ScrolledComposite)composite; + Point size = new Point(DEFAULT_WIDTH, DEFAULT_HEIGHT); + if (sc.content != null) { + Point preferredSize = sc.content.computeSize(wHint, hHint, flushCache); + Point currentSize = sc.content.getSize(); + size.x = sc.getExpandHorizontal() ? preferredSize.x : currentSize.x; + size.y = sc.getExpandVertical() ? preferredSize.y : currentSize.y; + } + size.x = Math.max(size.x, sc.minWidth); + size.y = Math.max(size.y, sc.minHeight); + if (wHint != SWT.DEFAULT) size.x = wHint; + if (hHint != SWT.DEFAULT) size.y = hHint; + return size; +} + +protected boolean flushCache(Control control) { + return true; +} + +protected void layout(Composite composite, boolean flushCache) { + if (inLayout) return; + ScrolledComposite sc = (ScrolledComposite)composite; + if (sc.content == null) return; + ScrollBar hBar = sc.getHorizontalBar(); + ScrollBar vBar = sc.getVerticalBar(); + if (hBar != null) { + if (hBar.getSize().y >= sc.getSize().y) { + return; + } + } + if (vBar != null) { + if (vBar.getSize().x >= sc.getSize().x) { + return; + } + } + inLayout = true; + Rectangle contentRect = sc.content.getBounds(); + if (!sc.alwaysShowScroll) { + boolean hVisible = sc.needHScroll(contentRect, false); + boolean vVisible = sc.needVScroll(contentRect, hVisible); + if (!hVisible && vVisible) hVisible = sc.needHScroll(contentRect, vVisible); + if (hBar != null) hBar.setVisible(hVisible); + if (vBar != null) vBar.setVisible(vVisible); + } + Rectangle hostRect = sc.getClientArea(); + if (sc.expandHorizontal) { + contentRect.width = Math.max(sc.minWidth, hostRect.width); + } + if (sc.expandVertical) { + contentRect.height = Math.max(sc.minHeight, hostRect.height); + } + + if (hBar != null) { + hBar.setMaximum (contentRect.width); + hBar.setThumb (Math.min (contentRect.width, hostRect.width)); + int hPage = contentRect.width - hostRect.width; + int hSelection = hBar.getSelection (); + if (hSelection >= hPage) { + if (hPage <= 0) { + hSelection = 0; + hBar.setSelection(0); + } + contentRect.x = -hSelection; + } + } + + if (vBar != null) { + vBar.setMaximum (contentRect.height); + vBar.setThumb (Math.min (contentRect.height, hostRect.height)); + int vPage = contentRect.height - hostRect.height; + int vSelection = vBar.getSelection (); + if (vSelection >= vPage) { + if (vPage <= 0) { + vSelection = 0; + vBar.setSelection(0); + } + contentRect.y = -vSelection; + } + } + + sc.content.setBounds (contentRect); + inLayout = false; +} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/StackLayout.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/StackLayout.java new file mode 100755 index 0000000000..cc76e978ef --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/StackLayout.java @@ -0,0 +1,148 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 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.custom; + + +import org.eclipse.swt.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.widgets.*; + +/** + * This Layout stacks all the controls one on top of the other and resizes all controls + * to have the same size and location. + * The control specified in topControl is visible and all other controls are not visible. + * Users must set the topControl value to flip between the visible items and then call + * layout() on the composite which has the StackLayout. + * + * <p> Here is an example which places ten buttons in a stack layout and + * flips between them: + * + * <pre><code> + * public static void main(String[] args) { + * Display display = new Display(); + * Shell shell = new Shell(display); + * shell.setLayout(new GridLayout()); + * + * final Composite parent = new Composite(shell, SWT.NONE); + * parent.setLayoutData(new GridData(GridData.FILL_BOTH)); + * final StackLayout layout = new StackLayout(); + * parent.setLayout(layout); + * final Button[] bArray = new Button[10]; + * for (int i = 0; i < 10; i++) { + * bArray[i] = new Button(parent, SWT.PUSH); + * bArray[i].setText("Button "+i); + * } + * layout.topControl = bArray[0]; + * + * Button b = new Button(shell, SWT.PUSH); + * b.setText("Show Next Button"); + * final int[] index = new int[1]; + * b.addListener(SWT.Selection, new Listener(){ + * public void handleEvent(Event e) { + * index[0] = (index[0] + 1) % 10; + * layout.topControl = bArray[index[0]]; + * parent.layout(); + * } + * }); + * + * shell.open(); + * while (shell != null && !shell.isDisposed()) { + * if (!display.readAndDispatch()) + * display.sleep(); + * } + * } + * </code></pre> + * + * @see <a href="http://www.eclipse.org/swt/snippets/#stacklayout">StackLayout snippets</a> + * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: LayoutExample</a> + * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> + */ + +public class StackLayout extends Layout { + + /** + * marginWidth specifies the number of pixels of horizontal margin + * that will be placed along the left and right edges of the layout. + * + * The default value is 0. + */ + public int marginWidth = 0; + /** + * marginHeight specifies the number of pixels of vertical margin + * that will be placed along the top and bottom edges of the layout. + * + * The default value is 0. + */ + public int marginHeight = 0; + + /** + * topControl the Control that is displayed at the top of the stack. + * All other controls that are children of the parent composite will not be visible. + */ + public Control topControl; + +protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) { + Control children[] = composite.getChildren(); + int maxWidth = 0; + int maxHeight = 0; + for (int i = 0; i < children.length; i++) { + Point size = children[i].computeSize(wHint, hHint, flushCache); + maxWidth = Math.max(size.x, maxWidth); + maxHeight = Math.max(size.y, maxHeight); + } + int width = maxWidth + 2 * marginWidth; + int height = maxHeight + 2 * marginHeight; + if (wHint != SWT.DEFAULT) width = wHint; + if (hHint != SWT.DEFAULT) height = hHint; + return new Point(width, height); +} + +protected boolean flushCache(Control control) { + return true; +} + +protected void layout(Composite composite, boolean flushCache) { + Control children[] = composite.getChildren(); + Rectangle rect = composite.getClientArea(); + rect.x += marginWidth; + rect.y += marginHeight; + rect.width -= 2 * marginWidth; + rect.height -= 2 * marginHeight; + for (int i = 0; i < children.length; i++) { + children[i].setBounds(rect); + children[i].setVisible(children[i] == topControl); + + } +} + +String getName () { + String string = getClass ().getName (); + int index = string.lastIndexOf ('.'); + if (index == -1) return string; + return string.substring (index + 1, string.length ()); +} + +/** + * Returns a string containing a concise, human-readable + * description of the receiver. + * + * @return a string representation of the layout + */ +public String toString () { + String string = getName ()+" {"; + if (marginWidth != 0) string += "marginWidth="+marginWidth+" "; + if (marginHeight != 0) string += "marginHeight="+marginHeight+" "; + if (topControl != null) string += "topControl="+topControl+" "; + string = string.trim(); + string += "}"; + return string; +} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/StyleRange.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/StyleRange.java new file mode 100755 index 0000000000..76469db5f9 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/StyleRange.java @@ -0,0 +1,213 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 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.custom; + +import org.eclipse.swt.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.internal.CloneableCompatibility; + +/** + * <code>StyleRange</code> defines a set of styles for a specified + * range of text. + * <p> + * The hashCode() method in this class uses the values of the public + * fields to compute the hash value. When storing instances of the + * class in hashed collections, do not modify these fields after the + * object has been inserted. + * </p> + * + * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> + */ +public class StyleRange extends TextStyle implements CloneableCompatibility { + + /** + * the start offset of the range, zero-based from the document start + */ + public int start; + + /** + * the length of the range + */ + public int length; + + /** + * the font style of the range. It may be a combination of + * SWT.NORMAL, SWT.ITALIC or SWT.BOLD + * + * Note: the font style is not used if the <code>font</code> attribute + * is set + */ + public int fontStyle = SWT.NORMAL; + +/** + * Create a new style range with no styles + * + * @since 3.2 + */ +public StyleRange() { +} + +/** + * Create a new style range from an existing text style. + * + * @param style the text style to copy + * + * @since 3.4 + */ +public StyleRange(TextStyle style) { + super(style); +} + +/** + * Create a new style range. + * + * @param start start offset of the style + * @param length length of the style + * @param foreground foreground color of the style, null if none + * @param background background color of the style, null if none + */ +public StyleRange(int start, int length, Color foreground, Color background) { + super(null, foreground, background); + this.start = start; + this.length = length; +} + +/** + * Create a new style range. + * + * @param start start offset of the style + * @param length length of the style + * @param foreground foreground color of the style, null if none + * @param background background color of the style, null if none + * @param fontStyle font style of the style, may be SWT.NORMAL, SWT.ITALIC or SWT.BOLD + */ +public StyleRange(int start, int length, Color foreground, Color background, int fontStyle) { + this(start, length, foreground, background); + this.fontStyle = fontStyle; +} + +/** + * Compares the argument to the receiver, and returns true + * if they represent the <em>same</em> object using a class + * specific comparison. + * + * @param object the object to compare with this object + * @return <code>true</code> if the object is the same as this object and <code>false</code> otherwise + * + * @see #hashCode() + */ +public boolean equals(Object object) { + if (object == this) return true; + if (object instanceof StyleRange) { + StyleRange style = (StyleRange)object; + if (start != style.start) return false; + if (length != style.length) return false; + return similarTo(style); + } + return false; +} + +/** + * Returns an integer hash code for the receiver. Any two + * objects that return <code>true</code> when passed to + * <code>equals</code> must return the same value for this + * method. + * + * @return the receiver's hash + * + * @see #equals(Object) + */ +public int hashCode() { + return super.hashCode() ^ fontStyle; +} +boolean isVariableHeight() { + return font != null || metrics != null || rise != 0; +} +/** + * Returns whether or not the receiver is unstyled (i.e., does not have any + * style attributes specified). + * + * @return true if the receiver is unstyled, false otherwise. + */ +public boolean isUnstyled() { + if (font != null) return false; + if (rise != 0) return false; + if (metrics != null) return false; + if (foreground != null) return false; + if (background != null) return false; + if (fontStyle != SWT.NORMAL) return false; + if (underline) return false; + if (strikeout) return false; + if (borderStyle != SWT.NONE) return false; + return true; +} + +/** + * Compares the specified object to this StyleRange and answer if the two + * are similar. The object must be an instance of StyleRange and have the + * same field values for except for start and length. + * + * @param style the object to compare with this object + * @return true if the objects are similar, false otherwise + */ +public boolean similarTo(StyleRange style) { + if (!super.equals(style)) return false; + if (fontStyle != style.fontStyle) return false; + return true; +} + +/** + * Returns a new StyleRange with the same values as this StyleRange. + * + * @return a shallow copy of this StyleRange + */ +public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + return null; + } +} + +/** + * Returns a string containing a concise, human-readable + * description of the receiver. + * + * @return a string representation of the StyleRange + */ +public String toString() { + StringBuffer buffer = new StringBuffer(); + buffer.append("StyleRange {"); + buffer.append(start); + buffer.append(", "); + buffer.append(length); + buffer.append(", fontStyle="); + switch (fontStyle) { + case SWT.BOLD: + buffer.append("bold"); + break; + case SWT.ITALIC: + buffer.append("italic"); + break; + case SWT.BOLD | SWT.ITALIC: + buffer.append("bold-italic"); + break; + default: + buffer.append("normal"); + } + String str = super.toString(); + int index = str.indexOf('{'); + str = str.substring(index + 1); + if (str.length() > 1) buffer.append(", "); + buffer.append(str); + return buffer.toString(); +} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/StyledText.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/StyledText.java new file mode 100755 index 0000000000..50e1587b2e --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/StyledText.java @@ -0,0 +1,9475 @@ +/******************************************************************************* + * 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.custom; + + +import java.util.*; + +import org.eclipse.swt.*; +import org.eclipse.swt.accessibility.*; +import org.eclipse.swt.dnd.*; +import org.eclipse.swt.events.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.internal.*; +import org.eclipse.swt.printing.*; +import org.eclipse.swt.widgets.*; + +/** + * A StyledText is an editable user interface object that displays lines + * of text. The following style attributes can be defined for the text: + * <ul> + * <li>foreground color + * <li>background color + * <li>font style (bold, italic, bold-italic, regular) + * <li>underline + * <li>strikeout + * </ul> + * <p> + * In addition to text style attributes, the background color of a line may + * be specified. + * </p><p> + * There are two ways to use this widget when specifying text style information. + * You may use the API that is defined for StyledText or you may define your own + * LineStyleListener. If you define your own listener, you will be responsible + * for maintaining the text style information for the widget. IMPORTANT: You may + * not define your own listener and use the StyledText API. The following + * StyledText API is not supported if you have defined a LineStyleListener: + * <ul> + * <li>getStyleRangeAtOffset(int) + * <li>getStyleRanges() + * <li>replaceStyleRanges(int,int,StyleRange[]) + * <li>setStyleRange(StyleRange) + * <li>setStyleRanges(StyleRange[]) + * </ul> + * </p><p> + * There are two ways to use this widget when specifying line background colors. + * You may use the API that is defined for StyledText or you may define your own + * LineBackgroundListener. If you define your own listener, you will be responsible + * for maintaining the line background color information for the widget. + * IMPORTANT: You may not define your own listener and use the StyledText API. + * The following StyledText API is not supported if you have defined a + * LineBackgroundListener: + * <ul> + * <li>getLineBackground(int) + * <li>setLineBackground(int,int,Color) + * </ul> + * </p><p> + * The content implementation for this widget may also be user-defined. To do so, + * you must implement the StyledTextContent interface and use the StyledText API + * setContent(StyledTextContent) to initialize the widget. + * </p><p> + * <dl> + * <dt><b>Styles:</b><dd>FULL_SELECTION, MULTI, READ_ONLY, SINGLE, WRAP + * <dt><b>Events:</b><dd>ExtendedModify, LineGetBackground, LineGetSegments, LineGetStyle, Modify, Selection, Verify, VerifyKey + * </dl> + * </p><p> + * IMPORTANT: This class is <em>not</em> intended to be subclassed. + * </p> + * + * @see <a href="http://www.eclipse.org/swt/snippets/#styledtext">StyledText snippets</a> + * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Examples: CustomControlExample, TextEditor</a> + * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> + * @noextend This class is not intended to be subclassed by clients. + */ +public class StyledText extends Canvas { + static final char TAB = '\t'; + static final String PlatformLineDelimiter = System.getProperty("line.separator"); + static final int BIDI_CARET_WIDTH = 3; + static final int DEFAULT_WIDTH = 64; + static final int DEFAULT_HEIGHT = 64; + static final int V_SCROLL_RATE = 50; + static final int H_SCROLL_RATE = 10; + + static final int ExtendedModify = 3000; + static final int LineGetBackground = 3001; + static final int LineGetStyle = 3002; + static final int TextChanging = 3003; + static final int TextSet = 3004; + static final int VerifyKey = 3005; + static final int TextChanged = 3006; + static final int LineGetSegments = 3007; + static final int PaintObject = 3008; + static final int WordNext = 3009; + static final int WordPrevious = 3010; + static final int CaretMoved = 3011; + + static final int PREVIOUS_OFFSET_TRAILING = 0; + static final int OFFSET_LEADING = 1; + + Color selectionBackground; // selection background color + Color selectionForeground; // selection foreground color + StyledTextContent content; // native content (default or user specified) + StyledTextRenderer renderer; + Listener listener; + TextChangeListener textChangeListener; // listener for TextChanging, TextChanged and TextSet events from StyledTextContent + int verticalScrollOffset = 0; // pixel based + int horizontalScrollOffset = 0; // pixel based + int topIndex = 0; // top visible line + int topIndexY; + int clientAreaHeight = 0; // the client area height. Needed to calculate content width for new visible lines during Resize callback + int clientAreaWidth = 0; // the client area width. Needed during Resize callback to determine if line wrap needs to be recalculated + int tabLength = 4; // number of characters in a tab + int leftMargin; + int topMargin; + int rightMargin; + int bottomMargin; + Color marginColor; + int columnX; // keep track of the horizontal caret position when changing lines/pages. Fixes bug 5935 + int caretOffset; + int caretAlignment; + Point selection = new Point(0, 0); // x and y are start and end caret offsets of selection + Point clipboardSelection; // x and y are start and end caret offsets of previous selection + int selectionAnchor; // position of selection anchor. 0 based offset from beginning of text + Point doubleClickSelection; // selection after last mouse double click + boolean editable = true; + boolean wordWrap = false; + boolean doubleClickEnabled = true; // see getDoubleClickEnabled + boolean overwrite = false; // insert/overwrite edit mode + int textLimit = -1; // limits the number of characters the user can type in the widget. Unlimited by default. + Hashtable keyActionMap = new Hashtable(); + Color background = null; // workaround for bug 4791 + Color foreground = null; // + Clipboard clipboard; + int clickCount; + int autoScrollDirection = SWT.NULL; // the direction of autoscrolling (up, down, right, left) + int autoScrollDistance = 0; + int lastTextChangeStart; // cache data of the + int lastTextChangeNewLineCount; // last text changing + int lastTextChangeNewCharCount; // event for use in the + int lastTextChangeReplaceLineCount; // text changed handler + int lastTextChangeReplaceCharCount; + int lastCharCount = 0; + int lastLineBottom; // the bottom pixel of the last line been replaced + boolean isMirrored; + boolean bidiColoring = false; // apply the BIDI algorithm on text segments of the same color + Image leftCaretBitmap = null; + Image rightCaretBitmap = null; + int caretDirection = SWT.NULL; + int caretWidth = 0; + Caret defaultCaret = null; + boolean updateCaretDirection = true; + boolean fixedLineHeight; + boolean dragDetect = true; + IME ime; + Cursor cursor; + int alignment; + boolean justify; + int indent; + int lineSpacing; + int alignmentMargin; + + //block selection + boolean blockSelection; + int blockXAnchor = -1, blockYAnchor = -1; + int blockXLocation = -1, blockYLocation = -1; + + + final static boolean IS_MAC, IS_GTK, IS_MOTIF; + static { + String platform = SWT.getPlatform(); + IS_MAC = "carbon".equals(platform) || "cocoa".equals(platform); + IS_GTK = "gtk".equals(platform); + IS_MOTIF = "motif".equals(platform); + } + + /** + * The Printing class implements printing of a range of text. + * An instance of <code>Printing</code> is returned in the + * StyledText#print(Printer) API. The run() method may be + * invoked from any thread. + */ + static class Printing implements Runnable { + final static int LEFT = 0; // left aligned header/footer segment + final static int CENTER = 1; // centered header/footer segment + final static int RIGHT = 2; // right aligned header/footer segment + + Printer printer; + StyledTextRenderer printerRenderer; + StyledTextPrintOptions printOptions; + Rectangle clientArea; + FontData fontData; + Font printerFont; + Hashtable resources; + int tabLength; + GC gc; // printer GC + int pageWidth; // width of a printer page in pixels + int startPage; // first page to print + int endPage; // last page to print + int startLine; // first (wrapped) line to print + int endLine; // last (wrapped) line to print + boolean singleLine; // widget single line mode + Point selection = null; // selected text + boolean mirrored; // indicates the printing gc should be mirrored + int lineSpacing; + int printMargin; + + /** + * Creates an instance of <code>Printing</code>. + * Copies the widget content and rendering data that needs + * to be requested from listeners. + * </p> + * @param parent StyledText widget to print. + * @param printer printer device to print on. + * @param printOptions print options + */ + Printing(StyledText styledText, Printer printer, StyledTextPrintOptions printOptions) { + this.printer = printer; + this.printOptions = printOptions; + this.mirrored = (styledText.getStyle() & SWT.MIRRORED) != 0; + singleLine = styledText.isSingleLine(); + startPage = 1; + endPage = Integer.MAX_VALUE; + PrinterData data = printer.getPrinterData(); + if (data.scope == PrinterData.PAGE_RANGE) { + startPage = data.startPage; + endPage = data.endPage; + if (endPage < startPage) { + int temp = endPage; + endPage = startPage; + startPage = temp; + } + } else if (data.scope == PrinterData.SELECTION) { + selection = styledText.getSelectionRange(); + } + printerRenderer = new StyledTextRenderer(printer, null); + printerRenderer.setContent(copyContent(styledText.getContent())); + cacheLineData(styledText); + } + /** + * Caches all line data that needs to be requested from a listener. + * </p> + * @param printerContent <code>StyledTextContent</code> to request + * line data for. + */ + void cacheLineData(StyledText styledText) { + StyledTextRenderer renderer = styledText.renderer; + renderer.copyInto(printerRenderer); + fontData = styledText.getFont().getFontData()[0]; + tabLength = styledText.tabLength; + int lineCount = printerRenderer.lineCount; + if (styledText.isListening(LineGetBackground) || (styledText.isBidi() && styledText.isListening(LineGetSegments)) || styledText.isListening(LineGetStyle)) { + StyledTextContent content = printerRenderer.content; + for (int i = 0; i < lineCount; i++) { + String line = content.getLine(i); + int lineOffset = content.getOffsetAtLine(i); + StyledTextEvent event = styledText.getLineBackgroundData(lineOffset, line); + if (event != null && event.lineBackground != null) { + printerRenderer.setLineBackground(i, 1, event.lineBackground); + } + if (styledText.isBidi()) { + int[] segments = styledText.getBidiSegments(lineOffset, line); + printerRenderer.setLineSegments(i, 1, segments); + } + event = styledText.getLineStyleData(lineOffset, line); + if (event != null) { + printerRenderer.setLineIndent(i, 1, event.indent); + printerRenderer.setLineAlignment(i, 1, event.alignment); + printerRenderer.setLineJustify(i, 1, event.justify); + printerRenderer.setLineBullet(i, 1, event.bullet); + StyleRange[] styles = event.styles; + if (styles != null && styles.length > 0) { + printerRenderer.setStyleRanges(event.ranges, styles); + } + } + } + } + Point screenDPI = styledText.getDisplay().getDPI(); + Point printerDPI = printer.getDPI(); + resources = new Hashtable (); + for (int i = 0; i < lineCount; i++) { + Color color = printerRenderer.getLineBackground(i, null); + if (color != null) { + if (printOptions.printLineBackground) { + Color printerColor = (Color)resources.get(color); + if (printerColor == null) { + printerColor = new Color (printer, color.getRGB()); + resources.put(color, printerColor); + } + printerRenderer.setLineBackground(i, 1, printerColor); + } else { + printerRenderer.setLineBackground(i, 1, null); + } + } + int indent = printerRenderer.getLineIndent(i, 0); + if (indent != 0) { + printerRenderer.setLineIndent(i, 1, indent * printerDPI.x / screenDPI.x); + } + } + StyleRange[] styles = printerRenderer.styles; + for (int i = 0; i < printerRenderer.styleCount; i++) { + StyleRange style = styles[i]; + Font font = style.font; + if (style.font != null) { + Font printerFont = (Font)resources.get(font); + if (printerFont == null) { + printerFont = new Font (printer, font.getFontData()); + resources.put(font, printerFont); + } + style.font = printerFont; + } + Color color = style.foreground; + if (color != null) { + Color printerColor = (Color)resources.get(color); + if (printOptions.printTextForeground) { + if (printerColor == null) { + printerColor = new Color (printer, color.getRGB()); + resources.put(color, printerColor); + } + style.foreground = printerColor; + } else { + style.foreground = null; + } + } + color = style.background; + if (color != null) { + Color printerColor = (Color)resources.get(color); + if (printOptions.printTextBackground) { + if (printerColor == null) { + printerColor = new Color (printer, color.getRGB()); + resources.put(color, printerColor); + } + style.background = printerColor; + } else { + style.background = null; + } + } + if (!printOptions.printTextFontStyle) { + style.fontStyle = SWT.NORMAL; + } + style.rise = style.rise * printerDPI.y / screenDPI.y; + GlyphMetrics metrics = style.metrics; + if (metrics != null) { + metrics.ascent = metrics.ascent * printerDPI.y / screenDPI.y; + metrics.descent = metrics.descent * printerDPI.y / screenDPI.y; + metrics.width = metrics.width * printerDPI.x / screenDPI.x; + } + } + lineSpacing = styledText.lineSpacing * printerDPI.y / screenDPI.y; + if (printOptions.printLineNumbers) { + printMargin = 3 * printerDPI.x / screenDPI.x; + } + } + /** + * Copies the text of the specified <code>StyledTextContent</code>. + * </p> + * @param original the <code>StyledTextContent</code> to copy. + */ + StyledTextContent copyContent(StyledTextContent original) { + StyledTextContent printerContent = new DefaultContent(); + int insertOffset = 0; + for (int i = 0; i < original.getLineCount(); i++) { + int insertEndOffset; + if (i < original.getLineCount() - 1) { + insertEndOffset = original.getOffsetAtLine(i + 1); + } else { + insertEndOffset = original.getCharCount(); + } + printerContent.replaceTextRange(insertOffset, 0, original.getTextRange(insertOffset, insertEndOffset - insertOffset)); + insertOffset = insertEndOffset; + } + return printerContent; + } + /** + * Disposes of the resources and the <code>PrintRenderer</code>. + */ + void dispose() { + if (gc != null) { + gc.dispose(); + gc = null; + } + if (resources != null) { + Enumeration enumeration = resources.elements(); + while (enumeration.hasMoreElements()) { + Resource resource = (Resource) enumeration.nextElement(); + resource.dispose(); + } + resources = null; + } + if (printerFont != null) { + printerFont.dispose(); + printerFont = null; + } + if (printerRenderer != null) { + printerRenderer.dispose(); + printerRenderer = null; + } + } + void init() { + Rectangle trim = printer.computeTrim(0, 0, 0, 0); + Point dpi = printer.getDPI(); + + printerFont = new Font(printer, fontData.getName(), fontData.getHeight(), SWT.NORMAL); + clientArea = printer.getClientArea(); + pageWidth = clientArea.width; + // one inch margin around text + clientArea.x = dpi.x + trim.x; + clientArea.y = dpi.y + trim.y; + clientArea.width -= (clientArea.x + trim.width); + clientArea.height -= (clientArea.y + trim.height); + + int style = mirrored ? SWT.RIGHT_TO_LEFT : SWT.LEFT_TO_RIGHT; + gc = new GC(printer, style); + gc.setFont(printerFont); + printerRenderer.setFont(printerFont, tabLength); + int lineHeight = printerRenderer.getLineHeight(); + if (printOptions.header != null) { + clientArea.y += lineHeight * 2; + clientArea.height -= lineHeight * 2; + } + if (printOptions.footer != null) { + clientArea.height -= lineHeight * 2; + } + + // TODO not wrapped + StyledTextContent content = printerRenderer.content; + startLine = 0; + endLine = singleLine ? 0 : content.getLineCount() - 1; + PrinterData data = printer.getPrinterData(); + if (data.scope == PrinterData.PAGE_RANGE) { + int pageSize = clientArea.height / lineHeight;//WRONG + startLine = (startPage - 1) * pageSize; + } else if (data.scope == PrinterData.SELECTION) { + startLine = content.getLineAtOffset(selection.x); + if (selection.y > 0) { + endLine = content.getLineAtOffset(selection.x + selection.y - 1); + } else { + endLine = startLine - 1; + } + } + } + /** + * Prints the lines in the specified page range. + */ + void print() { + Color background = gc.getBackground(); + Color foreground = gc.getForeground(); + int paintY = clientArea.y; + int paintX = clientArea.x; + int width = clientArea.width; + int page = startPage; + int pageBottom = clientArea.y + clientArea.height; + int orientation = gc.getStyle() & (SWT.RIGHT_TO_LEFT | SWT.LEFT_TO_RIGHT); + TextLayout printLayout = null; + if (printOptions.printLineNumbers || printOptions.header != null || printOptions.footer != null) { + printLayout = new TextLayout(printer); + printLayout.setFont(printerFont); + } + if (printOptions.printLineNumbers) { + int numberingWidth = 0; + int count = endLine - startLine + 1; + String[] lineLabels = printOptions.lineLabels; + if (lineLabels != null) { + for (int i = startLine; i < Math.min(count, lineLabels.length); i++) { + if (lineLabels[i] != null) { + printLayout.setText(lineLabels[i]); + int lineWidth = printLayout.getBounds().width; + numberingWidth = Math.max(numberingWidth, lineWidth); + } + } + } else { + StringBuffer buffer = new StringBuffer("0"); + while ((count /= 10) > 0) buffer.append("0"); + printLayout.setText(buffer.toString()); + numberingWidth = printLayout.getBounds().width; + } + numberingWidth += printMargin; + if (numberingWidth > width) numberingWidth = width; + paintX += numberingWidth; + width -= numberingWidth; + } + for (int i = startLine; i <= endLine && page <= endPage; i++) { + if (paintY == clientArea.y) { + printer.startPage(); + printDecoration(page, true, printLayout); + } + TextLayout layout = printerRenderer.getTextLayout(i, orientation, width, lineSpacing); + Color lineBackground = printerRenderer.getLineBackground(i, background); + int paragraphBottom = paintY + layout.getBounds().height; + if (paragraphBottom <= pageBottom) { + //normal case, the whole paragraph fits in the current page + printLine(paintX, paintY, gc, foreground, lineBackground, layout, printLayout, i); + paintY = paragraphBottom; + } else { + int lineCount = layout.getLineCount(); + while (paragraphBottom > pageBottom && lineCount > 0) { + lineCount--; + paragraphBottom -= layout.getLineBounds(lineCount).height + layout.getSpacing(); + } + if (lineCount == 0) { + //the whole paragraph goes to the next page + printDecoration(page, false, printLayout); + printer.endPage(); + page++; + if (page <= endPage) { + printer.startPage(); + printDecoration(page, true, printLayout); + paintY = clientArea.y; + printLine(paintX, paintY, gc, foreground, lineBackground, layout, printLayout, i); + paintY += layout.getBounds().height; + } + } else { + //draw paragraph top in the current page and paragraph bottom in the next + int height = paragraphBottom - paintY; + gc.setClipping(clientArea.x, paintY, clientArea.width, height); + printLine(paintX, paintY, gc, foreground, lineBackground, layout, printLayout, i); + gc.setClipping((Rectangle)null); + printDecoration(page, false, printLayout); + printer.endPage(); + page++; + if (page <= endPage) { + printer.startPage(); + printDecoration(page, true, printLayout); + paintY = clientArea.y - height; + int layoutHeight = layout.getBounds().height; + gc.setClipping(clientArea.x, clientArea.y, clientArea.width, layoutHeight - height); + printLine(paintX, paintY, gc, foreground, lineBackground, layout, printLayout, i); + gc.setClipping((Rectangle)null); + paintY += layoutHeight; + } + } + } + printerRenderer.disposeTextLayout(layout); + } + if (page <= endPage && paintY > clientArea.y) { + // close partial page + printDecoration(page, false, printLayout); + printer.endPage(); + } + if (printLayout != null) printLayout.dispose(); + } + /** + * Print header or footer decorations. + * + * @param page page number to print, if specified in the StyledTextPrintOptions header or footer. + * @param header true = print the header, false = print the footer + */ + void printDecoration(int page, boolean header, TextLayout layout) { + String text = header ? printOptions.header : printOptions.footer; + if (text == null) return; + int lastSegmentIndex = 0; + for (int i = 0; i < 3; i++) { + int segmentIndex = text.indexOf(StyledTextPrintOptions.SEPARATOR, lastSegmentIndex); + String segment; + if (segmentIndex == -1) { + segment = text.substring(lastSegmentIndex); + printDecorationSegment(segment, i, page, header, layout); + break; + } else { + segment = text.substring(lastSegmentIndex, segmentIndex); + printDecorationSegment(segment, i, page, header, layout); + lastSegmentIndex = segmentIndex + StyledTextPrintOptions.SEPARATOR.length(); + } + } + } + /** + * Print one segment of a header or footer decoration. + * Headers and footers have three different segments. + * One each for left aligned, centered, and right aligned text. + * + * @param segment decoration segment to print + * @param alignment alignment of the segment. 0=left, 1=center, 2=right + * @param page page number to print, if specified in the decoration segment. + * @param header true = print the header, false = print the footer + */ + void printDecorationSegment(String segment, int alignment, int page, boolean header, TextLayout layout) { + int pageIndex = segment.indexOf(StyledTextPrintOptions.PAGE_TAG); + if (pageIndex != -1) { + int pageTagLength = StyledTextPrintOptions.PAGE_TAG.length(); + StringBuffer buffer = new StringBuffer(segment.substring (0, pageIndex)); + buffer.append (page); + buffer.append (segment.substring(pageIndex + pageTagLength)); + segment = buffer.toString(); + } + if (segment.length() > 0) { + layout.setText(segment); + int segmentWidth = layout.getBounds().width; + int segmentHeight = printerRenderer.getLineHeight(); + int drawX = 0, drawY; + if (alignment == LEFT) { + drawX = clientArea.x; + } else if (alignment == CENTER) { + drawX = (pageWidth - segmentWidth) / 2; + } else if (alignment == RIGHT) { + drawX = clientArea.x + clientArea.width - segmentWidth; + } + if (header) { + drawY = clientArea.y - segmentHeight * 2; + } else { + drawY = clientArea.y + clientArea.height + segmentHeight; + } + layout.draw(gc, drawX, drawY); + } + } + void printLine(int x, int y, GC gc, Color foreground, Color background, TextLayout layout, TextLayout printLayout, int index) { + if (background != null) { + Rectangle rect = layout.getBounds(); + gc.setBackground(background); + gc.fillRectangle(x, y, rect.width, rect.height); + +// int lineCount = layout.getLineCount(); +// for (int i = 0; i < lineCount; i++) { +// Rectangle rect = layout.getLineBounds(i); +// rect.x += paintX; +// rect.y += paintY + layout.getSpacing(); +// rect.width = width;//layout bounds +// gc.fillRectangle(rect); +// } + } + if (printOptions.printLineNumbers) { + FontMetrics metrics = layout.getLineMetrics(0); + printLayout.setAscent(metrics.getAscent() + metrics.getLeading()); + printLayout.setDescent(metrics.getDescent()); + String[] lineLabels = printOptions.lineLabels; + if (lineLabels != null) { + if (0 <= index && index < lineLabels.length && lineLabels[index] != null) { + printLayout.setText(lineLabels[index]); + } else { + printLayout.setText(""); + } + } else { + printLayout.setText(String.valueOf(index)); + } + int paintX = x - printMargin - printLayout.getBounds().width; + printLayout.draw(gc, paintX, y); + printLayout.setAscent(-1); + printLayout.setDescent(-1); + } + gc.setForeground(foreground); + layout.draw(gc, x, y); + } + /** + * Starts a print job and prints the pages specified in the constructor. + */ + public void run() { + String jobName = printOptions.jobName; + if (jobName == null) { + jobName = "Printing"; + } + if (printer.startJob(jobName)) { + init(); + print(); + dispose(); + printer.endJob(); + } + } + } + /** + * The <code>RTFWriter</code> class is used to write widget content as + * rich text. The implementation complies with the RTF specification + * version 1.5. + * <p> + * toString() is guaranteed to return a valid RTF string only after + * close() has been called. + * </p><p> + * Whole and partial lines and line breaks can be written. Lines will be + * formatted using the styles queried from the LineStyleListener, if + * set, or those set directly in the widget. All styles are applied to + * the RTF stream like they are rendered by the widget. In addition, the + * widget font name and size is used for the whole text. + * </p> + */ + class RTFWriter extends TextWriter { + static final int DEFAULT_FOREGROUND = 0; + static final int DEFAULT_BACKGROUND = 1; + Vector colorTable, fontTable; + boolean WriteUnicode; + + /** + * Creates a RTF writer that writes content starting at offset "start" + * in the document. <code>start</code> and <code>length</code>can be set to specify partial + * lines. + * + * @param start start offset of content to write, 0 based from + * beginning of document + * @param length length of content to write + */ + public RTFWriter(int start, int length) { + super(start, length); + colorTable = new Vector(); + fontTable = new Vector(); + colorTable.addElement(getForeground()); + colorTable.addElement(getBackground()); + fontTable.addElement(getFont()); + setUnicode(); + } + /** + * Closes the RTF writer. Once closed no more content can be written. + * <b>NOTE:</b> <code>toString()</code> does not return a valid RTF string until + * <code>close()</code> has been called. + */ + public void close() { + if (!isClosed()) { + writeHeader(); + write("\n}}\0"); + super.close(); + } + } + /** + * Returns the index of the specified color in the RTF color table. + * + * @param color the color + * @param defaultIndex return value if color is null + * @return the index of the specified color in the RTF color table + * or "defaultIndex" if "color" is null. + */ + int getColorIndex(Color color, int defaultIndex) { + if (color == null) return defaultIndex; + int index = colorTable.indexOf(color); + if (index == -1) { + index = colorTable.size(); + colorTable.addElement(color); + } + return index; + } + /** + * Returns the index of the specified color in the RTF color table. + * + * @param color the color + * @param defaultIndex return value if color is null + * @return the index of the specified color in the RTF color table + * or "defaultIndex" if "color" is null. + */ + int getFontIndex(Font font) { + int index = fontTable.indexOf(font); + if (index == -1) { + index = fontTable.size(); + fontTable.addElement(font); + } + return index; + } + /** + * Determines if Unicode RTF should be written. + * Don't write Unicode RTF on Windows 95/98/ME or NT. + */ + void setUnicode() { + final String Win95 = "windows 95"; + final String Win98 = "windows 98"; + final String WinME = "windows me"; + final String WinNT = "windows nt"; + String osName = System.getProperty("os.name").toLowerCase(); + String osVersion = System.getProperty("os.version"); + int majorVersion = 0; + + if (osName.startsWith(WinNT) && osVersion != null) { + int majorIndex = osVersion.indexOf('.'); + if (majorIndex != -1) { + osVersion = osVersion.substring(0, majorIndex); + try { + majorVersion = Integer.parseInt(osVersion); + } catch (NumberFormatException exception) { + // ignore exception. version number remains unknown. + // will write without Unicode + } + } + } + WriteUnicode = !osName.startsWith(Win95) && + !osName.startsWith(Win98) && + !osName.startsWith(WinME) && + (!osName.startsWith(WinNT) || majorVersion > 4); + } + /** + * Appends the specified segment of "string" to the RTF data. + * Copy from <code>start</code> up to, but excluding, <code>end</code>. + * + * @param string string to copy a segment from. Must not contain + * line breaks. Line breaks should be written using writeLineDelimiter() + * @param start start offset of segment. 0 based. + * @param end end offset of segment + */ + void write(String string, int start, int end) { + for (int index = start; index < end; index++) { + char ch = string.charAt(index); + if (ch > 0xFF && WriteUnicode) { + // write the sub string from the last escaped character + // to the current one. Fixes bug 21698. + if (index > start) { + write(string.substring(start, index)); + } + write("\\u"); + write(Integer.toString((short) ch)); + write('?'); // ANSI representation (1 byte long, \\uc1) + start = index + 1; + } else if (ch == '}' || ch == '{' || ch == '\\') { + // write the sub string from the last escaped character + // to the current one. Fixes bug 21698. + if (index > start) { + write(string.substring(start, index)); + } + write('\\'); + write(ch); + start = index + 1; + } + } + // write from the last escaped character to the end. + // Fixes bug 21698. + if (start < end) { + write(string.substring(start, end)); + } + } + /** + * Writes the RTF header including font table and color table. + */ + void writeHeader() { + StringBuffer header = new StringBuffer(); + FontData fontData = getFont().getFontData()[0]; + header.append("{\\rtf1\\ansi"); + // specify code page, necessary for copy to work in bidi + // systems that don't support Unicode RTF. + String cpg = System.getProperty("file.encoding").toLowerCase(); + if (cpg.startsWith("cp") || cpg.startsWith("ms")) { + cpg = cpg.substring(2, cpg.length()); + header.append("\\ansicpg"); + header.append(cpg); + } + header.append("\\uc1\\deff0{\\fonttbl{\\f0\\fnil "); + header.append(fontData.getName()); + header.append(";"); + for (int i = 1; i < fontTable.size(); i++) { + header.append("\\f"); + header.append(i); + header.append(" "); + FontData fd = ((Font)fontTable.elementAt(i)).getFontData()[0]; + header.append(fd.getName()); + header.append(";"); + } + header.append("}}\n{\\colortbl"); + for (int i = 0; i < colorTable.size(); i++) { + Color color = (Color) colorTable.elementAt(i); + header.append("\\red"); + header.append(color.getRed()); + header.append("\\green"); + header.append(color.getGreen()); + header.append("\\blue"); + header.append(color.getBlue()); + header.append(";"); + } + // some RTF readers ignore the deff0 font tag. Explicitly + // set the font for the whole document to work around this. + header.append("}\n{\\f0\\fs"); + // font size is specified in half points + header.append(fontData.getHeight() * 2); + header.append(" "); + write(header.toString(), 0); + } + /** + * Appends the specified line text to the RTF data. Lines will be formatted + * using the styles queried from the LineStyleListener, if set, or those set + * directly in the widget. + * + * @param line line text to write as RTF. Must not contain line breaks + * Line breaks should be written using writeLineDelimiter() + * @param lineOffset offset of the line. 0 based from the start of the + * widget document. Any text occurring before the start offset or after the + * end offset specified during object creation is ignored. + * @exception SWTException <ul> + * <li>ERROR_IO when the writer is closed.</li> + * </ul> + */ + public void writeLine(String line, int lineOffset) { + if (isClosed()) { + SWT.error(SWT.ERROR_IO); + } + int lineIndex = content.getLineAtOffset(lineOffset); + int lineAlignment, lineIndent; + boolean lineJustify; + int[] ranges; + StyleRange[] styles; + StyledTextEvent event = getLineStyleData(lineOffset, line); + if (event != null) { + lineAlignment = event.alignment; + lineIndent = event.indent; + lineJustify = event.justify; + ranges = event.ranges; + styles = event.styles; + } else { + lineAlignment = renderer.getLineAlignment(lineIndex, alignment); + lineIndent = renderer.getLineIndent(lineIndex, indent); + lineJustify = renderer.getLineJustify(lineIndex, justify); + ranges = renderer.getRanges(lineOffset, line.length()); + styles = renderer.getStyleRanges(lineOffset, line.length(), false); + } + if (styles == null) styles = new StyleRange[0]; + Color lineBackground = renderer.getLineBackground(lineIndex, null); + event = getLineBackgroundData(lineOffset, line); + if (event != null && event.lineBackground != null) lineBackground = event.lineBackground; + writeStyledLine(line, lineOffset, ranges, styles, lineBackground, lineIndent, lineAlignment, lineJustify); + } + /** + * Appends the specified line delimiter to the RTF data. + * + * @param lineDelimiter line delimiter to write as RTF. + * @exception SWTException <ul> + * <li>ERROR_IO when the writer is closed.</li> + * </ul> + */ + public void writeLineDelimiter(String lineDelimiter) { + if (isClosed()) { + SWT.error(SWT.ERROR_IO); + } + write(lineDelimiter, 0, lineDelimiter.length()); + write("\\par "); + } + /** + * Appends the specified line text to the RTF data. + * <p> + * Use the colors and font styles specified in "styles" and "lineBackground". + * Formatting is written to reflect the text rendering by the text widget. + * Style background colors take precedence over the line background color. + * Background colors are written using the \highlight tag (vs. the \cb tag). + * </p> + * + * @param line line text to write as RTF. Must not contain line breaks + * Line breaks should be written using writeLineDelimiter() + * @param lineOffset offset of the line. 0 based from the start of the + * widget document. Any text occurring before the start offset or after the + * end offset specified during object creation is ignored. + * @param styles styles to use for formatting. Must not be null. + * @param lineBackground line background color to use for formatting. + * May be null. + */ + void writeStyledLine(String line, int lineOffset, int ranges[], StyleRange[] styles, Color lineBackground, int indent, int alignment, boolean justify) { + int lineLength = line.length(); + int startOffset = getStart(); + int writeOffset = startOffset - lineOffset; + if (writeOffset >= lineLength) return; + int lineIndex = Math.max(0, writeOffset); + + write("\\fi"); + write(indent); + switch (alignment) { + case SWT.LEFT: write("\\ql"); break; + case SWT.CENTER: write("\\qc"); break; + case SWT.RIGHT: write("\\qr"); break; + } + if (justify) write("\\qj"); + write(" "); + + if (lineBackground != null) { + write("{\\highlight"); + write(getColorIndex(lineBackground, DEFAULT_BACKGROUND)); + write(" "); + } + int endOffset = startOffset + super.getCharCount(); + int lineEndOffset = Math.min(lineLength, endOffset - lineOffset); + for (int i = 0; i < styles.length; i++) { + StyleRange style = styles[i]; + int start, end; + if (ranges != null) { + start = ranges[i << 1] - lineOffset; + end = start + ranges[(i << 1) + 1]; + } else { + start = style.start - lineOffset; + end = start + style.length; + } + // skip over partial first line + if (end < writeOffset) { + continue; + } + // style starts beyond line end or RTF write end + if (start >= lineEndOffset) { + break; + } + // write any unstyled text + if (lineIndex < start) { + // copy to start of style + // style starting beyond end of write range or end of line + // is guarded against above. + write(line, lineIndex, start); + lineIndex = start; + } + // write styled text + write("{\\cf"); + write(getColorIndex(style.foreground, DEFAULT_FOREGROUND)); + int colorIndex = getColorIndex(style.background, DEFAULT_BACKGROUND); + if (colorIndex != DEFAULT_BACKGROUND) { + write("\\highlight"); + write(colorIndex); + } + Font font = style.font; + if (font != null) { + int fontIndex = getFontIndex(font); + write("\\f"); + write(fontIndex); + FontData fontData = font.getFontData()[0]; + write("\\fs"); + write(fontData.getHeight() * 2); + } else { + if ((style.fontStyle & SWT.BOLD) != 0) { + write("\\b"); + } + if ((style.fontStyle & SWT.ITALIC) != 0) { + write("\\i"); + } + } + if (style.underline) { + write("\\ul"); + } + if (style.strikeout) { + write("\\strike"); + } + write(" "); + // copy to end of style or end of write range or end of line + int copyEnd = Math.min(end, lineEndOffset); + // guard against invalid styles and let style processing continue + copyEnd = Math.max(copyEnd, lineIndex); + write(line, lineIndex, copyEnd); + if (font == null) { + if ((style.fontStyle & SWT.BOLD) != 0) { + write("\\b0"); + } + if ((style.fontStyle & SWT.ITALIC) != 0) { + write("\\i0"); + } + } + if (style.underline) { + write("\\ul0"); + } + if (style.strikeout) { + write("\\strike0"); + } + write("}"); + lineIndex = copyEnd; + } + // write unstyled text at the end of the line + if (lineIndex < lineEndOffset) { + write(line, lineIndex, lineEndOffset); + } + if (lineBackground != null) write("}"); + } + } + /** + * The <code>TextWriter</code> class is used to write widget content to + * a string. Whole and partial lines and line breaks can be written. To write + * partial lines, specify the start and length of the desired segment + * during object creation. + * <p> + * </b>NOTE:</b> <code>toString()</code> is guaranteed to return a valid string only after close() + * has been called. + * </p> + */ + class TextWriter { + private StringBuffer buffer; + private int startOffset; // offset of first character that will be written + private int endOffset; // offset of last character that will be written. + // 0 based from the beginning of the widget text. + private boolean isClosed = false; + + /** + * Creates a writer that writes content starting at offset "start" + * in the document. <code>start</code> and <code>length</code> can be set to specify partial lines. + * + * @param start start offset of content to write, 0 based from beginning of document + * @param length length of content to write + */ + public TextWriter(int start, int length) { + buffer = new StringBuffer(length); + startOffset = start; + endOffset = start + length; + } + /** + * Closes the writer. Once closed no more content can be written. + * <b>NOTE:</b> <code>toString()</code> is not guaranteed to return a valid string unless + * the writer is closed. + */ + public void close() { + if (!isClosed) { + isClosed = true; + } + } + /** + * Returns the number of characters to write. + * @return the integer number of characters to write + */ + public int getCharCount() { + return endOffset - startOffset; + } + /** + * Returns the offset where writing starts. 0 based from the start of + * the widget text. Used to write partial lines. + * @return the integer offset where writing starts + */ + public int getStart() { + return startOffset; + } + /** + * Returns whether the writer is closed. + * @return a boolean specifying whether or not the writer is closed + */ + public boolean isClosed() { + return isClosed; + } + /** + * Returns the string. <code>close()</code> must be called before <code>toString()</code> + * is guaranteed to return a valid string. + * + * @return the string + */ + public String toString() { + return buffer.toString(); + } + /** + * Appends the given string to the data. + */ + void write(String string) { + buffer.append(string); + } + /** + * Inserts the given string to the data at the specified offset. + * <p> + * Do nothing if "offset" is < 0 or > getCharCount() + * </p> + * + * @param string text to insert + * @param offset offset in the existing data to insert "string" at. + */ + void write(String string, int offset) { + if (offset < 0 || offset > buffer.length()) { + return; + } + buffer.insert(offset, string); + } + /** + * Appends the given int to the data. + */ + void write(int i) { + buffer.append(i); + } + /** + * Appends the given character to the data. + */ + void write(char i) { + buffer.append(i); + } + /** + * Appends the specified line text to the data. + * + * @param line line text to write. Must not contain line breaks + * Line breaks should be written using writeLineDelimiter() + * @param lineOffset offset of the line. 0 based from the start of the + * widget document. Any text occurring before the start offset or after the + * end offset specified during object creation is ignored. + * @exception SWTException <ul> + * <li>ERROR_IO when the writer is closed.</li> + * </ul> + */ + public void writeLine(String line, int lineOffset) { + if (isClosed) { + SWT.error(SWT.ERROR_IO); + } + int writeOffset = startOffset - lineOffset; + int lineLength = line.length(); + int lineIndex; + if (writeOffset >= lineLength) { + return; // whole line is outside write range + } else if (writeOffset > 0) { + lineIndex = writeOffset; // line starts before write start + } else { + lineIndex = 0; + } + int copyEnd = Math.min(lineLength, endOffset - lineOffset); + if (lineIndex < copyEnd) { + write(line.substring(lineIndex, copyEnd)); + } + } + /** + * Appends the specified line delimiter to the data. + * + * @param lineDelimiter line delimiter to write + * @exception SWTException <ul> + * <li>ERROR_IO when the writer is closed.</li> + * </ul> + */ + public void writeLineDelimiter(String lineDelimiter) { + if (isClosed) { + SWT.error(SWT.ERROR_IO); + } + write(lineDelimiter); + } + } + +/** + * Constructs a new instance of this class given its parent + * and a style value describing its behavior and appearance. + * <p> + * The style value is either one of the style constants defined in + * class <code>SWT</code> which is applicable to instances of this + * class, or must be built by <em>bitwise OR</em>'ing together + * (that is, using the <code>int</code> "|" operator) two or more + * of those <code>SWT</code> style constants. The class description + * lists the style constants that are applicable to the class. + * Style bits are also inherited from superclasses. + * </p> + * + * @param parent a widget which will be the parent of the new instance (cannot be null) + * @param style the style of widget to construct + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the parent is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li> + * </ul> + * + * @see SWT#FULL_SELECTION + * @see SWT#MULTI + * @see SWT#READ_ONLY + * @see SWT#SINGLE + * @see SWT#WRAP + * @see #getStyle + */ +public StyledText(Composite parent, int style) { + super(parent, checkStyle(style)); + // set the fg in the OS to ensure that these are the same as StyledText, necessary + // for ensuring that the bg/fg the IME box uses is the same as what StyledText uses + super.setForeground(getForeground()); + super.setDragDetect(false); + Display display = getDisplay(); + isMirrored = (super.getStyle() & SWT.MIRRORED) != 0; + fixedLineHeight = true; + if ((style & SWT.READ_ONLY) != 0) { + setEditable(false); + } + leftMargin = rightMargin = isBidiCaret() ? BIDI_CARET_WIDTH - 1: 0; + if ((style & SWT.SINGLE) != 0 && (style & SWT.BORDER) != 0) { + leftMargin = topMargin = rightMargin = bottomMargin = 2; + } + alignment = style & (SWT.LEFT | SWT.RIGHT | SWT.CENTER); + if (alignment == 0) alignment = SWT.LEFT; + clipboard = new Clipboard(display); + installDefaultContent(); + renderer = new StyledTextRenderer(getDisplay(), this); + renderer.setContent(content); + renderer.setFont(getFont(), tabLength); + ime = new IME(this, SWT.NONE); + defaultCaret = new Caret(this, SWT.NONE); + if ((style & SWT.WRAP) != 0) { + setWordWrap(true); + } + if (isBidiCaret()) { + createCaretBitmaps(); + Runnable runnable = new Runnable() { + public void run() { + int direction = BidiUtil.getKeyboardLanguage() == BidiUtil.KEYBOARD_BIDI ? SWT.RIGHT : SWT.LEFT; + if (direction == caretDirection) return; + if (getCaret() != defaultCaret) return; + Point newCaretPos = getPointAtOffset(caretOffset); + setCaretLocation(newCaretPos, direction); + } + }; + BidiUtil.addLanguageListener(this, runnable); + } + setCaret(defaultCaret); + calculateScrollBars(); + createKeyBindings(); + super.setCursor(display.getSystemCursor(SWT.CURSOR_IBEAM)); + installListeners(); + initializeAccessible(); + setData("DEFAULT_DROP_TARGET_EFFECT", new StyledTextDropTargetEffect(this)); +} +/** + * Adds an extended modify listener. An ExtendedModify event is sent by the + * widget when the widget text has changed. + * + * @param extendedModifyListener the listener + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT when listener is null</li> + * </ul> + */ +public void addExtendedModifyListener(ExtendedModifyListener extendedModifyListener) { + checkWidget(); + if (extendedModifyListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + StyledTextListener typedListener = new StyledTextListener(extendedModifyListener); + addListener(ExtendedModify, typedListener); +} +/** + * Adds a bidirectional segment listener. + * <p> + * A BidiSegmentEvent is sent + * whenever a line of text is measured or rendered. The user can + * specify text ranges in the line that should be treated as if they + * had a different direction than the surrounding text. + * This may be used when adjacent segments of right-to-left text should + * not be reordered relative to each other. + * E.g., Multiple Java string literals in a right-to-left language + * should generally remain in logical order to each other, that is, the + * way they are stored. + * </p> + * + * @param listener the listener + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT when listener is null</li> + * </ul> + * @see BidiSegmentEvent + * @since 2.0 + */ +public void addBidiSegmentListener(BidiSegmentListener listener) { + checkWidget(); + if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + addListener(LineGetSegments, new StyledTextListener(listener)); +} +/** + * Adds a caret listener. CaretEvent is sent when the caret offset changes. + * + * @param listener the listener + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT when listener is null</li> + * </ul> + * + * @since 3.5 + */ +public void addCaretListener(CaretListener listener) { + checkWidget(); + if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + addListener(CaretMoved, new StyledTextListener(listener)); +} +/** + * Adds a line background listener. A LineGetBackground event is sent by the + * widget to determine the background color for a line. + * + * @param listener the listener + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT when listener is null</li> + * </ul> + */ +public void addLineBackgroundListener(LineBackgroundListener listener) { + checkWidget(); + if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + if (!isListening(LineGetBackground)) { + renderer.clearLineBackground(0, content.getLineCount()); + } + addListener(LineGetBackground, new StyledTextListener(listener)); +} +/** + * Adds a line style listener. A LineGetStyle event is sent by the widget to + * determine the styles for a line. + * + * @param listener the listener + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT when listener is null</li> + * </ul> + */ +public void addLineStyleListener(LineStyleListener listener) { + checkWidget(); + if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + if (!isListening(LineGetStyle)) { + setStyleRanges(0, 0, null, null, true); + renderer.clearLineStyle(0, content.getLineCount()); + } + addListener(LineGetStyle, new StyledTextListener(listener)); + setCaretLocation(); +} +/** + * Adds a modify listener. A Modify event is sent by the widget when the widget text + * has changed. + * + * @param modifyListener the listener + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT when listener is null</li> + * </ul> + */ +public void addModifyListener(ModifyListener modifyListener) { + checkWidget(); + if (modifyListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + addListener(SWT.Modify, new TypedListener(modifyListener)); +} +/** + * Adds a paint object listener. A paint object event is sent by the widget when an object + * needs to be drawn. + * + * @param listener the listener + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT when listener is null</li> + * </ul> + * + * @since 3.2 + * + * @see PaintObjectListener + * @see PaintObjectEvent + */ +public void addPaintObjectListener(PaintObjectListener listener) { + checkWidget(); + if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + addListener(PaintObject, new StyledTextListener(listener)); +} +/** + * Adds a selection listener. A Selection event is sent by the widget when the + * user changes the selection. + * <p> + * When <code>widgetSelected</code> is called, the event x and y fields contain + * the start and end caret indices of the selection. + * <code>widgetDefaultSelected</code> is not called for StyledTexts. + * </p> + * + * @param listener the listener which should be notified when the user changes the receiver's selection + + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the listener is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see SelectionListener + * @see #removeSelectionListener + * @see SelectionEvent + */ +public void addSelectionListener(SelectionListener listener) { + checkWidget(); + if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + addListener(SWT.Selection, new TypedListener(listener)); +} +/** + * Adds a verify key listener. A VerifyKey event is sent by the widget when a key + * is pressed. The widget ignores the key press if the listener sets the doit field + * of the event to false. + * + * @param listener the listener + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT when listener is null</li> + * </ul> + */ +public void addVerifyKeyListener(VerifyKeyListener listener) { + checkWidget(); + if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + addListener(VerifyKey, new StyledTextListener(listener)); +} +/** + * Adds a verify listener. A Verify event is sent by the widget when the widget text + * is about to change. The listener can set the event text and the doit field to + * change the text that is set in the widget or to force the widget to ignore the + * text change. + * + * @param verifyListener the listener + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT when listener is null</li> + * </ul> + */ +public void addVerifyListener(VerifyListener verifyListener) { + checkWidget(); + if (verifyListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + addListener(SWT.Verify, new TypedListener(verifyListener)); +} +/** + * Adds a word movement listener. A movement event is sent when the boundary + * of a word is needed. For example, this occurs during word next and word + * previous actions. + * + * @param movementListener the listener + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT when listener is null</li> + * </ul> + * + * @see MovementEvent + * @see MovementListener + * @see #removeWordMovementListener + * + * @since 3.3 + */ +public void addWordMovementListener(MovementListener movementListener) { + checkWidget(); + if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + addListener(WordNext, new StyledTextListener(movementListener)); + addListener(WordPrevious, new StyledTextListener(movementListener)); +} +/** + * Appends a string to the text at the end of the widget. + * + * @param string the string to be appended + * @see #replaceTextRange(int,int,String) + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT when listener is null</li> + * </ul> + */ +public void append(String string) { + checkWidget(); + if (string == null) { + SWT.error(SWT.ERROR_NULL_ARGUMENT); + } + int lastChar = Math.max(getCharCount(), 0); + replaceTextRange(lastChar, 0, string); +} +/** + * Calculates the scroll bars + */ +void calculateScrollBars() { + ScrollBar horizontalBar = getHorizontalBar(); + ScrollBar verticalBar = getVerticalBar(); + setScrollBars(true); + if (verticalBar != null) { + verticalBar.setIncrement(getVerticalIncrement()); + } + if (horizontalBar != null) { + horizontalBar.setIncrement(getHorizontalIncrement()); + } +} +/** + * Calculates the top index based on the current vertical scroll offset. + * The top index is the index of the topmost fully visible line or the + * topmost partially visible line if no line is fully visible. + * The top index starts at 0. + */ +void calculateTopIndex(int delta) { + int oldTopIndex = topIndex; + int oldTopIndexY = topIndexY; + if (isFixedLineHeight()) { + int verticalIncrement = getVerticalIncrement(); + if (verticalIncrement == 0) { + return; + } + topIndex = Compatibility.ceil(getVerticalScrollOffset(), verticalIncrement); + // Set top index to partially visible top line if no line is fully + // visible but at least some of the widget client area is visible. + // Fixes bug 15088. + if (topIndex > 0) { + if (clientAreaHeight > 0) { + int bottomPixel = getVerticalScrollOffset() + clientAreaHeight; + int fullLineTopPixel = topIndex * verticalIncrement; + int fullLineVisibleHeight = bottomPixel - fullLineTopPixel; + // set top index to partially visible line if no line fully fits in + // client area or if space is available but not used (the latter should + // never happen because we use claimBottomFreeSpace) + if (fullLineVisibleHeight < verticalIncrement) { + topIndex--; + } + } else if (topIndex >= content.getLineCount()) { + topIndex = content.getLineCount() - 1; + } + } + } else { + if (delta >= 0) { + delta -= topIndexY; + int lineIndex = topIndex; + int lineCount = content.getLineCount(); + while (lineIndex < lineCount) { + if (delta <= 0) break; + delta -= renderer.getLineHeight(lineIndex++); + } + if (lineIndex < lineCount && -delta + renderer.getLineHeight(lineIndex) <= clientAreaHeight - topMargin - bottomMargin) { + topIndex = lineIndex; + topIndexY = -delta; + } else { + topIndex = lineIndex - 1; + topIndexY = -renderer.getLineHeight(topIndex) - delta; + } + } else { + delta -= topIndexY; + int lineIndex = topIndex; + while (lineIndex > 0) { + int lineHeight = renderer.getLineHeight(lineIndex - 1); + if (delta + lineHeight > 0) break; + delta += lineHeight; + lineIndex--; + } + if (lineIndex == 0 || -delta + renderer.getLineHeight(lineIndex) <= clientAreaHeight - topMargin - bottomMargin) { + topIndex = lineIndex; + topIndexY = - delta; + } else { + topIndex = lineIndex - 1; + topIndexY = - renderer.getLineHeight(topIndex) - delta; + } + } + } + if (topIndex != oldTopIndex || oldTopIndexY != topIndexY) { + renderer.calculateClientArea(); + setScrollBars(false); + } +} +/** + * Hides the scroll bars if widget is created in single line mode. + */ +static int checkStyle(int style) { + if ((style & SWT.SINGLE) != 0) { + style &= ~(SWT.H_SCROLL | SWT.V_SCROLL | SWT.WRAP | SWT.MULTI); + } else { + style |= SWT.MULTI; + if ((style & SWT.WRAP) != 0) { + style &= ~SWT.H_SCROLL; + } + } + style |= SWT.NO_REDRAW_RESIZE | SWT.DOUBLE_BUFFERED | SWT.NO_BACKGROUND; + /* Clear SWT.CENTER to avoid the conflict with SWT.EMBEDDED */ + return style & ~SWT.CENTER; +} +/** + * Scrolls down the text to use new space made available by a resize or by + * deleted lines. + */ +void claimBottomFreeSpace() { + if (isFixedLineHeight()) { + int newVerticalOffset = Math.max(0, renderer.getHeight() - clientAreaHeight); + if (newVerticalOffset < getVerticalScrollOffset()) { + scrollVertical(newVerticalOffset - getVerticalScrollOffset(), true); + } + } else { + int bottomIndex = getPartialBottomIndex(); + int height = getLinePixel(bottomIndex + 1); + if (clientAreaHeight > height) { + scrollVertical(-getAvailableHeightAbove(clientAreaHeight - height), true); + } + } +} +/** + * Scrolls text to the right to use new space made available by a resize. + */ +void claimRightFreeSpace() { + int newHorizontalOffset = Math.max(0, renderer.getWidth() - clientAreaWidth); + if (newHorizontalOffset < horizontalScrollOffset) { + // item is no longer drawn past the right border of the client area + // align the right end of the item with the right border of the + // client area (window is scrolled right). + scrollHorizontal(newHorizontalOffset - horizontalScrollOffset, true); + } +} +void clearBlockSelection(boolean reset, boolean sendEvent) { + if (reset) resetSelection(); + blockXAnchor = blockYAnchor = -1; + blockXLocation = blockYLocation = -1; + caretDirection = SWT.NULL; + updateCaretVisibility(); + super.redraw(); + if (sendEvent) sendSelectionEvent(); +} +/** + * Removes the widget selection. + * + * @param sendEvent a Selection event is sent when set to true and when the selection is actually reset. + */ +void clearSelection(boolean sendEvent) { + int selectionStart = selection.x; + int selectionEnd = selection.y; + resetSelection(); + // redraw old selection, if any + if (selectionEnd - selectionStart > 0) { + int length = content.getCharCount(); + // called internally to remove selection after text is removed + // therefore make sure redraw range is valid. + int redrawStart = Math.min(selectionStart, length); + int redrawEnd = Math.min(selectionEnd, length); + if (redrawEnd - redrawStart > 0) { + internalRedrawRange(redrawStart, redrawEnd - redrawStart); + } + if (sendEvent) { + sendSelectionEvent(); + } + } +} +public Point computeSize (int wHint, int hHint, boolean changed) { + checkWidget(); + int lineCount = (getStyle() & SWT.SINGLE) != 0 ? 1 : content.getLineCount(); + int width = 0; + int height = 0; + if (wHint == SWT.DEFAULT || hHint == SWT.DEFAULT) { + Display display = getDisplay(); + int maxHeight = display.getClientArea().height; + for (int lineIndex = 0; lineIndex < lineCount; lineIndex++) { + TextLayout layout = renderer.getTextLayout(lineIndex); + int wrapWidth = layout.getWidth(); + if (wordWrap) layout.setWidth(wHint == 0 ? 1 : wHint); + Rectangle rect = layout.getBounds(); + height += rect.height; + width = Math.max(width, rect.width); + layout.setWidth(wrapWidth); + renderer.disposeTextLayout(layout); + if (isFixedLineHeight() && height > maxHeight) break; + } + if (isFixedLineHeight()) { + height = lineCount * renderer.getLineHeight(); + } + } + // Use default values if no text is defined. + if (width == 0) width = DEFAULT_WIDTH; + if (height == 0) height = DEFAULT_HEIGHT; + if (wHint != SWT.DEFAULT) width = wHint; + if (hHint != SWT.DEFAULT) height = hHint; + int wTrim = leftMargin + rightMargin + getCaretWidth(); + int hTrim = topMargin + bottomMargin; + Rectangle rect = computeTrim(0, 0, width + wTrim, height + hTrim); + return new Point (rect.width, rect.height); +} +/** + * Copies the selected text to the <code>DND.CLIPBOARD</code> clipboard. + * <p> + * The text will be put on the clipboard in plain text format and RTF format. + * The <code>DND.CLIPBOARD</code> clipboard is used for data that is + * transferred by keyboard accelerator (such as Ctrl+C/Ctrl+V) or + * by menu action. + * </p> + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void copy() { + checkWidget(); + copySelection(DND.CLIPBOARD); +} +/** + * Copies the selected text to the specified clipboard. The text will be put in the + * clipboard in plain text format and RTF format. + * <p> + * The clipboardType is one of the clipboard constants defined in class + * <code>DND</code>. The <code>DND.CLIPBOARD</code> clipboard is + * used for data that is transferred by keyboard accelerator (such as Ctrl+C/Ctrl+V) + * or by menu action. The <code>DND.SELECTION_CLIPBOARD</code> + * clipboard is used for data that is transferred by selecting text and pasting + * with the middle mouse button. + * </p> + * + * @param clipboardType indicates the type of clipboard + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.1 + */ +public void copy(int clipboardType) { + checkWidget(); + copySelection(clipboardType); +} +boolean copySelection(int type) { + if (type != DND.CLIPBOARD && type != DND.SELECTION_CLIPBOARD) return false; + try { + if (blockSelection && blockXLocation != -1) { + String text = getBlockSelectionText(PlatformLineDelimiter); + if (text.length() > 0) { + //TODO RTF support + TextTransfer plainTextTransfer = TextTransfer.getInstance(); + Object[] data = new Object[]{text}; + Transfer[] types = new Transfer[]{plainTextTransfer}; + clipboard.setContents(data, types, type); + return true; + } + } else { + int length = selection.y - selection.x; + if (length > 0) { + setClipboardContent(selection.x, length, type); + return true; + } + } + } catch (SWTError error) { + // Copy to clipboard failed. This happens when another application + // is accessing the clipboard while we copy. Ignore the error. + // Rethrow all other errors. Fixes bug 17578. + if (error.code != DND.ERROR_CANNOT_SET_CLIPBOARD) { + throw error; + } + } + return false; +} +/** + * Returns the alignment of the widget. + * + * @return the alignment + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see #getLineAlignment(int) + * + * @since 3.2 + */ +public int getAlignment() { + checkWidget(); + return alignment; +} +int getAvailableHeightAbove(int height) { + int maxHeight = verticalScrollOffset; + if (maxHeight == -1) { + int lineIndex = topIndex - 1; + maxHeight = -topIndexY; + if (topIndexY > 0) { + maxHeight += renderer.getLineHeight(lineIndex--); + } + while (height > maxHeight && lineIndex >= 0) { + maxHeight += renderer.getLineHeight(lineIndex--); + } + } + return Math.min(height, maxHeight); +} +int getAvailableHeightBellow(int height) { + int partialBottomIndex = getPartialBottomIndex(); + int topY = getLinePixel(partialBottomIndex); + int lineHeight = renderer.getLineHeight(partialBottomIndex); + int availableHeight = 0; + int clientAreaHeight = this.clientAreaHeight - topMargin - bottomMargin; + if (topY + lineHeight > clientAreaHeight) { + availableHeight = lineHeight - (clientAreaHeight - topY); + } + int lineIndex = partialBottomIndex + 1; + int lineCount = content.getLineCount(); + while (height > availableHeight && lineIndex < lineCount) { + availableHeight += renderer.getLineHeight(lineIndex++); + } + return Math.min(height, availableHeight); +} +/** + * Returns the color of the margins. + * + * @return the color of the margins. + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.5 + */ +public Color getMarginColor() { + checkWidget(); + return marginColor != null ? marginColor : getBackground(); +} +/** + * Returns a string that uses only the line delimiter specified by the + * StyledTextContent implementation. + * <p> + * Returns only the first line if the widget has the SWT.SINGLE style. + * </p> + * + * @param text the text that may have line delimiters that don't + * match the model line delimiter. Possible line delimiters + * are CR ('\r'), LF ('\n'), CR/LF ("\r\n") + * @return the converted text that only uses the line delimiter + * specified by the model. Returns only the first line if the widget + * has the SWT.SINGLE style. + */ +String getModelDelimitedText(String text) { + int length = text.length(); + if (length == 0) { + return text; + } + int crIndex = 0; + int lfIndex = 0; + int i = 0; + StringBuffer convertedText = new StringBuffer(length); + String delimiter = getLineDelimiter(); + while (i < length) { + if (crIndex != -1) { + crIndex = text.indexOf(SWT.CR, i); + } + if (lfIndex != -1) { + lfIndex = text.indexOf(SWT.LF, i); + } + if (lfIndex == -1 && crIndex == -1) { // no more line breaks? + break; + } else if ((crIndex < lfIndex && crIndex != -1) || lfIndex == -1) { + convertedText.append(text.substring(i, crIndex)); + if (lfIndex == crIndex + 1) { // CR/LF combination? + i = lfIndex + 1; + } else { + i = crIndex + 1; + } + } else { // LF occurs before CR! + convertedText.append(text.substring(i, lfIndex)); + i = lfIndex + 1; + } + if (isSingleLine()) { + break; + } + convertedText.append(delimiter); + } + // copy remaining text if any and if not in single line mode or no + // text copied thus far (because there only is one line) + if (i < length && (!isSingleLine() || convertedText.length() == 0)) { + convertedText.append(text.substring(i)); + } + return convertedText.toString(); +} +boolean checkDragDetect(Event event) { + if (!isListening(SWT.DragDetect)) return false; + if (IS_MOTIF) { + if (event.button != 2) return false; + } else { + if (event.button != 1) return false; + } + if (blockSelection && blockXLocation != -1) { + Rectangle rect = getBlockSelectionRectangle(); + if (rect.contains(event.x, event.y)) { + return dragDetect(event); + } + } else { + if (selection.x == selection.y) return false; + int offset = getOffsetAtPoint(event.x, event.y, null, true); + if (selection.x <= offset && offset < selection.y) { + return dragDetect(event); + } + } + return false; +} +/** + * Creates default key bindings. + */ +void createKeyBindings() { + int nextKey = isMirrored() ? SWT.ARROW_LEFT : SWT.ARROW_RIGHT; + int previousKey = isMirrored() ? SWT.ARROW_RIGHT : SWT.ARROW_LEFT; + + // Navigation + setKeyBinding(SWT.ARROW_UP, ST.LINE_UP); + setKeyBinding(SWT.ARROW_DOWN, ST.LINE_DOWN); + if (IS_MAC) { + setKeyBinding(previousKey | SWT.MOD1, ST.LINE_START); + setKeyBinding(nextKey | SWT.MOD1, ST.LINE_END); + setKeyBinding(SWT.HOME, ST.TEXT_START); + setKeyBinding(SWT.END, ST.TEXT_END); + setKeyBinding(SWT.ARROW_UP | SWT.MOD1, ST.TEXT_START); + setKeyBinding(SWT.ARROW_DOWN | SWT.MOD1, ST.TEXT_END); + setKeyBinding(nextKey | SWT.MOD3, ST.WORD_NEXT); + setKeyBinding(previousKey | SWT.MOD3, ST.WORD_PREVIOUS); + } else { + setKeyBinding(SWT.HOME, ST.LINE_START); + setKeyBinding(SWT.END, ST.LINE_END); + setKeyBinding(SWT.HOME | SWT.MOD1, ST.TEXT_START); + setKeyBinding(SWT.END | SWT.MOD1, ST.TEXT_END); + setKeyBinding(nextKey | SWT.MOD1, ST.WORD_NEXT); + setKeyBinding(previousKey | SWT.MOD1, ST.WORD_PREVIOUS); + } + setKeyBinding(SWT.PAGE_UP, ST.PAGE_UP); + setKeyBinding(SWT.PAGE_DOWN, ST.PAGE_DOWN); + setKeyBinding(SWT.PAGE_UP | SWT.MOD1, ST.WINDOW_START); + setKeyBinding(SWT.PAGE_DOWN | SWT.MOD1, ST.WINDOW_END); + setKeyBinding(nextKey, ST.COLUMN_NEXT); + setKeyBinding(previousKey, ST.COLUMN_PREVIOUS); + + // Selection + setKeyBinding(SWT.ARROW_UP | SWT.MOD2, ST.SELECT_LINE_UP); + setKeyBinding(SWT.ARROW_DOWN | SWT.MOD2, ST.SELECT_LINE_DOWN); + if (IS_MAC) { + setKeyBinding(previousKey | SWT.MOD1 | SWT.MOD2, ST.SELECT_LINE_START); + setKeyBinding(nextKey | SWT.MOD1 | SWT.MOD2, ST.SELECT_LINE_END); + setKeyBinding(SWT.HOME | SWT.MOD2, ST.SELECT_TEXT_START); + setKeyBinding(SWT.END | SWT.MOD2, ST.SELECT_TEXT_END); + setKeyBinding(SWT.ARROW_UP | SWT.MOD1 | SWT.MOD2, ST.SELECT_TEXT_START); + setKeyBinding(SWT.ARROW_DOWN | SWT.MOD1 | SWT.MOD2, ST.SELECT_TEXT_END); + setKeyBinding(nextKey | SWT.MOD2 | SWT.MOD3, ST.SELECT_WORD_NEXT); + setKeyBinding(previousKey | SWT.MOD2 | SWT.MOD3, ST.SELECT_WORD_PREVIOUS); + } else { + setKeyBinding(SWT.HOME | SWT.MOD2, ST.SELECT_LINE_START); + setKeyBinding(SWT.END | SWT.MOD2, ST.SELECT_LINE_END); + setKeyBinding(SWT.HOME | SWT.MOD1 | SWT.MOD2, ST.SELECT_TEXT_START); + setKeyBinding(SWT.END | SWT.MOD1 | SWT.MOD2, ST.SELECT_TEXT_END); + setKeyBinding(nextKey | SWT.MOD1 | SWT.MOD2, ST.SELECT_WORD_NEXT); + setKeyBinding(previousKey | SWT.MOD1 | SWT.MOD2, ST.SELECT_WORD_PREVIOUS); + } + setKeyBinding(SWT.PAGE_UP | SWT.MOD2, ST.SELECT_PAGE_UP); + setKeyBinding(SWT.PAGE_DOWN | SWT.MOD2, ST.SELECT_PAGE_DOWN); + setKeyBinding(SWT.PAGE_UP | SWT.MOD1 | SWT.MOD2, ST.SELECT_WINDOW_START); + setKeyBinding(SWT.PAGE_DOWN | SWT.MOD1 | SWT.MOD2, ST.SELECT_WINDOW_END); + setKeyBinding(nextKey | SWT.MOD2, ST.SELECT_COLUMN_NEXT); + setKeyBinding(previousKey | SWT.MOD2, ST.SELECT_COLUMN_PREVIOUS); + + // Modification + // Cut, Copy, Paste + setKeyBinding('X' | SWT.MOD1, ST.CUT); + setKeyBinding('C' | SWT.MOD1, ST.COPY); + setKeyBinding('V' | SWT.MOD1, ST.PASTE); + if (IS_MAC) { + setKeyBinding(SWT.DEL | SWT.MOD2, ST.DELETE_NEXT); + setKeyBinding(SWT.BS | SWT.MOD3, ST.DELETE_WORD_PREVIOUS); + setKeyBinding(SWT.DEL | SWT.MOD3, ST.DELETE_WORD_NEXT); + } else { + // Cut, Copy, Paste Wordstar style + setKeyBinding(SWT.DEL | SWT.MOD2, ST.CUT); + setKeyBinding(SWT.INSERT | SWT.MOD1, ST.COPY); + setKeyBinding(SWT.INSERT | SWT.MOD2, ST.PASTE); + } + setKeyBinding(SWT.BS | SWT.MOD2, ST.DELETE_PREVIOUS); + setKeyBinding(SWT.BS, ST.DELETE_PREVIOUS); + setKeyBinding(SWT.DEL, ST.DELETE_NEXT); + setKeyBinding(SWT.BS | SWT.MOD1, ST.DELETE_WORD_PREVIOUS); + setKeyBinding(SWT.DEL | SWT.MOD1, ST.DELETE_WORD_NEXT); + + // Miscellaneous + setKeyBinding(SWT.INSERT, ST.TOGGLE_OVERWRITE); +} +/** + * Create the bitmaps to use for the caret in bidi mode. This + * method only needs to be called upon widget creation and when the + * font changes (the caret bitmap height needs to match font height). + */ +void createCaretBitmaps() { + int caretWidth = BIDI_CARET_WIDTH; + Display display = getDisplay(); + if (leftCaretBitmap != null) { + if (defaultCaret != null && leftCaretBitmap.equals(defaultCaret.getImage())) { + defaultCaret.setImage(null); + } + leftCaretBitmap.dispose(); + } + int lineHeight = renderer.getLineHeight(); + leftCaretBitmap = new Image(display, caretWidth, lineHeight); + GC gc = new GC (leftCaretBitmap); + gc.setBackground(display.getSystemColor(SWT.COLOR_BLACK)); + gc.fillRectangle(0, 0, caretWidth, lineHeight); + gc.setForeground(display.getSystemColor(SWT.COLOR_WHITE)); + gc.drawLine(0,0,0,lineHeight); + gc.drawLine(0,0,caretWidth-1,0); + gc.drawLine(0,1,1,1); + gc.dispose(); + + if (rightCaretBitmap != null) { + if (defaultCaret != null && rightCaretBitmap.equals(defaultCaret.getImage())) { + defaultCaret.setImage(null); + } + rightCaretBitmap.dispose(); + } + rightCaretBitmap = new Image(display, caretWidth, lineHeight); + gc = new GC (rightCaretBitmap); + gc.setBackground(display.getSystemColor(SWT.COLOR_BLACK)); + gc.fillRectangle(0, 0, caretWidth, lineHeight); + gc.setForeground(display.getSystemColor(SWT.COLOR_WHITE)); + gc.drawLine(caretWidth-1,0,caretWidth-1,lineHeight); + gc.drawLine(0,0,caretWidth-1,0); + gc.drawLine(caretWidth-1,1,1,1); + gc.dispose(); +} +/** + * Moves the selected text to the clipboard. The text will be put in the + * clipboard in plain text format and RTF format. + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void cut() { + checkWidget(); + // Abort cut operation if copy to clipboard fails. + // Fixes bug 21030. + if (copySelection(DND.CLIPBOARD)) { + if (blockSelection && blockXLocation != -1) { + insertBlockSelectionText((char)0, SWT.NULL); + } else { + doDelete(); + } + } +} +/** + * A mouse move event has occurred. See if we should start autoscrolling. If + * the move position is outside of the client area, initiate autoscrolling. + * Otherwise, we've moved back into the widget so end autoscrolling. + */ +void doAutoScroll(Event event) { + int caretLine = getCaretLine(); + if (event.y > clientAreaHeight - bottomMargin && caretLine != content.getLineCount() - 1) { + doAutoScroll(SWT.DOWN, event.y - (clientAreaHeight - bottomMargin)); + } else if (event.y < topMargin && caretLine != 0) { + doAutoScroll(SWT.UP, topMargin - event.y); + } else if (event.x < leftMargin && !wordWrap) { + doAutoScroll(ST.COLUMN_PREVIOUS, leftMargin - event.x); + } else if (event.x > clientAreaWidth - rightMargin && !wordWrap) { + doAutoScroll(ST.COLUMN_NEXT, event.x - (clientAreaWidth - rightMargin)); + } else { + endAutoScroll(); + } +} +/** + * Initiates autoscrolling. + * + * @param direction SWT.UP, SWT.DOWN, SWT.COLUMN_NEXT, SWT.COLUMN_PREVIOUS + */ +void doAutoScroll(int direction, int distance) { + autoScrollDistance = distance; + // If we're already autoscrolling in the given direction do nothing + if (autoScrollDirection == direction) { + return; + } + + Runnable timer = null; + final Display display = getDisplay(); + // Set a timer that will simulate the user pressing and holding + // down a cursor key (i.e., arrowUp, arrowDown). + if (direction == SWT.UP) { + timer = new Runnable() { + public void run() { + if (autoScrollDirection == SWT.UP) { + if (blockSelection) { + int verticalScrollOffset = getVerticalScrollOffset(); + int y = blockYLocation - verticalScrollOffset; + int pixels = Math.max(-autoScrollDistance, -verticalScrollOffset); + if (pixels != 0) { + setBlockSelectionLocation(blockXLocation - horizontalScrollOffset, y + pixels, true); + scrollVertical(pixels, true); + } + } else { + doSelectionPageUp(autoScrollDistance); + } + display.timerExec(V_SCROLL_RATE, this); + } + } + }; + autoScrollDirection = direction; + display.timerExec(V_SCROLL_RATE, timer); + } else if (direction == SWT.DOWN) { + timer = new Runnable() { + public void run() { + if (autoScrollDirection == SWT.DOWN) { + if (blockSelection) { + int verticalScrollOffset = getVerticalScrollOffset(); + int y = blockYLocation - verticalScrollOffset; + int max = renderer.getHeight() - verticalScrollOffset - clientAreaHeight; + int pixels = Math.min(autoScrollDistance, Math.max(0,max)); + if (pixels != 0) { + setBlockSelectionLocation(blockXLocation - horizontalScrollOffset, y + pixels, true); + scrollVertical(pixels, true); + } + } else { + doSelectionPageDown(autoScrollDistance); + } + display.timerExec(V_SCROLL_RATE, this); + } + } + }; + autoScrollDirection = direction; + display.timerExec(V_SCROLL_RATE, timer); + } else if (direction == ST.COLUMN_NEXT) { + timer = new Runnable() { + public void run() { + if (autoScrollDirection == ST.COLUMN_NEXT) { + if (blockSelection) { + int x = blockXLocation - horizontalScrollOffset; + int max = renderer.getWidth() - horizontalScrollOffset - clientAreaWidth; + int pixels = Math.min(autoScrollDistance, Math.max(0,max)); + if (pixels != 0) { + setBlockSelectionLocation(x + pixels, blockYLocation - getVerticalScrollOffset(), true); + scrollHorizontal(pixels, true); + } + } else { + doVisualNext(); + setMouseWordSelectionAnchor(); + doMouseSelection(); + } + display.timerExec(H_SCROLL_RATE, this); + } + } + }; + autoScrollDirection = direction; + display.timerExec(H_SCROLL_RATE, timer); + } else if (direction == ST.COLUMN_PREVIOUS) { + timer = new Runnable() { + public void run() { + if (autoScrollDirection == ST.COLUMN_PREVIOUS) { + if (blockSelection) { + int x = blockXLocation - horizontalScrollOffset; + int pixels = Math.max(-autoScrollDistance, -horizontalScrollOffset); + if (pixels != 0) { + setBlockSelectionLocation(x + pixels, blockYLocation - getVerticalScrollOffset(), true); + scrollHorizontal(pixels, true); + } + } else { + doVisualPrevious(); + setMouseWordSelectionAnchor(); + doMouseSelection(); + } + display.timerExec(H_SCROLL_RATE, this); + } + } + }; + autoScrollDirection = direction; + display.timerExec(H_SCROLL_RATE, timer); + } +} +/** + * Deletes the previous character. Delete the selected text if any. + * Move the caret in front of the deleted text. + */ +void doBackspace() { + Event event = new Event(); + event.text = ""; + if (selection.x != selection.y) { + event.start = selection.x; + event.end = selection.y; + sendKeyEvent(event); + } else if (caretOffset > 0) { + int lineIndex = content.getLineAtOffset(caretOffset); + int lineOffset = content.getOffsetAtLine(lineIndex); + if (caretOffset == lineOffset) { + lineOffset = content.getOffsetAtLine(lineIndex - 1); + event.start = lineOffset + content.getLine(lineIndex - 1).length(); + event.end = caretOffset; + } else { + TextLayout layout = renderer.getTextLayout(lineIndex); + int start = layout.getPreviousOffset(caretOffset - lineOffset, SWT.MOVEMENT_CHAR); + renderer.disposeTextLayout(layout); + event.start = start + lineOffset; + event.end = caretOffset; + } + sendKeyEvent(event); + } +} +void doBlockColumn(boolean next) { + if (blockXLocation == -1) setBlockSelectionOffset(caretOffset, false); + int x = blockXLocation - horizontalScrollOffset; + int y = blockYLocation - getVerticalScrollOffset(); + int[] trailing = new int[1]; + int offset = getOffsetAtPoint(x, y, trailing, true); + if (offset != -1) { + offset += trailing[0]; + int lineIndex = content.getLineAtOffset(offset); + int newOffset; + if (next) { + newOffset = getClusterNext(offset, lineIndex); + } else { + newOffset = getClusterPrevious(offset, lineIndex); + } + offset = newOffset != offset ? newOffset : -1; + } + if (offset != -1) { + setBlockSelectionOffset(offset, true); + showCaret(); + } else { + int width = next ? renderer.averageCharWidth : -renderer.averageCharWidth; + int maxWidth = Math.max(clientAreaWidth - rightMargin - leftMargin, renderer.getWidth()); + x = Math.max(0, Math.min(blockXLocation + width, maxWidth)) - horizontalScrollOffset; + setBlockSelectionLocation(x, y, true); + Rectangle rect = new Rectangle(x, y, 0, 0); + showLocation(rect, true); + } +} +void doBlockWord(boolean next) { + if (blockXLocation == -1) setBlockSelectionOffset(caretOffset, false); + int x = blockXLocation - horizontalScrollOffset; + int y = blockYLocation - getVerticalScrollOffset(); + int[] trailing = new int[1]; + int offset = getOffsetAtPoint(x, y, trailing, true); + if (offset != -1) { + offset += trailing[0]; + int lineIndex = content.getLineAtOffset(offset); + int lineOffset = content.getOffsetAtLine(lineIndex); + String lineText = content.getLine(lineIndex); + int lineLength = lineText.length(); + int newOffset = offset; + if (next) { + if (offset < lineOffset + lineLength) { + newOffset = getWordNext(offset, SWT.MOVEMENT_WORD); + } + } else { + if (offset > lineOffset) { + newOffset = getWordPrevious(offset, SWT.MOVEMENT_WORD); + } + } + offset = newOffset != offset ? newOffset : -1; + } + if (offset != -1) { + setBlockSelectionOffset(offset, true); + showCaret(); + } else { + int width = (next ? renderer.averageCharWidth : -renderer.averageCharWidth) * 6; + int maxWidth = Math.max(clientAreaWidth - rightMargin - leftMargin, renderer.getWidth()); + x = Math.max(0, Math.min(blockXLocation + width, maxWidth)) - horizontalScrollOffset; + setBlockSelectionLocation(x, y, true); + Rectangle rect = new Rectangle(x, y, 0, 0); + showLocation(rect, true); + } +} +void doBlockLineVertical(boolean up) { + if (blockXLocation == -1) setBlockSelectionOffset(caretOffset, false); + int y = blockYLocation - getVerticalScrollOffset(); + int lineIndex = getLineIndex(y); + if (up) { + if (lineIndex > 0) { + y = getLinePixel(lineIndex - 1); + setBlockSelectionLocation(blockXLocation - horizontalScrollOffset, y, true); + if (y < topMargin) { + scrollVertical(y - topMargin, true); + } + } + } else { + int lineCount = content.getLineCount(); + if (lineIndex + 1 < lineCount) { + y = getLinePixel(lineIndex + 2) - 1; + setBlockSelectionLocation(blockXLocation - horizontalScrollOffset, y, true); + int bottom = clientAreaHeight - bottomMargin; + if (y > bottom) { + scrollVertical(y - bottom, true); + } + } + } +} +void doBlockLineHorizontal(boolean end) { + if (blockXLocation == -1) setBlockSelectionOffset(caretOffset, false); + int x = blockXLocation - horizontalScrollOffset; + int y = blockYLocation - getVerticalScrollOffset(); + int lineIndex = getLineIndex(y); + int lineOffset = content.getOffsetAtLine(lineIndex); + String lineText = content.getLine(lineIndex); + int lineLength = lineText.length(); + int[] trailing = new int[1]; + int offset = getOffsetAtPoint(x, y, trailing, true); + if (offset != -1) { + offset += trailing[0]; + int newOffset = offset; + if (end) { + if (offset < lineOffset + lineLength) { + newOffset = lineOffset + lineLength; + } + } else { + if (offset > lineOffset) { + newOffset = lineOffset; + } + } + offset = newOffset != offset ? newOffset : -1; + } else { + if (!end) offset = lineOffset + lineLength; + } + if (offset != -1) { + setBlockSelectionOffset(offset, true); + showCaret(); + } else { + int maxWidth = Math.max(clientAreaWidth - rightMargin - leftMargin, renderer.getWidth()); + x = (end ? maxWidth : 0) - horizontalScrollOffset; + setBlockSelectionLocation(x, y, true); + Rectangle rect = new Rectangle(x, y, 0, 0); + showLocation(rect, true); + } +} +void doBlockSelection(boolean sendEvent) { + if (caretOffset > selectionAnchor) { + selection.x = selectionAnchor; + selection.y = caretOffset; + } else { + selection.x = caretOffset; + selection.y = selectionAnchor; + } + updateCaretVisibility(); + setCaretLocation(); + super.redraw(); + if (sendEvent) { + sendSelectionEvent(); + } +} +/** + * Replaces the selection with the character or insert the character at the + * current caret position if no selection exists. + * <p> + * If a carriage return was typed replace it with the line break character + * used by the widget on this platform. + * </p> + * + * @param key the character typed by the user + */ +void doContent(char key) { + if (blockSelection && blockXLocation != -1) { + insertBlockSelectionText(key, SWT.NULL); + return; + } + + Event event = new Event(); + event.start = selection.x; + event.end = selection.y; + // replace a CR line break with the widget line break + // CR does not make sense on Windows since most (all?) applications + // don't recognize CR as a line break. + if (key == SWT.CR || key == SWT.LF) { + if (!isSingleLine()) { + event.text = getLineDelimiter(); + } + } else if (selection.x == selection.y && overwrite && key != TAB) { + // no selection and overwrite mode is on and the typed key is not a + // tab character (tabs are always inserted without overwriting)? + int lineIndex = content.getLineAtOffset(event.end); + int lineOffset = content.getOffsetAtLine(lineIndex); + String line = content.getLine(lineIndex); + // replace character at caret offset if the caret is not at the + // end of the line + if (event.end < lineOffset + line.length()) { + event.end++; + } + event.text = new String(new char[] {key}); + } else { + event.text = new String(new char[] {key}); + } + if (event.text != null) { + if (textLimit > 0 && content.getCharCount() - (event.end - event.start) >= textLimit) { + return; + } + sendKeyEvent(event); + } +} +/** + * Moves the caret after the last character of the widget content. + */ +void doContentEnd() { + // place caret at end of first line if receiver is in single + // line mode. fixes 4820. + if (isSingleLine()) { + doLineEnd(); + } else { + int length = content.getCharCount(); + if (caretOffset < length) { + setCaretOffset(length, SWT.DEFAULT); + showCaret(); + } + } +} +/** + * Moves the caret in front of the first character of the widget content. + */ +void doContentStart() { + if (caretOffset > 0) { + setCaretOffset(0, SWT.DEFAULT); + showCaret(); + } +} +/** + * Moves the caret to the start of the selection if a selection exists. + * Otherwise, if no selection exists move the cursor according to the + * cursor selection rules. + * + * @see #doSelectionCursorPrevious + */ +void doCursorPrevious() { + if (selection.y - selection.x > 0) { + setCaretOffset(selection.x, OFFSET_LEADING); + showCaret(); + } else { + doSelectionCursorPrevious(); + } +} +/** + * Moves the caret to the end of the selection if a selection exists. + * Otherwise, if no selection exists move the cursor according to the + * cursor selection rules. + * + * @see #doSelectionCursorNext + */ +void doCursorNext() { + if (selection.y - selection.x > 0) { + setCaretOffset(selection.y, PREVIOUS_OFFSET_TRAILING); + showCaret(); + } else { + doSelectionCursorNext(); + } +} +/** + * Deletes the next character. Delete the selected text if any. + */ +void doDelete() { + Event event = new Event(); + event.text = ""; + if (selection.x != selection.y) { + event.start = selection.x; + event.end = selection.y; + sendKeyEvent(event); + } else if (caretOffset < content.getCharCount()) { + int line = content.getLineAtOffset(caretOffset); + int lineOffset = content.getOffsetAtLine(line); + int lineLength = content.getLine(line).length(); + if (caretOffset == lineOffset + lineLength) { + event.start = caretOffset; + event.end = content.getOffsetAtLine(line + 1); + } else { + event.start = caretOffset; + event.end = getClusterNext(caretOffset, line); + } + sendKeyEvent(event); + } +} +/** + * Deletes the next word. + */ +void doDeleteWordNext() { + if (selection.x != selection.y) { + // if a selection exists, treat the as if + // only the delete key was pressed + doDelete(); + } else { + Event event = new Event(); + event.text = ""; + event.start = caretOffset; + event.end = getWordNext(caretOffset, SWT.MOVEMENT_WORD); + sendKeyEvent(event); + } +} +/** + * Deletes the previous word. + */ +void doDeleteWordPrevious() { + if (selection.x != selection.y) { + // if a selection exists, treat as if + // only the backspace key was pressed + doBackspace(); + } else { + Event event = new Event(); + event.text = ""; + event.start = getWordPrevious(caretOffset, SWT.MOVEMENT_WORD); + event.end = caretOffset; + sendKeyEvent(event); + } +} +/** + * Moves the caret one line down and to the same character offset relative + * to the beginning of the line. Move the caret to the end of the new line + * if the new line is shorter than the character offset. + */ +void doLineDown(boolean select) { + int caretLine = getCaretLine(); + int lineCount = content.getLineCount(); + int y = 0; + boolean lastLine = false; + if (wordWrap) { + int lineOffset = content.getOffsetAtLine(caretLine); + int offsetInLine = caretOffset - lineOffset; + TextLayout layout = renderer.getTextLayout(caretLine); + int lineIndex = getVisualLineIndex(layout, offsetInLine); + int layoutLineCount = layout.getLineCount(); + if (lineIndex == layoutLineCount - 1) { + lastLine = caretLine == lineCount - 1; + caretLine++; + } else { + y = layout.getLineBounds(lineIndex + 1).y; + } + renderer.disposeTextLayout(layout); + } else { + lastLine = caretLine == lineCount - 1; + caretLine++; + } + if (lastLine) { + if (select) { + setCaretOffset(content.getCharCount(), SWT.DEFAULT); + } + } else { + int[] alignment = new int[1]; + int offset = getOffsetAtPoint(columnX, y, caretLine, alignment); + setCaretOffset(offset, alignment[0]); + } + int oldColumnX = columnX; + int oldHScrollOffset = horizontalScrollOffset; + if (select) { + setMouseWordSelectionAnchor(); + // select first and then scroll to reduce flash when key + // repeat scrolls lots of lines + doSelection(ST.COLUMN_NEXT); + } + showCaret(); + int hScrollChange = oldHScrollOffset - horizontalScrollOffset; + columnX = oldColumnX + hScrollChange; +} +/** + * Moves the caret to the end of the line. + */ +void doLineEnd() { + int caretLine = getCaretLine(); + int lineOffset = content.getOffsetAtLine(caretLine); + int lineEndOffset; + if (wordWrap) { + TextLayout layout = renderer.getTextLayout(caretLine); + int offsetInLine = caretOffset - lineOffset; + int lineIndex = getVisualLineIndex(layout, offsetInLine); + int[] offsets = layout.getLineOffsets(); + lineEndOffset = lineOffset + offsets[lineIndex + 1]; + renderer.disposeTextLayout(layout); + } else { + int lineLength = content.getLine(caretLine).length(); + lineEndOffset = lineOffset + lineLength; + } + if (caretOffset < lineEndOffset) { + setCaretOffset(lineEndOffset, PREVIOUS_OFFSET_TRAILING); + showCaret(); + } +} +/** + * Moves the caret to the beginning of the line. + */ +void doLineStart() { + int caretLine = getCaretLine(); + int lineOffset = content.getOffsetAtLine(caretLine); + if (wordWrap) { + TextLayout layout = renderer.getTextLayout(caretLine); + int offsetInLine = caretOffset - lineOffset; + int lineIndex = getVisualLineIndex(layout, offsetInLine); + int[] offsets = layout.getLineOffsets(); + lineOffset += offsets[lineIndex]; + renderer.disposeTextLayout(layout); + } + if (caretOffset > lineOffset) { + setCaretOffset(lineOffset, OFFSET_LEADING); + showCaret(); + } +} +/** + * Moves the caret one line up and to the same character offset relative + * to the beginning of the line. Move the caret to the end of the new line + * if the new line is shorter than the character offset. + */ +void doLineUp(boolean select) { + int caretLine = getCaretLine(), y = 0; + boolean firstLine = false; + if (wordWrap) { + int lineOffset = content.getOffsetAtLine(caretLine); + int offsetInLine = caretOffset - lineOffset; + TextLayout layout = renderer.getTextLayout(caretLine); + int lineIndex = getVisualLineIndex(layout, offsetInLine); + if (lineIndex == 0) { + firstLine = caretLine == 0; + if (!firstLine) { + caretLine--; + y = renderer.getLineHeight(caretLine) - 1; + } + } else { + y = layout.getLineBounds(lineIndex - 1).y; + } + renderer.disposeTextLayout(layout); + } else { + firstLine = caretLine == 0; + caretLine--; + } + if (firstLine) { + if (select) { + setCaretOffset(0, SWT.DEFAULT); + } + } else { + int[] alignment = new int[1]; + int offset = getOffsetAtPoint(columnX, y, caretLine, alignment); + setCaretOffset(offset, alignment[0]); + } + int oldColumnX = columnX; + int oldHScrollOffset = horizontalScrollOffset; + if (select) setMouseWordSelectionAnchor(); + showCaret(); + if (select) doSelection(ST.COLUMN_PREVIOUS); + int hScrollChange = oldHScrollOffset - horizontalScrollOffset; + columnX = oldColumnX + hScrollChange; +} +void doMouseLinkCursor() { + Display display = getDisplay(); + Point point = display.getCursorLocation(); + point = display.map(null, this, point); + doMouseLinkCursor(point.x, point.y); +} +void doMouseLinkCursor(int x, int y) { + int offset = getOffsetAtPoint(x, y, null, true); + Display display = getDisplay(); + Cursor newCursor = cursor; + if (renderer.hasLink(offset)) { + newCursor = display.getSystemCursor(SWT.CURSOR_HAND); + } else { + if (cursor == null) { + int type = blockSelection ? SWT.CURSOR_CROSS : SWT.CURSOR_IBEAM; + newCursor = display.getSystemCursor(type); + } + } + if (newCursor != getCursor()) super.setCursor(newCursor); +} +/** + * Moves the caret to the specified location. + * + * @param x x location of the new caret position + * @param y y location of the new caret position + * @param select the location change is a selection operation. + * include the line delimiter in the selection + */ +void doMouseLocationChange(int x, int y, boolean select) { + int line = getLineIndex(y); + + updateCaretDirection = true; + + if (blockSelection) { + x = Math.max(leftMargin, Math.min(x, clientAreaWidth - rightMargin)); + y = Math.max(topMargin, Math.min(y, clientAreaHeight - bottomMargin)); + if (doubleClickEnabled && clickCount > 1) { + boolean wordSelect = (clickCount & 1) == 0; + if (wordSelect) { + Point left = getPointAtOffset(doubleClickSelection.x); + int[] trailing = new int[1]; + int offset = getOffsetAtPoint(x, y, trailing, true); + if (offset != -1) { + if (x > left.x) { + offset = getWordNext(offset + trailing[0], SWT.MOVEMENT_WORD_END); + setBlockSelectionOffset(doubleClickSelection.x, offset, true); + } else { + offset = getWordPrevious(offset + trailing[0], SWT.MOVEMENT_WORD_START); + setBlockSelectionOffset(doubleClickSelection.y, offset, true); + } + } else { + if (x > left.x) { + setBlockSelectionLocation(left.x, left.y, x, y, true); + } else { + Point right = getPointAtOffset(doubleClickSelection.y); + setBlockSelectionLocation(right.x, right.y, x, y, true); + } + } + } else { + setBlockSelectionLocation(blockXLocation, y, true); + } + return; + } else { + if (select) { + if (blockXLocation == -1) { + setBlockSelectionOffset(caretOffset, false); + } + } else { + clearBlockSelection(true, false); + } + int[] trailing = new int[1]; + int offset = getOffsetAtPoint(x, y, trailing, true); + if (offset != -1) { + if (select) { + setBlockSelectionOffset(offset + trailing[0], true); + return; + } + } else { + if (isFixedLineHeight() && renderer.fixedPitch) { + int avg = renderer.averageCharWidth; + x = ((x + avg / 2 - leftMargin + horizontalScrollOffset) / avg * avg) + leftMargin - horizontalScrollOffset; + } + setBlockSelectionLocation(x, y, true); + return; + } + } + } + + // allow caret to be placed below first line only if receiver is + // not in single line mode. fixes 4820. + if (line < 0 || (isSingleLine() && line > 0)) { + return; + } + int[] alignment = new int[1]; + int newCaretOffset = getOffsetAtPoint(x, y, alignment); + int newCaretAlignemnt = alignment[0]; + + if (doubleClickEnabled && clickCount > 1) { + newCaretOffset = doMouseWordSelect(x, newCaretOffset, line); + } + + int newCaretLine = content.getLineAtOffset(newCaretOffset); + + // Is the mouse within the left client area border or on + // a different line? If not the autoscroll selection + // could be incorrectly reset. Fixes 1GKM3XS + boolean vchange = 0 <= y && y < clientAreaHeight || newCaretLine == 0 || newCaretLine == content.getLineCount() - 1; + boolean hchange = 0 <= x && x < clientAreaWidth || wordWrap || newCaretLine != content.getLineAtOffset(caretOffset); + if (vchange && hchange && (newCaretOffset != caretOffset || newCaretAlignemnt != caretAlignment)) { + setCaretOffset(newCaretOffset, newCaretAlignemnt); + if (select) doMouseSelection(); + showCaret(); + } + if (!select) { + setCaretOffset(newCaretOffset, newCaretAlignemnt); + clearSelection(true); + } +} +/** + * Updates the selection based on the caret position + */ +void doMouseSelection() { + if (caretOffset <= selection.x || + (caretOffset > selection.x && + caretOffset < selection.y && selectionAnchor == selection.x)) { + doSelection(ST.COLUMN_PREVIOUS); + } else { + doSelection(ST.COLUMN_NEXT); + } +} +/** + * Returns the offset of the word at the specified offset. + * If the current selection extends from high index to low index + * (i.e., right to left, or caret is at left border of selection on + * non-bidi platforms) the start offset of the word preceding the + * selection is returned. If the current selection extends from + * low index to high index the end offset of the word following + * the selection is returned. + * + * @param x mouse x location + * @param newCaretOffset caret offset of the mouse cursor location + * @param line line index of the mouse cursor location + */ +int doMouseWordSelect(int x, int newCaretOffset, int line) { + // flip selection anchor based on word selection direction from + // base double click. Always do this here (and don't rely on doAutoScroll) + // because auto scroll only does not cover all possible mouse selections + // (e.g., mouse x < 0 && mouse y > caret line y) + if (newCaretOffset < selectionAnchor && selectionAnchor == selection.x) { + selectionAnchor = doubleClickSelection.y; + } else if (newCaretOffset > selectionAnchor && selectionAnchor == selection.y) { + selectionAnchor = doubleClickSelection.x; + } + if (0 <= x && x < clientAreaWidth) { + boolean wordSelect = (clickCount & 1) == 0; + if (caretOffset == selection.x) { + if (wordSelect) { + newCaretOffset = getWordPrevious(newCaretOffset, SWT.MOVEMENT_WORD_START); + } else { + newCaretOffset = content.getOffsetAtLine(line); + } + } else { + if (wordSelect) { + newCaretOffset = getWordNext(newCaretOffset, SWT.MOVEMENT_WORD_END); + } else { + int lineEnd = content.getCharCount(); + if (line + 1 < content.getLineCount()) { + lineEnd = content.getOffsetAtLine(line + 1); + } + newCaretOffset = lineEnd; + } + } + } + return newCaretOffset; +} +/** + * Scrolls one page down so that the last line (truncated or whole) + * of the current page becomes the fully visible top line. + * <p> + * The caret is scrolled the same number of lines so that its location + * relative to the top line remains the same. The exception is the end + * of the text where a full page scroll is not possible. In this case + * the caret is moved after the last character. + * </p> + * + * @param select whether or not to select the page + */ +void doPageDown(boolean select, int height) { + if (isSingleLine()) return; + int oldColumnX = columnX; + int oldHScrollOffset = horizontalScrollOffset; + if (isFixedLineHeight()) { + int lineCount = content.getLineCount(); + int caretLine = getCaretLine(); + if (caretLine < lineCount - 1) { + int lineHeight = renderer.getLineHeight(); + int lines = (height == -1 ? clientAreaHeight : height) / lineHeight; + int scrollLines = Math.min(lineCount - caretLine - 1, lines); + // ensure that scrollLines never gets negative and at least one + // line is scrolled. fixes bug 5602. + scrollLines = Math.max(1, scrollLines); + int[] alignment = new int[1]; + int offset = getOffsetAtPoint(columnX, getLinePixel(caretLine + scrollLines), alignment); + setCaretOffset(offset, alignment[0]); + if (select) { + doSelection(ST.COLUMN_NEXT); + } + // scroll one page down or to the bottom + int verticalMaximum = lineCount * getVerticalIncrement(); + int pageSize = clientAreaHeight; + int verticalScrollOffset = getVerticalScrollOffset(); + int scrollOffset = verticalScrollOffset + scrollLines * getVerticalIncrement(); + if (scrollOffset + pageSize > verticalMaximum) { + scrollOffset = verticalMaximum - pageSize; + } + if (scrollOffset > verticalScrollOffset) { + scrollVertical(scrollOffset - verticalScrollOffset, true); + } + } + } else { + int lineCount = content.getLineCount(); + int caretLine = getCaretLine(); + int lineIndex, lineHeight; + if (height == -1) { + lineIndex = getPartialBottomIndex(); + int topY = getLinePixel(lineIndex); + lineHeight = renderer.getLineHeight(lineIndex); + height = topY; + if (topY + lineHeight <= clientAreaHeight) { + height += lineHeight; + } else { + if (wordWrap) { + TextLayout layout = renderer.getTextLayout(lineIndex); + int y = clientAreaHeight - topY; + for (int i = 0; i < layout.getLineCount(); i++) { + Rectangle bounds = layout.getLineBounds(i); + if (bounds.contains(bounds.x, y)) { + height += bounds.y; + break; + } + } + renderer.disposeTextLayout(layout); + } + } + } else { + lineIndex = getLineIndex(height); + int topLineY = getLinePixel(lineIndex); + if (wordWrap) { + TextLayout layout = renderer.getTextLayout(lineIndex); + int y = height - topLineY; + for (int i = 0; i < layout.getLineCount(); i++) { + Rectangle bounds = layout.getLineBounds(i); + if (bounds.contains(bounds.x, y)) { + height = topLineY + bounds.y + bounds.height; + break; + } + } + renderer.disposeTextLayout(layout); + } else { + height = topLineY + renderer.getLineHeight(lineIndex); + } + } + int caretHeight = height; + if (wordWrap) { + TextLayout layout = renderer.getTextLayout(caretLine); + int offsetInLine = caretOffset - content.getOffsetAtLine(caretLine); + lineIndex = getVisualLineIndex(layout, offsetInLine); + caretHeight += layout.getLineBounds(lineIndex).y; + renderer.disposeTextLayout(layout); + } + lineIndex = caretLine; + lineHeight = renderer.getLineHeight(lineIndex); + while (caretHeight - lineHeight >= 0 && lineIndex < lineCount - 1) { + caretHeight -= lineHeight; + lineHeight = renderer.getLineHeight(++lineIndex); + } + int[] alignment = new int[1]; + int offset = getOffsetAtPoint(columnX, caretHeight, lineIndex, alignment); + setCaretOffset(offset, alignment[0]); + if (select) doSelection(ST.COLUMN_NEXT); + height = getAvailableHeightBellow(height); + scrollVertical(height, true); + if (height == 0) setCaretLocation(); + } + showCaret(); + int hScrollChange = oldHScrollOffset - horizontalScrollOffset; + columnX = oldColumnX + hScrollChange; +} +/** + * Moves the cursor to the end of the last fully visible line. + */ +void doPageEnd() { + // go to end of line if in single line mode. fixes 5673 + if (isSingleLine()) { + doLineEnd(); + } else { + int bottomOffset; + if (wordWrap) { + int lineIndex = getPartialBottomIndex(); + TextLayout layout = renderer.getTextLayout(lineIndex); + int y = (clientAreaHeight - bottomMargin) - getLinePixel(lineIndex); + int index = layout.getLineCount() - 1; + while (index >= 0) { + Rectangle bounds = layout.getLineBounds(index); + if (y >= bounds.y + bounds.height) break; + index--; + } + if (index == -1 && lineIndex > 0) { + bottomOffset = content.getOffsetAtLine(lineIndex - 1) + content.getLine(lineIndex - 1).length(); + } else { + bottomOffset = content.getOffsetAtLine(lineIndex) + Math.max(0, layout.getLineOffsets()[index + 1] - 1); + } + renderer.disposeTextLayout(layout); + } else { + int lineIndex = getBottomIndex(); + bottomOffset = content.getOffsetAtLine(lineIndex) + content.getLine(lineIndex).length(); + } + if (caretOffset < bottomOffset) { + setCaretOffset(bottomOffset, OFFSET_LEADING); + showCaret(); + } + } +} +/** + * Moves the cursor to the beginning of the first fully visible line. + */ +void doPageStart() { + int topOffset; + if (wordWrap) { + int y, lineIndex; + if (topIndexY > 0) { + lineIndex = topIndex - 1; + y = renderer.getLineHeight(lineIndex) - topIndexY; + } else { + lineIndex = topIndex; + y = -topIndexY; + } + TextLayout layout = renderer.getTextLayout(lineIndex); + int index = 0; + int lineCount = layout.getLineCount(); + while (index < lineCount) { + Rectangle bounds = layout.getLineBounds(index); + if (y <= bounds.y) break; + index++; + } + if (index == lineCount) { + topOffset = content.getOffsetAtLine(lineIndex + 1); + } else { + topOffset = content.getOffsetAtLine(lineIndex) + layout.getLineOffsets()[index]; + } + renderer.disposeTextLayout(layout); + } else { + topOffset = content.getOffsetAtLine(topIndex); + } + if (caretOffset > topOffset) { + setCaretOffset(topOffset, OFFSET_LEADING); + showCaret(); + } +} +/** + * Scrolls one page up so that the first line (truncated or whole) + * of the current page becomes the fully visible last line. + * The caret is scrolled the same number of lines so that its location + * relative to the top line remains the same. The exception is the beginning + * of the text where a full page scroll is not possible. In this case the + * caret is moved in front of the first character. + */ +void doPageUp(boolean select, int height) { + if (isSingleLine()) return; + int oldHScrollOffset = horizontalScrollOffset; + int oldColumnX = columnX; + if (isFixedLineHeight()) { + int caretLine = getCaretLine(); + if (caretLine > 0) { + int lineHeight = renderer.getLineHeight(); + int lines = (height == -1 ? clientAreaHeight : height) / lineHeight; + int scrollLines = Math.max(1, Math.min(caretLine, lines)); + caretLine -= scrollLines; + int[] alignment = new int[1]; + int offset = getOffsetAtPoint(columnX, getLinePixel(caretLine), alignment); + setCaretOffset(offset, alignment[0]); + if (select) { + doSelection(ST.COLUMN_PREVIOUS); + } + int verticalScrollOffset = getVerticalScrollOffset(); + int scrollOffset = Math.max(0, verticalScrollOffset - scrollLines * getVerticalIncrement()); + if (scrollOffset < verticalScrollOffset) { + scrollVertical(scrollOffset - verticalScrollOffset, true); + } + } + } else { + int caretLine = getCaretLine(); + int lineHeight, lineIndex; + if (height == -1) { + if (topIndexY == 0) { + height = clientAreaHeight; + } else { + int y; + if (topIndex > 0) { + lineIndex = topIndex - 1; + lineHeight = renderer.getLineHeight(lineIndex); + height = clientAreaHeight - topIndexY; + y = lineHeight - topIndexY; + } else { + lineIndex = topIndex; + lineHeight = renderer.getLineHeight(lineIndex); + height = clientAreaHeight - (lineHeight + topIndexY); + y = -topIndexY; + } + if (wordWrap) { + TextLayout layout = renderer.getTextLayout(lineIndex); + for (int i = 0; i < layout.getLineCount(); i++) { + Rectangle bounds = layout.getLineBounds(i); + if (bounds.contains(bounds.x, y)) { + height += lineHeight - (bounds.y + bounds.height); + break; + } + } + renderer.disposeTextLayout(layout); + } + } + } else { + lineIndex = getLineIndex(clientAreaHeight - height); + int topLineY = getLinePixel(lineIndex); + if (wordWrap) { + TextLayout layout = renderer.getTextLayout(lineIndex); + int y = topLineY; + for (int i = 0; i < layout.getLineCount(); i++) { + Rectangle bounds = layout.getLineBounds(i); + if (bounds.contains(bounds.x, y)) { + height = clientAreaHeight - (topLineY + bounds.y); + break; + } + } + renderer.disposeTextLayout(layout); + } else { + height = clientAreaHeight - topLineY; + } + } + int caretHeight = height; + if (wordWrap) { + TextLayout layout = renderer.getTextLayout(caretLine); + int offsetInLine = caretOffset - content.getOffsetAtLine(caretLine); + lineIndex = getVisualLineIndex(layout, offsetInLine); + caretHeight += layout.getBounds().height - layout.getLineBounds(lineIndex).y; + renderer.disposeTextLayout(layout); + } + lineIndex = caretLine; + lineHeight = renderer.getLineHeight(lineIndex); + while (caretHeight - lineHeight >= 0 && lineIndex > 0) { + caretHeight -= lineHeight; + lineHeight = renderer.getLineHeight(--lineIndex); + } + lineHeight = renderer.getLineHeight(lineIndex); + int[] alignment = new int[1]; + int offset = getOffsetAtPoint(columnX, lineHeight - caretHeight, lineIndex, alignment); + setCaretOffset(offset, alignment[0]); + if (select) doSelection(ST.COLUMN_PREVIOUS); + height = getAvailableHeightAbove(height); + scrollVertical(-height, true); + if (height == 0) setCaretLocation(); + } + showCaret(); + int hScrollChange = oldHScrollOffset - horizontalScrollOffset; + columnX = oldColumnX + hScrollChange; +} +/** + * Updates the selection to extend to the current caret position. + */ +void doSelection(int direction) { + int redrawStart = -1; + int redrawEnd = -1; + if (selectionAnchor == -1) { + selectionAnchor = selection.x; + } + if (direction == ST.COLUMN_PREVIOUS) { + if (caretOffset < selection.x) { + // grow selection + redrawEnd = selection.x; + redrawStart = selection.x = caretOffset; + // check if selection has reversed direction + if (selection.y != selectionAnchor) { + redrawEnd = selection.y; + selection.y = selectionAnchor; + } + // test whether selection actually changed. Fixes 1G71EO1 + } else if (selectionAnchor == selection.x && caretOffset < selection.y) { + // caret moved towards selection anchor (left side of selection). + // shrink selection + redrawEnd = selection.y; + redrawStart = selection.y = caretOffset; + } + } else { + if (caretOffset > selection.y) { + // grow selection + redrawStart = selection.y; + redrawEnd = selection.y = caretOffset; + // check if selection has reversed direction + if (selection.x != selectionAnchor) { + redrawStart = selection.x; + selection.x = selectionAnchor; + } + // test whether selection actually changed. Fixes 1G71EO1 + } else if (selectionAnchor == selection.y && caretOffset > selection.x) { + // caret moved towards selection anchor (right side of selection). + // shrink selection + redrawStart = selection.x; + redrawEnd = selection.x = caretOffset; + } + } + if (redrawStart != -1 && redrawEnd != -1) { + internalRedrawRange(redrawStart, redrawEnd - redrawStart); + sendSelectionEvent(); + } +} +/** + * Moves the caret to the next character or to the beginning of the + * next line if the cursor is at the end of a line. + */ +void doSelectionCursorNext() { + int caretLine = getCaretLine(); + int lineOffset = content.getOffsetAtLine(caretLine); + int offsetInLine = caretOffset - lineOffset; + int offset, alignment; + if (offsetInLine < content.getLine(caretLine).length()) { + TextLayout layout = renderer.getTextLayout(caretLine); + offsetInLine = layout.getNextOffset(offsetInLine, SWT.MOVEMENT_CLUSTER); + int lineStart = layout.getLineOffsets()[layout.getLineIndex(offsetInLine)]; + renderer.disposeTextLayout(layout); + offset = offsetInLine + lineOffset; + alignment = offsetInLine == lineStart ? OFFSET_LEADING : PREVIOUS_OFFSET_TRAILING; + setCaretOffset(offset, alignment); + showCaret(); + } else if (caretLine < content.getLineCount() - 1 && !isSingleLine()) { + caretLine++; + offset = content.getOffsetAtLine(caretLine); + alignment = PREVIOUS_OFFSET_TRAILING; + setCaretOffset(offset, alignment); + showCaret(); + } +} +/** + * Moves the caret to the previous character or to the end of the previous + * line if the cursor is at the beginning of a line. + */ +void doSelectionCursorPrevious() { + int caretLine = getCaretLine(); + int lineOffset = content.getOffsetAtLine(caretLine); + int offsetInLine = caretOffset - lineOffset; + if (offsetInLine > 0) { + int offset = getClusterPrevious(caretOffset, caretLine); + setCaretOffset(offset, OFFSET_LEADING); + showCaret(); + } else if (caretLine > 0) { + caretLine--; + lineOffset = content.getOffsetAtLine(caretLine); + int offset = lineOffset + content.getLine(caretLine).length(); + setCaretOffset(offset, OFFSET_LEADING); + showCaret(); + } +} +/** + * Moves the caret one line down and to the same character offset relative + * to the beginning of the line. Moves the caret to the end of the new line + * if the new line is shorter than the character offset. + * Moves the caret to the end of the text if the caret already is on the + * last line. + * Adjusts the selection according to the caret change. This can either add + * to or subtract from the old selection, depending on the previous selection + * direction. + */ +void doSelectionLineDown() { + int oldColumnX = columnX = getPointAtOffset(caretOffset).x; + doLineDown(true); + columnX = oldColumnX; +} +/** + * Moves the caret one line up and to the same character offset relative + * to the beginning of the line. Moves the caret to the end of the new line + * if the new line is shorter than the character offset. + * Moves the caret to the beginning of the document if it is already on the + * first line. + * Adjusts the selection according to the caret change. This can either add + * to or subtract from the old selection, depending on the previous selection + * direction. + */ +void doSelectionLineUp() { + int oldColumnX = columnX = getPointAtOffset(caretOffset).x; + doLineUp(true); + columnX = oldColumnX; +} +/** + * Scrolls one page down so that the last line (truncated or whole) + * of the current page becomes the fully visible top line. + * <p> + * The caret is scrolled the same number of lines so that its location + * relative to the top line remains the same. The exception is the end + * of the text where a full page scroll is not possible. In this case + * the caret is moved after the last character. + * <p></p> + * Adjusts the selection according to the caret change. This can either add + * to or subtract from the old selection, depending on the previous selection + * direction. + * </p> + */ +void doSelectionPageDown(int pixels) { + int oldColumnX = columnX = getPointAtOffset(caretOffset).x; + doPageDown(true, pixels); + columnX = oldColumnX; +} +/** + * Scrolls one page up so that the first line (truncated or whole) + * of the current page becomes the fully visible last line. + * <p> + * The caret is scrolled the same number of lines so that its location + * relative to the top line remains the same. The exception is the beginning + * of the text where a full page scroll is not possible. In this case the + * caret is moved in front of the first character. + * </p><p> + * Adjusts the selection according to the caret change. This can either add + * to or subtract from the old selection, depending on the previous selection + * direction. + * </p> + */ +void doSelectionPageUp(int pixels) { + int oldColumnX = columnX = getPointAtOffset(caretOffset).x; + doPageUp(true, pixels); + columnX = oldColumnX; +} +/** + * Moves the caret to the end of the next word . + */ +void doSelectionWordNext() { + int offset = getWordNext(caretOffset, SWT.MOVEMENT_WORD); + // don't change caret position if in single line mode and the cursor + // would be on a different line. fixes 5673 + if (!isSingleLine() || + content.getLineAtOffset(caretOffset) == content.getLineAtOffset(offset)) { + // Force symmetrical movement for word next and previous. Fixes 14536 + setCaretOffset(offset, OFFSET_LEADING); + showCaret(); + } +} +/** + * Moves the caret to the start of the previous word. + */ +void doSelectionWordPrevious() { + int offset = getWordPrevious(caretOffset, SWT.MOVEMENT_WORD); + setCaretOffset(offset, OFFSET_LEADING); + showCaret(); +} +/** + * Moves the caret one character to the left. Do not go to the previous line. + * When in a bidi locale and at a R2L character the caret is moved to the + * beginning of the R2L segment (visually right) and then one character to the + * left (visually left because it's now in a L2R segment). + */ +void doVisualPrevious() { + int offset = getClusterPrevious(caretOffset, getCaretLine()); + setCaretOffset(offset, SWT.DEFAULT); + showCaret(); +} +/** + * Moves the caret one character to the right. Do not go to the next line. + * When in a bidi locale and at a R2L character the caret is moved to the + * end of the R2L segment (visually left) and then one character to the + * right (visually right because it's now in a L2R segment). + */ +void doVisualNext() { + int offset = getClusterNext(caretOffset, getCaretLine()); + setCaretOffset(offset, SWT.DEFAULT); + showCaret(); +} +/** + * Moves the caret to the end of the next word. + * If a selection exists, move the caret to the end of the selection + * and remove the selection. + */ +void doWordNext() { + if (selection.y - selection.x > 0) { + setCaretOffset(selection.y, SWT.DEFAULT); + showCaret(); + } else { + doSelectionWordNext(); + } +} +/** + * Moves the caret to the start of the previous word. + * If a selection exists, move the caret to the start of the selection + * and remove the selection. + */ +void doWordPrevious() { + if (selection.y - selection.x > 0) { + setCaretOffset(selection.x, SWT.DEFAULT); + showCaret(); + } else { + doSelectionWordPrevious(); + } +} +/** + * Ends the autoscroll process. + */ +void endAutoScroll() { + autoScrollDirection = SWT.NULL; +} +public Color getBackground() { + checkWidget(); + if (background == null) { + return getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND); + } + return background; +} +/** + * Returns the baseline, in pixels. + * + * Note: this API should not be used if a StyleRange attribute causes lines to + * have different heights (i.e. different fonts, rise, etc). + * + * @return baseline the baseline + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @since 3.0 + * + * @see #getBaseline(int) + */ +public int getBaseline() { + checkWidget(); + return renderer.getBaseline(); +} +/** + * Returns the baseline at the given offset, in pixels. + * + * @param offset the offset + * + * @return baseline the baseline + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_RANGE when the offset is outside the valid range (< 0 or > getCharCount())</li> + * </ul> + * + * @since 3.2 + */ +public int getBaseline(int offset) { + checkWidget(); + if (!(0 <= offset && offset <= content.getCharCount())) { + SWT.error(SWT.ERROR_INVALID_RANGE); + } + if (isFixedLineHeight()) { + return renderer.getBaseline(); + } + int lineIndex = content.getLineAtOffset(offset); + int lineOffset = content.getOffsetAtLine(lineIndex); + TextLayout layout = renderer.getTextLayout(lineIndex); + int lineInParagraph = layout.getLineIndex(Math.min(offset - lineOffset, layout.getText().length())); + FontMetrics metrics = layout.getLineMetrics(lineInParagraph); + renderer.disposeTextLayout(layout); + return metrics.getAscent() + metrics.getLeading(); +} +/** + * Gets the BIDI coloring mode. When true the BIDI text display + * algorithm is applied to segments of text that are the same + * color. + * + * @return the current coloring mode + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @deprecated use BidiSegmentListener instead. + */ +public boolean getBidiColoring() { + checkWidget(); + return bidiColoring; +} +/** + * Returns whether the widget is in block selection mode. + * + * @return true if widget is in block selection mode, false otherwise + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.5 + */ +public boolean getBlockSelection() { + checkWidget(); + return blockSelection; +} +Rectangle getBlockSelectonPosition() { + int firstLine = getLineIndex(blockYAnchor - getVerticalScrollOffset()); + int lastLine = getLineIndex(blockYLocation - getVerticalScrollOffset()); + if (firstLine > lastLine) { + int temp = firstLine; + firstLine = lastLine; + lastLine = temp; + } + int left = blockXAnchor; + int right = blockXLocation; + if (left > right) { + left = blockXLocation; + right = blockXAnchor; + } + return new Rectangle (left - horizontalScrollOffset, firstLine, right - horizontalScrollOffset, lastLine); +} +/** + * Returns the block selection bounds. The bounds is + * relative to the upper left corner of the document. + * + * @return the block selection bounds + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.5 + */ +public Rectangle getBlockSelectionBounds() { + Rectangle rect; + if (blockSelection && blockXLocation != -1) { + rect = getBlockSelectionRectangle(); + } else { + Point startPoint = getPointAtOffset(selection.x); + Point endPoint = getPointAtOffset(selection.y); + int height = getLineHeight(selection.y); + rect = new Rectangle(startPoint.x, startPoint.y, endPoint.x - startPoint.x, endPoint.y + height - startPoint.y); + if (selection.x == selection.y) { + rect.width = getCaretWidth(); + } + } + rect.x += horizontalScrollOffset; + rect.y += getVerticalScrollOffset(); + return rect; +} +Rectangle getBlockSelectionRectangle() { + Rectangle rect = getBlockSelectonPosition(); + rect.y = getLinePixel(rect.y); + rect.width = rect.width - rect.x; + rect.height = getLinePixel(rect.height + 1) - rect.y; + return rect; +} +String getBlockSelectionText(String delimiter) { + Rectangle rect = getBlockSelectonPosition(); + int firstLine = rect.y; + int lastLine = rect.height; + int left = rect.x; + int right = rect.width; + StringBuffer buffer = new StringBuffer(); + for (int lineIndex = firstLine; lineIndex <= lastLine; lineIndex++) { + int start = getOffsetAtPoint(left, 0, lineIndex, null); + int end = getOffsetAtPoint(right, 0, lineIndex, null); + if (start > end) { + int temp = start; + start = end; + end = temp; + } + String text = content.getTextRange(start, end - start); + buffer.append(text); + if (lineIndex < lastLine) buffer.append(delimiter); + } + return buffer.toString(); +} +/** + * Returns the index of the last fully visible line. + * + * @return index of the last fully visible line. + */ +int getBottomIndex() { + int bottomIndex; + if (isFixedLineHeight()) { + int lineCount = 1; + int lineHeight = renderer.getLineHeight(); + if (lineHeight != 0) { + // calculate the number of lines that are fully visible + int partialTopLineHeight = topIndex * lineHeight - getVerticalScrollOffset(); + lineCount = (clientAreaHeight - partialTopLineHeight) / lineHeight; + } + bottomIndex = Math.min(content.getLineCount() - 1, topIndex + Math.max(0, lineCount - 1)); + } else { + int clientAreaHeight = this.clientAreaHeight - bottomMargin; + bottomIndex = getLineIndex(clientAreaHeight); + if (bottomIndex > 0) { + int linePixel = getLinePixel(bottomIndex); + int lineHeight = renderer.getLineHeight(bottomIndex); + if (linePixel + lineHeight > clientAreaHeight) { + if (getLinePixel(bottomIndex - 1) >= topMargin) { + bottomIndex--; + } + } + } + } + return bottomIndex; +} +/** + * Returns the bottom margin. + * + * @return the bottom margin. + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.5 + */ +public int getBottomMargin() { + checkWidget(); + return bottomMargin; +} +Rectangle getBoundsAtOffset(int offset) { + int lineIndex = content.getLineAtOffset(offset); + int lineOffset = content.getOffsetAtLine(lineIndex); + String line = content.getLine(lineIndex); + Rectangle bounds; + if (line.length() != 0) { + int offsetInLine = offset - lineOffset; + TextLayout layout = renderer.getTextLayout(lineIndex); + bounds = layout.getBounds(offsetInLine, offsetInLine); + renderer.disposeTextLayout(layout); + } else { + bounds = new Rectangle (0, 0, 0, renderer.getLineHeight()); + } + if (offset == caretOffset) { + int lineEnd = lineOffset + line.length(); + if (offset == lineEnd) { + bounds.width += getCaretWidth(); + } + } + bounds.x += leftMargin - horizontalScrollOffset; + bounds.y += getLinePixel(lineIndex); + return bounds; +} +/** + * Returns the caret position relative to the start of the text. + * + * @return the caret position relative to the start of the text. + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public int getCaretOffset() { + checkWidget(); + return caretOffset; +} +/** + * Returns the caret width. + * + * @return the caret width, 0 if caret is null. + */ +int getCaretWidth() { + Caret caret = getCaret(); + if (caret == null) return 0; + return caret.getSize().x; +} +Object getClipboardContent(int clipboardType) { + TextTransfer plainTextTransfer = TextTransfer.getInstance(); + return clipboard.getContents(plainTextTransfer, clipboardType); +} +int getClusterNext(int offset, int lineIndex) { + int lineOffset = content.getOffsetAtLine(lineIndex); + TextLayout layout = renderer.getTextLayout(lineIndex); + offset -= lineOffset; + offset = layout.getNextOffset(offset, SWT.MOVEMENT_CLUSTER); + offset += lineOffset; + renderer.disposeTextLayout(layout); + return offset; +} +int getClusterPrevious(int offset, int lineIndex) { + int lineOffset = content.getOffsetAtLine(lineIndex); + TextLayout layout = renderer.getTextLayout(lineIndex); + offset -= lineOffset; + offset = layout.getPreviousOffset(offset, SWT.MOVEMENT_CLUSTER); + offset += lineOffset; + renderer.disposeTextLayout(layout); + return offset; +} +/** + * Returns the content implementation that is used for text storage. + * + * @return content the user defined content implementation that is used for + * text storage or the default content implementation if no user defined + * content implementation has been set. + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public StyledTextContent getContent() { + checkWidget(); + return content; +} +public boolean getDragDetect () { + checkWidget (); + return dragDetect; +} +/** + * Returns whether the widget implements double click mouse behavior. + * + * @return true if double clicking a word selects the word, false if double clicks + * have the same effect as regular mouse clicks + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public boolean getDoubleClickEnabled() { + checkWidget(); + return doubleClickEnabled; +} +/** + * Returns whether the widget content can be edited. + * + * @return true if content can be edited, false otherwise + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public boolean getEditable() { + checkWidget(); + return editable; +} +public Color getForeground() { + checkWidget(); + if (foreground == null) { + return getDisplay().getSystemColor(SWT.COLOR_LIST_FOREGROUND); + } + return foreground; +} +/** + * Returns the horizontal scroll increment. + * + * @return horizontal scroll increment. + */ +int getHorizontalIncrement() { + return renderer.averageCharWidth; +} +/** + * Returns the horizontal scroll offset relative to the start of the line. + * + * @return horizontal scroll offset relative to the start of the line, + * measured in character increments starting at 0, if > 0 the content is scrolled + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public int getHorizontalIndex() { + checkWidget(); + return horizontalScrollOffset / getHorizontalIncrement(); +} +/** + * Returns the horizontal scroll offset relative to the start of the line. + * + * @return the horizontal scroll offset relative to the start of the line, + * measured in pixel starting at 0, if > 0 the content is scrolled. + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public int getHorizontalPixel() { + checkWidget(); + return horizontalScrollOffset; +} +/** + * Returns the line indentation of the widget. + * + * @return the line indentation + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see #getLineIndent(int) + * + * @since 3.2 + */ +public int getIndent() { + checkWidget(); + return indent; +} +/** + * Returns whether the widget justifies lines. + * + * @return whether lines are justified + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see #getLineJustify(int) + * + * @since 3.2 + */ +public boolean getJustify() { + checkWidget(); + return justify; +} +/** + * Returns the action assigned to the key. + * Returns SWT.NULL if there is no action associated with the key. + * + * @param key a key code defined in SWT.java or a character. + * Optionally ORd with a state mask. Preferred state masks are one or more of + * SWT.MOD1, SWT.MOD2, SWT.MOD3, since these masks account for modifier platform + * differences. However, there may be cases where using the specific state masks + * (i.e., SWT.CTRL, SWT.SHIFT, SWT.ALT, SWT.COMMAND) makes sense. + * @return one of the predefined actions defined in ST.java or SWT.NULL + * if there is no action associated with the key. + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public int getKeyBinding(int key) { + checkWidget(); + Integer action = (Integer) keyActionMap.get(new Integer(key)); + return action == null ? SWT.NULL : action.intValue(); +} +/** + * Gets the number of characters. + * + * @return number of characters in the widget + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public int getCharCount() { + checkWidget(); + return content.getCharCount(); +} +/** + * Returns the line at the given line index without delimiters. + * Index 0 is the first line of the content. When there are not + * any lines, getLine(0) is a valid call that answers an empty string. + * <p> + * + * @param lineIndex index of the line to return. + * @return the line text without delimiters + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_RANGE when the line index is outside the valid range (< 0 or >= getLineCount())</li> + * </ul> + * @since 3.4 + */ +public String getLine(int lineIndex) { + checkWidget(); + if (lineIndex < 0 || + (lineIndex > 0 && lineIndex >= content.getLineCount())) { + SWT.error(SWT.ERROR_INVALID_RANGE); + } + return content.getLine(lineIndex); +} +/** + * Returns the alignment of the line at the given index. + * + * @param index the index of the line + * + * @return the line alignment + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT when the index is invalid</li> + * </ul> + * + * @see #getAlignment() + * + * @since 3.2 + */ +public int getLineAlignment(int index) { + checkWidget(); + if (index < 0 || index > content.getLineCount()) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + return renderer.getLineAlignment(index, alignment); +} +/** + * Returns the line at the specified offset in the text + * where 0 < offset < getCharCount() so that getLineAtOffset(getCharCount()) + * returns the line of the insert location. + * + * @param offset offset relative to the start of the content. + * 0 <= offset <= getCharCount() + * @return line at the specified offset in the text + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_RANGE when the offset is outside the valid range (< 0 or > getCharCount())</li> + * </ul> + */ +public int getLineAtOffset(int offset) { + checkWidget(); + if (offset < 0 || offset > getCharCount()) { + SWT.error(SWT.ERROR_INVALID_RANGE); + } + return content.getLineAtOffset(offset); +} +/** + * Returns the background color of the line at the given index. + * Returns null if a LineBackgroundListener has been set or if no background + * color has been specified for the line. Should not be called if a + * LineBackgroundListener has been set since the listener maintains the + * line background colors. + * + * @param index the index of the line + * @return the background color of the line at the given index. + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT when the index is invalid</li> + * </ul> + */ +public Color getLineBackground(int index) { + checkWidget(); + if (index < 0 || index > content.getLineCount()) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + return isListening(LineGetBackground) ? null : renderer.getLineBackground(index, null); +} +/** + * Returns the bullet of the line at the given index. + * + * @param index the index of the line + * + * @return the line bullet + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT when the index is invalid</li> + * </ul> + * + * @since 3.2 + */ +public Bullet getLineBullet(int index) { + checkWidget(); + if (index < 0 || index > content.getLineCount()) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + return isListening(LineGetStyle) ? null : renderer.getLineBullet(index, null); +} +/** + * Returns the line background data for the given line or null if + * there is none. + * + * @param lineOffset offset of the line start relative to the start + * of the content. + * @param line line to get line background data for + * @return line background data for the given line. + */ +StyledTextEvent getLineBackgroundData(int lineOffset, String line) { + return sendLineEvent(LineGetBackground, lineOffset, line); +} +/** + * Gets the number of text lines. + * + * @return the number of lines in the widget + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public int getLineCount() { + checkWidget(); + return content.getLineCount(); +} +/** + * Returns the number of lines that can be completely displayed in the + * widget client area. + * + * @return number of lines that can be completely displayed in the widget + * client area. + */ +int getLineCountWhole() { + if (isFixedLineHeight()) { + int lineHeight = renderer.getLineHeight(); + return lineHeight != 0 ? clientAreaHeight / lineHeight : 1; + } + return getBottomIndex() - topIndex + 1; +} +/** + * Returns the line delimiter used for entering new lines by key down + * or paste operation. + * + * @return line delimiter used for entering new lines by key down + * or paste operation. + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public String getLineDelimiter() { + checkWidget(); + return content.getLineDelimiter(); +} +/** + * Returns the line height. + * <p> + * Note: this API should not be used if a StyleRange attribute causes lines to + * have different heights (i.e. different fonts, rise, etc). + * </p> + * + * @return line height in pixel. + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @see #getLineHeight(int) + */ +public int getLineHeight() { + checkWidget(); + return renderer.getLineHeight(); +} +/** + * Returns the line height at the given offset. + * + * @param offset the offset + * + * @return line height in pixels + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_RANGE when the offset is outside the valid range (< 0 or > getCharCount())</li> + * </ul> + * + * @since 3.2 + */ +public int getLineHeight(int offset) { + checkWidget(); + if (!(0 <= offset && offset <= content.getCharCount())) { + SWT.error(SWT.ERROR_INVALID_RANGE); + } + if (isFixedLineHeight()) { + return renderer.getLineHeight(); + } + int lineIndex = content.getLineAtOffset(offset); + int lineOffset = content.getOffsetAtLine(lineIndex); + TextLayout layout = renderer.getTextLayout(lineIndex); + int lineInParagraph = layout.getLineIndex(Math.min(offset - lineOffset, layout.getText().length())); + int height = layout.getLineBounds(lineInParagraph).height; + renderer.disposeTextLayout(layout); + return height; +} +/** + * Returns the indentation of the line at the given index. + * + * @param index the index of the line + * + * @return the line indentation + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT when the index is invalid</li> + * </ul> + * + * @see #getIndent() + * + * @since 3.2 + */ +public int getLineIndent(int index) { + checkWidget(); + if (index < 0 || index > content.getLineCount()) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + return isListening(LineGetStyle) ? 0 : renderer.getLineIndent(index, indent); +} +/** + * Returns whether the line at the given index is justified. + * + * @param index the index of the line + * + * @return whether the line is justified + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT when the index is invalid</li> + * </ul> + * + * @see #getJustify() + * + * @since 3.2 + */ +public boolean getLineJustify(int index) { + checkWidget(); + if (index < 0 || index > content.getLineCount()) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + return isListening(LineGetStyle) ? false : renderer.getLineJustify(index, justify); +} +/** + * Returns the line spacing of the widget. + * + * @return the line spacing + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.2 + */ +public int getLineSpacing() { + checkWidget(); + return lineSpacing; +} +/** + * Returns the line style data for the given line or null if there is + * none. + * <p> + * If there is a LineStyleListener but it does not set any styles, + * the StyledTextEvent.styles field will be initialized to an empty + * array. + * </p> + * + * @param lineOffset offset of the line start relative to the start of + * the content. + * @param line line to get line styles for + * @return line style data for the given line. Styles may start before + * line start and end after line end + */ +StyledTextEvent getLineStyleData(int lineOffset, String line) { + return sendLineEvent(LineGetStyle, lineOffset, line); +} +/** + * Returns the top pixel, relative to the client area, of a given line. + * Clamps out of ranges index. + * + * @param lineIndex the line index, the max value is lineCount. If + * lineIndex == lineCount it returns the bottom pixel of the last line. + * It means this function can be used to retrieve the bottom pixel of any line. + * + * @return the top pixel of a given line index + * + * @since 3.2 + */ +public int getLinePixel(int lineIndex) { + checkWidget(); + int lineCount = content.getLineCount(); + lineIndex = Math.max(0, Math.min(lineCount, lineIndex)); + if (isFixedLineHeight()) { + int lineHeight = renderer.getLineHeight(); + return lineIndex * lineHeight - getVerticalScrollOffset() + topMargin; + } + if (lineIndex == topIndex) return topIndexY + topMargin; + int height = topIndexY; + if (lineIndex > topIndex) { + for (int i = topIndex; i < lineIndex; i++) { + height += renderer.getLineHeight(i); + } + } else { + for (int i = topIndex - 1; i >= lineIndex; i--) { + height -= renderer.getLineHeight(i); + } + } + return height + topMargin; +} +/** + * Returns the line index for a y, relative to the client area. + * The line index returned is always in the range 0..lineCount - 1. + * + * @param y the y-coordinate pixel + * + * @return the line index for a given y-coordinate pixel + * + * @since 3.2 + */ +public int getLineIndex(int y) { + checkWidget(); + y -= topMargin; + if (isFixedLineHeight()) { + int lineHeight = renderer.getLineHeight(); + int lineIndex = (y + getVerticalScrollOffset()) / lineHeight; + int lineCount = content.getLineCount(); + lineIndex = Math.max(0, Math.min(lineCount - 1, lineIndex)); + return lineIndex; + } + if (y == topIndexY) return topIndex; + int line = topIndex; + if (y < topIndexY) { + while (y < topIndexY && line > 0) { + y += renderer.getLineHeight(--line); + } + } else { + int lineCount = content.getLineCount(); + int lineHeight = renderer.getLineHeight(line); + while (y - lineHeight >= topIndexY && line < lineCount - 1) { + y -= lineHeight; + lineHeight = renderer.getLineHeight(++line); + } + } + return line; +} +/** + * Returns the left margin. + * + * @return the left margin. + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.5 + */ +public int getLeftMargin() { + checkWidget(); + return leftMargin - alignmentMargin; +} +/** + * Returns the x, y location of the upper left corner of the character + * bounding box at the specified offset in the text. The point is + * relative to the upper left corner of the widget client area. + * + * @param offset offset relative to the start of the content. + * 0 <= offset <= getCharCount() + * @return x, y location of the upper left corner of the character + * bounding box at the specified offset in the text. + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_RANGE when the offset is outside the valid range (< 0 or > getCharCount())</li> + * </ul> + */ +public Point getLocationAtOffset(int offset) { + checkWidget(); + if (offset < 0 || offset > getCharCount()) { + SWT.error(SWT.ERROR_INVALID_RANGE); + } + return getPointAtOffset(offset); +} +/** + * Returns the character offset of the first character of the given line. + * + * @param lineIndex index of the line, 0 based relative to the first + * line in the content. 0 <= lineIndex < getLineCount(), except + * lineIndex may always be 0 + * @return offset offset of the first character of the line, relative to + * the beginning of the document. The first character of the document is + * at offset 0. + * When there are not any lines, getOffsetAtLine(0) is a valid call that + * answers 0. + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_RANGE when the line index is outside the valid range (< 0 or >= getLineCount())</li> + * </ul> + * @since 2.0 + */ +public int getOffsetAtLine(int lineIndex) { + checkWidget(); + if (lineIndex < 0 || + (lineIndex > 0 && lineIndex >= content.getLineCount())) { + SWT.error(SWT.ERROR_INVALID_RANGE); + } + return content.getOffsetAtLine(lineIndex); +} +/** + * Returns the offset of the character at the given location relative + * to the first character in the document. + * <p> + * The return value reflects the character offset that the caret will + * be placed at if a mouse click occurred at the specified location. + * If the x coordinate of the location is beyond the center of a character + * the returned offset will be behind the character. + * </p> + * + * @param point the origin of character bounding box relative to + * the origin of the widget client area. + * @return offset of the character at the given location relative + * to the first character in the document. + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT when point is null</li> + * <li>ERROR_INVALID_ARGUMENT when there is no character at the specified location</li> + * </ul> + */ +public int getOffsetAtLocation(Point point) { + checkWidget(); + if (point == null) { + SWT.error(SWT.ERROR_NULL_ARGUMENT); + } + int[] trailing = new int[1]; + int offset = getOffsetAtPoint(point.x, point.y, trailing, true); + if (offset == -1) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + return offset + trailing[0]; +} +int getOffsetAtPoint(int x, int y, int[] alignment) { + int lineIndex = getLineIndex(y); + y -= getLinePixel(lineIndex); + return getOffsetAtPoint(x, y, lineIndex, alignment); +} +int getOffsetAtPoint(int x, int y, int lineIndex, int[] alignment) { + TextLayout layout = renderer.getTextLayout(lineIndex); + x += horizontalScrollOffset - leftMargin; + int[] trailing = new int[1]; + int offsetInLine = layout.getOffset(x, y, trailing); + if (alignment != null) alignment[0] = OFFSET_LEADING; + if (trailing[0] != 0) { + int lineInParagraph = layout.getLineIndex(offsetInLine + trailing[0]); + int lineStart = layout.getLineOffsets()[lineInParagraph]; + if (offsetInLine + trailing[0] == lineStart) { + offsetInLine += trailing[0]; + if (alignment != null) alignment[0] = PREVIOUS_OFFSET_TRAILING; + } else { + String line = content.getLine(lineIndex); + int level = 0; + if (alignment != null) { + int offset = offsetInLine; + while (offset > 0 && Character.isDigit(line.charAt(offset))) offset--; + if (offset == 0 && Character.isDigit(line.charAt(offset))) { + level = isMirrored() ? 1 : 0; + } else { + level = layout.getLevel(offset) & 0x1; + } + } + offsetInLine += trailing[0]; + if (alignment != null) { + int trailingLevel = layout.getLevel(offsetInLine) & 0x1; + if ((level ^ trailingLevel) != 0) { + alignment[0] = PREVIOUS_OFFSET_TRAILING; + } else { + alignment[0] = OFFSET_LEADING; + } + } + } + } + renderer.disposeTextLayout(layout); + return offsetInLine + content.getOffsetAtLine(lineIndex); +} +int getOffsetAtPoint(int x, int y, int[] trailing, boolean inTextOnly) { + if (inTextOnly && y + getVerticalScrollOffset() < 0 || x + horizontalScrollOffset < 0) { + return -1; + } + int bottomIndex = getPartialBottomIndex(); + int height = getLinePixel(bottomIndex + 1); + if (inTextOnly && y > height) { + return -1; + } + int lineIndex = getLineIndex(y); + int lineOffset = content.getOffsetAtLine(lineIndex); + TextLayout layout = renderer.getTextLayout(lineIndex); + x += horizontalScrollOffset - leftMargin; + y -= getLinePixel(lineIndex); + int offset = layout.getOffset(x, y, trailing); + Rectangle rect = layout.getLineBounds(layout.getLineIndex(offset)); + renderer.disposeTextLayout(layout); + if (inTextOnly && !(rect.x <= x && x <= rect.x + rect.width)) { + return -1; + } + return offset + lineOffset; +} +/** + * Returns the orientation of the receiver. + * + * @return the orientation style + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 2.1.2 + */ +public int getOrientation () { + checkWidget(); + return isMirrored() ? SWT.RIGHT_TO_LEFT : SWT.LEFT_TO_RIGHT; +} +/** + * Returns the index of the last partially visible line. + * + * @return index of the last partially visible line. + */ +int getPartialBottomIndex() { + if (isFixedLineHeight()) { + int lineHeight = renderer.getLineHeight(); + int partialLineCount = Compatibility.ceil(clientAreaHeight, lineHeight); + return Math.max(0, Math.min(content.getLineCount(), topIndex + partialLineCount) - 1); + } + return getLineIndex(clientAreaHeight - bottomMargin); +} +/** + * Returns the index of the first partially visible line. + * + * @return index of the first partially visible line. + */ +int getPartialTopIndex() { + if (isFixedLineHeight()) { + int lineHeight = renderer.getLineHeight(); + return getVerticalScrollOffset() / lineHeight; + } + return topIndexY <= 0 ? topIndex : topIndex - 1; +} +/** + * Returns the content in the specified range using the platform line + * delimiter to separate lines. + * + * @param writer the TextWriter to write line text into + * @return the content in the specified range using the platform line + * delimiter to separate lines as written by the specified TextWriter. + */ +String getPlatformDelimitedText(TextWriter writer) { + int end = writer.getStart() + writer.getCharCount(); + int startLine = content.getLineAtOffset(writer.getStart()); + int endLine = content.getLineAtOffset(end); + String endLineText = content.getLine(endLine); + int endLineOffset = content.getOffsetAtLine(endLine); + + for (int i = startLine; i <= endLine; i++) { + writer.writeLine(content.getLine(i), content.getOffsetAtLine(i)); + if (i < endLine) { + writer.writeLineDelimiter(PlatformLineDelimiter); + } + } + if (end > endLineOffset + endLineText.length()) { + writer.writeLineDelimiter(PlatformLineDelimiter); + } + writer.close(); + return writer.toString(); +} +/** + * Returns all the ranges of text that have an associated StyleRange. + * Returns an empty array if a LineStyleListener has been set. + * Should not be called if a LineStyleListener has been set since the + * listener maintains the styles. + * <p> + * The ranges array contains start and length pairs. Each pair refers to + * the corresponding style in the styles array. For example, the pair + * that starts at ranges[n] with length ranges[n+1] uses the style + * at styles[n/2] returned by <code>getStyleRanges(int, int, boolean)</code>. + * </p> + * + * @return the ranges or an empty array if a LineStyleListener has been set. + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.2 + * + * @see #getStyleRanges(boolean) + */ +public int[] getRanges() { + checkWidget(); + if (!isListening(LineGetStyle)) { + int[] ranges = renderer.getRanges(0, content.getCharCount()); + if (ranges != null) return ranges; + } + return new int[0]; +} +/** + * Returns the ranges of text that have an associated StyleRange. + * Returns an empty array if a LineStyleListener has been set. + * Should not be called if a LineStyleListener has been set since the + * listener maintains the styles. + * <p> + * The ranges array contains start and length pairs. Each pair refers to + * the corresponding style in the styles array. For example, the pair + * that starts at ranges[n] with length ranges[n+1] uses the style + * at styles[n/2] returned by <code>getStyleRanges(int, int, boolean)</code>. + * </p> + * + * @param start the start offset of the style ranges to return + * @param length the number of style ranges to return + * + * @return the ranges or an empty array if a LineStyleListener has been set. + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_RANGE if start or length are outside the widget content</li> + * </ul> + * + * @since 3.2 + * + * @see #getStyleRanges(int, int, boolean) + */ +public int[] getRanges(int start, int length) { + checkWidget(); + int contentLength = getCharCount(); + int end = start + length; + if (start > end || start < 0 || end > contentLength) { + SWT.error(SWT.ERROR_INVALID_RANGE); + } + if (!isListening(LineGetStyle)) { + int[] ranges = renderer.getRanges(start, length); + if (ranges != null) return ranges; + } + return new int[0]; +} +/** + * Returns the right margin. + * + * @return the right margin. + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.5 + */ +public int getRightMargin() { + checkWidget(); + return rightMargin; +} +/** + * Returns the selection. + * <p> + * Text selections are specified in terms of caret positions. In a text + * widget that contains N characters, there are N+1 caret positions, + * ranging from 0..N + * </p> + * + * @return start and end of the selection, x is the offset of the first + * selected character, y is the offset after the last selected character. + * The selection values returned are visual (i.e., x will always always be + * <= y). To determine if a selection is right-to-left (RtoL) vs. left-to-right + * (LtoR), compare the caretOffset to the start and end of the selection + * (e.g., caretOffset == start of selection implies that the selection is RtoL). + * @see #getSelectionRange + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public Point getSelection() { + checkWidget(); + return new Point(selection.x, selection.y); +} +/** + * Returns the selection. + * + * @return start and length of the selection, x is the offset of the + * first selected character, relative to the first character of the + * widget content. y is the length of the selection. + * The selection values returned are visual (i.e., length will always always be + * positive). To determine if a selection is right-to-left (RtoL) vs. left-to-right + * (LtoR), compare the caretOffset to the start and end of the selection + * (e.g., caretOffset == start of selection implies that the selection is RtoL). + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public Point getSelectionRange() { + checkWidget(); + return new Point(selection.x, selection.y - selection.x); +} +/** + * Returns the ranges of text that are inside the block selection rectangle. + * <p> + * The ranges array contains start and length pairs. When the receiver is not + * in block selection mode the return arrays contains the start and length of + * the regular selection. + * + * @return the ranges array + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.5 + */ +public int[] getSelectionRanges() { + checkWidget(); + if (blockSelection && blockXLocation != -1) { + Rectangle rect = getBlockSelectonPosition(); + int firstLine = rect.y; + int lastLine = rect.height; + int left = rect.x; + int right = rect.width; + int[] ranges = new int[(lastLine - firstLine + 1) * 2]; + int index = 0; + for (int lineIndex = firstLine; lineIndex <= lastLine; lineIndex++) { + int start = getOffsetAtPoint(left, 0, lineIndex, null); + int end = getOffsetAtPoint(right, 0, lineIndex, null); + if (start > end) { + int temp = start; + start = end; + end = temp; + } + ranges[index++] = start; + ranges[index++] = end - start; + } + return ranges; + } + return new int[] {selection.x, selection.y - selection.x}; +} +/** + * Returns the receiver's selection background color. + * + * @return the selection background color + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @since 2.1 + */ +public Color getSelectionBackground() { + checkWidget(); + if (selectionBackground == null) { + return getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION); + } + return selectionBackground; +} +/** + * Gets the number of selected characters. + * + * @return the number of selected characters. + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public int getSelectionCount() { + checkWidget(); + if (blockSelection && blockXLocation != -1) { + return getBlockSelectionText(content.getLineDelimiter()).length(); + } + return getSelectionRange().y; +} +/** + * Returns the receiver's selection foreground color. + * + * @return the selection foreground color + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @since 2.1 + */ +public Color getSelectionForeground() { + checkWidget(); + if (selectionForeground == null) { + return getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT); + } + return selectionForeground; +} +/** + * Returns the selected text. + * + * @return selected text, or an empty String if there is no selection. + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public String getSelectionText() { + checkWidget(); + if (blockSelection && blockXLocation != -1) { + return getBlockSelectionText(content.getLineDelimiter()); + } + return content.getTextRange(selection.x, selection.y - selection.x); +} +public int getStyle() { + int style = super.getStyle(); + style &= ~(SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT | SWT.MIRRORED); + if (isMirrored()) { + style |= SWT.RIGHT_TO_LEFT | SWT.MIRRORED; + } else { + style |= SWT.LEFT_TO_RIGHT; + } + return style; +} + +/** + * Returns the text segments that should be treated as if they + * had a different direction than the surrounding text. + * + * @param lineOffset offset of the first character in the line. + * 0 based from the beginning of the document. + * @param line text of the line to specify bidi segments for + * @return text segments that should be treated as if they had a + * different direction than the surrounding text. Only the start + * index of a segment is specified, relative to the start of the + * line. Always starts with 0 and ends with the line length. + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if the segment indices returned + * by the listener do not start with 0, are not in ascending order, + * exceed the line length or have duplicates</li> + * </ul> + */ +int [] getBidiSegments(int lineOffset, String line) { + if (!isBidi()) return null; + if (!isListening(LineGetSegments)) { + return getBidiSegmentsCompatibility(line, lineOffset); + } + StyledTextEvent event = sendLineEvent(LineGetSegments, lineOffset, line); + int lineLength = line.length(); + int[] segments; + if (event == null || event.segments == null || event.segments.length == 0) { + segments = new int[] {0, lineLength}; + } else { + int segmentCount = event.segments.length; + + // test segment index consistency + if (event.segments[0] != 0) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + for (int i = 1; i < segmentCount; i++) { + if (event.segments[i] <= event.segments[i - 1] || event.segments[i] > lineLength) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + } + // ensure that last segment index is line end offset + if (event.segments[segmentCount - 1] != lineLength) { + segments = new int[segmentCount + 1]; + System.arraycopy(event.segments, 0, segments, 0, segmentCount); + segments[segmentCount] = lineLength; + } else { + segments = event.segments; + } + } + return segments; +} +/** + * @see #getBidiSegments + * Supports deprecated setBidiColoring API. Remove when API is removed. + */ +int [] getBidiSegmentsCompatibility(String line, int lineOffset) { + int lineLength = line.length(); + if (!bidiColoring) { + return new int[] {0, lineLength}; + } + StyleRange [] styles = null; + StyledTextEvent event = getLineStyleData(lineOffset, line); + if (event != null) { + styles = event.styles; + } else { + styles = renderer.getStyleRanges(lineOffset, lineLength, true); + } + if (styles == null || styles.length == 0) { + return new int[] {0, lineLength}; + } + int k=0, count = 1; + while (k < styles.length && styles[k].start == 0 && styles[k].length == lineLength) { + k++; + } + int[] offsets = new int[(styles.length - k) * 2 + 2]; + for (int i = k; i < styles.length; i++) { + StyleRange style = styles[i]; + int styleLineStart = Math.max(style.start - lineOffset, 0); + int styleLineEnd = Math.max(style.start + style.length - lineOffset, styleLineStart); + styleLineEnd = Math.min (styleLineEnd, line.length ()); + if (i > 0 && count > 1 && + ((styleLineStart >= offsets[count-2] && styleLineStart <= offsets[count-1]) || + (styleLineEnd >= offsets[count-2] && styleLineEnd <= offsets[count-1])) && + style.similarTo(styles[i-1])) { + offsets[count-2] = Math.min(offsets[count-2], styleLineStart); + offsets[count-1] = Math.max(offsets[count-1], styleLineEnd); + } else { + if (styleLineStart > offsets[count - 1]) { + offsets[count] = styleLineStart; + count++; + } + offsets[count] = styleLineEnd; + count++; + } + } + // add offset for last non-colored segment in line, if any + if (lineLength > offsets[count-1]) { + offsets [count] = lineLength; + count++; + } + if (count == offsets.length) { + return offsets; + } + int [] result = new int [count]; + System.arraycopy (offsets, 0, result, 0, count); + return result; +} +/** + * Returns the style range at the given offset. + * <p> + * Returns null if a LineStyleListener has been set or if a style is not set + * for the offset. + * Should not be called if a LineStyleListener has been set since the + * listener maintains the styles. + * </p> + * + * @param offset the offset to return the style for. + * 0 <= offset < getCharCount() must be true. + * @return a StyleRange with start == offset and length == 1, indicating + * the style at the given offset. null if a LineStyleListener has been set + * or if a style is not set for the given offset. + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT when the offset is invalid</li> + * </ul> + */ +public StyleRange getStyleRangeAtOffset(int offset) { + checkWidget(); + if (offset < 0 || offset >= getCharCount()) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + if (!isListening(LineGetStyle)) { + StyleRange[] ranges = renderer.getStyleRanges(offset, 1, true); + if (ranges != null) return ranges[0]; + } + return null; +} +/** + * Returns the styles. + * <p> + * Returns an empty array if a LineStyleListener has been set. + * Should not be called if a LineStyleListener has been set since the + * listener maintains the styles. + * <p></p> + * Note: Because a StyleRange includes the start and length, the + * same instance cannot occur multiple times in the array of styles. + * If the same style attributes, such as font and color, occur in + * multiple StyleRanges, <code>getStyleRanges(boolean)</code> + * can be used to get the styles without the ranges. + * </p> + * + * @return the styles or an empty array if a LineStyleListener has been set. + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see #getStyleRanges(boolean) + */ +public StyleRange[] getStyleRanges() { + checkWidget(); + return getStyleRanges(0, content.getCharCount(), true); +} +/** + * Returns the styles. + * <p> + * Returns an empty array if a LineStyleListener has been set. + * Should not be called if a LineStyleListener has been set since the + * listener maintains the styles. + * </p><p> + * Note: When <code>includeRanges</code> is true, the start and length + * fields of each StyleRange will be valid, however the StyleRange + * objects may need to be cloned. When <code>includeRanges</code> is + * false, <code>getRanges(int, int)</code> can be used to get the + * associated ranges. + * </p> + * + * @param includeRanges whether the start and length field of the StyleRanges should be set. + * + * @return the styles or an empty array if a LineStyleListener has been set. + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.2 + * + * @see #getRanges(int, int) + * @see #setStyleRanges(int[], StyleRange[]) + */ +public StyleRange[] getStyleRanges(boolean includeRanges) { + checkWidget(); + return getStyleRanges(0, content.getCharCount(), includeRanges); +} +/** + * Returns the styles for the given text range. + * <p> + * Returns an empty array if a LineStyleListener has been set. + * Should not be called if a LineStyleListener has been set since the + * listener maintains the styles. + * </p><p> + * Note: Because the StyleRange includes the start and length, the + * same instance cannot occur multiple times in the array of styles. + * If the same style attributes, such as font and color, occur in + * multiple StyleRanges, <code>getStyleRanges(int, int, boolean)</code> + * can be used to get the styles without the ranges. + * </p> + * @param start the start offset of the style ranges to return + * @param length the number of style ranges to return + * + * @return the styles or an empty array if a LineStyleListener has + * been set. The returned styles will reflect the given range. The first + * returned <code>StyleRange</code> will have a starting offset >= start + * and the last returned <code>StyleRange</code> will have an ending + * offset <= start + length - 1 + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li> + * </ul> + * + * @see #getStyleRanges(int, int, boolean) + * + * @since 3.0 + */ +public StyleRange[] getStyleRanges(int start, int length) { + checkWidget(); + return getStyleRanges(start, length, true); +} +/** + * Returns the styles for the given text range. + * <p> + * Returns an empty array if a LineStyleListener has been set. + * Should not be called if a LineStyleListener has been set since the + * listener maintains the styles. + * </p><p> + * Note: When <code>includeRanges</code> is true, the start and length + * fields of each StyleRange will be valid, however the StyleRange + * objects may need to be cloned. When <code>includeRanges</code> is + * false, <code>getRanges(int, int)</code> can be used to get the + * associated ranges. + * </p> + * + * @param start the start offset of the style ranges to return + * @param length the number of style ranges to return + * @param includeRanges whether the start and length field of the StyleRanges should be set. + * + * @return the styles or an empty array if a LineStyleListener has + * been set. The returned styles will reflect the given range. The first + * returned <code>StyleRange</code> will have a starting offset >= start + * and the last returned <code>StyleRange</code> will have an ending + * offset <= start + length - 1 + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li> + * </ul> + * + * @since 3.2 + * + * @see #getRanges(int, int) + * @see #setStyleRanges(int[], StyleRange[]) + */ +public StyleRange[] getStyleRanges(int start, int length, boolean includeRanges) { + checkWidget(); + int contentLength = getCharCount(); + int end = start + length; + if (start > end || start < 0 || end > contentLength) { + SWT.error(SWT.ERROR_INVALID_RANGE); + } + if (!isListening(LineGetStyle)) { + StyleRange[] ranges = renderer.getStyleRanges(start, length, includeRanges); + if (ranges != null) return ranges; + } + return new StyleRange[0]; +} +/** + * Returns the tab width measured in characters. + * + * @return tab width measured in characters + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public int getTabs() { + checkWidget(); + return tabLength; +} +/** + * Returns a copy of the widget content. + * + * @return copy of the widget content + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public String getText() { + checkWidget(); + return content.getTextRange(0, getCharCount()); +} +/** + * Returns the widget content between the two offsets. + * + * @param start offset of the first character in the returned String + * @param end offset of the last character in the returned String + * @return widget content starting at start and ending at end + * @see #getTextRange(int,int) + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li> + * </ul> + */ +public String getText(int start, int end) { + checkWidget(); + int contentLength = getCharCount(); + if (start < 0 || start >= contentLength || end < 0 || end >= contentLength || start > end) { + SWT.error(SWT.ERROR_INVALID_RANGE); + } + return content.getTextRange(start, end - start + 1); +} +/** + * Returns the smallest bounding rectangle that includes the characters between two offsets. + * + * @param start offset of the first character included in the bounding box + * @param end offset of the last character included in the bounding box + * @return bounding box of the text between start and end + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li> + * </ul> + * @since 3.1 + */ +public Rectangle getTextBounds(int start, int end) { + checkWidget(); + int contentLength = getCharCount(); + if (start < 0 || start >= contentLength || end < 0 || end >= contentLength || start > end) { + SWT.error(SWT.ERROR_INVALID_RANGE); + } + int lineStart = content.getLineAtOffset(start); + int lineEnd = content.getLineAtOffset(end); + Rectangle rect; + int y = getLinePixel(lineStart); + int height = 0; + int left = 0x7fffffff, right = 0; + for (int i = lineStart; i <= lineEnd; i++) { + int lineOffset = content.getOffsetAtLine(i); + TextLayout layout = renderer.getTextLayout(i); + int length = layout.getText().length(); + if (length > 0) { + if (i == lineStart) { + if (i == lineEnd) { + rect = layout.getBounds(start - lineOffset, end - lineOffset); + } else { + rect = layout.getBounds(start - lineOffset, length); + } + y += rect.y; + } else if (i == lineEnd) { + rect = layout.getBounds(0, end - lineOffset); + } else { + rect = layout.getBounds(); + } + left = Math.min(left, rect.x); + right = Math.max(right, rect.x + rect.width); + height += rect.height; + } else { + height += renderer.getLineHeight(); + } + renderer.disposeTextLayout(layout); + } + rect = new Rectangle (left, y, right-left, height); + rect.x += leftMargin - horizontalScrollOffset; + return rect; +} +/** + * Returns the widget content starting at start for length characters. + * + * @param start offset of the first character in the returned String + * @param length number of characters to return + * @return widget content starting at start and extending length characters. + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_RANGE when start and/or length are outside the widget content</li> + * </ul> + */ +public String getTextRange(int start, int length) { + checkWidget(); + int contentLength = getCharCount(); + int end = start + length; + if (start > end || start < 0 || end > contentLength) { + SWT.error(SWT.ERROR_INVALID_RANGE); + } + return content.getTextRange(start, length); +} +/** + * Returns the maximum number of characters that the receiver is capable of holding. + * + * @return the text limit + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public int getTextLimit() { + checkWidget(); + return textLimit; +} +/** + * Gets the top index. + * <p> + * The top index is the index of the fully visible line that is currently + * at the top of the widget or the topmost partially visible line if no line is fully visible. + * The top index changes when the widget is scrolled. Indexing is zero based. + * </p> + * + * @return the index of the top line + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public int getTopIndex() { + checkWidget(); + return topIndex; +} +/** + * Returns the top margin. + * + * @return the top margin. + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.5 + */ +public int getTopMargin() { + checkWidget(); + return topMargin; +} +/** + * Gets the top pixel. + * <p> + * The top pixel is the pixel position of the line that is + * currently at the top of the widget. The text widget can be scrolled by pixels + * by dragging the scroll thumb so that a partial line may be displayed at the top + * the widget. The top pixel changes when the widget is scrolled. The top pixel + * does not include the widget trimming. + * </p> + * + * @return pixel position of the top line + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public int getTopPixel() { + checkWidget(); + return getVerticalScrollOffset(); +} +/** + * Returns the vertical scroll increment. + * + * @return vertical scroll increment. + */ +int getVerticalIncrement() { + return renderer.getLineHeight(); +} +int getVerticalScrollOffset() { + if (verticalScrollOffset == -1) { + renderer.calculate(0, topIndex); + int height = 0; + for (int i = 0; i < topIndex; i++) { + height += renderer.getLineHeight(i); + } + height -= topIndexY; + verticalScrollOffset = height; + } + return verticalScrollOffset; +} +int getVisualLineIndex(TextLayout layout, int offsetInLine) { + int lineIndex = layout.getLineIndex(offsetInLine); + int[] offsets = layout.getLineOffsets(); + if (lineIndex != 0 && offsetInLine == offsets[lineIndex]) { + int lineY = layout.getLineBounds(lineIndex).y; + int caretY = getCaret().getLocation().y - topMargin - getLinePixel(getCaretLine()); + if (lineY > caretY) lineIndex--; + } + return lineIndex; +} +int getCaretDirection() { + if (!isBidiCaret()) return SWT.DEFAULT; + if (ime.getCompositionOffset() != -1) return SWT.DEFAULT; + if (!updateCaretDirection && caretDirection != SWT.NULL) return caretDirection; + updateCaretDirection = false; + int caretLine = getCaretLine(); + int lineOffset = content.getOffsetAtLine(caretLine); + String line = content.getLine(caretLine); + int offset = caretOffset - lineOffset; + int lineLength = line.length(); + if (lineLength == 0) return isMirrored() ? SWT.RIGHT : SWT.LEFT; + if (caretAlignment == PREVIOUS_OFFSET_TRAILING && offset > 0) offset--; + if (offset == lineLength && offset > 0) offset--; + while (offset > 0 && Character.isDigit(line.charAt(offset))) offset--; + if (offset == 0 && Character.isDigit(line.charAt(offset))) { + return isMirrored() ? SWT.RIGHT : SWT.LEFT; + } + TextLayout layout = renderer.getTextLayout(caretLine); + int level = layout.getLevel(offset); + renderer.disposeTextLayout(layout); + return ((level & 1) != 0) ? SWT.RIGHT : SWT.LEFT; +} +/* + * Returns the index of the line the caret is on. + */ +int getCaretLine() { + return content.getLineAtOffset(caretOffset); +} +int getWrapWidth () { + if (wordWrap && !isSingleLine()) { + int width = clientAreaWidth - leftMargin - rightMargin - getCaretWidth(); + return width > 0 ? width : 1; + } + return -1; +} +int getWordNext (int offset, int movement) { + int newOffset, lineOffset; + String lineText; + if (offset >= getCharCount()) { + newOffset = offset; + int lineIndex = content.getLineCount() - 1; + lineOffset = content.getOffsetAtLine(lineIndex); + lineText = content.getLine(lineIndex); + } else { + int lineIndex = content.getLineAtOffset(offset); + lineOffset = content.getOffsetAtLine(lineIndex); + lineText = content.getLine(lineIndex); + int lineLength = lineText.length(); + if (offset == lineOffset + lineLength) { + newOffset = content.getOffsetAtLine(lineIndex + 1); + } else { + TextLayout layout = renderer.getTextLayout(lineIndex); + newOffset = lineOffset + layout.getNextOffset(offset - lineOffset, movement); + renderer.disposeTextLayout(layout); + } + } + return sendWordBoundaryEvent(WordNext, movement, offset, newOffset, lineText, lineOffset); +} +int getWordPrevious(int offset, int movement) { + int newOffset, lineOffset; + String lineText; + if (offset <= 0) { + newOffset = 0; + int lineIndex = content.getLineAtOffset(newOffset); + lineOffset = content.getOffsetAtLine(lineIndex); + lineText = content.getLine(lineIndex); + } else { + int lineIndex = content.getLineAtOffset(offset); + lineOffset = content.getOffsetAtLine(lineIndex); + lineText = content.getLine(lineIndex); + if (offset == lineOffset) { + String nextLineText = content.getLine(lineIndex - 1); + int nextLineOffset = content.getOffsetAtLine(lineIndex - 1); + newOffset = nextLineOffset + nextLineText.length(); + } else { + TextLayout layout = renderer.getTextLayout(lineIndex); + newOffset = lineOffset + layout.getPreviousOffset(offset - lineOffset, movement); + renderer.disposeTextLayout(layout); + } + } + return sendWordBoundaryEvent(WordPrevious, movement, offset, newOffset, lineText, lineOffset); +} +/** + * Returns whether the widget wraps lines. + * + * @return true if widget wraps lines, false otherwise + * @since 2.0 + */ +public boolean getWordWrap() { + checkWidget(); + return wordWrap; +} +/** + * Returns the location of the given offset. + * <p> + * <b>NOTE:</b> Does not return correct values for true italic fonts (vs. slanted fonts). + * </p> + * + * @return location of the character at the given offset in the line. + */ +Point getPointAtOffset(int offset) { + int lineIndex = content.getLineAtOffset(offset); + String line = content.getLine(lineIndex); + int lineOffset = content.getOffsetAtLine(lineIndex); + int offsetInLine = offset - lineOffset; + int lineLength = line.length(); + if (lineIndex < content.getLineCount() - 1) { + int endLineOffset = content.getOffsetAtLine(lineIndex + 1) - 1; + if (lineLength < offsetInLine && offsetInLine <= endLineOffset) { + offsetInLine = lineLength; + } + } + Point point; + TextLayout layout = renderer.getTextLayout(lineIndex); + if (lineLength != 0 && offsetInLine <= lineLength) { + if (offsetInLine == lineLength) { + point = layout.getLocation(offsetInLine - 1, true); + } else { + switch (caretAlignment) { + case OFFSET_LEADING: + point = layout.getLocation(offsetInLine, false); + break; + case PREVIOUS_OFFSET_TRAILING: + default: + if (offsetInLine == 0) { + point = layout.getLocation(offsetInLine, false); + } else { + point = layout.getLocation(offsetInLine - 1, true); + } + break; + } + } + } else { + point = new Point(layout.getIndent(), 0); + } + renderer.disposeTextLayout(layout); + point.x += leftMargin - horizontalScrollOffset; + point.y += getLinePixel(lineIndex); + return point; +} +/** + * Inserts a string. The old selection is replaced with the new text. + * + * @param string the string + * @see #replaceTextRange(int,int,String) + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT when string is null</li> + * </ul> + */ +public void insert(String string) { + checkWidget(); + if (string == null) { + SWT.error(SWT.ERROR_NULL_ARGUMENT); + } + if (blockSelection) { + insertBlockSelectionText(string, false); + } else { + Point sel = getSelectionRange(); + replaceTextRange(sel.x, sel.y, string); + } +} +int insertBlockSelectionText(String text, boolean fillWithSpaces) { + int lineCount = 1; + for (int i = 0; i < text.length(); i++) { + char ch = text.charAt(i); + if (ch == '\n' || ch == '\r') { + lineCount++; + if (ch == '\r' && i + 1 < text.length() && text.charAt(i + 1) == '\n') { + i++; + } + } + } + String[] lines = new String[lineCount]; + int start = 0; + lineCount = 0; + for (int i = 0; i < text.length(); i++) { + char ch = text.charAt(i); + if (ch == '\n' || ch == '\r') { + lines[lineCount++] = text.substring(start, i); + if (ch == '\r' && i + 1 < text.length() && text.charAt(i + 1) == '\n') { + i++; + } + start = i + 1; + } + } + lines[lineCount++] = text.substring(start); + if (fillWithSpaces) { + int maxLength = 0; + for (int i = 0; i < lines.length; i++) { + int length = lines[i].length(); + maxLength = Math.max(maxLength, length); + } + for (int i = 0; i < lines.length; i++) { + String line = lines[i]; + int length = line.length(); + if (length < maxLength) { + int numSpaces = maxLength - length;; + StringBuffer buffer = new StringBuffer(length + numSpaces); + buffer.append(line); + for (int j = 0; j < numSpaces; j++) buffer.append(' '); + lines[i] = buffer.toString(); + } + } + } + int firstLine, lastLine, left, right; + if (blockXLocation != -1) { + Rectangle rect = getBlockSelectonPosition(); + firstLine = rect.y; + lastLine = rect.height; + left = rect.x; + right = rect.width; + } else { + firstLine = lastLine = getCaretLine(); + left = right = getPointAtOffset(caretOffset).x; + } + start = caretOffset; + int caretLine = getCaretLine(); + int index = 0, lineIndex = firstLine; + while (lineIndex <= lastLine) { + String string = index < lineCount ? lines[index++] : ""; + int lineStart = sendTextEvent(left, right, lineIndex, string, fillWithSpaces); + if (lineIndex == caretLine) start = lineStart; + lineIndex++; + } + while (index < lineCount) { + int lineStart = sendTextEvent(left, left, lineIndex, lines[index++], fillWithSpaces); + if (lineIndex == caretLine) start = lineStart; + lineIndex++; + } + return start; +} +void insertBlockSelectionText(char key, int action) { + if (key == SWT.CR || key == SWT.LF) return; + Rectangle rect = getBlockSelectonPosition(); + int firstLine = rect.y; + int lastLine = rect.height; + int left = rect.x; + int right = rect.width; + int[] trailing = new int[1]; + int offset = 0, delta = 0; + String text = key != 0 ? new String(new char[] {key}) : ""; + int length = text.length(); + for (int lineIndex = firstLine; lineIndex <= lastLine; lineIndex++) { + String line = content.getLine(lineIndex); + int lineOffset = content.getOffsetAtLine(lineIndex); + int lineEndOffset = lineOffset + line.length(); + int linePixel = getLinePixel(lineIndex); + int start = getOffsetAtPoint(left, linePixel, trailing, true); + boolean outOfLine = start == -1; + if (outOfLine) { + start = left < leftMargin ? lineOffset : lineEndOffset; + } else { + start += trailing[0]; + } + int end = getOffsetAtPoint(right, linePixel, trailing, true); + if (end == -1) { + end = right < leftMargin ? lineOffset : lineEndOffset; + } else { + end += trailing[0]; + } + if (start > end) { + int temp = start; + start = end; + end = temp; + } + if (start == end && !outOfLine) { + switch (action) { + case ST.DELETE_PREVIOUS: + if (start > lineOffset) start = getClusterPrevious(start, lineIndex); + break; + case ST.DELETE_NEXT: + if (end < lineEndOffset) end = getClusterNext(end, lineIndex); + break; + } + } + if (outOfLine) { + if (line.length() >= delta) { + delta = line.length(); + offset = lineEndOffset + length; + } + } else { + offset = start + length; + delta = content.getCharCount(); + } + Event event = new Event(); + event.text = text; + event.start = start; + event.end = end; + sendKeyEvent(event); + } + int x = getPointAtOffset(offset).x; + int verticalScrollOffset = getVerticalScrollOffset(); + setBlockSelectionLocation(x, blockYAnchor - verticalScrollOffset, x, blockYLocation - verticalScrollOffset, false); +} +/** + * Creates content change listeners and set the default content model. + */ +void installDefaultContent() { + textChangeListener = new TextChangeListener() { + public void textChanging(TextChangingEvent event) { + handleTextChanging(event); + } + public void textChanged(TextChangedEvent event) { + handleTextChanged(event); + } + public void textSet(TextChangedEvent event) { + handleTextSet(event); + } + }; + content = new DefaultContent(); + content.addTextChangeListener(textChangeListener); +} +/** + * Adds event listeners + */ +void installListeners() { + ScrollBar verticalBar = getVerticalBar(); + ScrollBar horizontalBar = getHorizontalBar(); + + listener = new Listener() { + public void handleEvent(Event event) { + switch (event.type) { + case SWT.Dispose: handleDispose(event); break; + case SWT.KeyDown: handleKeyDown(event); break; + case SWT.KeyUp: handleKeyUp(event); break; + case SWT.MouseDown: handleMouseDown(event); break; + case SWT.MouseUp: handleMouseUp(event); break; + case SWT.MouseMove: handleMouseMove(event); break; + case SWT.Paint: handlePaint(event); break; + case SWT.Resize: handleResize(event); break; + case SWT.Traverse: handleTraverse(event); break; + } + } + }; + addListener(SWT.Dispose, listener); + addListener(SWT.KeyDown, listener); + addListener(SWT.KeyUp, listener); + addListener(SWT.MouseDown, listener); + addListener(SWT.MouseUp, listener); + addListener(SWT.MouseMove, listener); + addListener(SWT.Paint, listener); + addListener(SWT.Resize, listener); + addListener(SWT.Traverse, listener); + ime.addListener(SWT.ImeComposition, new Listener() { + public void handleEvent(Event event) { + switch (event.detail) { + case SWT.COMPOSITION_SELECTION: handleCompositionSelection(event); break; + case SWT.COMPOSITION_CHANGED: handleCompositionChanged(event); break; + case SWT.COMPOSITION_OFFSET: handleCompositionOffset(event); break; + } + } + }); + if (verticalBar != null) { + verticalBar.addListener(SWT.Selection, new Listener() { + public void handleEvent(Event event) { + handleVerticalScroll(event); + } + }); + } + if (horizontalBar != null) { + horizontalBar.addListener(SWT.Selection, new Listener() { + public void handleEvent(Event event) { + handleHorizontalScroll(event); + } + }); + } +} +void internalRedrawRange(int start, int length) { + if (length <= 0) return; + int end = start + length; + int startLine = content.getLineAtOffset(start); + int endLine = content.getLineAtOffset(end); + int partialBottomIndex = getPartialBottomIndex(); + int partialTopIndex = getPartialTopIndex(); + if (startLine > partialBottomIndex || endLine < partialTopIndex) { + return; + } + if (partialTopIndex > startLine) { + startLine = partialTopIndex; + start = 0; + } else { + start -= content.getOffsetAtLine(startLine); + } + if (partialBottomIndex < endLine) { + endLine = partialBottomIndex + 1; + end = 0; + } else { + end -= content.getOffsetAtLine(endLine); + } + + TextLayout layout = renderer.getTextLayout(startLine); + int lineX = leftMargin - horizontalScrollOffset, startLineY = getLinePixel(startLine); + int[] offsets = layout.getLineOffsets(); + int startIndex = layout.getLineIndex(Math.min(start, layout.getText().length())); + + /* Redraw end of line before start line if wrapped and start offset is first char */ + if (wordWrap && startIndex > 0 && offsets[startIndex] == start) { + Rectangle rect = layout.getLineBounds(startIndex - 1); + rect.x = rect.width; + rect.width = clientAreaWidth - rightMargin - rect.x; + rect.x += lineX; + rect.y += startLineY; + super.redraw(rect.x, rect.y, rect.width, rect.height, false); + } + + if (startLine == endLine) { + int endIndex = layout.getLineIndex(Math.min(end, layout.getText().length())); + if (startIndex == endIndex) { + /* Redraw rect between start and end offset if start and end offsets are in same wrapped line */ + Rectangle rect = layout.getBounds(start, end - 1); + rect.x += lineX; + rect.y += startLineY; + super.redraw(rect.x, rect.y, rect.width, rect.height, false); + renderer.disposeTextLayout(layout); + return; + } + } + + /* Redraw start line from the start offset to the end of client area */ + Rectangle startRect = layout.getBounds(start, offsets[startIndex + 1] - 1); + if (startRect.height == 0) { + Rectangle bounds = layout.getLineBounds(startIndex); + startRect.x = bounds.width; + startRect.y = bounds.y; + startRect.height = bounds.height; + } + startRect.x += lineX; + startRect.y += startLineY; + startRect.width = clientAreaWidth - rightMargin - startRect.x; + super.redraw(startRect.x, startRect.y, startRect.width, startRect.height, false); + + /* Redraw end line from the beginning of the line to the end offset */ + if (startLine != endLine) { + renderer.disposeTextLayout(layout); + layout = renderer.getTextLayout(endLine); + offsets = layout.getLineOffsets(); + } + int endIndex = layout.getLineIndex(Math.min(end, layout.getText().length())); + Rectangle endRect = layout.getBounds(offsets[endIndex], end - 1); + if (endRect.height == 0) { + Rectangle bounds = layout.getLineBounds(endIndex); + endRect.y = bounds.y; + endRect.height = bounds.height; + } + endRect.x += lineX; + endRect.y += getLinePixel(endLine); + super.redraw(endRect.x, endRect.y, endRect.width, endRect.height, false); + renderer.disposeTextLayout(layout); + + /* Redraw all lines in between start and end line */ + int y = startRect.y + startRect.height; + if (endRect.y > y) { + super.redraw(leftMargin, y, clientAreaWidth - rightMargin - leftMargin, endRect.y - y, false); + } +} +void handleCompositionOffset (Event event) { + int[] trailing = new int [1]; + event.index = getOffsetAtPoint(event.x, event.y, trailing, true); + event.count = trailing[0]; +} +void handleCompositionSelection (Event event) { + event.start = selection.x; + event.end = selection.y; + event.text = getSelectionText(); +} +void handleCompositionChanged(Event event) { + String text = event.text; + int start = event.start; + int end = event.end; + int length = text.length(); + if (length == ime.getCommitCount()) { + content.replaceTextRange(start, end - start, ""); + setCaretOffset(ime.getCompositionOffset(), SWT.DEFAULT); + caretWidth = 0; + caretDirection = SWT.NULL; + } else { + content.replaceTextRange(start, end - start, text); + setCaretOffset(ime.getCaretOffset(), SWT.DEFAULT); + if (ime.getWideCaret()) { + start = ime.getCompositionOffset(); + int lineIndex = getCaretLine(); + int lineOffset = content.getOffsetAtLine(lineIndex); + TextLayout layout = renderer.getTextLayout(lineIndex); + caretWidth = layout.getBounds(start - lineOffset, start + length - 1 - lineOffset).width; + renderer.disposeTextLayout(layout); + } + } + showCaret(); +} +/** + * Frees resources. + */ +void handleDispose(Event event) { + removeListener(SWT.Dispose, listener); + notifyListeners(SWT.Dispose, event); + event.type = SWT.None; + + clipboard.dispose(); + if (renderer != null) { + renderer.dispose(); + renderer = null; + } + if (content != null) { + content.removeTextChangeListener(textChangeListener); + content = null; + } + if (defaultCaret != null) { + defaultCaret.dispose(); + defaultCaret = null; + } + if (leftCaretBitmap != null) { + leftCaretBitmap.dispose(); + leftCaretBitmap = null; + } + if (rightCaretBitmap != null) { + rightCaretBitmap.dispose(); + rightCaretBitmap = null; + } + if (isBidiCaret()) { + BidiUtil.removeLanguageListener(this); + } + selectionBackground = null; + selectionForeground = null; + marginColor = null; + textChangeListener = null; + selection = null; + doubleClickSelection = null; + keyActionMap = null; + background = null; + foreground = null; + clipboard = null; +} +/** + * Scrolls the widget horizontally. + */ +void handleHorizontalScroll(Event event) { + int scrollPixel = getHorizontalBar().getSelection() - horizontalScrollOffset; + scrollHorizontal(scrollPixel, false); +} +/** + * If an action has been registered for the key stroke execute the action. + * Otherwise, if a character has been entered treat it as new content. + * + * @param event keyboard event + */ +void handleKey(Event event) { + int action; + caretAlignment = PREVIOUS_OFFSET_TRAILING; + if (event.keyCode != 0) { + // special key pressed (e.g., F1) + action = getKeyBinding(event.keyCode | event.stateMask); + } else { + // character key pressed + action = getKeyBinding(event.character | event.stateMask); + if (action == SWT.NULL) { + // see if we have a control character + if ((event.stateMask & SWT.CTRL) != 0 && event.character <= 31) { + // get the character from the CTRL+char sequence, the control + // key subtracts 64 from the value of the key that it modifies + int c = event.character + 64; + action = getKeyBinding(c | event.stateMask); + } + } + } + if (action == SWT.NULL) { + boolean ignore = false; + + if (IS_MAC) { + // Ignore accelerator key combinations (we do not want to + // insert a character in the text in this instance). Do not + // ignore COMMAND+ALT combinations since that key sequence + // produces characters on the mac. + ignore = (event.stateMask ^ SWT.COMMAND) == 0 || + (event.stateMask ^ (SWT.COMMAND | SWT.SHIFT)) == 0; + } else if (IS_MOTIF) { + // Ignore accelerator key combinations (we do not want to + // insert a character in the text in this instance). Do not + // ignore ALT combinations since this key sequence + // produces characters on motif. + ignore = (event.stateMask ^ SWT.CTRL) == 0 || + (event.stateMask ^ (SWT.CTRL | SWT.SHIFT)) == 0; + } else { + // Ignore accelerator key combinations (we do not want to + // insert a character in the text in this instance). Don't + // ignore CTRL+ALT combinations since that is the Alt Gr + // key on some keyboards. See bug 20953. + ignore = (event.stateMask ^ SWT.ALT) == 0 || + (event.stateMask ^ SWT.CTRL) == 0 || + (event.stateMask ^ (SWT.ALT | SWT.SHIFT)) == 0 || + (event.stateMask ^ (SWT.CTRL | SWT.SHIFT)) == 0; + } + // -ignore anything below SPACE except for line delimiter keys and tab. + // -ignore DEL + if (!ignore && event.character > 31 && event.character != SWT.DEL || + event.character == SWT.CR || event.character == SWT.LF || + event.character == TAB) { + doContent(event.character); + update(); + } + } else { + invokeAction(action); + } +} +/** + * If a VerifyKey listener exists, verify that the key that was entered + * should be processed. + * + * @param event keyboard event + */ +void handleKeyDown(Event event) { + if (clipboardSelection == null) { + clipboardSelection = new Point(selection.x, selection.y); + } + + Event verifyEvent = new Event(); + verifyEvent.character = event.character; + verifyEvent.keyCode = event.keyCode; + verifyEvent.stateMask = event.stateMask; + verifyEvent.doit = true; + notifyListeners(VerifyKey, verifyEvent); + if (verifyEvent.doit) { + handleKey(event); + } +} +/** + * Update the Selection Clipboard. + * + * @param event keyboard event + */ +void handleKeyUp(Event event) { + if (clipboardSelection != null) { + if (clipboardSelection.x != selection.x || clipboardSelection.y != selection.y) { + copySelection(DND.SELECTION_CLIPBOARD); + } + } + clipboardSelection = null; +} +/** + * Updates the caret location and selection if mouse button 1 has been + * pressed. + */ +void handleMouseDown(Event event) { + //force focus (object support) + forceFocus(); + + //drag detect + if (dragDetect && checkDragDetect(event)) return; + + //paste clipboard selection + if (event.button == 2) { + String text = (String)getClipboardContent(DND.SELECTION_CLIPBOARD); + if (text != null && text.length() > 0) { + // position cursor + doMouseLocationChange(event.x, event.y, false); + // insert text + Event e = new Event(); + e.start = selection.x; + e.end = selection.y; + e.text = getModelDelimitedText(text); + sendKeyEvent(e); + } + } + + //set selection + if ((event.button != 1) || (IS_MAC && (event.stateMask & SWT.MOD4) != 0)) { + return; + } + clickCount = event.count; + if (clickCount == 1) { + boolean select = (event.stateMask & SWT.MOD2) != 0; + doMouseLocationChange(event.x, event.y, select); + } else { + if (doubleClickEnabled) { + boolean wordSelect = (clickCount & 1) == 0; + int offset = getOffsetAtPoint(event.x, event.y, null); + int lineIndex = content.getLineAtOffset(offset); + int lineOffset = content.getOffsetAtLine(lineIndex); + if (wordSelect) { + int min = blockSelection ? lineOffset : 0; + int max = blockSelection ? lineOffset + content.getLine(lineIndex).length() : content.getCharCount(); + int start = Math.max(min, getWordPrevious(offset, SWT.MOVEMENT_WORD_START)); + int end = Math.min(max, getWordNext(start, SWT.MOVEMENT_WORD_END)); + setSelection(start, end - start, false, true); + sendSelectionEvent(); + } else { + if (blockSelection) { + setBlockSelectionLocation(leftMargin, event.y, clientAreaWidth - rightMargin, event.y, true); + } else { + int lineEnd = content.getCharCount(); + if (lineIndex + 1 < content.getLineCount()) { + lineEnd = content.getOffsetAtLine(lineIndex + 1); + } + setSelection(lineOffset, lineEnd - lineOffset, false, false); + sendSelectionEvent(); + } + } + doubleClickSelection = new Point(selection.x, selection.y); + showCaret(); + } + } +} +/** + * Updates the caret location and selection if mouse button 1 is pressed + * during the mouse move. + */ +void handleMouseMove(Event event) { + if (clickCount > 0) { + update(); + doAutoScroll(event); + doMouseLocationChange(event.x, event.y, true); + } + if (renderer.hasLinks) { + doMouseLinkCursor(event.x, event.y); + } +} +/** + * Autoscrolling ends when the mouse button is released. + */ +void handleMouseUp(Event event) { + clickCount = 0; + endAutoScroll(); + if (event.button == 1) { + copySelection(DND.SELECTION_CLIPBOARD); + } +} +/** + * Renders the invalidated area specified in the paint event. + * + * @param event paint event + */ +void handlePaint(Event event) { + if (event.width == 0 || event.height == 0) return; + if (clientAreaWidth == 0 || clientAreaHeight == 0) return; + + int startLine = getLineIndex(event.y); + int y = getLinePixel(startLine); + int endY = event.y + event.height; + GC gc = event.gc; + Color background = getBackground(); + Color foreground = getForeground(); + if (endY > 0) { + int lineCount = isSingleLine() ? 1 : content.getLineCount(); + int x = leftMargin - horizontalScrollOffset; + for (int i = startLine; y < endY && i < lineCount; i++) { + y += renderer.drawLine(i, x, y, gc, background, foreground); + } + if (y < endY) { + gc.setBackground(background); + drawBackground(gc, 0, y, clientAreaWidth, endY - y); + } + } + if (blockSelection && blockXLocation != -1) { + gc.setBackground(getSelectionBackground()); + Rectangle rect = getBlockSelectionRectangle(); + gc.drawRectangle(rect.x, rect.y, Math.max(1, rect.width - 1), Math.max(1, rect.height - 1)); + gc.setAdvanced(true); + if (gc.getAdvanced()) { + gc.setAlpha(100); + gc.fillRectangle(rect); + gc.setAdvanced(false); + } + } + + // fill the margin background + gc.setBackground(marginColor != null ? marginColor : background); + if (topMargin > 0) { + drawBackground(gc, 0, 0, clientAreaWidth, topMargin); + } + if (bottomMargin > 0) { + drawBackground(gc, 0, clientAreaHeight - bottomMargin, clientAreaWidth, bottomMargin); + } + if (leftMargin - alignmentMargin > 0) { + drawBackground(gc, 0, 0, leftMargin - alignmentMargin, clientAreaHeight); + } + if (rightMargin > 0) { + drawBackground(gc, clientAreaWidth - rightMargin, 0, rightMargin, clientAreaHeight); + } +} +/** + * Recalculates the scroll bars. Rewraps all lines when in word + * wrap mode. + * + * @param event resize event + */ +void handleResize(Event event) { + int oldHeight = clientAreaHeight; + int oldWidth = clientAreaWidth; + Rectangle clientArea = getClientArea(); + clientAreaHeight = clientArea.height; + clientAreaWidth = clientArea.width; + /* Redraw the old or new right/bottom margin if needed */ + if (oldWidth != clientAreaWidth) { + if (rightMargin > 0) { + int x = (oldWidth < clientAreaWidth ? oldWidth : clientAreaWidth) - rightMargin; + super.redraw(x, 0, rightMargin, oldHeight, false); + } + } + if (oldHeight != clientAreaHeight) { + if (bottomMargin > 0) { + int y = (oldHeight < clientAreaHeight ? oldHeight : clientAreaHeight) - bottomMargin; + super.redraw(0, y, oldWidth, bottomMargin, false); + } + } + if (wordWrap) { + if (oldWidth != clientAreaWidth) { + renderer.reset(0, content.getLineCount()); + verticalScrollOffset = -1; + renderer.calculateIdle(); + super.redraw(); + } + if (oldHeight != clientAreaHeight) { + if (oldHeight == 0) topIndexY = 0; + setScrollBars(true); + } + setCaretLocation(); + } else { + renderer.calculateClientArea(); + setScrollBars(true); + claimRightFreeSpace(); + // StyledText allows any value for horizontalScrollOffset when clientArea is zero + // in setHorizontalPixel() and setHorisontalOffset(). Fixes bug 168429. + if (clientAreaWidth != 0) { + ScrollBar horizontalBar = getHorizontalBar(); + if (horizontalBar != null && horizontalBar.getVisible()) { + if (horizontalScrollOffset != horizontalBar.getSelection()) { + horizontalBar.setSelection(horizontalScrollOffset); + horizontalScrollOffset = horizontalBar.getSelection(); + } + } + } + } + updateCaretVisibility(); + claimBottomFreeSpace(); + setAlignment(); + //TODO FIX TOP INDEX DURING RESIZE +// if (oldHeight != clientAreaHeight || wordWrap) { +// calculateTopIndex(0); +// } +} +/** + * Updates the caret position and selection and the scroll bars to reflect + * the content change. + */ +void handleTextChanged(TextChangedEvent event) { + int offset = ime.getCompositionOffset(); + if (offset != -1 && lastTextChangeStart < offset) { + ime.setCompositionOffset(offset + lastTextChangeNewCharCount - lastTextChangeReplaceCharCount); + } + int firstLine = content.getLineAtOffset(lastTextChangeStart); + resetCache(firstLine, 0); + if (!isFixedLineHeight() && topIndex > firstLine) { + topIndex = firstLine; + topIndexY = 0; + super.redraw(); + } else { + int lastLine = firstLine + lastTextChangeNewLineCount; + int firstLineTop = getLinePixel(firstLine); + int newLastLineBottom = getLinePixel(lastLine + 1); + if (lastLineBottom != newLastLineBottom) { + super.redraw(); + } else { + super.redraw(0, firstLineTop, clientAreaWidth, newLastLineBottom - firstLineTop, false); + redrawLinesBullet(renderer.redrawLines); + } + } + renderer.redrawLines = null; + // update selection/caret location after styles have been changed. + // otherwise any text measuring could be incorrect + // + // also, this needs to be done after all scrolling. Otherwise, + // selection redraw would be flushed during scroll which is wrong. + // in some cases new text would be drawn in scroll source area even + // though the intent is to scroll it. + if (!(blockSelection && blockXLocation != -1)) { + updateSelection(lastTextChangeStart, lastTextChangeReplaceCharCount, lastTextChangeNewCharCount); + } + if (lastTextChangeReplaceLineCount > 0 || wordWrap) { + claimBottomFreeSpace(); + } + if (lastTextChangeReplaceCharCount > 0) { + claimRightFreeSpace(); + } + + sendAccessibleTextChanged(lastTextChangeStart, lastTextChangeNewCharCount, lastTextChangeReplaceCharCount); + lastCharCount += lastTextChangeNewCharCount; + lastCharCount -= lastTextChangeReplaceCharCount; + setAlignment(); +} +/** + * Updates the screen to reflect a pending content change. + * + * @param event .start the start offset of the change + * @param event .newText text that is going to be inserted or empty String + * if no text will be inserted + * @param event .replaceCharCount length of text that is going to be replaced + * @param event .newCharCount length of text that is going to be inserted + * @param event .replaceLineCount number of lines that are going to be replaced + * @param event .newLineCount number of new lines that are going to be inserted + */ +void handleTextChanging(TextChangingEvent event) { + if (event.replaceCharCount < 0) { + event.start += event.replaceCharCount; + event.replaceCharCount *= -1; + } + lastTextChangeStart = event.start; + lastTextChangeNewLineCount = event.newLineCount; + lastTextChangeNewCharCount = event.newCharCount; + lastTextChangeReplaceLineCount = event.replaceLineCount; + lastTextChangeReplaceCharCount = event.replaceCharCount; + int lineIndex = content.getLineAtOffset(event.start); + int srcY = getLinePixel(lineIndex + event.replaceLineCount + 1); + int destY = getLinePixel(lineIndex + 1) + event.newLineCount * renderer.getLineHeight(); + lastLineBottom = destY; + if (srcY < 0 && destY < 0) { + lastLineBottom += srcY - destY; + verticalScrollOffset += destY - srcY; + calculateTopIndex(destY - srcY); + setScrollBars(true); + } else { + scrollText(srcY, destY); + } + + renderer.textChanging(event); + + // Update the caret offset if it is greater than the length of the content. + // This is necessary since style range API may be called between the + // handleTextChanging and handleTextChanged events and this API sets the + // caretOffset. + int newEndOfText = content.getCharCount() - event.replaceCharCount + event.newCharCount; + if (caretOffset > newEndOfText) setCaretOffset(newEndOfText, SWT.DEFAULT); +} +/** + * Called when the widget content is set programmatically, overwriting + * the old content. Resets the caret position, selection and scroll offsets. + * Recalculates the content width and scroll bars. Redraws the widget. + * + * @param event text change event. + */ +void handleTextSet(TextChangedEvent event) { + reset(); + int newCharCount = getCharCount(); + sendAccessibleTextChanged(0, newCharCount, lastCharCount); + lastCharCount = newCharCount; + setAlignment(); +} +/** + * Called when a traversal key is pressed. + * Allow tab next traversal to occur when the widget is in single + * line mode or in multi line and non-editable mode . + * When in editable multi line mode we want to prevent the tab + * traversal and receive the tab key event instead. + * + * @param event the event + */ +void handleTraverse(Event event) { + switch (event.detail) { + case SWT.TRAVERSE_ESCAPE: + case SWT.TRAVERSE_PAGE_NEXT: + case SWT.TRAVERSE_PAGE_PREVIOUS: + event.doit = true; + break; + case SWT.TRAVERSE_RETURN: + case SWT.TRAVERSE_TAB_NEXT: + case SWT.TRAVERSE_TAB_PREVIOUS: + if ((getStyle() & SWT.SINGLE) != 0) { + event.doit = true; + } else { + if (!editable || (event.stateMask & SWT.MODIFIER_MASK) != 0) { + event.doit = true; + } + } + break; + } +} +/** + * Scrolls the widget vertically. + */ +void handleVerticalScroll(Event event) { + int scrollPixel = getVerticalBar().getSelection() - getVerticalScrollOffset(); + scrollVertical(scrollPixel, false); +} +/** + * Add accessibility support for the widget. + */ +void initializeAccessible() { + final Accessible accessible = getAccessible(); + accessible.addAccessibleListener(new AccessibleAdapter() { + public void getName (AccessibleEvent e) { + String name = null; + Label label = getAssociatedLabel (); + if (label != null) { + name = stripMnemonic (label.getText()); + } + e.result = name; + } + public void getHelp(AccessibleEvent e) { + e.result = getToolTipText(); + } + public void getKeyboardShortcut(AccessibleEvent e) { + String shortcut = null; + Label label = getAssociatedLabel (); + if (label != null) { + String text = label.getText (); + if (text != null) { + char mnemonic = _findMnemonic (text); + if (mnemonic != '\0') { + shortcut = "Alt+"+mnemonic; //$NON-NLS-1$ + } + } + } + e.result = shortcut; + } + }); + accessible.addAccessibleTextListener(new AccessibleTextAdapter() { + public void getCaretOffset(AccessibleTextEvent e) { + e.offset = StyledText.this.getCaretOffset(); + } + public void getSelectionRange(AccessibleTextEvent e) { + Point selection = StyledText.this.getSelectionRange(); + e.offset = selection.x; + e.length = selection.y; + } + }); + accessible.addAccessibleControlListener(new AccessibleControlAdapter() { + public void getRole(AccessibleControlEvent e) { + e.detail = ACC.ROLE_TEXT; + } + public void getState(AccessibleControlEvent e) { + int state = 0; + if (isEnabled()) state |= ACC.STATE_FOCUSABLE; + if (isFocusControl()) state |= ACC.STATE_FOCUSED; + if (!isVisible()) state |= ACC.STATE_INVISIBLE; + if (!getEditable()) state |= ACC.STATE_READONLY; + e.detail = state; + } + public void getValue(AccessibleControlEvent e) { + e.result = StyledText.this.getText(); + } + }); + addListener(SWT.FocusIn, new Listener() { + public void handleEvent(Event event) { + accessible.setFocus(ACC.CHILDID_SELF); + } + }); +} +/* + * Return the Label immediately preceding the receiver in the z-order, + * or null if none. + */ +Label getAssociatedLabel () { + Control[] siblings = getParent ().getChildren (); + for (int i = 0; i < siblings.length; i++) { + if (siblings [i] == StyledText.this) { + if (i > 0 && siblings [i-1] instanceof Label) { + return (Label) siblings [i-1]; + } + } + } + return null; +} +String stripMnemonic (String string) { + int index = 0; + int length = string.length (); + do { + while ((index < length) && (string.charAt (index) != '&')) index++; + if (++index >= length) return string; + if (string.charAt (index) != '&') { + return string.substring(0, index-1) + string.substring(index, length); + } + index++; + } while (index < length); + return string; +} +/* + * Return the lowercase of the first non-'&' character following + * an '&' character in the given string. If there are no '&' + * characters in the given string, return '\0'. + */ +char _findMnemonic (String string) { + if (string == null) return '\0'; + int index = 0; + int length = string.length (); + do { + while (index < length && string.charAt (index) != '&') index++; + if (++index >= length) return '\0'; + if (string.charAt (index) != '&') return Character.toLowerCase (string.charAt (index)); + index++; + } while (index < length); + return '\0'; +} +/** + * Executes the action. + * + * @param action one of the actions defined in ST.java + */ +public void invokeAction(int action) { + checkWidget(); + if (blockSelection && invokeBlockAction(action)) return; + updateCaretDirection = true; + switch (action) { + // Navigation + case ST.LINE_UP: + doLineUp(false); + clearSelection(true); + break; + case ST.LINE_DOWN: + doLineDown(false); + clearSelection(true); + break; + case ST.LINE_START: + doLineStart(); + clearSelection(true); + break; + case ST.LINE_END: + doLineEnd(); + clearSelection(true); + break; + case ST.COLUMN_PREVIOUS: + doCursorPrevious(); + clearSelection(true); + break; + case ST.COLUMN_NEXT: + doCursorNext(); + clearSelection(true); + break; + case ST.PAGE_UP: + doPageUp(false, -1); + clearSelection(true); + break; + case ST.PAGE_DOWN: + doPageDown(false, -1); + clearSelection(true); + break; + case ST.WORD_PREVIOUS: + doWordPrevious(); + clearSelection(true); + break; + case ST.WORD_NEXT: + doWordNext(); + clearSelection(true); + break; + case ST.TEXT_START: + doContentStart(); + clearSelection(true); + break; + case ST.TEXT_END: + doContentEnd(); + clearSelection(true); + break; + case ST.WINDOW_START: + doPageStart(); + clearSelection(true); + break; + case ST.WINDOW_END: + doPageEnd(); + clearSelection(true); + break; + // Selection + case ST.SELECT_LINE_UP: + doSelectionLineUp(); + break; + case ST.SELECT_ALL: + selectAll(); + break; + case ST.SELECT_LINE_DOWN: + doSelectionLineDown(); + break; + case ST.SELECT_LINE_START: + doLineStart(); + doSelection(ST.COLUMN_PREVIOUS); + break; + case ST.SELECT_LINE_END: + doLineEnd(); + doSelection(ST.COLUMN_NEXT); + break; + case ST.SELECT_COLUMN_PREVIOUS: + doSelectionCursorPrevious(); + doSelection(ST.COLUMN_PREVIOUS); + break; + case ST.SELECT_COLUMN_NEXT: + doSelectionCursorNext(); + doSelection(ST.COLUMN_NEXT); + break; + case ST.SELECT_PAGE_UP: + doSelectionPageUp(-1); + break; + case ST.SELECT_PAGE_DOWN: + doSelectionPageDown(-1); + break; + case ST.SELECT_WORD_PREVIOUS: + doSelectionWordPrevious(); + doSelection(ST.COLUMN_PREVIOUS); + break; + case ST.SELECT_WORD_NEXT: + doSelectionWordNext(); + doSelection(ST.COLUMN_NEXT); + break; + case ST.SELECT_TEXT_START: + doContentStart(); + doSelection(ST.COLUMN_PREVIOUS); + break; + case ST.SELECT_TEXT_END: + doContentEnd(); + doSelection(ST.COLUMN_NEXT); + break; + case ST.SELECT_WINDOW_START: + doPageStart(); + doSelection(ST.COLUMN_PREVIOUS); + break; + case ST.SELECT_WINDOW_END: + doPageEnd(); + doSelection(ST.COLUMN_NEXT); + break; + // Modification + case ST.CUT: + cut(); + break; + case ST.COPY: + copy(); + break; + case ST.PASTE: + paste(); + break; + case ST.DELETE_PREVIOUS: + doBackspace(); + break; + case ST.DELETE_NEXT: + doDelete(); + break; + case ST.DELETE_WORD_PREVIOUS: + doDeleteWordPrevious(); + break; + case ST.DELETE_WORD_NEXT: + doDeleteWordNext(); + break; + // Miscellaneous + case ST.TOGGLE_OVERWRITE: + overwrite = !overwrite; // toggle insert/overwrite mode + break; + case ST.TOGGLE_BLOCKSELECTION: + setBlockSelection(!blockSelection); + break; + } +} +/** +* Returns true if an action should not be performed when block +* selection in active +*/ +boolean invokeBlockAction(int action) { + switch (action) { + // Navigation + case ST.LINE_UP: + case ST.LINE_DOWN: + case ST.LINE_START: + case ST.LINE_END: + case ST.COLUMN_PREVIOUS: + case ST.COLUMN_NEXT: + case ST.PAGE_UP: + case ST.PAGE_DOWN: + case ST.WORD_PREVIOUS: + case ST.WORD_NEXT: + case ST.TEXT_START: + case ST.TEXT_END: + case ST.WINDOW_START: + case ST.WINDOW_END: + clearBlockSelection(false, false); + return false; + // Selection + case ST.SELECT_LINE_UP: + doBlockLineVertical(true); + return true; + case ST.SELECT_LINE_DOWN: + doBlockLineVertical(false); + return true; + case ST.SELECT_LINE_START: + doBlockLineHorizontal(false); + return true; + case ST.SELECT_LINE_END: + doBlockLineHorizontal(true); + return false; + case ST.SELECT_COLUMN_PREVIOUS: + doBlockColumn(false); + return true; + case ST.SELECT_COLUMN_NEXT: + doBlockColumn(true); + return true; + case ST.SELECT_WORD_PREVIOUS: + doBlockWord(false); + return true; + case ST.SELECT_WORD_NEXT: + doBlockWord(true); + return true; + case ST.SELECT_ALL: + return false; + case ST.SELECT_PAGE_UP: + case ST.SELECT_PAGE_DOWN: + case ST.SELECT_TEXT_START: + case ST.SELECT_TEXT_END: + case ST.SELECT_WINDOW_START: + case ST.SELECT_WINDOW_END: + //blocked actions + return true; + // Modification + case ST.CUT: + case ST.COPY: + case ST.PASTE: + return false; + case ST.DELETE_PREVIOUS: + case ST.DELETE_NEXT: + if (blockXLocation != -1) { + insertBlockSelectionText((char)0, action); + return true; + } + return false; + case ST.DELETE_WORD_PREVIOUS: + case ST.DELETE_WORD_NEXT: + //blocked actions + return blockXLocation != -1; + } + return false; +} +/** + * Temporary until SWT provides this + */ +boolean isBidi() { + return IS_GTK || IS_MAC || BidiUtil.isBidiPlatform() || isMirrored; +} +boolean isBidiCaret() { + return BidiUtil.isBidiPlatform(); +} +boolean isFixedLineHeight() { + return fixedLineHeight; +} +/** + * Returns whether the given offset is inside a multi byte line delimiter. + * Example: + * "Line1\r\n" isLineDelimiter(5) == false but isLineDelimiter(6) == true + * + * @return true if the given offset is inside a multi byte line delimiter. + * false if the given offset is before or after a line delimiter. + */ +boolean isLineDelimiter(int offset) { + int line = content.getLineAtOffset(offset); + int lineOffset = content.getOffsetAtLine(line); + int offsetInLine = offset - lineOffset; + // offsetInLine will be greater than line length if the line + // delimiter is longer than one character and the offset is set + // in between parts of the line delimiter. + return offsetInLine > content.getLine(line).length(); +} +/** + * Returns whether the widget is mirrored (right oriented/right to left + * writing order). + * + * @return isMirrored true=the widget is right oriented, false=the widget + * is left oriented + */ +boolean isMirrored() { + return isMirrored; +} +/** + * Returns whether the widget can have only one line. + * + * @return true if widget can have only one line, false if widget can have + * multiple lines + */ +boolean isSingleLine() { + return (getStyle() & SWT.SINGLE) != 0; +} +/** + * Sends the specified verify event, replace/insert text as defined by + * the event and send a modify event. + * + * @param event the text change event. + * <ul> + * <li>event.start - the replace start offset</li> + * <li>event.end - the replace end offset</li> + * <li>event.text - the new text</li> + * </ul> + * @param updateCaret whether or not he caret should be set behind + * the new text + */ +void modifyContent(Event event, boolean updateCaret) { + event.doit = true; + notifyListeners(SWT.Verify, event); + if (event.doit) { + StyledTextEvent styledTextEvent = null; + int replacedLength = event.end - event.start; + if (isListening(ExtendedModify)) { + styledTextEvent = new StyledTextEvent(content); + styledTextEvent.start = event.start; + styledTextEvent.end = event.start + event.text.length(); + styledTextEvent.text = content.getTextRange(event.start, replacedLength); + } + if (updateCaret) { + //Fix advancing flag for delete/backspace key on direction boundary + if (event.text.length() == 0) { + int lineIndex = content.getLineAtOffset(event.start); + int lineOffset = content.getOffsetAtLine(lineIndex); + TextLayout layout = renderer.getTextLayout(lineIndex); + int levelStart = layout.getLevel(event.start - lineOffset); + int lineIndexEnd = content.getLineAtOffset(event.end); + if (lineIndex != lineIndexEnd) { + renderer.disposeTextLayout(layout); + lineOffset = content.getOffsetAtLine(lineIndexEnd); + layout = renderer.getTextLayout(lineIndexEnd); + } + int levelEnd = layout.getLevel(event.end - lineOffset); + renderer.disposeTextLayout(layout); + if (levelStart != levelEnd) { + caretAlignment = PREVIOUS_OFFSET_TRAILING; + } else { + caretAlignment = OFFSET_LEADING; + } + } + } + content.replaceTextRange(event.start, replacedLength, event.text); + // set the caret position prior to sending the modify event. + // fixes 1GBB8NJ + if (updateCaret && !(blockSelection && blockXLocation != -1)) { + // always update the caret location. fixes 1G8FODP + setSelection(event.start + event.text.length(), 0, true, false); + showCaret(); + } + notifyListeners(SWT.Modify, event); + if (isListening(ExtendedModify)) { + notifyListeners(ExtendedModify, styledTextEvent); + } + } +} +void paintObject(GC gc, int x, int y, int ascent, int descent, StyleRange style, Bullet bullet, int bulletIndex) { + if (isListening(PaintObject)) { + StyledTextEvent event = new StyledTextEvent (content) ; + event.gc = gc; + event.x = x; + event.y = y; + event.ascent = ascent; + event.descent = descent; + event.style = style; + event.bullet = bullet; + event.bulletIndex = bulletIndex; + notifyListeners(PaintObject, event); + } +} +/** + * Replaces the selection with the text on the <code>DND.CLIPBOARD</code> + * clipboard or, if there is no selection, inserts the text at the current + * caret offset. If the widget has the SWT.SINGLE style and the + * clipboard text contains more than one line, only the first line without + * line delimiters is inserted in the widget. + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void paste(){ + checkWidget(); + String text = (String) getClipboardContent(DND.CLIPBOARD); + if (text != null && text.length() > 0) { + if (blockSelection) { + boolean fillWithSpaces = isFixedLineHeight() && renderer.fixedPitch; + int offset = insertBlockSelectionText(text, fillWithSpaces); + setCaretOffset(offset, SWT.DEFAULT); + clearBlockSelection(true, true); + setCaretLocation(); + return; + } + Event event = new Event(); + event.start = selection.x; + event.end = selection.y; + event.text = getModelDelimitedText(text); + sendKeyEvent(event); + } +} +/** + * Prints the widget's text to the default printer. + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void print() { + checkWidget(); + Printer printer = new Printer(); + StyledTextPrintOptions options = new StyledTextPrintOptions(); + options.printTextForeground = true; + options.printTextBackground = true; + options.printTextFontStyle = true; + options.printLineBackground = true; + new Printing(this, printer, options).run(); + printer.dispose(); +} +/** + * Returns a runnable that will print the widget's text + * to the specified printer. + * <p> + * The runnable may be run in a non-UI thread. + * </p> + * + * @param printer the printer to print to + * + * @return a <code>Runnable</code> for printing the receiver's text + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT when printer is null</li> + * </ul> + */ +public Runnable print(Printer printer) { + checkWidget(); + if (printer == null) { + SWT.error(SWT.ERROR_NULL_ARGUMENT); + } + StyledTextPrintOptions options = new StyledTextPrintOptions(); + options.printTextForeground = true; + options.printTextBackground = true; + options.printTextFontStyle = true; + options.printLineBackground = true; + return print(printer, options); +} +/** + * Returns a runnable that will print the widget's text + * to the specified printer. + * <p> + * The runnable may be run in a non-UI thread. + * </p> + * + * @param printer the printer to print to + * @param options print options to use during printing + * + * @return a <code>Runnable</code> for printing the receiver's text + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT when printer or options is null</li> + * </ul> + * @since 2.1 + */ +public Runnable print(Printer printer, StyledTextPrintOptions options) { + checkWidget(); + if (printer == null || options == null) { + SWT.error(SWT.ERROR_NULL_ARGUMENT); + } + return new Printing(this, printer, options); +} +/** + * Causes the entire bounds of the receiver to be marked + * as needing to be redrawn. The next time a paint request + * is processed, the control will be completely painted. + * <p> + * Recalculates the content width for all lines in the bounds. + * When a <code>LineStyleListener</code> is used a redraw call + * is the only notification to the widget that styles have changed + * and that the content width may have changed. + * </p> + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see Control#update() + */ +public void redraw() { + super.redraw(); + int itemCount = getPartialBottomIndex() - topIndex + 1; + renderer.reset(topIndex, itemCount); + renderer.calculate(topIndex, itemCount); + setScrollBars(false); + doMouseLinkCursor(); +} +/** + * Causes the rectangular area of the receiver specified by + * the arguments to be marked as needing to be redrawn. + * The next time a paint request is processed, that area of + * the receiver will be painted. If the <code>all</code> flag + * is <code>true</code>, any children of the receiver which + * intersect with the specified area will also paint their + * intersecting areas. If the <code>all</code> flag is + * <code>false</code>, the children will not be painted. + * <p> + * Marks the content width of all lines in the specified rectangle + * as unknown. Recalculates the content width of all visible lines. + * When a <code>LineStyleListener</code> is used a redraw call + * is the only notification to the widget that styles have changed + * and that the content width may have changed. + * </p> + * + * @param x the x coordinate of the area to draw + * @param y the y coordinate of the area to draw + * @param width the width of the area to draw + * @param height the height of the area to draw + * @param all <code>true</code> if children should redraw, and <code>false</code> otherwise + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see Control#update() + */ +public void redraw(int x, int y, int width, int height, boolean all) { + super.redraw(x, y, width, height, all); + if (height > 0) { + int firstLine = getLineIndex(y); + int lastLine = getLineIndex(y + height); + resetCache(firstLine, lastLine - firstLine + 1); + doMouseLinkCursor(); + } +} +void redrawLines(int startLine, int lineCount, boolean bottomChanged) { + // do nothing if redraw range is completely invisible + int endLine = startLine + lineCount - 1; + int partialBottomIndex = getPartialBottomIndex(); + int partialTopIndex = getPartialTopIndex(); + if (startLine > partialBottomIndex || endLine < partialTopIndex) { + return; + } + // only redraw visible lines + if (startLine < partialTopIndex) { + startLine = partialTopIndex; + } + if (endLine > partialBottomIndex) { + endLine = partialBottomIndex;; + } + int redrawTop = getLinePixel(startLine); + int redrawBottom = getLinePixel(endLine + 1); + if (bottomChanged) redrawBottom = clientAreaHeight - bottomMargin; + int redrawWidth = clientAreaWidth - leftMargin - rightMargin; + super.redraw(leftMargin, redrawTop, redrawWidth, redrawBottom - redrawTop, true); +} +void redrawLinesBullet (int[] redrawLines) { + if (redrawLines == null) return; + int topIndex = getPartialTopIndex(); + int bottomIndex = getPartialBottomIndex(); + for (int i = 0; i < redrawLines.length; i++) { + int lineIndex = redrawLines[i]; + if (!(topIndex <= lineIndex && lineIndex <= bottomIndex)) continue; + int width = -1; + Bullet bullet = renderer.getLineBullet(lineIndex, null); + if (bullet != null) { + StyleRange style = bullet.style; + GlyphMetrics metrics = style.metrics; + width = metrics.width; + } + if (width == -1) width = getClientArea().width; + int height = renderer.getLineHeight(lineIndex); + int y = getLinePixel(lineIndex); + super.redraw(0, y, width, height, false); + } +} +/** + * Redraws the specified text range. + * + * @param start offset of the first character to redraw + * @param length number of characters to redraw + * @param clearBackground true if the background should be cleared as + * part of the redraw operation. If true, the entire redraw range will + * be cleared before anything is redrawn. If the redraw range includes + * the last character of a line (i.e., the entire line is redrawn) the + * line is cleared all the way to the right border of the widget. + * The redraw operation will be faster and smoother if clearBackground + * is set to false. Whether or not the flag can be set to false depends + * on the type of change that has taken place. If font styles or + * background colors for the redraw range have changed, clearBackground + * should be set to true. If only foreground colors have changed for + * the redraw range, clearBackground can be set to false. + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_RANGE when start and/or end are outside the widget content</li> + * </ul> + */ +public void redrawRange(int start, int length, boolean clearBackground) { + checkWidget(); + int end = start + length; + int contentLength = content.getCharCount(); + if (start > end || start < 0 || end > contentLength) { + SWT.error(SWT.ERROR_INVALID_RANGE); + } + int firstLine = content.getLineAtOffset(start); + int lastLine = content.getLineAtOffset(end); + resetCache(firstLine, lastLine - firstLine + 1); + internalRedrawRange(start, length); + doMouseLinkCursor(); +} +/** + * Removes the specified bidirectional segment listener. + * + * @param listener the listener which should no longer be notified + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT when listener is null</li> + * </ul> + * + * @since 2.0 + */ +public void removeBidiSegmentListener(BidiSegmentListener listener) { + checkWidget(); + if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + removeListener(LineGetSegments, listener); +} +/** + * Removes the specified caret listener. + * + * @param listener the listener which should no longer be notified + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT when listener is null</li> + * </ul> + * + * @since 3.5 + */ +public void removeCaretListener(CaretListener listener) { + checkWidget(); + if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + removeListener(CaretMoved, listener); +} +/** + * Removes the specified extended modify listener. + * + * @param extendedModifyListener the listener which should no longer be notified + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT when listener is null</li> + * </ul> + */ +public void removeExtendedModifyListener(ExtendedModifyListener extendedModifyListener) { + checkWidget(); + if (extendedModifyListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + removeListener(ExtendedModify, extendedModifyListener); +} +/** + * Removes the specified line background listener. + * + * @param listener the listener which should no longer be notified + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT when listener is null</li> + * </ul> + */ +public void removeLineBackgroundListener(LineBackgroundListener listener) { + checkWidget(); + if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + removeListener(LineGetBackground, listener); +} +/** + * Removes the specified line style listener. + * + * @param listener the listener which should no longer be notified + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT when listener is null</li> + * </ul> + */ +public void removeLineStyleListener(LineStyleListener listener) { + checkWidget(); + if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + removeListener(LineGetStyle, listener); + setCaretLocation(); +} +/** + * Removes the specified modify listener. + * + * @param modifyListener the listener which should no longer be notified + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT when listener is null</li> + * </ul> + */ +public void removeModifyListener(ModifyListener modifyListener) { + checkWidget(); + if (modifyListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + removeListener(SWT.Modify, modifyListener); +} +/** + * Removes the specified listener. + * + * @param listener the listener which should no longer be notified + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT when listener is null</li> + * </ul> + * @since 3.2 + */ +public void removePaintObjectListener(PaintObjectListener listener) { + checkWidget(); + if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + removeListener(PaintObject, listener); +} +/** + * Removes the listener from the collection of listeners who will + * be notified when the user changes the receiver's selection. + * + * @param listener the listener which should no longer be notified + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the listener is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see SelectionListener + * @see #addSelectionListener + */ +public void removeSelectionListener(SelectionListener listener) { + checkWidget(); + if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + removeListener(SWT.Selection, listener); +} +/** + * Removes the specified verify listener. + * + * @param verifyListener the listener which should no longer be notified + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT when listener is null</li> + * </ul> + */ +public void removeVerifyListener(VerifyListener verifyListener) { + checkWidget(); + if (verifyListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + removeListener(SWT.Verify, verifyListener); +} +/** + * Removes the specified key verify listener. + * + * @param listener the listener which should no longer be notified + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT when listener is null</li> + * </ul> + */ +public void removeVerifyKeyListener(VerifyKeyListener listener) { + if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + removeListener(VerifyKey, listener); +} +/** + * Removes the specified word movement listener. + * + * @param listener the listener which should no longer be notified + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT when listener is null</li> + * </ul> + * + * @see MovementEvent + * @see MovementListener + * @see #addWordMovementListener + * + * @since 3.3 + */ + +public void removeWordMovementListener(MovementListener listener) { + checkWidget(); + if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + removeListener(WordNext, listener); + removeListener(WordPrevious, listener); +} +/** + * Replaces the styles in the given range with new styles. This method + * effectively deletes the styles in the given range and then adds the + * the new styles. + * <p> + * Note: Because a StyleRange includes the start and length, the + * same instance cannot occur multiple times in the array of styles. + * If the same style attributes, such as font and color, occur in + * multiple StyleRanges, <code>setStyleRanges(int, int, int[], StyleRange[])</code> + * can be used to share styles and reduce memory usage. + * </p><p> + * Should not be called if a LineStyleListener has been set since the + * listener maintains the styles. + * </p> + * + * @param start offset of first character where styles will be deleted + * @param length length of the range to delete styles in + * @param ranges StyleRange objects containing the new style information. + * The ranges should not overlap and should be within the specified start + * and length. The style rendering is undefined if the ranges do overlap + * or are ill-defined. Must not be null. + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_RANGE when either start or end is outside the valid range (0 <= offset <= getCharCount())</li> + * <li>ERROR_NULL_ARGUMENT when ranges is null</li> + * </ul> + * + * @since 2.0 + * + * @see #setStyleRanges(int, int, int[], StyleRange[]) + */ +public void replaceStyleRanges(int start, int length, StyleRange[] ranges) { + checkWidget(); + if (isListening(LineGetStyle)) return; + if (ranges == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + setStyleRanges(start, length, null, ranges, false); +} +/** + * Replaces the given text range with new text. + * If the widget has the SWT.SINGLE style and "text" contains more than + * one line, only the first line is rendered but the text is stored + * unchanged. A subsequent call to getText will return the same text + * that was set. Note that only a single line of text should be set when + * the SWT.SINGLE style is used. + * <p> + * <b>NOTE:</b> During the replace operation the current selection is + * changed as follows: + * <ul> + * <li>selection before replaced text: selection unchanged + * <li>selection after replaced text: adjust the selection so that same text + * remains selected + * <li>selection intersects replaced text: selection is cleared and caret + * is placed after inserted text + * </ul> + * </p> + * + * @param start offset of first character to replace + * @param length number of characters to replace. Use 0 to insert text + * @param text new text. May be empty to delete text. + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_RANGE when either start or end is outside the valid range (0 <= offset <= getCharCount())</li> + * <li>ERROR_INVALID_ARGUMENT when either start or end is inside a multi byte line delimiter. + * Splitting a line delimiter for example by inserting text in between the CR and LF and deleting part of a line delimiter is not supported</li> + * <li>ERROR_NULL_ARGUMENT when string is null</li> + * </ul> + */ +public void replaceTextRange(int start, int length, String text) { + checkWidget(); + if (text == null) { + SWT.error(SWT.ERROR_NULL_ARGUMENT); + } + int contentLength = getCharCount(); + int end = start + length; + if (start > end || start < 0 || end > contentLength) { + SWT.error(SWT.ERROR_INVALID_RANGE); + } + Event event = new Event(); + event.start = start; + event.end = end; + event.text = text; + modifyContent(event, false); +} +/** + * Resets the caret position, selection and scroll offsets. Recalculate + * the content width and scroll bars. Redraw the widget. + */ +void reset() { + ScrollBar verticalBar = getVerticalBar(); + ScrollBar horizontalBar = getHorizontalBar(); + setCaretOffset(0, SWT.DEFAULT); + topIndex = 0; + topIndexY = 0; + verticalScrollOffset = 0; + horizontalScrollOffset = 0; + resetSelection(); + renderer.setContent(content); + if (verticalBar != null) { + verticalBar.setSelection(0); + } + if (horizontalBar != null) { + horizontalBar.setSelection(0); + } + resetCache(0, 0); + setCaretLocation(); + super.redraw(); +} +void resetCache(int firstLine, int count) { + int maxLineIndex = renderer.maxWidthLineIndex; + renderer.reset(firstLine, count); + renderer.calculateClientArea(); + if (0 <= maxLineIndex && maxLineIndex < content.getLineCount()) { + renderer.calculate(maxLineIndex, 1); + } + setScrollBars(true); + if (!isFixedLineHeight()) { + if (topIndex > firstLine) { + verticalScrollOffset = -1; + } + renderer.calculateIdle(); + } +} +/** + * Resets the selection. + */ +void resetSelection() { + selection.x = selection.y = caretOffset; + selectionAnchor = -1; +} + +public void scroll(int destX, int destY, int x, int y, int width, int height, boolean all) { + super.scroll(destX, destY, x, y, width, height, false); + if (all) { + int deltaX = destX - x, deltaY = destY - y; + Control[] children = getChildren(); + for (int i=0; i<children.length; i++) { + Control child = children[i]; + Rectangle rect = child.getBounds(); + child.setLocation(rect.x + deltaX, rect.y + deltaY); + } + } +} + +/** + * Scrolls the widget horizontally. + * + * @param pixels number of pixels to scroll, > 0 = scroll left, + * < 0 scroll right + * @param adjustScrollBar + * true= the scroll thumb will be moved to reflect the new scroll offset. + * false = the scroll thumb will not be moved + * @return + * true=the widget was scrolled + * false=the widget was not scrolled, the given offset is not valid. + */ +boolean scrollHorizontal(int pixels, boolean adjustScrollBar) { + if (pixels == 0) { + return false; + } + ScrollBar horizontalBar = getHorizontalBar(); + if (horizontalBar != null && adjustScrollBar) { + horizontalBar.setSelection(horizontalScrollOffset + pixels); + } + int scrollHeight = clientAreaHeight - topMargin - bottomMargin; + if (pixels > 0) { + int sourceX = leftMargin + pixels; + int scrollWidth = clientAreaWidth - sourceX - rightMargin; + if (scrollWidth > 0) { + scroll(leftMargin, topMargin, sourceX, topMargin, scrollWidth, scrollHeight, true); + } + if (sourceX > scrollWidth) { + super.redraw(leftMargin + scrollWidth, topMargin, pixels - scrollWidth, scrollHeight, true); + } + } else { + int destinationX = leftMargin - pixels; + int scrollWidth = clientAreaWidth - destinationX - rightMargin; + if (scrollWidth > 0) { + scroll(destinationX, topMargin, leftMargin, topMargin, scrollWidth, scrollHeight, true); + } + if (destinationX > scrollWidth) { + super.redraw(leftMargin + scrollWidth, topMargin, -pixels - scrollWidth, scrollHeight, true); + } + } + horizontalScrollOffset += pixels; + setCaretLocation(); + return true; +} +/** + * Scrolls the widget vertically. + * + * @param pixel the new vertical scroll offset + * @param adjustScrollBar + * true= the scroll thumb will be moved to reflect the new scroll offset. + * false = the scroll thumb will not be moved + * @return + * true=the widget was scrolled + * false=the widget was not scrolled + */ +boolean scrollVertical(int pixels, boolean adjustScrollBar) { + if (pixels == 0) { + return false; + } + if (verticalScrollOffset != -1) { + ScrollBar verticalBar = getVerticalBar(); + if (verticalBar != null && adjustScrollBar) { + verticalBar.setSelection(verticalScrollOffset + pixels); + } + int scrollWidth = clientAreaWidth - leftMargin - rightMargin; + if (pixels > 0) { + int sourceY = topMargin + pixels; + int scrollHeight = clientAreaHeight - sourceY - bottomMargin; + if (scrollHeight > 0) { + scroll(leftMargin, topMargin, leftMargin, sourceY, scrollWidth, scrollHeight, true); + } + if (sourceY > scrollHeight) { + int redrawY = Math.max(0, topMargin + scrollHeight); + int redrawHeight = Math.min(clientAreaHeight, pixels - scrollHeight); + super.redraw(leftMargin, redrawY, scrollWidth, redrawHeight, true); + } + } else { + int destinationY = topMargin - pixels; + int scrollHeight = clientAreaHeight - destinationY - bottomMargin; + if (scrollHeight > 0) { + scroll(leftMargin, destinationY, leftMargin, topMargin, scrollWidth, scrollHeight, true); + } + if (destinationY > scrollHeight) { + int redrawY = Math.max(0, topMargin + scrollHeight); + int redrawHeight = Math.min(clientAreaHeight, -pixels - scrollHeight); + super.redraw(leftMargin, redrawY, scrollWidth, redrawHeight, true); + } + } + verticalScrollOffset += pixels; + calculateTopIndex(pixels); + } else { + calculateTopIndex(pixels); + super.redraw(); + } + setCaretLocation(); + return true; +} +void scrollText(int srcY, int destY) { + if (srcY == destY) return; + int deltaY = destY - srcY; + int scrollWidth = clientAreaWidth - leftMargin - rightMargin, scrollHeight; + if (deltaY > 0) { + scrollHeight = clientAreaHeight - srcY - bottomMargin; + } else { + scrollHeight = clientAreaHeight - destY - bottomMargin; + } + scroll(leftMargin, destY, leftMargin, srcY, scrollWidth, scrollHeight, true); + if ((0 < srcY + scrollHeight) && (topMargin > srcY)) { + super.redraw(leftMargin, deltaY, scrollWidth, topMargin, false); + } + if ((0 < destY + scrollHeight) && (topMargin > destY)) { + super.redraw(leftMargin, 0, scrollWidth, topMargin, false); + } + if ((clientAreaHeight - bottomMargin < srcY + scrollHeight) && (clientAreaHeight > srcY)) { + super.redraw(leftMargin, clientAreaHeight - bottomMargin + deltaY, scrollWidth, bottomMargin, false); + } + if ((clientAreaHeight - bottomMargin < destY + scrollHeight) && (clientAreaHeight > destY)) { + super.redraw(leftMargin, clientAreaHeight - bottomMargin, scrollWidth, bottomMargin, false); + } +} +void sendAccessibleTextChanged(int start, int newCharCount, int replaceCharCount) { + Accessible accessible = getAccessible(); + if (replaceCharCount != 0) { + accessible.textChanged(ACC.TEXT_DELETE, start, replaceCharCount); + } + if (newCharCount != 0) { + accessible.textChanged(ACC.TEXT_INSERT, start, newCharCount); + } +} +/** + * Selects all the text. + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void selectAll() { + checkWidget(); + if (blockSelection) { + renderer.calculate(0, content.getLineCount()); + setScrollBars(false); + int verticalScrollOffset = getVerticalScrollOffset(); + int left = leftMargin - horizontalScrollOffset; + int top = topMargin - verticalScrollOffset; + int right = renderer.getWidth() - rightMargin - horizontalScrollOffset; + int bottom = renderer.getHeight() - bottomMargin - verticalScrollOffset; + setBlockSelectionLocation(left, top, right, bottom, false); + return; + } + setSelection(0, Math.max(getCharCount(),0)); +} +/** + * Replaces/inserts text as defined by the event. + * + * @param event the text change event. + * <ul> + * <li>event.start - the replace start offset</li> + * <li>event.end - the replace end offset</li> + * <li>event.text - the new text</li> + * </ul> + */ +void sendKeyEvent(Event event) { + if (editable) { + modifyContent(event, true); + } +} +/** + * Returns a StyledTextEvent that can be used to request data such + * as styles and background color for a line. + * <p> + * The specified line may be a visual (wrapped) line if in word + * wrap mode. The returned object will always be for a logical + * (unwrapped) line. + * </p> + * + * @param lineOffset offset of the line. This may be the offset of + * a visual line if the widget is in word wrap mode. + * @param line line text. This may be the text of a visual line if + * the widget is in word wrap mode. + * @return StyledTextEvent that can be used to request line data + * for the given line. + */ +StyledTextEvent sendLineEvent(int eventType, int lineOffset, String line) { + StyledTextEvent event = null; + if (isListening(eventType)) { + event = new StyledTextEvent(content); + event.detail = lineOffset; + event.text = line; + event.alignment = alignment; + event.indent = indent; + event.justify = justify; + notifyListeners(eventType, event); + } + return event; +} +/** + * Sends the specified selection event. + */ +void sendSelectionEvent() { + getAccessible().textSelectionChanged(); + Event event = new Event(); + event.x = selection.x; + event.y = selection.y; + notifyListeners(SWT.Selection, event); +} +int sendTextEvent(int left, int right, int lineIndex, String text, boolean fillWithSpaces) { + int lineWidth = 0, start, end; + StringBuffer buffer = new StringBuffer(); + if (lineIndex < content.getLineCount()) { + int[] trailing = new int[1]; + start = getOffsetAtPoint(left, getLinePixel(lineIndex), trailing, true); + if (start == -1) { + int lineOffset = content.getOffsetAtLine(lineIndex); + int lineLegth = content.getLine(lineIndex).length(); + start = end = lineOffset + lineLegth; + if (fillWithSpaces) { + TextLayout layout = renderer.getTextLayout(lineIndex); + lineWidth = layout.getBounds().width; + renderer.disposeTextLayout(layout); + } + } else { + start += trailing[0]; + end = left == right ? start : getOffsetAtPoint(right, 0, lineIndex, null); + fillWithSpaces = false; + } + } else { + start = end = content.getCharCount(); + buffer.append(content.getLineDelimiter()); + } + if (start > end) { + int temp = start; + start = end; + end = temp; + } + if (fillWithSpaces) { + int spacesWidth = left - lineWidth + horizontalScrollOffset - leftMargin; + int spacesCount = spacesWidth / renderer.averageCharWidth; + for (int i = 0; i < spacesCount; i++) { + buffer.append(' '); + } + } + buffer.append(text); + Event event = new Event(); + event.start = start; + event.end = end; + event.text = buffer.toString(); + sendKeyEvent(event); + return event.start + event.text.length(); +} +int sendWordBoundaryEvent(int eventType, int movement, int offset, int newOffset, String lineText, int lineOffset) { + if (isListening(eventType)) { + StyledTextEvent event = new StyledTextEvent(content); + event.detail = lineOffset; + event.text = lineText; + event.count = movement; + event.start = offset; + event.end = newOffset; + notifyListeners(eventType, event); + offset = event.end; + if (offset != newOffset) { + int length = getCharCount(); + if (offset < 0) { + offset = 0; + } else if (offset > length) { + offset = length; + } else { + if (isLineDelimiter(offset)) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + } + } + return offset; + } + return newOffset; +} +void setAlignment() { + if ((getStyle() & SWT.SINGLE) == 0) return; + int alignment = renderer.getLineAlignment(0, this.alignment); + int newAlignmentMargin = 0; + if (alignment != SWT.LEFT) { + renderer.calculate(0, 1); + int width = renderer.getWidth() - alignmentMargin; + newAlignmentMargin = clientAreaWidth - width; + if (newAlignmentMargin < 0) newAlignmentMargin = 0; + if (alignment == SWT.CENTER) newAlignmentMargin /= 2; + } + if (alignmentMargin != newAlignmentMargin) { + leftMargin -= alignmentMargin; + leftMargin += newAlignmentMargin; + alignmentMargin = newAlignmentMargin; + resetCache(0, 1); + setCaretLocation(); + super.redraw(); + } +} +/** + * Sets the alignment of the widget. The argument should be one of <code>SWT.LEFT</code>, + * <code>SWT.CENTER</code> or <code>SWT.RIGHT</code>. The alignment applies for all lines. + * </p><p> + * Note that if <code>SWT.MULTI</code> is set, then <code>SWT.WRAP</code> must also be set + * in order to stabilize the right edge before setting alignment. + * </p> + * + * @param alignment the new alignment + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see #setLineAlignment(int, int, int) + * + * @since 3.2 + */ +public void setAlignment(int alignment) { + checkWidget(); + alignment &= (SWT.LEFT | SWT.RIGHT | SWT.CENTER); + if (alignment == 0 || this.alignment == alignment) return; + this.alignment = alignment; + resetCache(0, content.getLineCount()); + setCaretLocation(); + setAlignment(); + super.redraw(); +} +/** + * @see Control#setBackground(Color) + */ +public void setBackground(Color color) { + checkWidget(); + background = color; + super.setBackground(color); + resetCache(0, content.getLineCount()); + setCaretLocation(); + super.redraw(); +} +/** + * Sets the block selection mode. + * + * @param blockSelection true=enable block selection, false=disable block selection + * + * @since 3.5 + */ +public void setBlockSelection(boolean blockSelection) { + checkWidget(); + if ((getStyle() & SWT.SINGLE) != 0) return; + if (blockSelection == this.blockSelection) return; + if (wordWrap) return; + this.blockSelection = blockSelection; + if (cursor == null) { + Display display = getDisplay(); + int type = blockSelection ? SWT.CURSOR_CROSS : SWT.CURSOR_IBEAM; + super.setCursor(display.getSystemCursor(type)); + } + if (blockSelection) { + int start = selection.x; + int end = selection.y; + if (start != end) { + setBlockSelectionOffset(start, end, false); + } + } else { + clearBlockSelection(false, false); + } +} +/** + * Sets the block selection bounds. The bounds is + * relative to the upper left corner of the document. + * + * @param rect the new bounds for the block selection + * + * @see #setBlockSelectionBounds(int, int, int, int) + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT when point is null</li> + * </ul> + * + * @since 3.5 + */ +public void setBlockSelectionBounds(Rectangle rect) { + checkWidget(); + if (rect == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + setBlockSelectionBounds(rect.x, rect.y, rect.width, rect.height); +} +/** + * Sets the block selection bounds. The bounds is + * relative to the upper left corner of the document. + * + * @param x the new x coordinate for the block selection + * @param y the new y coordinate for the block selection + * @param width the new width for the block selection + * @param height the new height for the block selection + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.5 + */ +public void setBlockSelectionBounds(int x, int y, int width, int height) { + checkWidget(); + int verticalScrollOffset = getVerticalScrollOffset(); + if (!blockSelection) { + x -= horizontalScrollOffset; + y -= verticalScrollOffset; + int start = getOffsetAtPoint(x, y, null); + int end = getOffsetAtPoint(x+width-1, y+height-1, null); + setSelection(start, end - start, false, false); + setCaretLocation(); + return; + } + int minY = topMargin; + int minX = leftMargin; + int maxY = renderer.getHeight() - bottomMargin; + int maxX = Math.max(clientAreaWidth, renderer.getWidth()) - rightMargin; + int anchorX = Math.max(minX, Math.min(maxX, x)) - horizontalScrollOffset; + int anchorY = Math.max(minY, Math.min(maxY, y)) - verticalScrollOffset; + int locationX = Math.max(minX, Math.min(maxX, x + width)) - horizontalScrollOffset; + int locationY = Math.max(minY, Math.min(maxY, y + height - 1)) - verticalScrollOffset; + if (isFixedLineHeight() && renderer.fixedPitch) { + int avg = renderer.averageCharWidth; + anchorX = ((anchorX - leftMargin + horizontalScrollOffset) / avg * avg) + leftMargin - horizontalScrollOffset; + locationX = ((locationX + avg / 2 - leftMargin + horizontalScrollOffset) / avg * avg) + leftMargin - horizontalScrollOffset; + } + setBlockSelectionLocation(anchorX, anchorY, locationX, locationY, false); +} +void setBlockSelectionLocation (int x, int y, boolean sendEvent) { + int verticalScrollOffset = getVerticalScrollOffset(); + blockXLocation = x + horizontalScrollOffset; + blockYLocation = y + verticalScrollOffset; + int[] alignment = new int[1]; + int offset = getOffsetAtPoint(x, y, alignment); + setCaretOffset(offset, alignment[0]); + if (blockXAnchor == -1) { + blockXAnchor = blockXLocation; + blockYAnchor = blockYLocation; + selectionAnchor = caretOffset; + } + doBlockSelection(sendEvent); +} +void setBlockSelectionLocation (int anchorX, int anchorY, int x, int y, boolean sendEvent) { + int verticalScrollOffset = getVerticalScrollOffset(); + blockXAnchor = anchorX + horizontalScrollOffset; + blockYAnchor = anchorY + verticalScrollOffset; + selectionAnchor = getOffsetAtPoint(anchorX, anchorY, null); + setBlockSelectionLocation(x, y, sendEvent); +} +void setBlockSelectionOffset (int offset, boolean sendEvent) { + Point point = getPointAtOffset(offset); + int verticalScrollOffset = getVerticalScrollOffset(); + blockXLocation = point.x + horizontalScrollOffset; + blockYLocation = point.y + verticalScrollOffset; + setCaretOffset(offset, SWT.DEFAULT); + if (blockXAnchor == -1) { + blockXAnchor = blockXLocation; + blockYAnchor = blockYLocation; + selectionAnchor = caretOffset; + } + doBlockSelection(sendEvent); +} +void setBlockSelectionOffset (int anchorOffset, int offset, boolean sendEvent) { + int verticalScrollOffset = getVerticalScrollOffset(); + Point anchorPoint = getPointAtOffset(anchorOffset); + blockXAnchor = anchorPoint.x + horizontalScrollOffset; + blockYAnchor = anchorPoint.y + verticalScrollOffset; + selectionAnchor = anchorOffset; + setBlockSelectionOffset(offset, sendEvent); +} +/** + * Sets the receiver's caret. Set the caret's height and location. + * + * </p> + * @param caret the new caret for the receiver + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void setCaret(Caret caret) { + checkWidget (); + super.setCaret(caret); + caretDirection = SWT.NULL; + if (caret != null) { + setCaretLocation(); + } +} +/** + * Sets the BIDI coloring mode. When true the BIDI text display + * algorithm is applied to segments of text that are the same + * color. + * + * @param mode the new coloring mode + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @deprecated use BidiSegmentListener instead. + */ +public void setBidiColoring(boolean mode) { + checkWidget(); + bidiColoring = mode; +} +/** + * Sets the bottom margin. + * + * @param bottomMargin the bottom margin. + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.5 + */ +public void setBottomMargin (int bottomMargin) { + checkWidget(); + setMargins(leftMargin, topMargin, rightMargin, bottomMargin); +} +/** + * Moves the Caret to the current caret offset. + */ +void setCaretLocation() { + Point newCaretPos = getPointAtOffset(caretOffset); + setCaretLocation(newCaretPos, getCaretDirection()); +} +void setCaretLocation(Point location, int direction) { + Caret caret = getCaret(); + if (caret != null) { + boolean isDefaultCaret = caret == defaultCaret; + int lineHeight = renderer.getLineHeight(); + int caretHeight = lineHeight; + if (!isFixedLineHeight() && isDefaultCaret) { + caretHeight = getBoundsAtOffset(caretOffset).height; + if (caretHeight != lineHeight) { + direction = SWT.DEFAULT; + } + } + int imageDirection = direction; + if (isMirrored()) { + if (imageDirection == SWT.LEFT) { + imageDirection = SWT.RIGHT; + } else if (imageDirection == SWT.RIGHT) { + imageDirection = SWT.LEFT; + } + } + if (isDefaultCaret && imageDirection == SWT.RIGHT) { + location.x -= (caret.getSize().x - 1); + } + if (isDefaultCaret) { + caret.setBounds(location.x, location.y, caretWidth, caretHeight); + } else { + caret.setLocation(location); + } + getAccessible().textCaretMoved(getCaretOffset()); + if (direction != caretDirection) { + caretDirection = direction; + if (isDefaultCaret) { + if (imageDirection == SWT.DEFAULT) { + defaultCaret.setImage(null); + } else if (imageDirection == SWT.LEFT) { + defaultCaret.setImage(leftCaretBitmap); + } else if (imageDirection == SWT.RIGHT) { + defaultCaret.setImage(rightCaretBitmap); + } + } + if (caretDirection == SWT.LEFT) { + BidiUtil.setKeyboardLanguage(BidiUtil.KEYBOARD_NON_BIDI); + } else if (caretDirection == SWT.RIGHT) { + BidiUtil.setKeyboardLanguage(BidiUtil.KEYBOARD_BIDI); + } + } + updateCaretVisibility(); + } + columnX = location.x; +} +/** + * Sets the caret offset. + * + * @param offset caret offset, relative to the first character in the text. + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a + * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter) + * </ul> + */ +public void setCaretOffset(int offset) { + checkWidget(); + int length = getCharCount(); + if (length > 0 && offset != caretOffset) { + if (offset < 0) { + offset = 0; + } else if (offset > length) { + offset = length; + } else { + if (isLineDelimiter(offset)) { + // offset is inside a multi byte line delimiter. This is an + // illegal operation and an exception is thrown. Fixes 1GDKK3R + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + } + setCaretOffset(offset, PREVIOUS_OFFSET_TRAILING); + // clear the selection if the caret is moved. + // don't notify listeners about the selection change. + if (blockSelection) { + clearBlockSelection(true, false); + } else { + clearSelection(false); + } + } + setCaretLocation(); +} +void setCaretOffset(int offset, int alignment) { + if (caretOffset != offset) { + caretOffset = offset; + if (isListening(CaretMoved)) { + StyledTextEvent event = new StyledTextEvent(content); + event.end = caretOffset; + notifyListeners(CaretMoved, event); + } + } + if (alignment != SWT.DEFAULT) { + caretAlignment = alignment; + } +} +/** + * Copies the specified text range to the clipboard. The text will be placed + * in the clipboard in plain text format and RTF format. + * + * @param start start index of the text + * @param length length of text to place in clipboard + * + * @exception SWTError, see Clipboard.setContents + * @see org.eclipse.swt.dnd.Clipboard#setContents + */ +void setClipboardContent(int start, int length, int clipboardType) throws SWTError { + if (clipboardType == DND.SELECTION_CLIPBOARD && !(IS_MOTIF || IS_GTK)) return; + TextTransfer plainTextTransfer = TextTransfer.getInstance(); + TextWriter plainTextWriter = new TextWriter(start, length); + String plainText = getPlatformDelimitedText(plainTextWriter); + Object[] data; + Transfer[] types; + if (clipboardType == DND.SELECTION_CLIPBOARD) { + data = new Object[]{plainText}; + types = new Transfer[]{plainTextTransfer}; + } else { + RTFTransfer rtfTransfer = RTFTransfer.getInstance(); + RTFWriter rtfWriter = new RTFWriter(start, length); + String rtfText = getPlatformDelimitedText(rtfWriter); + data = new Object[]{rtfText, plainText}; + types = new Transfer[]{rtfTransfer, plainTextTransfer}; + } + clipboard.setContents(data, types, clipboardType); +} +/** + * Sets the content implementation to use for text storage. + * + * @param newContent StyledTextContent implementation to use for text storage. + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT when listener is null</li> + * </ul> + */ +public void setContent(StyledTextContent newContent) { + checkWidget(); + if (newContent == null) { + SWT.error(SWT.ERROR_NULL_ARGUMENT); + } + if (content != null) { + content.removeTextChangeListener(textChangeListener); + } + content = newContent; + content.addTextChangeListener(textChangeListener); + reset(); +} +/** + * Sets the receiver's cursor to the cursor specified by the + * argument. Overridden to handle the null case since the + * StyledText widget uses an ibeam as its default cursor. + * + * @see Control#setCursor(Cursor) + */ +public void setCursor (Cursor cursor) { + checkWidget(); + if (cursor != null && cursor.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT); + this.cursor = cursor; + if (cursor == null) { + Display display = getDisplay(); + int type = blockSelection ? SWT.CURSOR_CROSS : SWT.CURSOR_IBEAM; + super.setCursor(display.getSystemCursor(type)); + } else { + super.setCursor(cursor); + } +} +/** + * Sets whether the widget implements double click mouse behavior. + * </p> + * + * @param enable if true double clicking a word selects the word, if false + * double clicks have the same effect as regular mouse clicks. + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void setDoubleClickEnabled(boolean enable) { + checkWidget(); + doubleClickEnabled = enable; +} +public void setDragDetect (boolean dragDetect) { + checkWidget (); + this.dragDetect = dragDetect; +} +/** + * Sets whether the widget content can be edited. + * </p> + * + * @param editable if true content can be edited, if false content can not be + * edited + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void setEditable(boolean editable) { + checkWidget(); + this.editable = editable; +} +/** + * Sets a new font to render text with. + * <p> + * <b>NOTE:</b> Italic fonts are not supported unless they have no overhang + * and the same baseline as regular fonts. + * </p> + * + * @param font new font + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void setFont(Font font) { + checkWidget(); + int oldLineHeight = renderer.getLineHeight(); + super.setFont(font); + renderer.setFont(getFont(), tabLength); + // keep the same top line visible. fixes 5815 + if (isFixedLineHeight()) { + int lineHeight = renderer.getLineHeight(); + if (lineHeight != oldLineHeight) { + int vscroll = (getVerticalScrollOffset() * lineHeight / oldLineHeight) - getVerticalScrollOffset(); + scrollVertical(vscroll, true); + } + } + resetCache(0, content.getLineCount()); + claimBottomFreeSpace(); + calculateScrollBars(); + if (isBidiCaret()) createCaretBitmaps(); + caretDirection = SWT.NULL; + setCaretLocation(); + super.redraw(); +} +public void setForeground(Color color) { + checkWidget(); + foreground = color; + super.setForeground(getForeground()); + resetCache(0, content.getLineCount()); + setCaretLocation(); + super.redraw(); +} +/** + * Sets the horizontal scroll offset relative to the start of the line. + * Do nothing if there is no text set. + * <p> + * <b>NOTE:</b> The horizontal index is reset to 0 when new text is set in the + * widget. + * </p> + * + * @param offset horizontal scroll offset relative to the start + * of the line, measured in character increments starting at 0, if + * equal to 0 the content is not scrolled, if > 0 = the content is scrolled. + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void setHorizontalIndex(int offset) { + checkWidget(); + if (getCharCount() == 0) { + return; + } + if (offset < 0) { + offset = 0; + } + offset *= getHorizontalIncrement(); + // allow any value if client area width is unknown or 0. + // offset will be checked in resize handler. + // don't use isVisible since width is known even if widget + // is temporarily invisible + if (clientAreaWidth > 0) { + int width = renderer.getWidth(); + // prevent scrolling if the content fits in the client area. + // align end of longest line with right border of client area + // if offset is out of range. + if (offset > width - clientAreaWidth) { + offset = Math.max(0, width - clientAreaWidth); + } + } + scrollHorizontal(offset - horizontalScrollOffset, true); +} +/** + * Sets the horizontal pixel offset relative to the start of the line. + * Do nothing if there is no text set. + * <p> + * <b>NOTE:</b> The horizontal pixel offset is reset to 0 when new text + * is set in the widget. + * </p> + * + * @param pixel horizontal pixel offset relative to the start + * of the line. + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @since 2.0 + */ +public void setHorizontalPixel(int pixel) { + checkWidget(); + if (getCharCount() == 0) { + return; + } + if (pixel < 0) { + pixel = 0; + } + // allow any value if client area width is unknown or 0. + // offset will be checked in resize handler. + // don't use isVisible since width is known even if widget + // is temporarily invisible + if (clientAreaWidth > 0) { + int width = renderer.getWidth(); + // prevent scrolling if the content fits in the client area. + // align end of longest line with right border of client area + // if offset is out of range. + if (pixel > width - clientAreaWidth) { + pixel = Math.max(0, width - clientAreaWidth); + } + } + scrollHorizontal(pixel - horizontalScrollOffset, true); +} +/** + * Sets the line indentation of the widget. + * <p> + * It is the amount of blank space, in pixels, at the beginning of each line. + * When a line wraps in several lines only the first one is indented. + * </p> + * + * @param indent the new indent + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see #setLineIndent(int, int, int) + * + * @since 3.2 + */ +public void setIndent(int indent) { + checkWidget(); + if (this.indent == indent || indent < 0) return; + this.indent = indent; + resetCache(0, content.getLineCount()); + setCaretLocation(); + super.redraw(); +} +/** + * Sets whether the widget should justify lines. + * + * @param justify whether lines should be justified + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see #setLineJustify(int, int, boolean) + * + * @since 3.2 + */ +public void setJustify(boolean justify) { + checkWidget(); + if (this.justify == justify) return; + this.justify = justify; + resetCache(0, content.getLineCount()); + setCaretLocation(); + super.redraw(); +} +/** + * Maps a key to an action. + * <p> + * One action can be associated with N keys. However, each key can only + * have one action (key:action is N:1 relation). + * </p> + * + * @param key a key code defined in SWT.java or a character. + * Optionally ORd with a state mask. Preferred state masks are one or more of + * SWT.MOD1, SWT.MOD2, SWT.MOD3, since these masks account for modifier platform + * differences. However, there may be cases where using the specific state masks + * (i.e., SWT.CTRL, SWT.SHIFT, SWT.ALT, SWT.COMMAND) makes sense. + * @param action one of the predefined actions defined in ST.java. + * Use SWT.NULL to remove a key binding. + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void setKeyBinding(int key, int action) { + checkWidget(); + int modifierValue = key & SWT.MODIFIER_MASK; + char keyChar = (char)(key & SWT.KEY_MASK); + if (Compatibility.isLetter(keyChar)) { + // make the keybinding case insensitive by adding it + // in its upper and lower case form + char ch = Character.toUpperCase(keyChar); + int newKey = ch | modifierValue; + if (action == SWT.NULL) { + keyActionMap.remove(new Integer(newKey)); + } else { + keyActionMap.put(new Integer(newKey), new Integer(action)); + } + ch = Character.toLowerCase(keyChar); + newKey = ch | modifierValue; + if (action == SWT.NULL) { + keyActionMap.remove(new Integer(newKey)); + } else { + keyActionMap.put(new Integer(newKey), new Integer(action)); + } + } else { + if (action == SWT.NULL) { + keyActionMap.remove(new Integer(key)); + } else { + keyActionMap.put(new Integer(key), new Integer(action)); + } + } +} +/** + * Sets the left margin. + * + * @param leftMargin the left margin. + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.5 + */ +public void setLeftMargin (int leftMargin) { + checkWidget(); + setMargins(leftMargin, topMargin, rightMargin, bottomMargin); +} +/** + * Sets the alignment of the specified lines. The argument should be one of <code>SWT.LEFT</code>, + * <code>SWT.CENTER</code> or <code>SWT.RIGHT</code>. + * <p><p> + * Note that if <code>SWT.MULTI</code> is set, then <code>SWT.WRAP</code> must also be set + * in order to stabilize the right edge before setting alignment. + * </p> + * Should not be called if a LineStyleListener has been set since the listener + * maintains the line attributes. + * </p><p> + * All line attributes are maintained relative to the line text, not the + * line index that is specified in this method call. + * During text changes, when entire lines are inserted or removed, the line + * attributes that are associated with the lines after the change + * will "move" with their respective text. An entire line is defined as + * extending from the first character on a line to the last and including the + * line delimiter. + * </p><p> + * When two lines are joined by deleting a line delimiter, the top line + * attributes take precedence and the attributes of the bottom line are deleted. + * For all other text changes line attributes will remain unchanged. + * + * @param startLine first line the alignment is applied to, 0 based + * @param lineCount number of lines the alignment applies to. + * @param alignment line alignment + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li> + * </ul> + * @see #setAlignment(int) + * @since 3.2 + */ +public void setLineAlignment(int startLine, int lineCount, int alignment) { + checkWidget(); + if (isListening(LineGetStyle)) return; + if (startLine < 0 || startLine + lineCount > content.getLineCount()) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + + renderer.setLineAlignment(startLine, lineCount, alignment); + resetCache(startLine, lineCount); + redrawLines(startLine, lineCount, false); + int caretLine = getCaretLine(); + if (startLine <= caretLine && caretLine < startLine + lineCount) { + setCaretLocation(); + } + setAlignment(); +} +/** + * Sets the background color of the specified lines. + * <p> + * The background color is drawn for the width of the widget. All + * line background colors are discarded when setText is called. + * The text background color if defined in a StyleRange overlays the + * line background color. + * </p><p> + * Should not be called if a LineBackgroundListener has been set since the + * listener maintains the line backgrounds. + * </p><p> + * All line attributes are maintained relative to the line text, not the + * line index that is specified in this method call. + * During text changes, when entire lines are inserted or removed, the line + * attributes that are associated with the lines after the change + * will "move" with their respective text. An entire line is defined as + * extending from the first character on a line to the last and including the + * line delimiter. + * </p><p> + * When two lines are joined by deleting a line delimiter, the top line + * attributes take precedence and the attributes of the bottom line are deleted. + * For all other text changes line attributes will remain unchanged. + * </p> + * + * @param startLine first line the color is applied to, 0 based + * @param lineCount number of lines the color applies to. + * @param background line background color + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li> + * </ul> + */ +public void setLineBackground(int startLine, int lineCount, Color background) { + checkWidget(); + if (isListening(LineGetBackground)) return; + if (startLine < 0 || startLine + lineCount > content.getLineCount()) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + if (background != null) { + renderer.setLineBackground(startLine, lineCount, background); + } else { + renderer.clearLineBackground(startLine, lineCount); + } + redrawLines(startLine, lineCount, false); +} +/** + * Sets the bullet of the specified lines. + * <p> + * Should not be called if a LineStyleListener has been set since the listener + * maintains the line attributes. + * </p><p> + * All line attributes are maintained relative to the line text, not the + * line index that is specified in this method call. + * During text changes, when entire lines are inserted or removed, the line + * attributes that are associated with the lines after the change + * will "move" with their respective text. An entire line is defined as + * extending from the first character on a line to the last and including the + * line delimiter. + * </p><p> + * When two lines are joined by deleting a line delimiter, the top line + * attributes take precedence and the attributes of the bottom line are deleted. + * For all other text changes line attributes will remain unchanged. + * </p> + * + * @param startLine first line the bullet is applied to, 0 based + * @param lineCount number of lines the bullet applies to. + * @param bullet line bullet + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li> + * </ul> + * @since 3.2 + */ +public void setLineBullet(int startLine, int lineCount, Bullet bullet) { + checkWidget(); + if (isListening(LineGetStyle)) return; + if (startLine < 0 || startLine + lineCount > content.getLineCount()) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + int oldBottom = getLinePixel(startLine + lineCount); + renderer.setLineBullet(startLine, lineCount, bullet); + resetCache(startLine, lineCount); + int newBottom = getLinePixel(startLine + lineCount); + redrawLines(startLine, lineCount, oldBottom != newBottom); + int caretLine = getCaretLine(); + if (startLine <= caretLine && caretLine < startLine + lineCount) { + setCaretLocation(); + } +} +void setVariableLineHeight () { + if (!fixedLineHeight) return; + fixedLineHeight = false; + renderer.calculateIdle(); +} +/** + * Sets the indent of the specified lines. + * <p> + * Should not be called if a LineStyleListener has been set since the listener + * maintains the line attributes. + * </p><p> + * All line attributes are maintained relative to the line text, not the + * line index that is specified in this method call. + * During text changes, when entire lines are inserted or removed, the line + * attributes that are associated with the lines after the change + * will "move" with their respective text. An entire line is defined as + * extending from the first character on a line to the last and including the + * line delimiter. + * </p><p> + * When two lines are joined by deleting a line delimiter, the top line + * attributes take precedence and the attributes of the bottom line are deleted. + * For all other text changes line attributes will remain unchanged. + * </p> + * + * @param startLine first line the indent is applied to, 0 based + * @param lineCount number of lines the indent applies to. + * @param indent line indent + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li> + * </ul> + * @see #setIndent(int) + * @since 3.2 + */ +public void setLineIndent(int startLine, int lineCount, int indent) { + checkWidget(); + if (isListening(LineGetStyle)) return; + if (startLine < 0 || startLine + lineCount > content.getLineCount()) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + int oldBottom = getLinePixel(startLine + lineCount); + renderer.setLineIndent(startLine, lineCount, indent); + resetCache(startLine, lineCount); + int newBottom = getLinePixel(startLine + lineCount); + redrawLines(startLine, lineCount, oldBottom != newBottom); + int caretLine = getCaretLine(); + if (startLine <= caretLine && caretLine < startLine + lineCount) { + setCaretLocation(); + } +} +/** + * Sets the justify of the specified lines. + * <p> + * Should not be called if a LineStyleListener has been set since the listener + * maintains the line attributes. + * </p><p> + * All line attributes are maintained relative to the line text, not the + * line index that is specified in this method call. + * During text changes, when entire lines are inserted or removed, the line + * attributes that are associated with the lines after the change + * will "move" with their respective text. An entire line is defined as + * extending from the first character on a line to the last and including the + * line delimiter. + * </p><p> + * When two lines are joined by deleting a line delimiter, the top line + * attributes take precedence and the attributes of the bottom line are deleted. + * For all other text changes line attributes will remain unchanged. + * </p> + * + * @param startLine first line the justify is applied to, 0 based + * @param lineCount number of lines the justify applies to. + * @param justify true if lines should be justified + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT when the specified line range is invalid</li> + * </ul> + * @see #setJustify(boolean) + * @since 3.2 + */ +public void setLineJustify(int startLine, int lineCount, boolean justify) { + checkWidget(); + if (isListening(LineGetStyle)) return; + if (startLine < 0 || startLine + lineCount > content.getLineCount()) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + + renderer.setLineJustify(startLine, lineCount, justify); + resetCache(startLine, lineCount); + redrawLines(startLine, lineCount, false); + int caretLine = getCaretLine(); + if (startLine <= caretLine && caretLine < startLine + lineCount) { + setCaretLocation(); + } +} +/** + * Sets the line spacing of the widget. The line spacing applies for all lines. + * + * @param lineSpacing the line spacing + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @since 3.2 + */ +public void setLineSpacing(int lineSpacing) { + checkWidget(); + if (this.lineSpacing == lineSpacing || lineSpacing < 0) return; + this.lineSpacing = lineSpacing; + setVariableLineHeight(); + resetCache(0, content.getLineCount()); + setCaretLocation(); + super.redraw(); +} +/** + * Sets the color of the margins. + * + * @param color the new color (or null) + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.5 + */ +public void setMarginColor(Color color) { + checkWidget(); + if (color != null && color.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT); + marginColor = color; + super.redraw(); +} +/** + * Sets the margins. + * + * @param leftMargin the left margin. + * @param topMargin the top margin. + * @param rightMargin the right margin. + * @param bottomMargin the bottom margin. + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.5 + */ +public void setMargins (int leftMargin, int topMargin, int rightMargin, int bottomMargin) { + checkWidget(); + this.leftMargin = Math.max(0, leftMargin); + this.topMargin = Math.max(0, topMargin); + this.rightMargin = Math.max(0, rightMargin); + this.bottomMargin = Math.max(0, bottomMargin); + resetCache(0, content.getLineCount()); + setScrollBars(true); + setCaretLocation(); + setAlignment(); + super.redraw(); +} +/** + * Flips selection anchor based on word selection direction. + */ +void setMouseWordSelectionAnchor() { + if (clickCount > 1) { + if (caretOffset < doubleClickSelection.x) { + selectionAnchor = doubleClickSelection.y; + } else if (caretOffset > doubleClickSelection.y) { + selectionAnchor = doubleClickSelection.x; + } + } +} +/** + * Sets the orientation of the receiver, which must be one + * of the constants <code>SWT.LEFT_TO_RIGHT</code> or <code>SWT.RIGHT_TO_LEFT</code>. + * + * @param orientation new orientation style + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 2.1.2 + */ +public void setOrientation(int orientation) { + if ((orientation & (SWT.RIGHT_TO_LEFT | SWT.LEFT_TO_RIGHT)) == 0) { + return; + } + if ((orientation & SWT.RIGHT_TO_LEFT) != 0 && (orientation & SWT.LEFT_TO_RIGHT) != 0) { + return; + } + if ((orientation & SWT.RIGHT_TO_LEFT) != 0 && isMirrored()) { + return; + } + if ((orientation & SWT.LEFT_TO_RIGHT) != 0 && !isMirrored()) { + return; + } + if (!BidiUtil.setOrientation(this, orientation)) { + return; + } + isMirrored = (orientation & SWT.RIGHT_TO_LEFT) != 0; + caretDirection = SWT.NULL; + resetCache(0, content.getLineCount()); + setCaretLocation(); + keyActionMap.clear(); + createKeyBindings(); + super.redraw(); +} +/** + * Sets the right margin. + * + * @param rightMargin the right margin. + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.5 + */ +public void setRightMargin (int rightMargin) { + checkWidget(); + setMargins(leftMargin, topMargin, rightMargin, bottomMargin); +} +/** + * Adjusts the maximum and the page size of the scroll bars to + * reflect content width/length changes. + * + * @param vertical indicates if the vertical scrollbar also needs to be set + */ +void setScrollBars(boolean vertical) { + int inactive = 1; + if (vertical || !isFixedLineHeight()) { + ScrollBar verticalBar = getVerticalBar(); + if (verticalBar != null) { + int maximum = renderer.getHeight(); + // only set the real values if the scroll bar can be used + // (ie. because the thumb size is less than the scroll maximum) + // avoids flashing on Motif, fixes 1G7RE1J and 1G5SE92 + if (clientAreaHeight < maximum) { + verticalBar.setMaximum(maximum - topMargin - bottomMargin); + verticalBar.setThumb(clientAreaHeight - topMargin - bottomMargin); + verticalBar.setPageIncrement(clientAreaHeight - topMargin - bottomMargin); + } else if (verticalBar.getThumb() != inactive || verticalBar.getMaximum() != inactive) { + verticalBar.setValues( + verticalBar.getSelection(), + verticalBar.getMinimum(), + inactive, + inactive, + verticalBar.getIncrement(), + inactive); + } + } + } + ScrollBar horizontalBar = getHorizontalBar(); + if (horizontalBar != null && horizontalBar.getVisible()) { + int maximum = renderer.getWidth(); + // only set the real values if the scroll bar can be used + // (ie. because the thumb size is less than the scroll maximum) + // avoids flashing on Motif, fixes 1G7RE1J and 1G5SE92 + if (clientAreaWidth < maximum) { + horizontalBar.setMaximum(maximum - leftMargin - rightMargin); + horizontalBar.setThumb(clientAreaWidth - leftMargin - rightMargin); + horizontalBar.setPageIncrement(clientAreaWidth - leftMargin - rightMargin); + } else if (horizontalBar.getThumb() != inactive || horizontalBar.getMaximum() != inactive) { + horizontalBar.setValues( + horizontalBar.getSelection(), + horizontalBar.getMinimum(), + inactive, + inactive, + horizontalBar.getIncrement(), + inactive); + } + } +} +/** + * Sets the selection to the given position and scrolls it into view. Equivalent to setSelection(start,start). + * + * @param start new caret position + * @see #setSelection(int,int) + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a + * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter) + * </ul> + */ +public void setSelection(int start) { + // checkWidget test done in setSelectionRange + setSelection(start, start); +} +/** + * Sets the selection and scrolls it into view. + * <p> + * Indexing is zero based. Text selections are specified in terms of + * caret positions. In a text widget that contains N characters, there are + * N+1 caret positions, ranging from 0..N + * </p> + * + * @param point x=selection start offset, y=selection end offset + * The caret will be placed at the selection start when x > y. + * @see #setSelection(int,int) + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT when point is null</li> + * <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a + * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter) + * </ul> + */ +public void setSelection(Point point) { + checkWidget(); + if (point == null) SWT.error (SWT.ERROR_NULL_ARGUMENT); + setSelection(point.x, point.y); +} +/** + * Sets the receiver's selection background color to the color specified + * by the argument, or to the default system color for the control + * if the argument is null. + * + * @param color the new color (or null) + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @since 2.1 + */ +public void setSelectionBackground (Color color) { + checkWidget (); + if (color != null) { + if (color.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + selectionBackground = color; + resetCache(0, content.getLineCount()); + setCaretLocation(); + super.redraw(); +} +/** + * Sets the receiver's selection foreground color to the color specified + * by the argument, or to the default system color for the control + * if the argument is null. + * <p> + * Note that this is a <em>HINT</em>. Some platforms do not allow the application + * to change the selection foreground color. + * </p> + * @param color the new color (or null) + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @since 2.1 + */ +public void setSelectionForeground (Color color) { + checkWidget (); + if (color != null) { + if (color.isDisposed()) SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + selectionForeground = color; + resetCache(0, content.getLineCount()); + setCaretLocation(); + super.redraw(); +} +/** + * Sets the selection and scrolls it into view. + * <p> + * Indexing is zero based. Text selections are specified in terms of + * caret positions. In a text widget that contains N characters, there are + * N+1 caret positions, ranging from 0..N + * </p> + * + * @param start selection start offset. The caret will be placed at the + * selection start when start > end. + * @param end selection end offset + * @see #setSelectionRange(int,int) + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a + * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter) + * </ul> + */ +public void setSelection(int start, int end) { + setSelectionRange(start, end - start); + showSelection(); +} +/** + * Sets the selection. + * <p> + * The new selection may not be visible. Call showSelection to scroll + * the selection into view. + * </p> + * + * @param start offset of the first selected character, start >= 0 must be true. + * @param length number of characters to select, 0 <= start + length + * <= getCharCount() must be true. + * A negative length places the caret at the selection start. + * @param sendEvent a Selection event is sent when set to true and when + * the selection is reset. + */ +void setSelection(int start, int length, boolean sendEvent, boolean doBlock) { + int end = start + length; + if (start > end) { + int temp = end; + end = start; + start = temp; + } + // is the selection range different or is the selection direction + // different? + if (selection.x != start || selection.y != end || + (length > 0 && selectionAnchor != selection.x) || + (length < 0 && selectionAnchor != selection.y)) { + if (blockSelection && doBlock) { + setBlockSelectionOffset(start, end, sendEvent); + } else { + clearSelection(sendEvent); + if (length < 0) { + selectionAnchor = selection.y = end; + selection.x = start; + setCaretOffset(start, PREVIOUS_OFFSET_TRAILING); + } else { + selectionAnchor = selection.x = start; + selection.y = end; + setCaretOffset(end, PREVIOUS_OFFSET_TRAILING); + } + internalRedrawRange(selection.x, selection.y - selection.x); + } + } +} +/** + * Sets the selection. + * <p> + * The new selection may not be visible. Call showSelection to scroll the selection + * into view. A negative length places the caret at the visual start of the selection. + * </p> + * + * @param start offset of the first selected character + * @param length number of characters to select + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT when either the start or the end of the selection range is inside a + * multi byte line delimiter (and thus neither clearly in front of or after the line delimiter) + * </ul> + */ +public void setSelectionRange(int start, int length) { + checkWidget(); + int contentLength = getCharCount(); + start = Math.max(0, Math.min (start, contentLength)); + int end = start + length; + if (end < 0) { + length = -start; + } else { + if (end > contentLength) length = contentLength - start; + } + if (isLineDelimiter(start) || isLineDelimiter(start + length)) { + // the start offset or end offset of the selection range is inside a + // multi byte line delimiter. This is an illegal operation and an exception + // is thrown. Fixes 1GDKK3R + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + setSelection(start, length, false, true); + setCaretLocation(); +} +/** + * Adds the specified style. + * <p> + * The new style overwrites existing styles for the specified range. + * Existing style ranges are adjusted if they partially overlap with + * the new style. To clear an individual style, call setStyleRange + * with a StyleRange that has null attributes. + * </p><p> + * Should not be called if a LineStyleListener has been set since the + * listener maintains the styles. + * </p> + * + * @param range StyleRange object containing the style information. + * Overwrites the old style in the given range. May be null to delete + * all styles. + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_RANGE when the style range is outside the valid range (> getCharCount())</li> + * </ul> + */ +public void setStyleRange(StyleRange range) { + checkWidget(); + if (isListening(LineGetStyle)) return; + if (range != null) { + if (range.isUnstyled()) { + setStyleRanges(range.start, range.length, null, null, false); + } else { + setStyleRanges(range.start, 0, null, new StyleRange[]{range}, false); + } + } else { + setStyleRanges(0, 0, null, null, true); + } +} +/** + * Clears the styles in the range specified by <code>start</code> and + * <code>length</code> and adds the new styles. + * <p> + * The ranges array contains start and length pairs. Each pair refers to + * the corresponding style in the styles array. For example, the pair + * that starts at ranges[n] with length ranges[n+1] uses the style + * at styles[n/2]. The range fields within each StyleRange are ignored. + * If ranges or styles is null, the specified range is cleared. + * </p><p> + * Note: It is expected that the same instance of a StyleRange will occur + * multiple times within the styles array, reducing memory usage. + * </p><p> + * Should not be called if a LineStyleListener has been set since the + * listener maintains the styles. + * </p> + * + * @param start offset of first character where styles will be deleted + * @param length length of the range to delete styles in + * @param ranges the array of ranges. The ranges must not overlap and must be in order. + * @param styles the array of StyleRanges. The range fields within the StyleRange are unused. + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT when an element in the styles array is null</li> + * <li>ERROR_INVALID_RANGE when the number of ranges and style do not match (ranges.length * 2 == styles.length)</li> + * <li>ERROR_INVALID_RANGE when a range is outside the valid range (> getCharCount() or less than zero)</li> + * <li>ERROR_INVALID_RANGE when a range overlaps</li> + * </ul> + * + * @since 3.2 + */ +public void setStyleRanges(int start, int length, int[] ranges, StyleRange[] styles) { + checkWidget(); + if (isListening(LineGetStyle)) return; + if (ranges == null || styles == null) { + setStyleRanges(start, length, null, null, false); + } else { + setStyleRanges(start, length, ranges, styles, false); + } +} +/** + * Sets styles to be used for rendering the widget content. + * <p> + * All styles in the widget will be replaced with the given set of ranges and styles. + * The ranges array contains start and length pairs. Each pair refers to + * the corresponding style in the styles array. For example, the pair + * that starts at ranges[n] with length ranges[n+1] uses the style + * at styles[n/2]. The range fields within each StyleRange are ignored. + * If either argument is null, the styles are cleared. + * </p><p> + * Note: It is expected that the same instance of a StyleRange will occur + * multiple times within the styles array, reducing memory usage. + * </p><p> + * Should not be called if a LineStyleListener has been set since the + * listener maintains the styles. + * </p> + * + * @param ranges the array of ranges. The ranges must not overlap and must be in order. + * @param styles the array of StyleRanges. The range fields within the StyleRange are unused. + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT when an element in the styles array is null</li> + * <li>ERROR_INVALID_RANGE when the number of ranges and style do not match (ranges.length * 2 == styles.length)</li> + * <li>ERROR_INVALID_RANGE when a range is outside the valid range (> getCharCount() or less than zero)</li> + * <li>ERROR_INVALID_RANGE when a range overlaps</li> + * </ul> + * + * @since 3.2 + */ +public void setStyleRanges(int[] ranges, StyleRange[] styles) { + checkWidget(); + if (isListening(LineGetStyle)) return; + if (ranges == null || styles == null) { + setStyleRanges(0, 0, null, null, true); + } else { + setStyleRanges(0, 0, ranges, styles, true); + } +} +void setStyleRanges(int start, int length, int[] ranges, StyleRange[] styles, boolean reset) { + int charCount = content.getCharCount(); + int end = start + length; + if (start > end || start < 0) { + SWT.error(SWT.ERROR_INVALID_RANGE); + } + if (styles != null) { + if (end > charCount) { + SWT.error(SWT.ERROR_INVALID_RANGE); + } + if (ranges != null) { + if (ranges.length != styles.length << 1) SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + int lastOffset = 0; + boolean variableHeight = false; + for (int i = 0; i < styles.length; i ++) { + if (styles[i] == null) SWT.error(SWT.ERROR_INVALID_ARGUMENT); + int rangeStart, rangeLength; + if (ranges != null) { + rangeStart = ranges[i << 1]; + rangeLength = ranges[(i << 1) + 1]; + } else { + rangeStart = styles[i].start; + rangeLength = styles[i].length; + } + if (rangeLength < 0) SWT.error(SWT.ERROR_INVALID_ARGUMENT); + if (!(0 <= rangeStart && rangeStart + rangeLength <= charCount)) SWT.error(SWT.ERROR_INVALID_ARGUMENT); + if (lastOffset > rangeStart) SWT.error(SWT.ERROR_INVALID_ARGUMENT); + variableHeight |= styles[i].isVariableHeight(); + lastOffset = rangeStart + rangeLength; + } + if (variableHeight) setVariableLineHeight(); + } + int rangeStart = start, rangeEnd = end; + if (styles != null && styles.length > 0) { + if (ranges != null) { + rangeStart = ranges[0]; + rangeEnd = ranges[ranges.length - 2] + ranges[ranges.length - 1]; + } else { + rangeStart = styles[0].start; + rangeEnd = styles[styles.length - 1].start + styles[styles.length - 1].length; + } + } + int expectedBottom = 0; + if (!isFixedLineHeight() && !reset) { + int lineEnd = content.getLineAtOffset(Math.max(end, rangeEnd)); + int partialTopIndex = getPartialTopIndex(); + int partialBottomIndex = getPartialBottomIndex(); + if (partialTopIndex <= lineEnd && lineEnd <= partialBottomIndex) { + expectedBottom = getLinePixel(lineEnd + 1); + } + } + if (reset) { + renderer.setStyleRanges(null, null); + } else { + renderer.updateRanges(start, length, length); + } + if (styles != null && styles.length > 0) { + renderer.setStyleRanges(ranges, styles); + } + if (reset) { + resetCache(0, content.getLineCount()); + super.redraw(); + } else { + int lineStart = content.getLineAtOffset(Math.min(start, rangeStart)); + int lineEnd = content.getLineAtOffset(Math.max(end, rangeEnd)); + resetCache(lineStart, lineEnd - lineStart + 1); + int partialTopIndex = getPartialTopIndex(); + int partialBottomIndex = getPartialBottomIndex(); + if (!(lineStart > partialBottomIndex || lineEnd < partialTopIndex)) { + int top = 0; + int bottom = clientAreaHeight; + if (partialTopIndex <= lineStart && lineStart <= partialBottomIndex) { + top = Math.max(0, getLinePixel(lineStart)); + } + if (partialTopIndex <= lineEnd && lineEnd <= partialBottomIndex) { + bottom = getLinePixel(lineEnd + 1); + } + if (!isFixedLineHeight() && bottom != expectedBottom) { + bottom = clientAreaHeight; + } + super.redraw(0, top, clientAreaWidth, bottom - top, false); + } + } + setCaretLocation(); + doMouseLinkCursor(); +} +/** + * Sets styles to be used for rendering the widget content. All styles + * in the widget will be replaced with the given set of styles. + * <p> + * Note: Because a StyleRange includes the start and length, the + * same instance cannot occur multiple times in the array of styles. + * If the same style attributes, such as font and color, occur in + * multiple StyleRanges, <code>setStyleRanges(int[], StyleRange[])</code> + * can be used to share styles and reduce memory usage. + * </p><p> + * Should not be called if a LineStyleListener has been set since the + * listener maintains the styles. + * </p> + * + * @param ranges StyleRange objects containing the style information. + * The ranges should not overlap. The style rendering is undefined if + * the ranges do overlap. Must not be null. The styles need to be in order. + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT when the list of ranges is null</li> + * <li>ERROR_INVALID_RANGE when the last of the style ranges is outside the valid range (> getCharCount())</li> + * </ul> + * + * @see #setStyleRanges(int[], StyleRange[]) + */ +public void setStyleRanges(StyleRange[] ranges) { + checkWidget(); + if (isListening(LineGetStyle)) return; + if (ranges == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + setStyleRanges(0, 0, null, ranges, true); +} +/** + * Sets the tab width. + * + * @param tabs tab width measured in characters. + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void setTabs(int tabs) { + checkWidget(); + tabLength = tabs; + renderer.setFont(null, tabs); + resetCache(0, content.getLineCount()); + setCaretLocation(); + super.redraw(); +} +/** + * Sets the widget content. + * If the widget has the SWT.SINGLE style and "text" contains more than + * one line, only the first line is rendered but the text is stored + * unchanged. A subsequent call to getText will return the same text + * that was set. + * <p> + * <b>Note:</b> Only a single line of text should be set when the SWT.SINGLE + * style is used. + * </p> + * + * @param text new widget content. Replaces existing content. Line styles + * that were set using StyledText API are discarded. The + * current selection is also discarded. + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT when string is null</li> + * </ul> + */ +public void setText(String text) { + checkWidget(); + if (text == null) { + SWT.error(SWT.ERROR_NULL_ARGUMENT); + } + Event event = new Event(); + event.start = 0; + event.end = getCharCount(); + event.text = text; + event.doit = true; + notifyListeners(SWT.Verify, event); + if (event.doit) { + StyledTextEvent styledTextEvent = null; + if (isListening(ExtendedModify)) { + styledTextEvent = new StyledTextEvent(content); + styledTextEvent.start = event.start; + styledTextEvent.end = event.start + event.text.length(); + styledTextEvent.text = content.getTextRange(event.start, event.end - event.start); + } + content.setText(event.text); + notifyListeners(SWT.Modify, event); + if (styledTextEvent != null) { + notifyListeners(ExtendedModify, styledTextEvent); + } + } +} +/** + * Sets the text limit to the specified number of characters. + * <p> + * The text limit specifies the amount of text that + * the user can type into the widget. + * </p> + * + * @param limit the new text limit. + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @exception IllegalArgumentException <ul> + * <li>ERROR_CANNOT_BE_ZERO when limit is 0</li> + * </ul> + */ +public void setTextLimit(int limit) { + checkWidget(); + if (limit == 0) { + SWT.error(SWT.ERROR_CANNOT_BE_ZERO); + } + textLimit = limit; +} +/** + * Sets the top index. Do nothing if there is no text set. + * <p> + * The top index is the index of the line that is currently at the top + * of the widget. The top index changes when the widget is scrolled. + * Indexing starts from zero. + * Note: The top index is reset to 0 when new text is set in the widget. + * </p> + * + * @param topIndex new top index. Must be between 0 and + * getLineCount() - fully visible lines per page. If no lines are fully + * visible the maximum value is getLineCount() - 1. An out of range + * index will be adjusted accordingly. + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void setTopIndex(int topIndex) { + checkWidget(); + if (getCharCount() == 0) { + return; + } + int lineCount = content.getLineCount(), pixel; + if (isFixedLineHeight()) { + int pageSize = Math.max(1, Math.min(lineCount, getLineCountWhole())); + if (topIndex < 0) { + topIndex = 0; + } else if (topIndex > lineCount - pageSize) { + topIndex = lineCount - pageSize; + } + pixel = getLinePixel(topIndex); + } else { + topIndex = Math.max(0, Math.min(lineCount - 1, topIndex)); + pixel = getLinePixel(topIndex); + if (pixel > 0) { + pixel = getAvailableHeightBellow(pixel); + } else { + pixel = getAvailableHeightAbove(pixel); + } + } + scrollVertical(pixel, true); +} +/** + * Sets the top margin. + * + * @param topMargin the top margin. + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.5 + */ +public void setTopMargin (int topMargin) { + checkWidget(); + setMargins(leftMargin, topMargin, rightMargin, bottomMargin); +} +/** + * Sets the top pixel offset. Do nothing if there is no text set. + * <p> + * The top pixel offset is the vertical pixel offset of the widget. The + * widget is scrolled so that the given pixel position is at the top. + * The top index is adjusted to the corresponding top line. + * Note: The top pixel is reset to 0 when new text is set in the widget. + * </p> + * + * @param pixel new top pixel offset. Must be between 0 and + * (getLineCount() - visible lines per page) / getLineHeight()). An out + * of range offset will be adjusted accordingly. + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * @since 2.0 + */ +public void setTopPixel(int pixel) { + checkWidget(); + if (getCharCount() == 0) { + return; + } + if (pixel < 0) pixel = 0; + int lineCount = content.getLineCount(); + int height = clientAreaHeight - topMargin - bottomMargin; + int verticalOffset = getVerticalScrollOffset(); + if (isFixedLineHeight()) { + int maxTopPixel = Math.max(0, lineCount * getVerticalIncrement() - height); + if (pixel > maxTopPixel) pixel = maxTopPixel; + pixel -= verticalOffset; + } else { + pixel -= verticalOffset; + if (pixel > 0) { + pixel = getAvailableHeightBellow(pixel); + } + } + scrollVertical(pixel, true); +} +/** + * Sets whether the widget wraps lines. + * <p> + * This overrides the creation style bit SWT.WRAP. + * </p> + * + * @param wrap true=widget wraps lines, false=widget does not wrap lines + * @since 2.0 + */ +public void setWordWrap(boolean wrap) { + checkWidget(); + if ((getStyle() & SWT.SINGLE) != 0) return; + if (wordWrap == wrap) return; + if (wordWrap && blockSelection) setBlockSelection(false); + wordWrap = wrap; + setVariableLineHeight(); + resetCache(0, content.getLineCount()); + horizontalScrollOffset = 0; + ScrollBar horizontalBar = getHorizontalBar(); + if (horizontalBar != null) { + horizontalBar.setVisible(!wordWrap); + } + setScrollBars(true); + setCaretLocation(); + super.redraw(); +} +boolean showLocation(Rectangle rect, boolean scrollPage) { + boolean scrolled = false; + if (rect.y < topMargin) { + scrolled = scrollVertical(rect.y - topMargin, true); + } else if (rect.y + rect.height > clientAreaHeight - bottomMargin) { + if (clientAreaHeight - topMargin - bottomMargin <= 0) { + scrolled = scrollVertical(rect.y - topMargin, true); + } else { + scrolled = scrollVertical(rect.y + rect.height - (clientAreaHeight - bottomMargin), true); + } + } + int width = clientAreaWidth - rightMargin - leftMargin; + if (width > 0) { + int minScroll = scrollPage ? width / 4 : 0; + if (rect.x < leftMargin) { + int scrollWidth = Math.max(leftMargin - rect.x, minScroll); + int maxScroll = horizontalScrollOffset; + scrolled = scrollHorizontal(-Math.min(maxScroll, scrollWidth), true); + } else if (rect.x + rect.width > (clientAreaWidth - rightMargin)) { + int scrollWidth = Math.max(rect.x + rect.width - (clientAreaWidth - rightMargin), minScroll); + int maxScroll = renderer.getWidth() - horizontalScrollOffset - clientAreaWidth; + scrolled = scrollHorizontal(Math.min(maxScroll, scrollWidth), true); + } + } + return scrolled; +} +/** + * Sets the caret location and scrolls the caret offset into view. + */ +void showCaret() { + Rectangle bounds = getBoundsAtOffset(caretOffset); + if (!showLocation(bounds, true)) { + setCaretLocation(); + } +} +/** + * Scrolls the selection into view. + * <p> + * The end of the selection will be scrolled into view. + * Note that if a right-to-left selection exists, the end of the selection is + * the visual beginning of the selection (i.e., where the caret is located). + * </p> + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void showSelection() { + checkWidget(); + // is selection from right-to-left? + boolean rightToLeft = caretOffset == selection.x; + int startOffset, endOffset; + if (rightToLeft) { + startOffset = selection.y; + endOffset = selection.x; + } else { + startOffset = selection.x; + endOffset = selection.y; + } + + Rectangle startBounds = getBoundsAtOffset(startOffset); + Rectangle endBounds = getBoundsAtOffset(endOffset); + + // can the selection be fully displayed within the widget's visible width? + int w = clientAreaWidth - leftMargin - rightMargin; + boolean selectionFits = rightToLeft ? startBounds.x - endBounds.x <= w : endBounds.x - startBounds.x <= w; + if (selectionFits) { + // show as much of the selection as possible by first showing + // the start of the selection + if (showLocation(startBounds, false)) { + // endX value could change if showing startX caused a scroll to occur + endBounds = getBoundsAtOffset(endOffset); + } + // the character at endOffset is not part of the selection + endBounds.width = endOffset == caretOffset ? getCaretWidth() : 0; + showLocation(endBounds, false); + } else { + // just show the end of the selection since the selection start + // will not be visible + showLocation(endBounds, true); + } +} +void updateCaretVisibility() { + Caret caret = getCaret(); + if (caret != null) { + if (blockSelection && blockXLocation != -1) { + caret.setVisible(false); + } else { + Point location = caret.getLocation(); + Point size = caret.getSize(); + boolean visible = + topMargin <= location.y + size.y && location.y <= clientAreaHeight - bottomMargin && + leftMargin <= location.x + size.x && location.x <= clientAreaWidth - rightMargin; + caret.setVisible(visible); + } + } +} +/** + * Updates the selection and caret position depending on the text change. + * <p> + * If the selection intersects with the replaced text, the selection is + * reset and the caret moved to the end of the new text. + * If the selection is behind the replaced text it is moved so that the + * same text remains selected. If the selection is before the replaced text + * it is left unchanged. + * </p> + * + * @param startOffset offset of the text change + * @param replacedLength length of text being replaced + * @param newLength length of new text + */ +void updateSelection(int startOffset, int replacedLength, int newLength) { + if (selection.y <= startOffset) { + // selection ends before text change + if (wordWrap) setCaretLocation(); + return; + } + if (selection.x < startOffset) { + // clear selection fragment before text change + internalRedrawRange(selection.x, startOffset - selection.x); + } + if (selection.y > startOffset + replacedLength && selection.x < startOffset + replacedLength) { + // clear selection fragment after text change. + // do this only when the selection is actually affected by the + // change. Selection is only affected if it intersects the change (1GDY217). + int netNewLength = newLength - replacedLength; + int redrawStart = startOffset + newLength; + internalRedrawRange(redrawStart, selection.y + netNewLength - redrawStart); + } + if (selection.y > startOffset && selection.x < startOffset + replacedLength) { + // selection intersects replaced text. set caret behind text change + setSelection(startOffset + newLength, 0, true, false); + } else { + // move selection to keep same text selected + setSelection(selection.x + newLength - replacedLength, selection.y - selection.x, true, false); + } + setCaretLocation(); +} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/StyledTextContent.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/StyledTextContent.java new file mode 100755 index 0000000000..0e6728cf34 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/StyledTextContent.java @@ -0,0 +1,207 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 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.custom; + + +/** + * Clients may implement the StyledTextContent interface to provide a + * custom store for the StyledText widget content. The StyledText widget + * interacts with its StyledTextContent in order to access and update + * the text that is being displayed and edited in the widget. + * A custom content implementation can be set in the widget using the + * StyledText.setContent API. + */ +public interface StyledTextContent { + +/** + * Called by StyledText to add itself as an Observer to content changes. + * See TextChangeListener for a description of the listener methods that + * are called when text changes occur. + * <p> + * + * @param listener the listener + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT when listener is null</li> + * </ul> + */ +public void addTextChangeListener(TextChangeListener listener); + +/** + * Return the number of characters in the content. + * <p> + * + * @return the number of characters in the content. + */ +public int getCharCount(); + +/** + * Return the line at the given line index without delimiters. + * <p> + * + * @param lineIndex index of the line to return. Does not include + * delimiters of preceding lines. Index 0 is the first line of the + * content. + * @return the line text without delimiters + */ +public String getLine(int lineIndex); + +/** + * Return the line index at the given character offset. + * <p> + * + * @param offset offset of the line to return. The first character of the + * document is at offset 0. An offset of getLength() is valid and should + * answer the number of lines. + * @return the line index. The first line is at index 0. If the character + * at offset is a delimiter character, answer the line index of the line + * that is delimited. + * For example, if text = "\r\n\r\n", and delimiter = "\r\n", then: + * <ul> + * <li>getLineAtOffset(0) == 0 + * <li>getLineAtOffset(1) == 0 + * <li>getLineAtOffset(2) == 1 + * <li>getLineAtOffset(3) == 1 + * <li>getLineAtOffset(4) == 2 + * </ul> + */ +public int getLineAtOffset(int offset); + +/** + * Return the number of lines. Should answer 1 when no text is specified. + * The StyledText widget relies on this behavior for drawing the cursor. + * <p> + * + * @return the number of lines. For example: + * <ul> + * <li> text value ==> getLineCount + * <li> null ==> 1 + * <li> "" ==> 1 + * <li> "a\n" ==> 2 + * <li> "\n\n" ==> 3 + * </ul> + */ +public int getLineCount(); + +/** + * Return the line delimiter that should be used by the StyledText + * widget when inserting new lines. New lines entered using key strokes + * and paste operations use this line delimiter. + * Implementors may use System.getProperty("line.separator") to return + * the platform line delimiter. + * <p> + * + * @return the line delimiter that should be used by the StyledText widget + * when inserting new lines. + */ +public String getLineDelimiter(); + +/** + * Return the character offset of the first character of the given line. + * <p> + * <b>NOTE:</b> When there is no text (i.e., no lines), getOffsetAtLine(0) + * is a valid call that should return 0. + * </p> + * + * @param lineIndex index of the line. The first line is at index 0. + * @return offset offset of the first character of the line. The first + * character of the document is at offset 0. The return value should + * include line delimiters. + * For example, if text = "\r\ntest\r\n" and delimiter = "\r\n", then: + * <ul> + * <li>getOffsetAtLine(0) == 0 + * <li>getOffsetAtLine(1) == 2 + * <li>getOffsetAtLine(2) == 8 + * </ul> + */ +public int getOffsetAtLine(int lineIndex); + +/** + * Returns a string representing the content at the given range. + * <p> + * + * @param start the start offset of the text to return. Offset 0 is the + * first character of the document. + * @param length the length of the text to return + * @return the text at the given range + */ +public String getTextRange(int start, int length); + +/** + * Remove the specified text changed listener. + * <p> + * + * @param listener the listener which should no longer be notified + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT when listener is null</li> + * </ul> + */ +public void removeTextChangeListener(TextChangeListener listener); + +/** + * Replace the text with "newText" starting at position "start" + * for a length of "replaceLength". + * <p> + * Implementors have to notify the TextChangeListeners that were added + * using <code>addTextChangeListener</code> before and after the content + * is changed. A <code>TextChangingEvent</code> has to be sent to the + * textChanging method before the content is changed and a + * <code>TextChangedEvent</code> has to be sent to the textChanged method + * after the content has changed. + * The text change that occurs after the <code>TextChangingEvent</code> + * has been sent has to be consistent with the data provided in the + * <code>TextChangingEvent</code>. + * This data will be cached by the widget and will be used when the + * <code>TextChangedEvent</code> is received. + * <p> + * The <code>TextChangingEvent</code> should be set as follows: + * <ul> + * <li>event.start = start of the replaced text + * <li>event.newText = text that is going to be inserted or empty String + * if no text will be inserted + * <li>event.replaceCharCount = length of text that is going to be replaced + * <li>event.newCharCount = length of text that is going to be inserted + * <li>event.replaceLineCount = number of lines that are going to be replaced + * <li>event.newLineCount = number of new lines that are going to be inserted + * </ul> + * <b>NOTE:</b> newLineCount is the number of inserted lines and replaceLineCount + * is the number of deleted lines based on the change that occurs visually. + * For example: + * <ul> + * <li>(replaceText, newText) ==> (replaceLineCount, newLineCount) + * <li>("", "\n") ==> (0, 1) + * <li>("\n\n", "a") ==> (2, 0) + * <li>("a", "\n\n") ==> (0, 2) + * <li>("\n", "") ==> (1, 0) + * </ul> + * </p> + * + * @param start start offset of text to replace, none of the offsets include + * delimiters of preceding lines, offset 0 is the first character of the + * document + * @param replaceLength length of text to replace + * @param text text to replace + * @see TextChangeListener + */ +public void replaceTextRange(int start, int replaceLength, String text); + +/** + * Set text to "text". + * Implementors have to send a <code>TextChangedEvent</code> to the + * textSet method of the TextChangeListeners that were added using + * <code>addTextChangeListener</code>. + * <p> + * + * @param text the new text + * @see TextChangeListener + */ +public void setText(String text); +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/StyledTextDropTargetEffect.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/StyledTextDropTargetEffect.java new file mode 100644 index 0000000000..ba6c2ff230 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/StyledTextDropTargetEffect.java @@ -0,0 +1,242 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 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.custom; + +import org.eclipse.swt.*; +import org.eclipse.swt.dnd.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.widgets.*; + +/** + * This adapter class provides a default drag under effect (eg. select and scroll) + * when a drag occurs over a <code>StyledText</code>. + * + * <p>Classes that wish to provide their own drag under effect for a <code>StyledText</code> + * can extend this class, override the <code>StyledTextDropTargetEffect.dragOver</code> + * method and override any other applicable methods in <code>StyledTextDropTargetEffect</code> to + * display their own drag under effect.</p> + * + * Subclasses that override any methods of this class should call the corresponding + * <code>super</code> method to get the default drag under effect implementation. + * + * <p>The feedback value is either one of the FEEDBACK constants defined in + * class <code>DND</code> which is applicable to instances of this class, + * or it must be built by <em>bitwise OR</em>'ing together + * (that is, using the <code>int</code> "|" operator) two or more + * of those <code>DND</code> effect constants. + * </p> + * <p> + * <dl> + * <dt><b>Feedback:</b></dt> + * <dd>FEEDBACK_SELECT, FEEDBACK_SCROLL</dd> + * </dl> + * </p> + * + * @see DropTargetAdapter + * @see DropTargetEvent + * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> + * + * @since 3.3 + */ +public class StyledTextDropTargetEffect extends DropTargetEffect { + static final int CARET_WIDTH = 2; + static final int SCROLL_HYSTERESIS = 100; // milli seconds + static final int SCROLL_TOLERANCE = 20; // pixels + + int currentOffset = -1; + long scrollBeginTime; + int scrollX = -1, scrollY = -1; + Listener paintListener; + + /** + * Creates a new <code>StyledTextDropTargetEffect</code> to handle the drag under effect on the specified + * <code>StyledText</code>. + * + * @param styledText the <code>StyledText</code> over which the user positions the cursor to drop the data + */ + public StyledTextDropTargetEffect(StyledText styledText) { + super(styledText); + paintListener = new Listener () { + public void handleEvent (Event event) { + if (currentOffset != -1) { + StyledText text = (StyledText) getControl(); + Point position = text.getLocationAtOffset(currentOffset); + int height = text.getLineHeight(currentOffset); + event.gc.setBackground(event.display.getSystemColor (SWT.COLOR_BLACK)); + event.gc.fillRectangle(position.x, position.y, CARET_WIDTH, height); + } + } + }; + } + + /** + * This implementation of <code>dragEnter</code> provides a default drag under effect + * for the feedback specified in <code>event.feedback</code>. + * + * For additional information see <code>DropTargetAdapter.dragEnter</code>. + * + * Subclasses that override this method should call <code>super.dragEnter(event)</code> + * to get the default drag under effect implementation. + * + * @param event the information associated with the drag start event + * + * @see DropTargetAdapter + * @see DropTargetEvent + */ + public void dragEnter(DropTargetEvent event) { + currentOffset = -1; + scrollBeginTime = 0; + scrollX = -1; + scrollY = -1; + getControl().removeListener(SWT.Paint, paintListener); + getControl().addListener (SWT.Paint, paintListener); + } + + /** + * This implementation of <code>dragLeave</code> provides a default drag under effect + * for the feedback specified in <code>event.feedback</code>. + * + * For additional information see <code>DropTargetAdapter.dragLeave</code>. + * + * Subclasses that override this method should call <code>super.dragLeave(event)</code> + * to get the default drag under effect implementation. + * + * @param event the information associated with the drag leave event + * + * @see DropTargetAdapter + * @see DropTargetEvent + */ + public void dragLeave(DropTargetEvent event) { + StyledText text = (StyledText) getControl(); + if (currentOffset != -1) { + refreshCaret(text, currentOffset, -1); + } + text.removeListener(SWT.Paint, paintListener); + scrollBeginTime = 0; + scrollX = -1; + scrollY = -1; + } + + /** + * This implementation of <code>dragOver</code> provides a default drag under effect + * for the feedback specified in <code>event.feedback</code>. + * + * For additional information see <code>DropTargetAdapter.dragOver</code>. + * + * Subclasses that override this method should call <code>super.dragOver(event)</code> + * to get the default drag under effect implementation. + * + * @param event the information associated with the drag over event + * + * @see DropTargetAdapter + * @see DropTargetEvent + * @see DND#FEEDBACK_SELECT + * @see DND#FEEDBACK_SCROLL + */ + public void dragOver(DropTargetEvent event) { + int effect = event.feedback; + StyledText text = (StyledText) getControl(); + + Point pt = text.getDisplay().map(null, text, event.x, event.y); + if ((effect & DND.FEEDBACK_SCROLL) == 0) { + scrollBeginTime = 0; + scrollX = scrollY = -1; + } else { + if (text.getCharCount() == 0) { + scrollBeginTime = 0; + scrollX = scrollY = -1; + } else { + if (scrollX != -1 && scrollY != -1 && scrollBeginTime != 0 && + (pt.x >= scrollX && pt.x <= (scrollX + SCROLL_TOLERANCE) || + pt.y >= scrollY && pt.y <= (scrollY + SCROLL_TOLERANCE))) { + if (System.currentTimeMillis() >= scrollBeginTime) { + Rectangle area = text.getClientArea(); + GC gc = new GC(text); + FontMetrics fm = gc.getFontMetrics(); + gc.dispose(); + int charWidth = fm.getAverageCharWidth(); + int scrollAmount = 10*charWidth; + if (pt.x < area.x + 3*charWidth) { + int leftPixel = text.getHorizontalPixel(); + text.setHorizontalPixel(leftPixel - scrollAmount); + } + if (pt.x > area.width - 3*charWidth) { + int leftPixel = text.getHorizontalPixel(); + text.setHorizontalPixel(leftPixel + scrollAmount); + } + int lineHeight = text.getLineHeight(); + if (pt.y < area.y + lineHeight) { + int topPixel = text.getTopPixel(); + text.setTopPixel(topPixel - lineHeight); + } + if (pt.y > area.height - lineHeight) { + int topPixel = text.getTopPixel(); + text.setTopPixel(topPixel + lineHeight); + } + scrollBeginTime = 0; + scrollX = scrollY = -1; + } + } else { + scrollBeginTime = System.currentTimeMillis() + SCROLL_HYSTERESIS; + scrollX = pt.x; + scrollY = pt.y; + } + } + } + + if ((effect & DND.FEEDBACK_SELECT) != 0) { + int[] trailing = new int [1]; + int newOffset = text.getOffsetAtPoint(pt.x, pt.y, trailing, false); + newOffset += trailing [0]; + if (newOffset != currentOffset) { + refreshCaret(text, currentOffset, newOffset); + currentOffset = newOffset; + } + } + } + + void refreshCaret(StyledText text, int oldOffset, int newOffset) { + if (oldOffset != newOffset) { + if (oldOffset != -1) { + Point oldPos = text.getLocationAtOffset(oldOffset); + int oldHeight = text.getLineHeight(oldOffset); + text.redraw (oldPos.x, oldPos.y, CARET_WIDTH, oldHeight, false); + } + if (newOffset != -1) { + Point newPos = text.getLocationAtOffset(newOffset); + int newHeight = text.getLineHeight(newOffset); + text.redraw (newPos.x, newPos.y, CARET_WIDTH, newHeight, false); + } + } + } + + /** + * This implementation of <code>dropAccept</code> provides a default drag under effect + * for the feedback specified in <code>event.feedback</code>. + * + * For additional information see <code>DropTargetAdapter.dropAccept</code>. + * + * Subclasses that override this method should call <code>super.dropAccept(event)</code> + * to get the default drag under effect implementation. + * + * @param event the information associated with the drop accept event + * + * @see DropTargetAdapter + * @see DropTargetEvent + */ + public void dropAccept(DropTargetEvent event) { + if (currentOffset != -1) { + StyledText text = (StyledText) getControl(); + text.setSelection(currentOffset); + currentOffset = -1; + } + } +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/StyledTextEvent.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/StyledTextEvent.java new file mode 100755 index 0000000000..84e3331af8 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/StyledTextEvent.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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.custom; + +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.widgets.*; + +/** + * + */ +class StyledTextEvent extends Event { + // used by LineStyleEvent + int[] ranges; + StyleRange[] styles; + int alignment; + int indent; + boolean justify; + Bullet bullet; + int bulletIndex; + // used by LineBackgroundEvent + Color lineBackground; + // used by BidiSegmentEvent + int[] segments; + // used by TextChangedEvent + int replaceCharCount; + int newCharCount; + int replaceLineCount; + int newLineCount; + // used by PaintObjectEvent + int x; + int y; + int ascent; + int descent; + StyleRange style; + +StyledTextEvent (StyledTextContent content) { + super(); + data = content; +} +} + + diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/StyledTextListener.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/StyledTextListener.java new file mode 100755 index 0000000000..166e911bce --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/StyledTextListener.java @@ -0,0 +1,101 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 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.custom; + +import org.eclipse.swt.events.*; +import org.eclipse.swt.widgets.*; +import org.eclipse.swt.internal.SWTEventListener; + +class StyledTextListener extends TypedListener { +/** + */ +StyledTextListener(SWTEventListener listener) { + super(listener); +} +/** + * Process StyledText events by invoking the event's handler. + * + * @param e the event to handle + */ +public void handleEvent(Event e) { + + switch (e.type) { + case StyledText.ExtendedModify: + ExtendedModifyEvent extendedModifyEvent = new ExtendedModifyEvent((StyledTextEvent) e); + ((ExtendedModifyListener) eventListener).modifyText(extendedModifyEvent); + break; + case StyledText.LineGetBackground: + LineBackgroundEvent lineBgEvent = new LineBackgroundEvent((StyledTextEvent) e); + ((LineBackgroundListener) eventListener).lineGetBackground(lineBgEvent); + ((StyledTextEvent) e).lineBackground = lineBgEvent.lineBackground; + break; + case StyledText.LineGetSegments: + BidiSegmentEvent segmentEvent = new BidiSegmentEvent((StyledTextEvent) e); + ((BidiSegmentListener) eventListener).lineGetSegments(segmentEvent); + ((StyledTextEvent) e).segments = segmentEvent.segments; + break; + case StyledText.LineGetStyle: + LineStyleEvent lineStyleEvent = new LineStyleEvent((StyledTextEvent) e); + ((LineStyleListener) eventListener).lineGetStyle(lineStyleEvent); + ((StyledTextEvent) e).ranges = lineStyleEvent.ranges; + ((StyledTextEvent) e).styles = lineStyleEvent.styles; + ((StyledTextEvent) e).alignment = lineStyleEvent.alignment; + ((StyledTextEvent) e).indent = lineStyleEvent.indent; + ((StyledTextEvent) e).justify = lineStyleEvent.justify; + ((StyledTextEvent) e).bullet = lineStyleEvent.bullet; + ((StyledTextEvent) e).bulletIndex = lineStyleEvent.bulletIndex; + break; + case StyledText.PaintObject: + PaintObjectEvent paintObjectEvent = new PaintObjectEvent((StyledTextEvent) e); + ((PaintObjectListener) eventListener).paintObject(paintObjectEvent); + break; + case StyledText.VerifyKey: + VerifyEvent verifyEvent = new VerifyEvent(e); + ((VerifyKeyListener) eventListener).verifyKey(verifyEvent); + e.doit = verifyEvent.doit; + break; + case StyledText.TextChanged: { + TextChangedEvent textChangedEvent = new TextChangedEvent((StyledTextContent) e.data); + ((TextChangeListener) eventListener).textChanged(textChangedEvent); + break; + } + case StyledText.TextChanging: + TextChangingEvent textChangingEvent = new TextChangingEvent((StyledTextContent) e.data, (StyledTextEvent) e); + ((TextChangeListener) eventListener).textChanging(textChangingEvent); + break; + case StyledText.TextSet: { + TextChangedEvent textChangedEvent = new TextChangedEvent((StyledTextContent) e.data); + ((TextChangeListener) eventListener).textSet(textChangedEvent); + break; + } + case StyledText.WordNext: { + MovementEvent wordBoundaryEvent = new MovementEvent((StyledTextEvent) e); + ((MovementListener) eventListener).getNextOffset(wordBoundaryEvent); + ((StyledTextEvent) e).end = wordBoundaryEvent.newOffset; + break; + } + case StyledText.WordPrevious: { + MovementEvent wordBoundaryEvent = new MovementEvent((StyledTextEvent) e); + ((MovementListener) eventListener).getPreviousOffset(wordBoundaryEvent); + ((StyledTextEvent) e).end = wordBoundaryEvent.newOffset; + break; + } + case StyledText.CaretMoved: { + CaretEvent caretEvent = new CaretEvent((StyledTextEvent) e); + ((CaretListener) eventListener).caretMoved(caretEvent); + ((StyledTextEvent) e).end = caretEvent.caretOffset; + break; + } + } +} +} + + diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/StyledTextPrintOptions.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/StyledTextPrintOptions.java new file mode 100644 index 0000000000..0daad1f85e --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/StyledTextPrintOptions.java @@ -0,0 +1,104 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 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.custom; + +/** + * Use StyledTextPrintOptions to specify printing options for the + * StyledText.print(Printer, StyledTextPrintOptions) API. + * <p> + * The following example prints a right aligned page number in the footer, + * sets the job name to "Example" and prints line background colors but no other + * formatting: + * </p> + * <pre> + * StyledTextPrintOptions options = new StyledTextPrintOptions(); + * options.footer = "\t\t<page>"; + * options.jobName = "Example"; + * options.printLineBackground = true; + * + * Runnable runnable = styledText.print(new Printer(), options); + * runnable.run(); + * </pre> + * + * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> + * + * @since 2.1 + */ +public class StyledTextPrintOptions { + /** + * Page number placeholder constant for use in <code>header</code> + * and <code>footer</code>. Value is <code><page></code> + */ + public static final String PAGE_TAG = "<page>"; + /** + * Separator constant for use in <code>header</code> and + * <code>footer</code>. Value is <code>\t</code> + */ + public static final String SEPARATOR = "\t"; + /** + * Formatted text to print in the header of each page. + * <p>"left '\t' center '\t' right"</p> + * <p>left, center, right = <page> | #CDATA</p> + * <p>Header and footer are defined as three separate regions for arbitrary + * text or the page number placeholder <page> + * (<code>StyledTextPrintOptions.PAGE_TAG</code>). The three regions are + * left aligned, centered and right aligned. They are separated by a tab + * character (<code>StyledTextPrintOptions.SEPARATOR</code>). + */ + public String header = null; + /** + * Formatted text to print in the footer of each page. + * <p>"left '\t' center '\t' right"</p> + * <p>left, center, right = <page> | #CDATA</p> + * <p>Header and footer are defined as three separate regions for arbitrary + * text or the page number placeholder <page> + * (<code>StyledTextPrintOptions.PAGE_TAG</code>). The three regions are + * left aligned, centered and right aligned. They are separated by a tab + * character (<code>StyledTextPrintOptions.SEPARATOR</code>). + */ + public String footer = null; + /** + * Name of the print job. + */ + public String jobName = null; + + /** + * Print the text foreground color. Default value is <code>false</code>. + */ + public boolean printTextForeground = false; + /** + * Print the text background color. Default value is <code>false</code>. + */ + public boolean printTextBackground = false; + /** + * Print the font styles. Default value is <code>false</code>. + */ + public boolean printTextFontStyle = false; + /** + * Print the line background color. Default value is <code>false</code>. + */ + public boolean printLineBackground = false; + + /** + * Print line numbers. Default value is <code>false</code>. + * + * @since 3.3 + */ + public boolean printLineNumbers = false; + + /** + * Labels used for printing line numbers. + * + * @since 3.4 + */ + public String[] lineLabels = null; + +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/StyledTextRenderer.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/StyledTextRenderer.java new file mode 100644 index 0000000000..86dcf4d905 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/StyledTextRenderer.java @@ -0,0 +1,1568 @@ +/******************************************************************************* + * 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.custom; + + +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.widgets.*; + +/** + * A StyledTextRenderer renders the content of a StyledText widget. + * This class can be used to render to the display or to a printer. + */ +class StyledTextRenderer { + Device device; + StyledText styledText; + StyledTextContent content; + + /* Font info */ + Font regularFont, boldFont, italicFont, boldItalicFont; + int tabWidth; + int ascent, descent; + int averageCharWidth; + + /* Line data */ + int topIndex = -1; + TextLayout[] layouts; + int lineCount; + int[] lineWidth; + int[] lineHeight; + LineInfo[] lines; + int maxWidth; + int maxWidthLineIndex; + boolean idleRunning; + + /* Bullet */ + Bullet[] bullets; + int[] bulletsIndices; + int[] redrawLines; + + /* Style data */ + int[] ranges; + int styleCount; + StyleRange[] styles; + StyleRange[] stylesSet; + int stylesSetCount = 0; + boolean hasLinks, fixedPitch; + final static int BULLET_MARGIN = 8; + + final static boolean COMPACT_STYLES = true; + final static boolean MERGE_STYLES = true; + + final static int GROW = 32; + final static int IDLE_TIME = 50; + final static int CACHE_SIZE = 128; + + final static int BACKGROUND = 1 << 0; + final static int ALIGNMENT = 1 << 1; + final static int INDENT = 1 << 2; + final static int JUSTIFY = 1 << 3; + final static int SEGMENTS = 1 << 5; + + static class LineInfo { + int flags; + Color background; + int alignment; + int indent; + boolean justify; + int[] segments; + + public LineInfo() { + } + public LineInfo(LineInfo info) { + if (info != null) { + flags = info.flags; + background = info.background; + alignment = info.alignment; + indent = info.indent; + justify = info.justify; + segments = info.segments; + } + } + } + +StyledTextRenderer(Device device, StyledText styledText) { + this.device = device; + this.styledText = styledText; +} +int addMerge(int[] mergeRanges, StyleRange[] mergeStyles, int mergeCount, int modifyStart, int modifyEnd) { + int rangeCount = styleCount << 1; + StyleRange endStyle = null; + int endStart = 0, endLength = 0; + if (modifyEnd < rangeCount) { + endStyle = styles[modifyEnd >> 1]; + endStart = ranges[modifyEnd]; + endLength = ranges[modifyEnd + 1]; + } + int grow = mergeCount - (modifyEnd - modifyStart); + if (rangeCount + grow >= ranges.length) { + int[] tmpRanges = new int[ranges.length + grow + (GROW << 1)]; + System.arraycopy(ranges, 0, tmpRanges, 0, modifyStart); + StyleRange[] tmpStyles = new StyleRange[styles.length + (grow >> 1) + GROW]; + System.arraycopy(styles, 0, tmpStyles, 0, modifyStart >> 1); + if (rangeCount > modifyEnd) { + System.arraycopy(ranges, modifyEnd, tmpRanges, modifyStart + mergeCount, rangeCount - modifyEnd); + System.arraycopy(styles, modifyEnd >> 1, tmpStyles, (modifyStart + mergeCount) >> 1, styleCount - (modifyEnd >> 1)); + } + ranges = tmpRanges; + styles = tmpStyles; + } else { + if (rangeCount > modifyEnd) { + System.arraycopy(ranges, modifyEnd, ranges, modifyStart + mergeCount, rangeCount - modifyEnd); + System.arraycopy(styles, modifyEnd >> 1, styles, (modifyStart + mergeCount) >> 1, styleCount - (modifyEnd >> 1)); + } + } + if (MERGE_STYLES) { + int j = modifyStart; + for (int i = 0; i < mergeCount; i += 2) { + if (j > 0 && ranges[j - 2] + ranges[j - 1] == mergeRanges[i] && mergeStyles[i >> 1].similarTo(styles[(j - 2) >> 1])) { + ranges[j - 1] += mergeRanges[i + 1]; + } else { + styles[j >> 1] = mergeStyles[i >> 1]; + ranges[j++] = mergeRanges[i]; + ranges[j++] = mergeRanges[i + 1]; + } + } + if (endStyle != null && ranges[j - 2] + ranges[j - 1] == endStart && endStyle.similarTo(styles[(j - 2) >> 1])) { + ranges[j - 1] += endLength; + modifyEnd += 2; + mergeCount += 2; + } + if (rangeCount > modifyEnd) { + System.arraycopy(ranges, modifyStart + mergeCount, ranges, j, rangeCount - modifyEnd); + System.arraycopy(styles, (modifyStart + mergeCount) >> 1, styles, j >> 1, styleCount - (modifyEnd >> 1)); + } + grow = (j - modifyStart) - (modifyEnd - modifyStart); + } else { + System.arraycopy(mergeRanges, 0, ranges, modifyStart, mergeCount); + System.arraycopy(mergeStyles, 0, styles, modifyStart >> 1, mergeCount >> 1); + } + styleCount += grow >> 1; + return grow; +} +int addMerge(StyleRange[] mergeStyles, int mergeCount, int modifyStart, int modifyEnd) { + int grow = mergeCount - (modifyEnd - modifyStart); + StyleRange endStyle = null; + if (modifyEnd < styleCount) endStyle = styles[modifyEnd]; + if (styleCount + grow >= styles.length) { + StyleRange[] tmpStyles = new StyleRange[styles.length + grow + GROW]; + System.arraycopy(styles, 0, tmpStyles, 0, modifyStart); + if (styleCount > modifyEnd) { + System.arraycopy(styles, modifyEnd, tmpStyles, modifyStart + mergeCount, styleCount - modifyEnd); + } + styles = tmpStyles; + } else { + if (styleCount > modifyEnd) { + System.arraycopy(styles, modifyEnd, styles, modifyStart + mergeCount, styleCount - modifyEnd); + } + } + if (MERGE_STYLES) { + int j = modifyStart; + for (int i = 0; i < mergeCount; i++) { + StyleRange newStyle = mergeStyles[i], style; + if (j > 0 && (style = styles[j - 1]).start + style.length == newStyle.start && newStyle.similarTo(style)) { + style.length += newStyle.length; + } else { + styles[j++] = newStyle; + } + } + StyleRange style = styles[j - 1]; + if (endStyle != null && style.start + style.length == endStyle.start && endStyle.similarTo(style)) { + style.length += endStyle.length; + modifyEnd++; + mergeCount++; + } + if (styleCount > modifyEnd) { + System.arraycopy(styles, modifyStart + mergeCount, styles, j, styleCount - modifyEnd); + } + grow = (j - modifyStart) - (modifyEnd - modifyStart); + } else { + System.arraycopy(mergeStyles, 0, styles, modifyStart, mergeCount); + } + styleCount += grow; + return grow; +} +void calculate(int startLine, int lineCount) { + int endLine = startLine + lineCount; + if (startLine < 0 || endLine > lineWidth.length) { + return; + } + int hTrim = styledText.leftMargin + styledText.rightMargin + styledText.getCaretWidth(); + for (int i = startLine; i < endLine; i++) { + if (lineWidth[i] == -1 || lineHeight[i] == -1) { + TextLayout layout = getTextLayout(i); + Rectangle rect = layout.getBounds(); + lineWidth[i] = rect.width + hTrim; + lineHeight[i] = rect.height; + disposeTextLayout(layout); + } + if (lineWidth[i] > maxWidth) { + maxWidth = lineWidth[i]; + maxWidthLineIndex = i; + } + } +} +void calculateClientArea () { + int index = styledText.getTopIndex(); + int lineCount = content.getLineCount(); + int height = styledText.getClientArea().height; + int y = 0; + while (height > y && lineCount > index) { + calculate(index, 1); + y += lineHeight[index++]; + } +} +void calculateIdle () { + if (idleRunning) return; + Runnable runnable = new Runnable() { + public void run() { + if (styledText == null) return; + int i; + long start = System.currentTimeMillis(); + for (i = 0; i < lineCount; i++) { + if (lineHeight[i] == -1 || lineWidth[i] == -1) { + calculate(i, 1); + if (System.currentTimeMillis() - start > IDLE_TIME) break; + } + } + if (i < lineCount) { + Display display = styledText.getDisplay(); + display.asyncExec(this); + } else { + idleRunning = false; + styledText.setScrollBars(true); + ScrollBar bar = styledText.getVerticalBar(); + if (bar != null) { + bar.setSelection(styledText.getVerticalScrollOffset()); + } + } + } + }; + Display display = styledText.getDisplay(); + display.asyncExec(runnable); + idleRunning = true; +} +void clearLineBackground(int startLine, int count) { + if (lines == null) return; + for (int i = startLine; i < startLine + count; i++) { + LineInfo info = lines[i]; + if (info != null) { + info.flags &= ~BACKGROUND; + info.background = null; + if (info.flags == 0) lines[i] = null; + } + } +} +void clearLineStyle(int startLine, int count) { + if (lines == null) return; + for (int i = startLine; i < startLine + count; i++) { + LineInfo info = lines[i]; + if (info != null) { + info.flags &= ~(ALIGNMENT | INDENT | JUSTIFY); + if (info.flags == 0) lines[i] = null; + } + } +} +void copyInto(StyledTextRenderer renderer) { + if (ranges != null) { + int[] newRanges = renderer.ranges = new int[styleCount << 1]; + System.arraycopy(ranges, 0, newRanges, 0, newRanges.length); + } + if (styles != null) { + StyleRange[] newStyles = renderer.styles = new StyleRange[styleCount]; + for (int i = 0; i < newStyles.length; i++) { + newStyles[i] = (StyleRange)styles[i].clone(); + } + renderer.styleCount = styleCount; + } + if (lines != null) { + LineInfo[] newLines = renderer.lines = new LineInfo[lineCount]; + for (int i = 0; i < newLines.length; i++) { + newLines[i] = new LineInfo(lines[i]); + } + renderer.lineCount = lineCount; + } +} +void dispose() { + if (boldFont != null) boldFont.dispose(); + if (italicFont != null) italicFont.dispose(); + if (boldItalicFont != null) boldItalicFont.dispose(); + boldFont = italicFont = boldItalicFont = null; + reset(); + content = null; + device = null; + styledText = null; +} +void disposeTextLayout (TextLayout layout) { + if (layouts != null) { + for (int i = 0; i < layouts.length; i++) { + if (layouts[i] == layout) return; + } + } + layout.dispose(); +} +void drawBullet(Bullet bullet, GC gc, int paintX, int paintY, int index, int lineAscent, int lineDescent) { + StyleRange style = bullet.style; + GlyphMetrics metrics = style.metrics; + Color color = style.foreground; + if (color != null) gc.setForeground(color); + if ((bullet.type & ST.BULLET_DOT) != 0 && StyledText.IS_MOTIF) { + int size = Math.max(4, (lineAscent + lineDescent) / 4); + if ((size & 1) == 0) size++; + if (color == null) { + Display display = styledText.getDisplay(); + color = display.getSystemColor(SWT.COLOR_BLACK); + } + gc.setBackground(color); + int x = paintX + Math.max(0, metrics.width - size - BULLET_MARGIN); + gc.fillArc(x, paintY + size, size + 1, size + 1, 0, 360); + return; + } + Font font = style.font; + if (font != null) gc.setFont(font); + String string = ""; + int type = bullet.type & (ST.BULLET_DOT|ST.BULLET_NUMBER|ST.BULLET_LETTER_LOWER|ST.BULLET_LETTER_UPPER); + switch (type) { + case ST.BULLET_DOT: string = "\u2022"; break; + case ST.BULLET_NUMBER: string = String.valueOf(index); break; + case ST.BULLET_LETTER_LOWER: string = String.valueOf((char) (index % 26 + 97)); break; + case ST.BULLET_LETTER_UPPER: string = String.valueOf((char) (index % 26 + 65)); break; + } + if ((bullet.type & ST.BULLET_TEXT) != 0) string += bullet.text; + Display display = styledText.getDisplay(); + TextLayout layout = new TextLayout(display); + layout.setText(string); + layout.setAscent(lineAscent); + layout.setDescent(lineDescent); + style = (StyleRange)style.clone(); + style.metrics = null; + if (style.font == null) style.font = getFont(style.fontStyle); + layout.setStyle(style, 0, string.length()); + int x = paintX + Math.max(0, metrics.width - layout.getBounds().width - BULLET_MARGIN); + layout.draw(gc, x, paintY); + layout.dispose(); +} +int drawLine(int lineIndex, int paintX, int paintY, GC gc, Color widgetBackground, Color widgetForeground) { + TextLayout layout = getTextLayout(lineIndex); + String line = content.getLine(lineIndex); + int lineOffset = content.getOffsetAtLine(lineIndex); + int lineLength = line.length(); + Point selection = styledText.getSelection(); + int selectionStart = selection.x - lineOffset; + int selectionEnd = selection.y - lineOffset; + if (styledText.getBlockSelection()) { + selectionStart = selectionEnd = 0; + } + Rectangle client = styledText.getClientArea(); + Color lineBackground = getLineBackground(lineIndex, null); + StyledTextEvent event = styledText.getLineBackgroundData(lineOffset, line); + if (event != null && event.lineBackground != null) lineBackground = event.lineBackground; + int height = layout.getBounds().height; + if (lineBackground != null) { + gc.setBackground(lineBackground); + gc.fillRectangle(client.x, paintY, client.width, height); + } else { + gc.setBackground(widgetBackground); + styledText.drawBackground(gc, client.x, paintY, client.width, height); + } + gc.setForeground(widgetForeground); + if (selectionStart == selectionEnd || (selectionEnd <= 0 && selectionStart > lineLength - 1)) { + layout.draw(gc, paintX, paintY); + } else { + int start = Math.max(0, selectionStart); + int end = Math.min(lineLength, selectionEnd); + Color selectionFg = styledText.getSelectionForeground(); + Color selectionBg = styledText.getSelectionBackground(); + int flags; + if ((styledText.getStyle() & SWT.FULL_SELECTION) != 0) { + flags = SWT.FULL_SELECTION; + } else { + flags = SWT.DELIMITER_SELECTION; + } + if (selectionStart <= lineLength && lineLength < selectionEnd ) { + flags |= SWT.LAST_LINE_SELECTION; + } + layout.draw(gc, paintX, paintY, start, end - 1, selectionFg, selectionBg, flags); + } + + // draw objects + Bullet bullet = null; + int bulletIndex = -1; + if (bullets != null) { + if (bulletsIndices != null) { + int index = lineIndex - topIndex; + if (0 <= index && index < CACHE_SIZE) { + bullet = bullets[index]; + bulletIndex = bulletsIndices[index]; + } + } else { + for (int i = 0; i < bullets.length; i++) { + bullet = bullets[i]; + bulletIndex = bullet.indexOf(lineIndex); + if (bulletIndex != -1) break; + } + } + } + if (bulletIndex != -1 && bullet != null) { + FontMetrics metrics = layout.getLineMetrics(0); + int lineAscent = metrics.getAscent() + metrics.getLeading(); + if (bullet.type == ST.BULLET_CUSTOM) { + bullet.style.start = lineOffset; + styledText.paintObject(gc, paintX, paintY, lineAscent, metrics.getDescent(), bullet.style, bullet, bulletIndex); + } else { + drawBullet(bullet, gc, paintX, paintY, bulletIndex, lineAscent, metrics.getDescent()); + } + } + TextStyle[] styles = layout.getStyles(); + int[] ranges = null; + for (int i = 0; i < styles.length; i++) { + if (styles[i].metrics != null) { + if (ranges == null) ranges = layout.getRanges(); + int start = ranges[i << 1]; + int length = ranges[(i << 1) + 1] - start; + Point point = layout.getLocation(start, false); + FontMetrics metrics = layout.getLineMetrics(layout.getLineIndex(start)); + StyleRange style = (StyleRange)((StyleRange)styles[i]).clone(); + style.start = start + lineOffset; + style.length = length; + int lineAscent = metrics.getAscent() + metrics.getLeading(); + styledText.paintObject(gc, point.x + paintX, point.y + paintY, lineAscent, metrics.getDescent(), style, null, 0); + } + } + disposeTextLayout(layout); + return height; +} +int getBaseline() { + return ascent; +} +Font getFont(int style) { + switch (style) { + case SWT.BOLD: + if (boldFont != null) return boldFont; + return boldFont = new Font(device, getFontData(style)); + case SWT.ITALIC: + if (italicFont != null) return italicFont; + return italicFont = new Font(device, getFontData(style)); + case SWT.BOLD | SWT.ITALIC: + if (boldItalicFont != null) return boldItalicFont; + return boldItalicFont = new Font(device, getFontData(style)); + default: + return regularFont; + } +} +FontData[] getFontData(int style) { + FontData[] fontDatas = regularFont.getFontData(); + for (int i = 0; i < fontDatas.length; i++) { + fontDatas[i].setStyle(style); + } + return fontDatas; +} +int getHeight () { + int defaultLineHeight = getLineHeight(); + if (styledText.isFixedLineHeight()) { + return lineCount * defaultLineHeight + styledText.topMargin + styledText.bottomMargin; + } + int totalHeight = 0; + int width = styledText.getWrapWidth(); + for (int i = 0; i < lineCount; i++) { + int height = lineHeight[i]; + if (height == -1) { + if (width > 0) { + int length = content.getLine(i).length(); + height = ((length * averageCharWidth / width) + 1) * defaultLineHeight; + } else { + height = defaultLineHeight; + } + } + totalHeight += height; + } + return totalHeight + styledText.topMargin + styledText.bottomMargin; +} +boolean hasLink(int offset) { + if (offset == -1) return false; + int lineIndex = content.getLineAtOffset(offset); + int lineOffset = content.getOffsetAtLine(lineIndex); + String line = content.getLine(lineIndex); + StyledTextEvent event = styledText.getLineStyleData(lineOffset, line); + if (event != null) { + StyleRange[] styles = event.styles; + if (styles != null) { + int[] ranges = event.ranges; + if (ranges != null) { + for (int i = 0; i < ranges.length; i+=2) { + if (ranges[i] <= offset && offset < ranges[i] + ranges[i+1] && styles[i >> 1].underline && styles[i >> 1].underlineStyle == SWT.UNDERLINE_LINK) { + return true; + } + } + } else { + for (int i = 0; i < styles.length; i++) { + if (styles[i].start <= offset && offset < styles[i].start + styles[i].length && styles[i >> 1].underline && styles[i >> 1].underlineStyle == SWT.UNDERLINE_LINK) { + return true; + } + } + } + } + } else { + if (ranges != null) { + int rangeCount = styleCount << 1; + int index = getRangeIndex(offset, -1, rangeCount); + if (index >= rangeCount) return false; + int rangeStart = ranges[index]; + int rangeLength = ranges[index + 1]; + StyleRange rangeStyle = styles[index >> 1]; + if (rangeStart <= offset && offset < rangeStart + rangeLength && rangeStyle.underline && rangeStyle.underlineStyle == SWT.UNDERLINE_LINK) { + return true; + } + } + } + return false; +} +int getLineAlignment(int index, int defaultAlignment) { + if (lines == null) return defaultAlignment; + LineInfo info = lines[index]; + if (info != null && (info.flags & ALIGNMENT) != 0) { + return info.alignment; + } + return defaultAlignment; +} +Color getLineBackground(int index, Color defaultBackground) { + if (lines == null) return defaultBackground; + LineInfo info = lines[index]; + if (info != null && (info.flags & BACKGROUND) != 0) { + return info.background; + } + return defaultBackground; +} +Bullet getLineBullet (int index, Bullet defaultBullet) { + if (bullets == null) return defaultBullet; + if (bulletsIndices != null) return defaultBullet; + for (int i = 0; i < bullets.length; i++) { + Bullet bullet = bullets[i]; + if (bullet.indexOf(index) != -1) return bullet; + } + return defaultBullet; +} +int getLineHeight() { + return ascent + descent; +} +int getLineHeight(int lineIndex) { + if (lineHeight[lineIndex] == -1) { + calculate(lineIndex, 1); + } + return lineHeight[lineIndex]; +} +int getLineIndent(int index, int defaultIndent) { + if (lines == null) return defaultIndent; + LineInfo info = lines[index]; + if (info != null && (info.flags & INDENT) != 0) { + return info.indent; + } + return defaultIndent; +} +boolean getLineJustify(int index, boolean defaultJustify) { + if (lines == null) return defaultJustify; + LineInfo info = lines[index]; + if (info != null && (info.flags & JUSTIFY) != 0) { + return info.justify; + } + return defaultJustify; +} +int[] getLineSegments(int index, int[] defaultSegments) { + if (lines == null) return defaultSegments; + LineInfo info = lines[index]; + if (info != null && (info.flags & SEGMENTS) != 0) { + return info.segments; + } + return defaultSegments; +} +int getRangeIndex(int offset, int low, int high) { + if (styleCount == 0) return 0; + if (ranges != null) { + while (high - low > 2) { + int index = ((high + low) / 2) / 2 * 2; + int end = ranges[index] + ranges[index + 1]; + if (end > offset) { + high = index; + } else { + low = index; + } + } + } else { + while (high - low > 1) { + int index = ((high + low) / 2); + int end = styles[index].start + styles[index].length; + if (end > offset) { + high = index; + } else { + low = index; + } + } + } + return high; +} +int[] getRanges(int start, int length) { + if (length == 0) return null; + int[] newRanges; + int end = start + length - 1; + if (ranges != null) { + int rangeCount = styleCount << 1; + int rangeStart = getRangeIndex(start, -1, rangeCount); + if (rangeStart >= rangeCount) return null; + if (ranges[rangeStart] > end) return null; + int rangeEnd = Math.min(rangeCount - 2, getRangeIndex(end, rangeStart - 1, rangeCount)); + if (ranges[rangeEnd] > end) rangeEnd = Math.max(rangeStart, rangeEnd - 2); + newRanges = new int[rangeEnd - rangeStart + 2]; + System.arraycopy(ranges, rangeStart, newRanges, 0, newRanges.length); + } else { + int rangeStart = getRangeIndex(start, -1, styleCount); + if (rangeStart >= styleCount) return null; + if (styles[rangeStart].start > end) return null; + int rangeEnd = Math.min(styleCount - 1, getRangeIndex(end, rangeStart - 1, styleCount)); + if (styles[rangeEnd].start > end) rangeEnd = Math.max(rangeStart, rangeEnd - 1); + newRanges = new int[(rangeEnd - rangeStart + 1) << 1]; + for (int i = rangeStart, j = 0; i <= rangeEnd; i++, j += 2) { + StyleRange style = styles[i]; + newRanges[j] = style.start; + newRanges[j + 1] = style.length; + } + } + if (start > newRanges[0]) { + newRanges[1] = newRanges[0] + newRanges[1] - start; + newRanges[0] = start; + } + if (end < newRanges[newRanges.length - 2] + newRanges[newRanges.length - 1] - 1) { + newRanges[newRanges.length - 1] = end - newRanges[newRanges.length - 2] + 1; + } + return newRanges; +} +StyleRange[] getStyleRanges(int start, int length, boolean includeRanges) { + if (length == 0) return null; + StyleRange[] newStyles; + int end = start + length - 1; + if (ranges != null) { + int rangeCount = styleCount << 1; + int rangeStart = getRangeIndex(start, -1, rangeCount); + if (rangeStart >= rangeCount) return null; + if (ranges[rangeStart] > end) return null; + int rangeEnd = Math.min(rangeCount - 2, getRangeIndex(end, rangeStart - 1, rangeCount)); + if (ranges[rangeEnd] > end) rangeEnd = Math.max(rangeStart, rangeEnd - 2); + newStyles = new StyleRange[((rangeEnd - rangeStart) >> 1) + 1]; + if (includeRanges) { + for (int i = rangeStart, j = 0; i <= rangeEnd; i += 2, j++) { + newStyles[j] = (StyleRange)styles[i >> 1].clone(); + newStyles[j].start = ranges[i]; + newStyles[j].length = ranges[i + 1]; + } + } else { + System.arraycopy(styles, rangeStart >> 1, newStyles, 0, newStyles.length); + } + } else { + int rangeStart = getRangeIndex(start, -1, styleCount); + if (rangeStart >= styleCount) return null; + if (styles[rangeStart].start > end) return null; + int rangeEnd = Math.min(styleCount - 1, getRangeIndex(end, rangeStart - 1, styleCount)); + if (styles[rangeEnd].start > end) rangeEnd = Math.max(rangeStart, rangeEnd - 1); + newStyles = new StyleRange[rangeEnd - rangeStart + 1]; + System.arraycopy(styles, rangeStart, newStyles, 0, newStyles.length); + } + if (includeRanges || ranges == null) { + StyleRange style = newStyles[0]; + if (start > style.start) { + newStyles[0] = style = (StyleRange)style.clone(); + style.length = style.start + style.length - start; + style.start = start; + } + style = newStyles[newStyles.length - 1]; + if (end < style.start + style.length - 1) { + newStyles[newStyles.length - 1] = style = (StyleRange)style.clone(); + style.length = end - style.start + 1; + } + } + return newStyles; +} +StyleRange getStyleRange(StyleRange style) { + if (style.underline && style.underlineStyle == SWT.UNDERLINE_LINK) hasLinks = true; + if (style.start == 0 && style.length == 0 && style.fontStyle == SWT.NORMAL) return style; + StyleRange clone = (StyleRange)style.clone(); + clone.start = clone.length = 0; + clone.fontStyle = SWT.NORMAL; + if (clone.font == null) clone.font = getFont(style.fontStyle); + return clone; +} +TextLayout getTextLayout(int lineIndex) { + return getTextLayout(lineIndex, styledText.getOrientation(), styledText.getWrapWidth(), styledText.lineSpacing); +} +TextLayout getTextLayout(int lineIndex, int orientation, int width, int lineSpacing) { + TextLayout layout = null; + if (styledText != null) { + int topIndex = styledText.topIndex > 0 ? styledText.topIndex - 1 : 0; + if (layouts == null || topIndex != this.topIndex) { + TextLayout[] newLayouts = new TextLayout[CACHE_SIZE]; + if (layouts != null) { + for (int i = 0; i < layouts.length; i++) { + if (layouts[i] != null) { + int layoutIndex = (i + this.topIndex) - topIndex; + if (0 <= layoutIndex && layoutIndex < newLayouts.length) { + newLayouts[layoutIndex] = layouts[i]; + } else { + layouts[i].dispose(); + } + } + } + } + if (bullets != null && bulletsIndices != null && topIndex != this.topIndex) { + int delta = topIndex - this.topIndex; + if (delta > 0) { + if (delta < bullets.length) { + System.arraycopy(bullets, delta, bullets, 0, bullets.length - delta); + System.arraycopy(bulletsIndices, delta, bulletsIndices, 0, bulletsIndices.length - delta); + } + int startIndex = Math.max(0, bullets.length - delta); + for (int i = startIndex; i < bullets.length; i++) bullets[i] = null; + } else { + if (-delta < bullets.length) { + System.arraycopy(bullets, 0, bullets, -delta, bullets.length + delta); + System.arraycopy(bulletsIndices, 0, bulletsIndices, -delta, bulletsIndices.length + delta); + } + int endIndex = Math.min(bullets.length, -delta); + for (int i = 0; i < endIndex; i++) bullets[i] = null; + } + } + this.topIndex = topIndex; + layouts = newLayouts; + } + if (layouts != null) { + int layoutIndex = lineIndex - topIndex; + if (0 <= layoutIndex && layoutIndex < layouts.length) { + layout = layouts[layoutIndex]; + if (layout != null) { + if (lineWidth[lineIndex] != -1) return layout; + } else { + layout = layouts[layoutIndex] = new TextLayout(device); + } + } + } + } + if (layout == null) layout = new TextLayout(device); + String line = content.getLine(lineIndex); + int lineOffset = content.getOffsetAtLine(lineIndex); + int[] segments = null; + int indent = 0; + int alignment = SWT.LEFT; + boolean justify = false; + Bullet bullet = null; + int[] ranges = null; + StyleRange[] styles = null; + int rangeStart = 0, styleCount = 0; + StyledTextEvent event = null; + if (styledText != null) { + event = styledText.getLineStyleData(lineOffset, line); + segments = styledText.getBidiSegments(lineOffset, line); + indent = styledText.indent; + alignment = styledText.alignment; + justify = styledText.justify; + } + if (event != null) { + indent = event.indent; + alignment = event.alignment; + justify = event.justify; + bullet = event.bullet; + ranges = event.ranges; + styles = event.styles; + if (styles != null) { + styleCount = styles.length; + if (styledText.isFixedLineHeight()) { + for (int i = 0; i < styleCount; i++) { + if (styles[i].isVariableHeight()) { + styledText.verticalScrollOffset = -1; + styledText.setVariableLineHeight(); + styledText.redraw(); + break; + } + } + } + } + if (bullets == null || bulletsIndices == null) { + bullets = new Bullet[CACHE_SIZE]; + bulletsIndices = new int[CACHE_SIZE]; + } + int index = lineIndex - topIndex; + if (0 <= index && index < CACHE_SIZE) { + bullets[index] = bullet; + bulletsIndices[index] = event.bulletIndex; + } + } else { + if (lines != null) { + LineInfo info = lines[lineIndex]; + if (info != null) { + if ((info.flags & INDENT) != 0) indent = info.indent; + if ((info.flags & ALIGNMENT) != 0) alignment = info.alignment; + if ((info.flags & JUSTIFY) != 0) justify = info.justify; + if ((info.flags & SEGMENTS) != 0) segments = info.segments; + } + } + if (bulletsIndices != null) { + bullets = null; + bulletsIndices = null; + } + if (bullets != null) { + for (int i = 0; i < bullets.length; i++) { + if (bullets[i].indexOf(lineIndex) != -1) { + bullet = bullets[i]; + break; + } + } + } + ranges = this.ranges; + styles = this.styles; + styleCount = this.styleCount; + if (ranges != null) { + rangeStart = getRangeIndex(lineOffset, -1, styleCount << 1); + } else { + rangeStart = getRangeIndex(lineOffset, -1, styleCount); + } + } + if (bullet != null) { + StyleRange style = bullet.style; + GlyphMetrics metrics = style.metrics; + indent += metrics.width; + } + layout.setFont(regularFont); + layout.setAscent(ascent); + layout.setDescent(descent); + layout.setText(line); + layout.setOrientation(orientation); + layout.setSegments(segments); + layout.setWidth(width); + layout.setSpacing(lineSpacing); + layout.setTabs(new int[]{tabWidth}); + layout.setIndent(indent); + layout.setAlignment(alignment); + layout.setJustify(justify); + + int lastOffset = 0; + int length = line.length(); + if (styles != null) { + if (ranges != null) { + int rangeCount = styleCount << 1; + for (int i = rangeStart; i < rangeCount; i += 2) { + int start, end; + if (lineOffset > ranges[i]) { + start = 0; + end = Math.min (length, ranges[i + 1] - lineOffset + ranges[i]); + } else { + start = ranges[i] - lineOffset; + end = Math.min(length, start + ranges[i + 1]); + } + if (start >= length) break; + if (lastOffset < start) { + layout.setStyle(null, lastOffset, start - 1); + } + layout.setStyle(getStyleRange(styles[i >> 1]), start, end); + lastOffset = Math.max(lastOffset, end); + } + } else { + for (int i = rangeStart; i < styleCount; i++) { + int start, end; + if (lineOffset > styles[i].start) { + start = 0; + end = Math.min (length, styles[i].length - lineOffset + styles[i].start); + } else { + start = styles[i].start - lineOffset; + end = Math.min(length, start + styles[i].length); + } + if (start >= length) break; + if (lastOffset < start) { + layout.setStyle(null, lastOffset, start - 1); + } + layout.setStyle(getStyleRange(styles[i]), start, end); + lastOffset = Math.max(lastOffset, end); + } + } + } + if (lastOffset < length) layout.setStyle(null, lastOffset, length); + if (styledText != null && styledText.ime != null) { + IME ime = styledText.ime; + int compositionOffset = ime.getCompositionOffset(); + if (compositionOffset != -1) { + int commitCount = ime.getCommitCount(); + int compositionLength = ime.getText().length(); + if (compositionLength != commitCount) { + int compositionLine = content.getLineAtOffset(compositionOffset); + if (compositionLine == lineIndex) { + int[] imeRanges = ime.getRanges(); + TextStyle[] imeStyles = ime.getStyles(); + if (imeRanges.length > 0) { + for (int i = 0; i < imeStyles.length; i++) { + int start = imeRanges[i*2] - lineOffset; + int end = imeRanges[i*2+1] - lineOffset; + TextStyle imeStyle = imeStyles[i], userStyle; + for (int j = start; j <= end; j++) { + userStyle = layout.getStyle(j); + if (userStyle == null && j > 0) userStyle = layout.getStyle(j - 1); + if (userStyle == null && j + 1 < length) userStyle = layout.getStyle(j + 1); + if (userStyle == null) { + layout.setStyle(imeStyle, j, j); + } else { + TextStyle newStyle = new TextStyle(imeStyle); + if (newStyle.font == null) newStyle.font = userStyle.font; + if (newStyle.foreground == null) newStyle.foreground = userStyle.foreground; + if (newStyle.background == null) newStyle.background = userStyle.background; + layout.setStyle(newStyle, j, j); + } + } + } + } else { + int start = compositionOffset - lineOffset; + int end = start + compositionLength - 1; + TextStyle userStyle = layout.getStyle(start); + if (userStyle == null) { + if (start > 0) userStyle = layout.getStyle(start - 1); + if (userStyle == null && end + 1 < length) userStyle = layout.getStyle(end + 1); + if (userStyle != null) { + TextStyle newStyle = new TextStyle(); + newStyle.font = userStyle.font; + newStyle.foreground = userStyle.foreground; + newStyle.background = userStyle.background; + layout.setStyle(newStyle, start, end); + } + } + } + } + } + } + } + + if (styledText != null && styledText.isFixedLineHeight()) { + int index = -1; + int lineCount = layout.getLineCount(); + int height = getLineHeight(); + for (int i = 0; i < lineCount; i++) { + int lineHeight = layout.getLineBounds(i).height; + if (lineHeight > height) { + height = lineHeight; + index = i; + } + } + if (index != -1) { + FontMetrics metrics = layout.getLineMetrics(index); + ascent = metrics.getAscent() + metrics.getLeading(); + descent = metrics.getDescent(); + if (layouts != null) { + for (int i = 0; i < layouts.length; i++) { + if (layouts[i] != null && layouts[i] != layout) { + layouts[i].setAscent(ascent); + layouts[i].setDescent(descent); + } + } + } + if (styledText.verticalScrollOffset != 0) { + int topIndex = styledText.topIndex; + int topIndexY = styledText.topIndexY; + int lineHeight = getLineHeight(); + if (topIndexY >= 0) { + styledText.verticalScrollOffset = (topIndex - 1) * lineHeight + lineHeight - topIndexY; + } else { + styledText.verticalScrollOffset = topIndex * lineHeight - topIndexY; + } + } + styledText.calculateScrollBars(); + if (styledText.isBidiCaret()) styledText.createCaretBitmaps(); + styledText.caretDirection = SWT.NULL; + styledText.setCaretLocation(); + styledText.redraw(); + } + } + return layout; +} +int getWidth() { + return maxWidth; +} +void reset() { + if (layouts != null) { + for (int i = 0; i < layouts.length; i++) { + TextLayout layout = layouts[i]; + if (layout != null) layout.dispose(); + } + layouts = null; + } + topIndex = -1; + stylesSetCount = styleCount = lineCount = 0; + ranges = null; + styles = null; + stylesSet = null; + lines = null; + lineWidth = null; + lineHeight = null; + bullets = null; + bulletsIndices = null; + redrawLines = null; + hasLinks = false; +} +void reset(int startLine, int lineCount) { + int endLine = startLine + lineCount; + if (startLine < 0 || endLine > lineWidth.length) return; + for (int i = startLine; i < endLine; i++) { + lineWidth[i] = -1; + lineHeight[i] = -1; + } + if (startLine <= maxWidthLineIndex && maxWidthLineIndex < endLine) { + maxWidth = 0; + maxWidthLineIndex = -1; + if (lineCount != this.lineCount) { + for (int i = 0; i < this.lineCount; i++) { + if (lineWidth[i] > maxWidth) { + maxWidth = lineWidth[i]; + maxWidthLineIndex = i; + } + } + } + } +} +void setContent(StyledTextContent content) { + reset(); + this.content = content; + lineCount = content.getLineCount(); + lineWidth = new int[lineCount]; + lineHeight = new int[lineCount]; + reset(0, lineCount); +} +void setFont(Font font, int tabs) { + TextLayout layout = new TextLayout(device); + layout.setFont(regularFont); + if (font != null) { + if (boldFont != null) boldFont.dispose(); + if (italicFont != null) italicFont.dispose(); + if (boldItalicFont != null) boldItalicFont.dispose(); + boldFont = italicFont = boldItalicFont = null; + regularFont = font; + layout.setText(" "); + layout.setFont(font); + layout.setStyle(new TextStyle(getFont(SWT.NORMAL), null, null), 0, 0); + layout.setStyle(new TextStyle(getFont(SWT.BOLD), null, null), 1, 1); + layout.setStyle(new TextStyle(getFont(SWT.ITALIC), null, null), 2, 2); + layout.setStyle(new TextStyle(getFont(SWT.BOLD | SWT.ITALIC), null, null), 3, 3); + FontMetrics metrics = layout.getLineMetrics(0); + ascent = metrics.getAscent() + metrics.getLeading(); + descent = metrics.getDescent(); + boldFont.dispose(); + italicFont.dispose(); + boldItalicFont.dispose(); + boldFont = italicFont = boldItalicFont = null; + } + layout.dispose(); + layout = new TextLayout(device); + layout.setFont(regularFont); + StringBuffer tabBuffer = new StringBuffer(tabs); + for (int i = 0; i < tabs; i++) { + tabBuffer.append(' '); + } + layout.setText(tabBuffer.toString()); + tabWidth = layout.getBounds().width; + layout.dispose(); + if (styledText != null) { + GC gc = new GC(styledText); + averageCharWidth = gc.getFontMetrics().getAverageCharWidth(); + fixedPitch = gc.stringExtent("l").x == gc.stringExtent("W").x; //$NON-NLS-1$ //$NON-NLS-2$ + gc.dispose(); + } +} +void setLineAlignment(int startLine, int count, int alignment) { + if (lines == null) lines = new LineInfo[lineCount]; + for (int i = startLine; i < startLine + count; i++) { + if (lines[i] == null) { + lines[i] = new LineInfo(); + } + lines[i].flags |= ALIGNMENT; + lines[i].alignment = alignment; + } +} +void setLineBackground(int startLine, int count, Color background) { + if (lines == null) lines = new LineInfo[lineCount]; + for (int i = startLine; i < startLine + count; i++) { + if (lines[i] == null) { + lines[i] = new LineInfo(); + } + lines[i].flags |= BACKGROUND; + lines[i].background = background; + } +} +void setLineBullet(int startLine, int count, Bullet bullet) { + if (bulletsIndices != null) { + bulletsIndices = null; + bullets = null; + } + if (bullets == null) { + if (bullet == null) return; + bullets = new Bullet[1]; + bullets[0] = bullet; + } + int index = 0; + while (index < bullets.length) { + if (bullet == bullets[index]) break; + index++; + } + if (bullet != null) { + if (index == bullets.length) { + Bullet[] newBulletsList = new Bullet[bullets.length + 1]; + System.arraycopy(bullets, 0, newBulletsList, 0, bullets.length); + newBulletsList[index] = bullet; + bullets = newBulletsList; + } + bullet.addIndices(startLine, count); + } else { + updateBullets(startLine, count, 0, false); + styledText.redrawLinesBullet(redrawLines); + redrawLines = null; + } +} +void setLineIndent(int startLine, int count, int indent) { + if (lines == null) lines = new LineInfo[lineCount]; + for (int i = startLine; i < startLine + count; i++) { + if (lines[i] == null) { + lines[i] = new LineInfo(); + } + lines[i].flags |= INDENT; + lines[i].indent = indent; + } +} +void setLineJustify(int startLine, int count, boolean justify) { + if (lines == null) lines = new LineInfo[lineCount]; + for (int i = startLine; i < startLine + count; i++) { + if (lines[i] == null) { + lines[i] = new LineInfo(); + } + lines[i].flags |= JUSTIFY; + lines[i].justify = justify; + } +} +void setLineSegments(int startLine, int count, int[] segments) { + if (lines == null) lines = new LineInfo[lineCount]; + for (int i = startLine; i < startLine + count; i++) { + if (lines[i] == null) { + lines[i] = new LineInfo(); + } + lines[i].flags |= SEGMENTS; + lines[i].segments = segments; + } +} +void setStyleRanges (int[] newRanges, StyleRange[] newStyles) { + if (newStyles == null) { + stylesSetCount = styleCount = 0; + ranges = null; + styles = null; + stylesSet = null; + hasLinks = false; + return; + } + if (newRanges == null && COMPACT_STYLES) { + newRanges = new int[newStyles.length << 1]; + StyleRange[] tmpStyles = new StyleRange[newStyles.length]; + if (stylesSet == null) stylesSet = new StyleRange[4]; + for (int i = 0, j = 0; i < newStyles.length; i++) { + StyleRange newStyle = newStyles[i]; + newRanges[j++] = newStyle.start; + newRanges[j++] = newStyle.length; + int index = 0; + while (index < stylesSetCount) { + if (stylesSet[index].similarTo(newStyle)) break; + index++; + } + if (index == stylesSetCount) { + if (stylesSetCount == stylesSet.length) { + StyleRange[] tmpStylesSet = new StyleRange[stylesSetCount + 4]; + System.arraycopy(stylesSet, 0, tmpStylesSet, 0, stylesSetCount); + stylesSet = tmpStylesSet; + } + stylesSet[stylesSetCount++] = newStyle; + } + tmpStyles[i] = stylesSet[index]; + } + newStyles = tmpStyles; + } + + if (styleCount == 0) { + if (newRanges != null) { + ranges = new int[newRanges.length]; + System.arraycopy(newRanges, 0, ranges, 0, ranges.length); + } + styles = new StyleRange[newStyles.length]; + System.arraycopy(newStyles, 0, styles, 0, styles.length); + styleCount = newStyles.length; + return; + } + if (newRanges != null && ranges == null) { + ranges = new int[styles.length << 1]; + for (int i = 0, j = 0; i < styleCount; i++) { + ranges[j++] = styles[i].start; + ranges[j++] = styles[i].length; + } + } + if (newRanges == null && ranges != null) { + newRanges = new int[newStyles.length << 1]; + for (int i = 0, j = 0; i < newStyles.length; i++) { + newRanges[j++] = newStyles[i].start; + newRanges[j++] = newStyles[i].length; + } + } + if (ranges != null) { + int rangeCount = styleCount << 1; + int start = newRanges[0]; + int modifyStart = getRangeIndex(start, -1, rangeCount), modifyEnd; + boolean insert = modifyStart == rangeCount; + if (!insert) { + int end = newRanges[newRanges.length - 2] + newRanges[newRanges.length - 1]; + modifyEnd = getRangeIndex(end, modifyStart - 1, rangeCount); + insert = modifyStart == modifyEnd && ranges[modifyStart] >= end; + } + if (insert) { + addMerge(newRanges, newStyles, newRanges.length, modifyStart, modifyStart); + return; + } + modifyEnd = modifyStart; + int[] mergeRanges = new int[6]; + StyleRange[] mergeStyles = new StyleRange[3]; + for (int i = 0; i < newRanges.length; i += 2) { + int newStart = newRanges[i]; + int newEnd = newStart + newRanges[i + 1]; + if (newStart == newEnd) continue; + int modifyLast = 0, mergeCount = 0; + while (modifyEnd < rangeCount) { + if (newStart >= ranges[modifyStart] + ranges[modifyStart + 1]) modifyStart += 2; + if (ranges[modifyEnd] + ranges[modifyEnd + 1] > newEnd) break; + modifyEnd += 2; + } + if (ranges[modifyStart] < newStart && newStart < ranges[modifyStart] + ranges[modifyStart + 1]) { + mergeStyles[mergeCount >> 1] = styles[modifyStart >> 1]; + mergeRanges[mergeCount] = ranges[modifyStart]; + mergeRanges[mergeCount + 1] = newStart - ranges[modifyStart]; + mergeCount += 2; + } + mergeStyles[mergeCount >> 1] = newStyles[i >> 1]; + mergeRanges[mergeCount] = newStart; + mergeRanges[mergeCount + 1] = newRanges[i + 1]; + mergeCount += 2; + if (modifyEnd < rangeCount && ranges[modifyEnd] < newEnd && newEnd < ranges[modifyEnd] + ranges[modifyEnd + 1]) { + mergeStyles[mergeCount >> 1] = styles[modifyEnd >> 1]; + mergeRanges[mergeCount] = newEnd; + mergeRanges[mergeCount + 1] = ranges[modifyEnd] + ranges[modifyEnd + 1] - newEnd; + mergeCount += 2; + modifyLast = 2; + } + int grow = addMerge(mergeRanges, mergeStyles, mergeCount, modifyStart, modifyEnd + modifyLast); + rangeCount += grow; + modifyStart = modifyEnd += grow; + } + } else { + int start = newStyles[0].start; + int modifyStart = getRangeIndex(start, -1, styleCount), modifyEnd; + boolean insert = modifyStart == styleCount; + if (!insert) { + int end = newStyles[newStyles.length - 1].start + newStyles[newStyles.length - 1].length; + modifyEnd = getRangeIndex(end, modifyStart - 1, styleCount); + insert = modifyStart == modifyEnd && styles[modifyStart].start >= end; + } + if (insert) { + addMerge(newStyles, newStyles.length, modifyStart, modifyStart); + return; + } + modifyEnd = modifyStart; + StyleRange[] mergeStyles = new StyleRange[3]; + for (int i = 0; i < newStyles.length; i++) { + StyleRange newStyle = newStyles[i], style; + int newStart = newStyle.start; + int newEnd = newStart + newStyle.length; + if (newStart == newEnd) continue; + int modifyLast = 0, mergeCount = 0; + while (modifyEnd < styleCount) { + if (newStart >= styles[modifyStart].start + styles[modifyStart].length) modifyStart++; + if (styles[modifyEnd].start + styles[modifyEnd].length > newEnd) break; + modifyEnd++; + } + style = styles[modifyStart]; + if (style.start < newStart && newStart < style.start + style.length) { + style = mergeStyles[mergeCount++] = (StyleRange)style.clone(); + style.length = newStart - style.start; + } + mergeStyles[mergeCount++] = newStyle; + if (modifyEnd < styleCount) { + style = styles[modifyEnd]; + if (style.start < newEnd && newEnd < style.start + style.length) { + style = mergeStyles[mergeCount++] = (StyleRange)style.clone(); + style.length += style.start - newEnd; + style.start = newEnd; + modifyLast = 1; + } + } + int grow = addMerge(mergeStyles, mergeCount, modifyStart, modifyEnd + modifyLast); + modifyStart = modifyEnd += grow; + } + } +} +void textChanging(TextChangingEvent event) { + int start = event.start; + int newCharCount = event.newCharCount, replaceCharCount = event.replaceCharCount; + int newLineCount = event.newLineCount, replaceLineCount = event.replaceLineCount; + + updateRanges(start, replaceCharCount, newCharCount); + + int startLine = content.getLineAtOffset(start); + if (replaceCharCount == content.getCharCount()) lines = null; + if (replaceLineCount == lineCount) { + lineCount = newLineCount; + lineWidth = new int[lineCount]; + lineHeight = new int[lineCount]; + reset(0, lineCount); + } else { + int delta = newLineCount - replaceLineCount; + if (lineCount + delta > lineWidth.length) { + int[] newWidths = new int[lineCount + delta + GROW]; + System.arraycopy(lineWidth, 0, newWidths, 0, lineCount); + lineWidth = newWidths; + int[] newHeights = new int[lineCount + delta + GROW]; + System.arraycopy(lineHeight, 0, newHeights, 0, lineCount); + lineHeight = newHeights; + } + if (lines != null) { + if (lineCount + delta > lines.length) { + LineInfo[] newLines = new LineInfo[lineCount + delta + GROW]; + System.arraycopy(lines, 0, newLines, 0, lineCount); + lines = newLines; + } + } + int startIndex = startLine + replaceLineCount + 1; + int endIndex = startLine + newLineCount + 1; + System.arraycopy(lineWidth, startIndex, lineWidth, endIndex, lineCount - startIndex); + System.arraycopy(lineHeight, startIndex, lineHeight, endIndex, lineCount - startIndex); + for (int i = startLine; i < endIndex; i++) { + lineWidth[i] = lineHeight[i] = -1; + } + for (int i = lineCount + delta; i < lineCount; i++) { + lineWidth[i] = lineHeight[i] = -1; + } + if (layouts != null) { + int layoutStartLine = startLine - topIndex; + int layoutEndLine = layoutStartLine + replaceLineCount + 1; + for (int i = layoutStartLine; i < layoutEndLine; i++) { + if (0 <= i && i < layouts.length) { + if (layouts[i] != null) layouts[i].dispose(); + layouts[i] = null; + if (bullets != null && bulletsIndices != null) bullets[i] = null; + } + } + if (delta > 0) { + for (int i = layouts.length - 1; i >= layoutEndLine; i--) { + if (0 <= i && i < layouts.length) { + endIndex = i + delta; + if (0 <= endIndex && endIndex < layouts.length) { + layouts[endIndex] = layouts[i]; + layouts[i] = null; + if (bullets != null && bulletsIndices != null) { + bullets[endIndex] = bullets[i]; + bulletsIndices[endIndex] = bulletsIndices[i]; + bullets[i] = null; + } + } else { + if (layouts[i] != null) layouts[i].dispose(); + layouts[i] = null; + if (bullets != null && bulletsIndices != null) bullets[i] = null; + } + } + } + } else if (delta < 0) { + for (int i = layoutEndLine; i < layouts.length; i++) { + if (0 <= i && i < layouts.length) { + endIndex = i + delta; + if (0 <= endIndex && endIndex < layouts.length) { + layouts[endIndex] = layouts[i]; + layouts[i] = null; + if (bullets != null && bulletsIndices != null) { + bullets[endIndex] = bullets[i]; + bulletsIndices[endIndex] = bulletsIndices[i]; + bullets[i] = null; + } + } else { + if (layouts[i] != null) layouts[i].dispose(); + layouts[i] = null; + if (bullets != null && bulletsIndices != null) bullets[i] = null; + } + } + } + } + } + if (replaceLineCount != 0 || newLineCount != 0) { + int startLineOffset = content.getOffsetAtLine(startLine); + if (startLineOffset != start) startLine++; + updateBullets(startLine, replaceLineCount, newLineCount, true); + if (lines != null) { + startIndex = startLine + replaceLineCount; + endIndex = startLine + newLineCount; + System.arraycopy(lines, startIndex, lines, endIndex, lineCount - startIndex); + for (int i = startLine; i < endIndex; i++) { + lines[i] = null; + } + for (int i = lineCount + delta; i < lineCount; i++) { + lines[i] = null; + } + } + } + lineCount += delta; + if (maxWidthLineIndex != -1 && startLine <= maxWidthLineIndex && maxWidthLineIndex <= startLine + replaceLineCount) { + maxWidth = 0; + maxWidthLineIndex = -1; + for (int i = 0; i < lineCount; i++) { + if (lineWidth[i] > maxWidth) { + maxWidth = lineWidth[i]; + maxWidthLineIndex = i; + } + } + } + } +} +void updateBullets(int startLine, int replaceLineCount, int newLineCount, boolean update) { + if (bullets == null) return; + if (bulletsIndices != null) return; + for (int i = 0; i < bullets.length; i++) { + Bullet bullet = bullets[i]; + int[] lines = bullet.removeIndices(startLine, replaceLineCount, newLineCount, update); + if (lines != null) { + if (redrawLines == null) { + redrawLines = lines; + } else { + int[] newRedrawBullets = new int[redrawLines.length + lines.length]; + System.arraycopy(redrawLines, 0, newRedrawBullets, 0, redrawLines.length); + System.arraycopy(lines, 0, newRedrawBullets, redrawLines.length, lines.length); + redrawLines = newRedrawBullets; + } + } + } + int removed = 0; + for (int i = 0; i < bullets.length; i++) { + if (bullets[i].size() == 0) removed++; + } + if (removed > 0) { + if (removed == bullets.length) { + bullets = null; + } else { + Bullet[] newBulletsList = new Bullet[bullets.length - removed]; + for (int i = 0, j = 0; i < bullets.length; i++) { + Bullet bullet = bullets[i]; + if (bullet.size() > 0) newBulletsList[j++] = bullet; + } + bullets = newBulletsList; + } + } +} +void updateRanges(int start, int replaceCharCount, int newCharCount) { + if (styleCount == 0 || (replaceCharCount == 0 && newCharCount == 0)) return; + if (ranges != null) { + int rangeCount = styleCount << 1; + int modifyStart = getRangeIndex(start, -1, rangeCount); + if (modifyStart == rangeCount) return; + int end = start + replaceCharCount; + int modifyEnd = getRangeIndex(end, modifyStart - 1, rangeCount); + int offset = newCharCount - replaceCharCount; + if (modifyStart == modifyEnd && ranges[modifyStart] < start && end < ranges[modifyEnd] + ranges[modifyEnd + 1]) { + if (newCharCount == 0) { + ranges[modifyStart + 1] -= replaceCharCount; + modifyEnd += 2; + } else { + if (rangeCount + 2 > ranges.length) { + int[] newRanges = new int[ranges.length + (GROW << 1)]; + System.arraycopy(ranges, 0, newRanges, 0, rangeCount); + ranges = newRanges; + StyleRange[] newStyles = new StyleRange[styles.length + GROW]; + System.arraycopy(styles, 0, newStyles, 0, styleCount); + styles = newStyles; + } + System.arraycopy(ranges, modifyStart + 2, ranges, modifyStart + 4, rangeCount - (modifyStart + 2)); + System.arraycopy(styles, (modifyStart + 2) >> 1, styles, (modifyStart + 4) >> 1, styleCount - ((modifyStart + 2) >> 1)); + ranges[modifyStart + 3] = ranges[modifyStart] + ranges[modifyStart + 1] - end; + ranges[modifyStart + 2] = start + newCharCount; + ranges[modifyStart + 1] = start - ranges[modifyStart]; + styles[(modifyStart >> 1) + 1] = styles[modifyStart >> 1]; + rangeCount += 2; + styleCount++; + modifyEnd += 4; + } + if (offset != 0) { + for (int i = modifyEnd; i < rangeCount; i += 2) { + ranges[i] += offset; + } + } + } else { + if (ranges[modifyStart] < start && start < ranges[modifyStart] + ranges[modifyStart + 1]) { + ranges[modifyStart + 1] = start - ranges[modifyStart]; + modifyStart += 2; + } + if (modifyEnd < rangeCount && ranges[modifyEnd] < end && end < ranges[modifyEnd] + ranges[modifyEnd + 1]) { + ranges[modifyEnd + 1] = ranges[modifyEnd] + ranges[modifyEnd + 1] - end; + ranges[modifyEnd] = end; + } + if (offset != 0) { + for (int i = modifyEnd; i < rangeCount; i += 2) { + ranges[i] += offset; + } + } + System.arraycopy(ranges, modifyEnd, ranges, modifyStart, rangeCount - modifyEnd); + System.arraycopy(styles, modifyEnd >> 1, styles, modifyStart >> 1, styleCount - (modifyEnd >> 1)); + styleCount -= (modifyEnd - modifyStart) >> 1; + } + } else { + int modifyStart = getRangeIndex(start, -1, styleCount); + if (modifyStart == styleCount) return; + int end = start + replaceCharCount; + int modifyEnd = getRangeIndex(end, modifyStart - 1, styleCount); + int offset = newCharCount - replaceCharCount; + if (modifyStart == modifyEnd && styles[modifyStart].start < start && end < styles[modifyEnd].start + styles[modifyEnd].length) { + if (newCharCount == 0) { + styles[modifyStart].length -= replaceCharCount; + modifyEnd++; + } else { + if (styleCount + 1 > styles.length) { + StyleRange[] newStyles = new StyleRange[styles.length + GROW]; + System.arraycopy(styles, 0, newStyles, 0, styleCount); + styles = newStyles; + } + System.arraycopy(styles, modifyStart + 1, styles, modifyStart + 2, styleCount - (modifyStart + 1)); + styles[modifyStart + 1] = (StyleRange)styles[modifyStart].clone(); + styles[modifyStart + 1].length = styles[modifyStart].start + styles[modifyStart].length - end; + styles[modifyStart + 1].start = start + newCharCount; + styles[modifyStart].length = start - styles[modifyStart].start; + styleCount++; + modifyEnd += 2; + } + if (offset != 0) { + for (int i = modifyEnd; i < styleCount; i++) { + styles[i].start += offset; + } + } + } else { + if (styles[modifyStart].start < start && start < styles[modifyStart].start + styles[modifyStart].length) { + styles[modifyStart].length = start - styles[modifyStart].start; + modifyStart++; + } + if (modifyEnd < styleCount && styles[modifyEnd].start < end && end < styles[modifyEnd].start + styles[modifyEnd].length) { + styles[modifyEnd].length = styles[modifyEnd].start + styles[modifyEnd].length - end; + styles[modifyEnd].start = end; + } + if (offset != 0) { + for (int i = modifyEnd; i < styleCount; i++) { + styles[i].start += offset; + } + } + System.arraycopy(styles, modifyEnd, styles, modifyStart, styleCount - modifyEnd); + styleCount -= modifyEnd - modifyStart; + } + } +} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TableCursor.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TableCursor.java new file mode 100644 index 0000000000..0408aa5fd7 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TableCursor.java @@ -0,0 +1,778 @@ +/******************************************************************************* + * 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.custom; + +import org.eclipse.swt.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.widgets.*; +import org.eclipse.swt.accessibility.*; +import org.eclipse.swt.events.*; + +/** + * A TableCursor provides a way for the user to navigate around a Table + * using the keyboard. It also provides a mechanism for selecting an + * individual cell in a table. + * + * <p> Here is an example of using a TableCursor to navigate to a cell and then edit it. + * + * <code><pre> + * public static void main(String[] args) { + * Display display = new Display(); + * Shell shell = new Shell(display); + * shell.setLayout(new GridLayout()); + * + * // create a a table with 3 columns and fill with data + * final Table table = new Table(shell, SWT.BORDER | SWT.MULTI | SWT.FULL_SELECTION); + * table.setLayoutData(new GridData(GridData.FILL_BOTH)); + * TableColumn column1 = new TableColumn(table, SWT.NONE); + * TableColumn column2 = new TableColumn(table, SWT.NONE); + * TableColumn column3 = new TableColumn(table, SWT.NONE); + * for (int i = 0; i < 100; i++) { + * TableItem item = new TableItem(table, SWT.NONE); + * item.setText(new String[] { "cell "+i+" 0", "cell "+i+" 1", "cell "+i+" 2"}); + * } + * column1.pack(); + * column2.pack(); + * column3.pack(); + * + * // create a TableCursor to navigate around the table + * final TableCursor cursor = new TableCursor(table, SWT.NONE); + * // create an editor to edit the cell when the user hits "ENTER" + * // while over a cell in the table + * final ControlEditor editor = new ControlEditor(cursor); + * editor.grabHorizontal = true; + * editor.grabVertical = true; + * + * cursor.addSelectionListener(new SelectionAdapter() { + * // when the TableEditor is over a cell, select the corresponding row in + * // the table + * public void widgetSelected(SelectionEvent e) { + * table.setSelection(new TableItem[] {cursor.getRow()}); + * } + * // when the user hits "ENTER" in the TableCursor, pop up a text editor so that + * // they can change the text of the cell + * public void widgetDefaultSelected(SelectionEvent e){ + * final Text text = new Text(cursor, SWT.NONE); + * TableItem row = cursor.getRow(); + * int column = cursor.getColumn(); + * text.setText(row.getText(column)); + * text.addKeyListener(new KeyAdapter() { + * public void keyPressed(KeyEvent e) { + * // close the text editor and copy the data over + * // when the user hits "ENTER" + * if (e.character == SWT.CR) { + * TableItem row = cursor.getRow(); + * int column = cursor.getColumn(); + * row.setText(column, text.getText()); + * text.dispose(); + * } + * // close the text editor when the user hits "ESC" + * if (e.character == SWT.ESC) { + * text.dispose(); + * } + * } + * }); + * editor.setEditor(text); + * text.setFocus(); + * } + * }); + * // Hide the TableCursor when the user hits the "MOD1" or "MOD2" key. + * // This allows the user to select multiple items in the table. + * cursor.addKeyListener(new KeyAdapter() { + * public void keyPressed(KeyEvent e) { + * if (e.keyCode == SWT.MOD1 || + * e.keyCode == SWT.MOD2 || + * (e.stateMask & SWT.MOD1) != 0 || + * (e.stateMask & SWT.MOD2) != 0) { + * cursor.setVisible(false); + * } + * } + * }); + * // Show the TableCursor when the user releases the "MOD2" or "MOD1" key. + * // This signals the end of the multiple selection task. + * table.addKeyListener(new KeyAdapter() { + * public void keyReleased(KeyEvent e) { + * if (e.keyCode == SWT.MOD1 && (e.stateMask & SWT.MOD2) != 0) return; + * if (e.keyCode == SWT.MOD2 && (e.stateMask & SWT.MOD1) != 0) return; + * if (e.keyCode != SWT.MOD1 && (e.stateMask & SWT.MOD1) != 0) return; + * if (e.keyCode != SWT.MOD2 && (e.stateMask & SWT.MOD2) != 0) return; + * + * TableItem[] selection = table.getSelection(); + * TableItem row = (selection.length == 0) ? table.getItem(table.getTopIndex()) : selection[0]; + * table.showItem(row); + * cursor.setSelection(row, 0); + * cursor.setVisible(true); + * cursor.setFocus(); + * } + * }); + * + * shell.open(); + * while (!shell.isDisposed()) { + * if (!display.readAndDispatch()) + * display.sleep(); + * } + * display.dispose(); + * } + * </pre></code> + * + * <dl> + * <dt><b>Styles:</b></dt> + * <dd>BORDER</dd> + * <dt><b>Events:</b></dt> + * <dd>Selection, DefaultSelection</dd> + * </dl> + * + * @since 2.0 + * + * @see <a href="http://www.eclipse.org/swt/snippets/#tablecursor">TableCursor snippets</a> + * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> + */ +public class TableCursor extends Canvas { + Table table; + TableItem row = null; + TableColumn column = null; + Listener listener, tableListener, resizeListener, disposeItemListener, disposeColumnListener; + + Color background = null; + Color foreground = null; + + // By default, invert the list selection colors + static final int BACKGROUND = SWT.COLOR_LIST_SELECTION_TEXT; + static final int FOREGROUND = SWT.COLOR_LIST_SELECTION; + +/** + * Constructs a new instance of this class given its parent + * table and a style value describing its behavior and appearance. + * <p> + * The style value is either one of the style constants defined in + * class <code>SWT</code> which is applicable to instances of this + * class, or must be built by <em>bitwise OR</em>'ing together + * (that is, using the <code>int</code> "|" operator) two or more + * of those <code>SWT</code> style constants. The class description + * lists the style constants that are applicable to the class. + * Style bits are also inherited from superclasses. + * </p> + * + * @param parent a Table control which will be the parent of the new instance (cannot be null) + * @param style the style of control to construct + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the parent is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li> + * <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li> + * </ul> + * + * @see SWT#BORDER + * @see Widget#checkSubclass() + * @see Widget#getStyle() + */ +public TableCursor(Table parent, int style) { + super(parent, style); + table = parent; + setBackground(null); + setForeground(null); + + listener = new Listener() { + public void handleEvent(Event event) { + switch (event.type) { + case SWT.Dispose : + onDispose(event); + break; + case SWT.FocusIn : + case SWT.FocusOut : + redraw(); + break; + case SWT.KeyDown : + keyDown(event); + break; + case SWT.Paint : + paint(event); + break; + case SWT.Traverse : { + event.doit = true; + switch (event.detail) { + case SWT.TRAVERSE_ARROW_NEXT : + case SWT.TRAVERSE_ARROW_PREVIOUS : + case SWT.TRAVERSE_RETURN : + event.doit = false; + break; + } + break; + } + } + } + }; + int[] events = new int[] {SWT.Dispose, SWT.FocusIn, SWT.FocusOut, SWT.KeyDown, SWT.Paint, SWT.Traverse}; + for (int i = 0; i < events.length; i++) { + addListener(events[i], listener); + } + + tableListener = new Listener() { + public void handleEvent(Event event) { + switch (event.type) { + case SWT.MouseDown : + tableMouseDown(event); + break; + case SWT.FocusIn : + tableFocusIn(event); + break; + } + } + }; + table.addListener(SWT.FocusIn, tableListener); + table.addListener(SWT.MouseDown, tableListener); + + disposeItemListener = new Listener() { + public void handleEvent(Event event) { + unhookRowColumnListeners(); + row = null; + column = null; + _resize(); + } + }; + disposeColumnListener = new Listener() { + public void handleEvent(Event event) { + unhookRowColumnListeners(); + row = null; + column = null; + _resize(); + } + }; + resizeListener = new Listener() { + public void handleEvent(Event event) { + _resize(); + } + }; + ScrollBar hBar = table.getHorizontalBar(); + if (hBar != null) { + hBar.addListener(SWT.Selection, resizeListener); + } + ScrollBar vBar = table.getVerticalBar(); + if (vBar != null) { + vBar.addListener(SWT.Selection, resizeListener); + } + + getAccessible().addAccessibleControlListener(new AccessibleControlAdapter() { + public void getRole(AccessibleControlEvent e) { + e.detail = ACC.ROLE_TABLECELL; + } + }); + getAccessible().addAccessibleListener(new AccessibleAdapter() { + public void getName(AccessibleEvent e) { + if (row == null) return; + int columnIndex = column == null ? 0 : table.indexOf(column); + e.result = row.getText(columnIndex); + } + }); +} + +/** + * Adds the listener to the collection of listeners who will + * be notified when the user changes the receiver's selection, by sending + * it one of the messages defined in the <code>SelectionListener</code> + * interface. + * <p> + * When <code>widgetSelected</code> is called, the item field of the event object is valid. + * If the receiver has <code>SWT.CHECK</code> style set and the check selection changes, + * the event object detail field contains the value <code>SWT.CHECK</code>. + * <code>widgetDefaultSelected</code> is typically called when an item is double-clicked. + * </p> + * + * @param listener the listener which should be notified when the user changes the receiver's selection + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the listener is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see SelectionListener + * @see SelectionEvent + * @see #removeSelectionListener(SelectionListener) + * + */ +public void addSelectionListener(SelectionListener listener) { + checkWidget(); + if (listener == null) + SWT.error(SWT.ERROR_NULL_ARGUMENT); + TypedListener typedListener = new TypedListener(listener); + addListener(SWT.Selection, typedListener); + addListener(SWT.DefaultSelection, typedListener); +} + +void onDispose(Event event) { + removeListener(SWT.Dispose, listener); + notifyListeners(SWT.Dispose, event); + event.type = SWT.None; + + table.removeListener(SWT.FocusIn, tableListener); + table.removeListener(SWT.MouseDown, tableListener); + unhookRowColumnListeners(); + ScrollBar hBar = table.getHorizontalBar(); + if (hBar != null) { + hBar.removeListener(SWT.Selection, resizeListener); + } + ScrollBar vBar = table.getVerticalBar(); + if (vBar != null) { + vBar.removeListener(SWT.Selection, resizeListener); + } +} + +void keyDown(Event event) { + if (row == null) return; + switch (event.character) { + case SWT.CR : + notifyListeners(SWT.DefaultSelection, new Event()); + return; + } + int rowIndex = table.indexOf(row); + int columnIndex = column == null ? 0 : table.indexOf(column); + switch (event.keyCode) { + case SWT.ARROW_UP : + setRowColumn(Math.max(0, rowIndex - 1), columnIndex, true); + break; + case SWT.ARROW_DOWN : + setRowColumn(Math.min(rowIndex + 1, table.getItemCount() - 1), columnIndex, true); + break; + case SWT.ARROW_LEFT : + case SWT.ARROW_RIGHT : + { + int columnCount = table.getColumnCount(); + if (columnCount == 0) break; + int[] order = table.getColumnOrder(); + int index = 0; + while (index < order.length) { + if (order[index] == columnIndex) break; + index++; + } + if (index == order.length) index = 0; + int leadKey = (getStyle() & SWT.RIGHT_TO_LEFT) != 0 ? SWT.ARROW_RIGHT : SWT.ARROW_LEFT; + if (event.keyCode == leadKey) { + setRowColumn(rowIndex, order[Math.max(0, index - 1)], true); + } else { + setRowColumn(rowIndex, order[Math.min(columnCount - 1, index + 1)], true); + } + break; + } + case SWT.HOME : + setRowColumn(0, columnIndex, true); + break; + case SWT.END : + { + int i = table.getItemCount() - 1; + setRowColumn(i, columnIndex, true); + break; + } + case SWT.PAGE_UP : + { + int index = table.getTopIndex(); + if (index == rowIndex) { + Rectangle rect = table.getClientArea(); + TableItem item = table.getItem(index); + Rectangle itemRect = item.getBounds(0); + rect.height -= itemRect.y; + int height = table.getItemHeight(); + int page = Math.max(1, rect.height / height); + index = Math.max(0, index - page + 1); + } + setRowColumn(index, columnIndex, true); + break; + } + case SWT.PAGE_DOWN : + { + int index = table.getTopIndex(); + Rectangle rect = table.getClientArea(); + TableItem item = table.getItem(index); + Rectangle itemRect = item.getBounds(0); + rect.height -= itemRect.y; + int height = table.getItemHeight(); + int page = Math.max(1, rect.height / height); + int end = table.getItemCount() - 1; + index = Math.min(end, index + page - 1); + if (index == rowIndex) { + index = Math.min(end, index + page - 1); + } + setRowColumn(index, columnIndex, true); + break; + } + } +} + +void paint(Event event) { + if (row == null) return; + int columnIndex = column == null ? 0 : table.indexOf(column); + GC gc = event.gc; + Display display = getDisplay(); + gc.setBackground(getBackground()); + gc.setForeground(getForeground()); + gc.fillRectangle(event.x, event.y, event.width, event.height); + int x = 0; + Point size = getSize(); + Image image = row.getImage(columnIndex); + if (image != null) { + Rectangle imageSize = image.getBounds(); + int imageY = (size.y - imageSize.height) / 2; + gc.drawImage(image, x, imageY); + x += imageSize.width; + } + String text = row.getText(columnIndex); + if (text.length() > 0) { + Rectangle bounds = row.getBounds(columnIndex); + Point extent = gc.stringExtent(text); + // Temporary code - need a better way to determine table trim + String platform = SWT.getPlatform(); + if ("win32".equals(platform)) { //$NON-NLS-1$ + if (table.getColumnCount() == 0 || columnIndex == 0) { + x += 2; + } else { + int alignmnent = column.getAlignment(); + switch (alignmnent) { + case SWT.LEFT: + x += 6; + break; + case SWT.RIGHT: + x = bounds.width - extent.x - 6; + break; + case SWT.CENTER: + x += (bounds.width - x - extent.x) / 2; + break; + } + } + } else { + if (table.getColumnCount() == 0) { + x += 5; + } else { + int alignmnent = column.getAlignment(); + switch (alignmnent) { + case SWT.LEFT: + x += 5; + break; + case SWT.RIGHT: + x = bounds.width- extent.x - 2; + break; + case SWT.CENTER: + x += (bounds.width - x - extent.x) / 2 + 2; + break; + } + } + } + int textY = (size.y - extent.y) / 2; + gc.drawString(text, x, textY); + } + if (isFocusControl()) { + gc.setBackground(display.getSystemColor(SWT.COLOR_BLACK)); + gc.setForeground(display.getSystemColor(SWT.COLOR_WHITE)); + gc.drawFocus(0, 0, size.x, size.y); + } +} + +void tableFocusIn(Event event) { + if (isDisposed()) return; + if (isVisible()) { + if (row == null && column == null) return; + setFocus(); + } +} + +void tableMouseDown(Event event) { + if (isDisposed() || !isVisible()) return; + Point pt = new Point(event.x, event.y); + int lineWidth = table.getLinesVisible() ? table.getGridLineWidth() : 0; + TableItem item = table.getItem(pt); + if ((table.getStyle() & SWT.FULL_SELECTION) != 0) { + if (item == null) return; + } else { + int start = item != null ? table.indexOf(item) : table.getTopIndex(); + int end = table.getItemCount(); + Rectangle clientRect = table.getClientArea(); + for (int i = start; i < end; i++) { + TableItem nextItem = table.getItem(i); + Rectangle rect = nextItem.getBounds(0); + if (pt.y >= rect.y && pt.y < rect.y + rect.height + lineWidth) { + item = nextItem; + break; + } + if (rect.y > clientRect.y + clientRect.height) return; + } + if (item == null) return; + } + TableColumn newColumn = null; + int columnCount = table.getColumnCount(); + if (columnCount == 0) { + if ((table.getStyle() & SWT.FULL_SELECTION) == 0) { + Rectangle rect = item.getBounds(0); + rect.width += lineWidth; + rect.height += lineWidth; + if (!rect.contains(pt)) return; + } + } else { + for (int i = 0; i < columnCount; i++) { + Rectangle rect = item.getBounds(i); + rect.width += lineWidth; + rect.height += lineWidth; + if (rect.contains(pt)) { + newColumn = table.getColumn(i); + break; + } + } + if (newColumn == null) { + if ((table.getStyle() & SWT.FULL_SELECTION) == 0) return; + newColumn = table.getColumn(0); + } + } + setRowColumn(item, newColumn, true); + setFocus(); + return; +} +void setRowColumn(int row, int column, boolean notify) { + TableItem item = row == -1 ? null : table.getItem(row); + TableColumn col = column == -1 || table.getColumnCount() == 0 ? null : table.getColumn(column); + setRowColumn(item, col, notify); +} +void setRowColumn(TableItem row, TableColumn column, boolean notify) { + if (this.row == row && this.column == column) { + return; + } + if (this.row != null && this.row != row) { + this.row.removeListener(SWT.Dispose, disposeItemListener); + this.row = null; + } + if (this.column != null && this.column != column) { + this.column.removeListener(SWT.Dispose, disposeColumnListener); + this.column.removeListener(SWT.Move, resizeListener); + this.column.removeListener(SWT.Resize, resizeListener); + this.column = null; + } + if (row != null) { + if (this.row != row) { + this.row = row; + row.addListener(SWT.Dispose, disposeItemListener); + table.showItem(row); + } + if (this.column != column && column != null) { + this.column = column; + column.addListener(SWT.Dispose, disposeColumnListener); + column.addListener(SWT.Move, resizeListener); + column.addListener(SWT.Resize, resizeListener); + table.showColumn(column); + } + int columnIndex = column == null ? 0 : table.indexOf(column); + setBounds(row.getBounds(columnIndex)); + redraw(); + if (notify) { + notifyListeners(SWT.Selection, new Event()); + } + } + getAccessible().setFocus(ACC.CHILDID_SELF); +} + +public void setVisible(boolean visible) { + checkWidget(); + if (visible) _resize(); + super.setVisible(visible); +} + +/** + * Removes the listener from the collection of listeners who will + * be notified when the user changes the receiver's selection. + * + * @param listener the listener which should no longer be notified + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the listener is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see SelectionListener + * @see #addSelectionListener(SelectionListener) + * + * @since 3.0 + */ +public void removeSelectionListener(SelectionListener listener) { + checkWidget(); + if (listener == null) { + SWT.error(SWT.ERROR_NULL_ARGUMENT); + } + removeListener(SWT.Selection, listener); + removeListener(SWT.DefaultSelection, listener); +} + +void _resize() { + if (row == null) { + setBounds(-200, -200, 0, 0); + } else { + int columnIndex = column == null ? 0 : table.indexOf(column); + setBounds(row.getBounds(columnIndex)); + } +} +/** + * Returns the column over which the TableCursor is positioned. + * + * @return the column for the current position + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public int getColumn() { + checkWidget(); + return column == null ? 0 : table.indexOf(column); +} +/** + * Returns the background color that the receiver will use to draw. + * + * @return the receiver's background color + */ +public Color getBackground() { + checkWidget(); + if (background == null) { + return getDisplay().getSystemColor(BACKGROUND); + } + return background; +} +/** + * Returns the foreground color that the receiver will use to draw. + * + * @return the receiver's foreground color + */ +public Color getForeground() { + checkWidget(); + if (foreground == null) { + return getDisplay().getSystemColor(FOREGROUND); + } + return foreground; +} +/** + * Returns the row over which the TableCursor is positioned. + * + * @return the item for the current position + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public TableItem getRow() { + checkWidget(); + return row; +} +/** + * Sets the receiver's background color to the color specified + * by the argument, or to the default system color for the control + * if the argument is null. + * <p> + * Note: This operation is a hint and may be overridden by the platform. + * For example, on Windows the background of a Button cannot be changed. + * </p> + * @param color the new color (or null) + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void setBackground (Color color) { + background = color; + super.setBackground(getBackground()); + redraw(); +} +/** + * Sets the receiver's foreground color to the color specified + * by the argument, or to the default system color for the control + * if the argument is null. + * <p> + * Note: This operation is a hint and may be overridden by the platform. + * </p> + * @param color the new color (or null) + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void setForeground (Color color) { + foreground = color; + super.setForeground(getForeground()); + redraw(); +} +/** + * Positions the TableCursor over the cell at the given row and column in the parent table. + * + * @param row the index of the row for the cell to select + * @param column the index of column for the cell to select + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + */ +public void setSelection(int row, int column) { + checkWidget(); + int columnCount = table.getColumnCount(); + int maxColumnIndex = columnCount == 0 ? 0 : columnCount - 1; + if (row < 0 + || row >= table.getItemCount() + || column < 0 + || column > maxColumnIndex) + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + setRowColumn(row, column, false); +} +/** + * Positions the TableCursor over the cell at the given row and column in the parent table. + * + * @param row the TableItem of the row for the cell to select + * @param column the index of column for the cell to select + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + */ +public void setSelection(TableItem row, int column) { + checkWidget(); + int columnCount = table.getColumnCount(); + int maxColumnIndex = columnCount == 0 ? 0 : columnCount - 1; + if (row == null + || row.isDisposed() + || column < 0 + || column > maxColumnIndex) + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + setRowColumn(table.indexOf(row), column, false); +} +void unhookRowColumnListeners() { + if (column != null) { + column.removeListener(SWT.Dispose, disposeColumnListener); + column.removeListener(SWT.Move, resizeListener); + column.removeListener(SWT.Resize, resizeListener); + column = null; + } + if (row != null) { + row.removeListener(SWT.Dispose, disposeItemListener); + row = null; + } +} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TableEditor.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TableEditor.java new file mode 100755 index 0000000000..2f57b5f0f0 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TableEditor.java @@ -0,0 +1,260 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 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.custom; + + +import org.eclipse.swt.*; +import org.eclipse.swt.events.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.widgets.*; + +/** +* +* A TableEditor is a manager for a Control that appears above a cell in a Table and tracks with the +* moving and resizing of that cell. It can be used to display a text widget above a cell +* in a Table so that the user can edit the contents of that cell. It can also be used to display +* a button that can launch a dialog for modifying the contents of the associated cell. +* +* <p> Here is an example of using a TableEditor: +* <code><pre> +* final Table table = new Table(shell, SWT.FULL_SELECTION | SWT.HIDE_SELECTION); +* TableColumn column1 = new TableColumn(table, SWT.NONE); +* TableColumn column2 = new TableColumn(table, SWT.NONE); +* for (int i = 0; i < 10; i++) { +* TableItem item = new TableItem(table, SWT.NONE); +* item.setText(new String[] {"item " + i, "edit this value"}); +* } +* column1.pack(); +* column2.pack(); +* +* final TableEditor editor = new TableEditor(table); +* //The editor must have the same size as the cell and must +* //not be any smaller than 50 pixels. +* editor.horizontalAlignment = SWT.LEFT; +* editor.grabHorizontal = true; +* editor.minimumWidth = 50; +* // editing the second column +* final int EDITABLECOLUMN = 1; +* +* table.addSelectionListener(new SelectionAdapter() { +* public void widgetSelected(SelectionEvent e) { +* // Clean up any previous editor control +* Control oldEditor = editor.getEditor(); +* if (oldEditor != null) oldEditor.dispose(); +* +* // Identify the selected row +* TableItem item = (TableItem)e.item; +* if (item == null) return; +* +* // The control that will be the editor must be a child of the Table +* Text newEditor = new Text(table, SWT.NONE); +* newEditor.setText(item.getText(EDITABLECOLUMN)); +* newEditor.addModifyListener(new ModifyListener() { +* public void modifyText(ModifyEvent e) { +* Text text = (Text)editor.getEditor(); +* editor.getItem().setText(EDITABLECOLUMN, text.getText()); +* } +* }); +* newEditor.selectAll(); +* newEditor.setFocus(); +* editor.setEditor(newEditor, item, EDITABLECOLUMN); +* } +* }); +* </pre></code> +* +* @see <a href="http://www.eclipse.org/swt/snippets/#tableeditor">TableEditor snippets</a> +* @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> +*/ +public class TableEditor extends ControlEditor { + Table table; + TableItem item; + int column = -1; + ControlListener columnListener; + Runnable timer; + static final int TIMEOUT = 1500; +/** +* Creates a TableEditor for the specified Table. +* +* @param table the Table Control above which this editor will be displayed +* +*/ +public TableEditor (Table table) { + super(table); + this.table = table; + + columnListener = new ControlListener() { + public void controlMoved(ControlEvent e){ + layout (); + } + public void controlResized(ControlEvent e){ + layout (); + } + }; + timer = new Runnable () { + public void run() { + layout (); + } + }; + + // To be consistent with older versions of SWT, grabVertical defaults to true + grabVertical = true; +} +Rectangle computeBounds () { + if (item == null || column == -1 || item.isDisposed()) return new Rectangle(0, 0, 0, 0); + Rectangle cell = item.getBounds(column); + Rectangle rect = item.getImageBounds(column); + cell.x = rect.x + rect.width; + cell.width -= rect.width; + Rectangle area = table.getClientArea(); + if (cell.x < area.x + area.width) { + if (cell.x + cell.width > area.x + area.width) { + cell.width = area.x + area.width - cell.x; + } + } + Rectangle editorRect = new Rectangle(cell.x, cell.y, minimumWidth, minimumHeight); + + if (grabHorizontal) { + editorRect.width = Math.max(cell.width, minimumWidth); + } + + if (grabVertical) { + editorRect.height = Math.max(cell.height, minimumHeight); + } + + if (horizontalAlignment == SWT.RIGHT) { + editorRect.x += cell.width - editorRect.width; + } else if (horizontalAlignment == SWT.LEFT) { + // do nothing - cell.x is the right answer + } else { // default is CENTER + editorRect.x += (cell.width - editorRect.width)/2; + } + + if (verticalAlignment == SWT.BOTTOM) { + editorRect.y += cell.height - editorRect.height; + } else if (verticalAlignment == SWT.TOP) { + // do nothing - cell.y is the right answer + } else { // default is CENTER + editorRect.y += (cell.height - editorRect.height)/2; + } + return editorRect; +} +/** + * Removes all associations between the TableEditor and the cell in the table. The + * Table and the editor Control are <b>not</b> disposed. + */ +public void dispose () { + if (table != null && !table.isDisposed()) { + if (this.column > -1 && this.column < table.getColumnCount()){ + TableColumn tableColumn = table.getColumn(this.column); + tableColumn.removeControlListener(columnListener); + } + } + columnListener = null; + table = null; + item = null; + column = -1; + timer = null; + super.dispose(); +} +/** +* Returns the zero based index of the column of the cell being tracked by this editor. +* +* @return the zero based index of the column of the cell being tracked by this editor +*/ +public int getColumn () { + return column; +} +/** +* Returns the TableItem for the row of the cell being tracked by this editor. +* +* @return the TableItem for the row of the cell being tracked by this editor +*/ +public TableItem getItem () { + return item; +} +void resize () { + layout(); + /* + * On some platforms, the table scrolls when an item that + * is partially visible at the bottom of the table is + * selected. Ensure that the correct row is edited by + * laying out one more time in a timerExec(). + */ + if (table != null) { + Display display = table.getDisplay(); + display.timerExec(-1, timer); + display.timerExec(TIMEOUT, timer); + } +} +/** +* Sets the zero based index of the column of the cell being tracked by this editor. +* +* @param column the zero based index of the column of the cell being tracked by this editor +*/ +public void setColumn(int column) { + int columnCount = table.getColumnCount(); + // Separately handle the case where the table has no TableColumns. + // In this situation, there is a single default column. + if (columnCount == 0) { + this.column = (column == 0) ? 0 : -1; + resize(); + return; + } + if (this.column > -1 && this.column < columnCount){ + TableColumn tableColumn = table.getColumn(this.column); + tableColumn.removeControlListener(columnListener); + this.column = -1; + } + + if (column < 0 || column >= table.getColumnCount()) return; + + this.column = column; + TableColumn tableColumn = table.getColumn(this.column); + tableColumn.addControlListener(columnListener); + resize(); +} +/** +* Specifies the <code>TableItem</code> that is to be edited. +* +* @param item the item to be edited +*/ +public void setItem (TableItem item) { + this.item = item; + resize(); +} +public void setEditor (Control editor) { + super.setEditor(editor); + resize(); +} +/** +* Specify the Control that is to be displayed and the cell in the table that it is to be positioned above. +* +* <p>Note: The Control provided as the editor <b>must</b> be created with its parent being the Table control +* specified in the TableEditor constructor. +* +* @param editor the Control that is displayed above the cell being edited +* @param item the TableItem for the row of the cell being tracked by this editor +* @param column the zero based index of the column of the cell being tracked by this editor +*/ +public void setEditor (Control editor, TableItem item, int column) { + setItem(item); + setColumn(column); + setEditor(editor); +} +public void layout () { + if (table == null || table.isDisposed()) return; + if (item == null || item.isDisposed()) return; + int columnCount = table.getColumnCount(); + if (columnCount == 0 && column != 0) return; + if (columnCount > 0 && (column < 0 || column >= columnCount)) return; + super.layout(); +} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TableTree.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TableTree.java new file mode 100755 index 0000000000..4ac4da4a09 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TableTree.java @@ -0,0 +1,818 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 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.custom; + + +import org.eclipse.swt.*; +import org.eclipse.swt.events.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.widgets.*; + +/** + * A TableTree is a selectable user interface object + * that displays a hierarchy of items, and issues + * notification when an item is selected. + * A TableTree may be single or multi select. + * <p> + * The item children that may be added to instances of this class + * must be of type <code>TableTreeItem</code>. + * </p><p> + * Note that although this class is a subclass of <code>Composite</code>, + * it does not make sense to add <code>Control</code> children to it, + * or set a layout on it. + * </p><p> + * <dl> + * <dt><b>Styles:</b> <dd> SINGLE, MULTI, CHECK, FULL_SELECTION + * <dt><b>Events:</b> <dd> Selection, DefaultSelection, Collapse, Expand + * </dl> + * <p> + * Note: Only one of the styles SINGLE, and MULTI may be specified. + * </p> + * + * @deprecated As of 3.1 use Tree, TreeItem and TreeColumn + */ +public class TableTree extends Composite { + Table table; + TableTreeItem[] items = EMPTY_ITEMS; + Image plusImage, minusImage, sizeImage; + Listener listener; + + /* + * TableTreeItems are not treated as children but rather as items. + * When the TableTree is disposed, all children are disposed because + * TableTree inherits this behaviour from Composite. The items + * must be disposed separately. Because TableTree is not part of + * the org.eclipse.swt.widgets package, the method releaseWidget can + * not be overridden (this is how items are disposed of in Table and Tree). + * Instead, the items are disposed of in response to the dispose event on the + * TableTree. The "inDispose" flag is used to distinguish between disposing + * one TableTreeItem (e.g. when removing an entry from the TableTree) and + * disposing the entire TableTree. + */ + boolean inDispose = false; + + static final TableTreeItem[] EMPTY_ITEMS = new TableTreeItem [0]; + static final String[] EMPTY_TEXTS = new String [0]; + static final Image[] EMPTY_IMAGES = new Image [0]; + static final String ITEMID = "TableTreeItemID"; //$NON-NLS-1$ + +/** + * Constructs a new instance of this class given its parent + * and a style value describing its behavior and appearance. + * <p> + * The style value is either one of the style constants defined in + * class <code>SWT</code> which is applicable to instances of this + * class, or must be built by <em>bitwise OR</em>'ing together + * (that is, using the <code>int</code> "|" operator) two or more + * of those <code>SWT</code> style constants. The class description + * lists the style constants that are applicable to the class. + * Style bits are also inherited from superclasses. + * </p> + * + * @param parent a widget which will be the parent of the new instance (cannot be null) + * @param style the style of widget to construct + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the parent is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li> + * </ul> + * + * @see SWT#SINGLE + * @see SWT#MULTI + * @see SWT#CHECK + * @see SWT#FULL_SELECTION + * @see #getStyle + */ +public TableTree(Composite parent, int style) { + super(parent, checkStyle (style)); + table = new Table(this, style); + Listener tableListener = new Listener() { + public void handleEvent(Event e) { + switch (e.type) { + case SWT.MouseDown: onMouseDown(e); break; + case SWT.Selection: onSelection(e); break; + case SWT.DefaultSelection: onSelection(e); break; + case SWT.KeyDown: onKeyDown(e); break; + } + } + }; + int[] tableEvents = new int[]{SWT.MouseDown, + SWT.Selection, + SWT.DefaultSelection, + SWT.KeyDown}; + for (int i = 0; i < tableEvents.length; i++) { + table.addListener(tableEvents[i], tableListener); + } + + listener = new Listener() { + public void handleEvent(Event e) { + switch (e.type) { + case SWT.Dispose: onDispose(e); break; + case SWT.Resize: onResize(e); break; + case SWT.FocusIn: onFocusIn(e); break; + } + } + }; + int[] events = new int[]{SWT.Dispose, + SWT.Resize, + SWT.FocusIn}; + for (int i = 0; i < events.length; i++) { + addListener(events[i], listener); + } +} + +int addItem(TableTreeItem item, int index) { + if (index < 0 || index > items.length) SWT.error(SWT.ERROR_INVALID_ARGUMENT); + TableTreeItem[] newItems = new TableTreeItem[items.length + 1]; + System.arraycopy(items, 0, newItems, 0, index); + newItems[index] = item; + System.arraycopy(items, index, newItems, index + 1, items.length - index); + items = newItems; + + /* Return the index in the table where this table should be inserted */ + if (index == items.length - 1 ) + return table.getItemCount(); + else + return table.indexOf(items[index+1].tableItem); +} + +/** + * Adds the listener to the collection of listeners who will + * be notified when the user changes the receiver's selection, by sending + * it one of the messages defined in the <code>SelectionListener</code> + * interface. + * <p> + * When <code>widgetSelected</code> is called, the item field of the event object is valid. + * If the receiver has <code>SWT.CHECK</code> style set and the check selection changes, + * the event object detail field contains the value <code>SWT.CHECK</code>. + * <code>widgetDefaultSelected</code> is typically called when an item is double-clicked. + * The item field of the event object is valid for default selection, but the detail field is not used. + * </p> + * + * @param listener the listener which should be notified when the user changes the receiver's selection + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the listener is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see SelectionListener + * @see #removeSelectionListener + * @see SelectionEvent + */ +public void addSelectionListener(SelectionListener listener) { + checkWidget(); + if (listener == null) SWT.error (SWT.ERROR_NULL_ARGUMENT); + TypedListener typedListener = new TypedListener (listener); + addListener (SWT.Selection,typedListener); + addListener (SWT.DefaultSelection,typedListener); +} + +/** + * Adds the listener to the collection of listeners who will + * be notified when an item in the receiver is expanded or collapsed + * by sending it one of the messages defined in the <code>TreeListener</code> + * interface. + * + * @param listener the listener which should be notified + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the listener is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see TreeListener + * @see #removeTreeListener + */ +public void addTreeListener(TreeListener listener) { + checkWidget(); + if (listener == null) SWT.error (SWT.ERROR_NULL_ARGUMENT); + TypedListener typedListener = new TypedListener (listener); + addListener (SWT.Expand, typedListener); + addListener (SWT.Collapse, typedListener); +} +private static int checkStyle (int style) { + int mask = SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT; + style = style & mask; + return style; +} +public Point computeSize (int wHint, int hHint, boolean changed) { + checkWidget(); + return table.computeSize (wHint, hHint, changed); +} +public Rectangle computeTrim (int x, int y, int width, int height) { + checkWidget(); + return table.computeTrim(x, y, width, height); +} + +/** + * Deselects all items. + * <p> + * If an item is selected, it is deselected. + * If an item is not selected, it remains unselected. + * + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread + * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed + * </ul> + */ +public void deselectAll () { + checkWidget(); + table.deselectAll(); +} + +/* Expand upward from the specified leaf item. */ +void expandItem (TableTreeItem item) { + if (item == null) return; + expandItem(item.parentItem); + if (!item.getVisible()) item.setVisible(true); + if ( !item.expanded && item.items.length > 0) { + item.setExpanded(true); + Event event = new Event(); + event.item = item; + notifyListeners(SWT.Expand, event); + } +} +public Color getBackground () { + // This method must be overridden otherwise, in a TableTree in which the first + // item has no sub items, a grey (Widget background colour) square will appear in + // the first column of the first item. + // It is not possible in the constructor to set the background of the TableTree + // to be the same as the background of the Table because this interferes with + // the TableTree adapting to changes in the System color settings. + return table.getBackground(); +} +public Rectangle getClientArea () { + return table.getClientArea(); +} +public Color getForeground () { + return table.getForeground(); +} +public Font getFont () { + return table.getFont(); +} +/** + * Gets the number of items. + * <p> + * @return the number of items in the widget + */ +public int getItemCount () { + //checkWidget(); + return items.length; +} + +/** + * Gets the height of one item. + * <p> + * This operation will fail if the height of + * one item could not be queried from the OS. + * + * @return the height of one item in the widget + * + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread + * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed + * </ul> + */ +public int getItemHeight () { + checkWidget(); + return table.getItemHeight(); +} + +/** + * Gets the items. + * <p> + * @return the items in the widget + */ +public TableTreeItem [] getItems () { + //checkWidget(); + TableTreeItem[] newItems = new TableTreeItem[items.length]; + System.arraycopy(items, 0, newItems, 0, items.length); + return newItems; +} + +/** + * Gets the selected items. + * <p> + * This operation will fail if the selected + * items cannot be queried from the OS. + * + * @return the selected items in the widget + * + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li> + * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li> + * </ul> + */ +public TableTreeItem [] getSelection () { + checkWidget(); + TableItem[] selection = table.getSelection(); + TableTreeItem [] result = new TableTreeItem[selection.length]; + for (int i = 0; i < selection.length; i++){ + result[i] = (TableTreeItem) selection[i].getData(ITEMID); + } + return result; +} + +/** + * Gets the number of selected items. + * <p> + * This operation will fail if the number of selected + * items cannot be queried from the OS. + * + * @return the number of selected items in the widget + * + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li> + * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li> + * </ul> + */ +public int getSelectionCount () { + checkWidget(); + return table.getSelectionCount(); +} + +public int getStyle () { + checkWidget(); + return table.getStyle(); +} + +/** + * Returns the underlying Table control. + * + * @return the underlying Table control + */ +public Table getTable () { + //checkWidget(); + return table; +} + +void createImages () { + + int itemHeight = sizeImage.getBounds().height; + // Calculate border around image. + // At least 9 pixels are needed to draw the image + // Leave at least a 6 pixel border. + int indent = Math.min(6, (itemHeight - 9) / 2); + indent = Math.max(0, indent); + int size = Math.max (10, itemHeight - 2 * indent); + size = ((size + 1) / 2) * 2; // size must be an even number + int midpoint = indent + size / 2; + + Color foreground = getForeground(); + Color plusMinus = getDisplay().getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW); + Color background = getBackground(); + + /* Plus image */ + PaletteData palette = new PaletteData(new RGB[]{foreground.getRGB(), background.getRGB(), plusMinus.getRGB()}); + ImageData imageData = new ImageData(itemHeight, itemHeight, 4, palette); + imageData.transparentPixel = 1; + plusImage = new Image(getDisplay(), imageData); + GC gc = new GC(plusImage); + gc.setBackground(background); + gc.fillRectangle(0, 0, itemHeight, itemHeight); + gc.setForeground(plusMinus); + gc.drawRectangle(indent, indent, size, size); + gc.setForeground(foreground); + gc.drawLine(midpoint, indent + 2, midpoint, indent + size - 2); + gc.drawLine(indent + 2, midpoint, indent + size - 2, midpoint); + gc.dispose(); + + /* Minus image */ + palette = new PaletteData(new RGB[]{foreground.getRGB(), background.getRGB(), plusMinus.getRGB()}); + imageData = new ImageData(itemHeight, itemHeight, 4, palette); + imageData.transparentPixel = 1; + minusImage = new Image(getDisplay(), imageData); + gc = new GC(minusImage); + gc.setBackground(background); + gc.fillRectangle(0, 0, itemHeight, itemHeight); + gc.setForeground(plusMinus); + gc.drawRectangle(indent, indent, size, size); + gc.setForeground(foreground); + gc.drawLine(indent + 2, midpoint, indent + size - 2, midpoint); + gc.dispose(); +} + +Image getPlusImage() { + if (plusImage == null) createImages(); + return plusImage; +} + +Image getMinusImage() { + if (minusImage == null) createImages(); + return minusImage; +} + +/** + * Gets the index of an item. + * + * <p>The widget is searched starting at 0 until an + * item is found that is equal to the search item. + * If no item is found, -1 is returned. Indexing + * is zero based. This index is relative to the parent only. + * + * @param item the search item + * @return the index of the item or -1 + */ +public int indexOf (TableTreeItem item) { + //checkWidget(); + for (int i = 0; i < items.length; i++) { + if (item == items[i]) return i; + } + return -1; +} + +void onDispose(Event e) { + removeListener(SWT.Dispose, listener); + notifyListeners(SWT.Dispose, e); + e.type = SWT.None; + /* + * Usually when an item is disposed, destroyItem will change the size of the items array + * and dispose of the underlying table items. + * Since the whole table tree is being disposed, this is not necessary. For speed + * the inDispose flag is used to skip over this part of the item dispose. + */ + inDispose = true; + for (int i = 0; i < items.length; i++) { + items[i].dispose(); + } + inDispose = false; + if (plusImage != null) plusImage.dispose(); + if (minusImage != null) minusImage.dispose(); + if (sizeImage != null) sizeImage.dispose(); + plusImage = minusImage = sizeImage = null; +} + +void onResize(Event e) { + Point size = getSize(); + table.setBounds(0, 0, size.x, size.y); +} + +void onSelection(Event e) { + Event event = new Event(); + TableItem tableItem = (TableItem)e.item; + TableTreeItem item = getItem(tableItem); + event.item = item; + + if (e.type == SWT.Selection && e.detail == SWT.CHECK && item != null) { + event.detail = SWT.CHECK; + item.checked = tableItem.getChecked(); + } + notifyListeners(e.type, event); +} +/** + * Returns the item at the given, zero-relative index in the + * receiver. Throws an exception if the index is out of range. + * + * @param index the index of the item to return + * @return the item at the given index + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list minus 1 (inclusive)</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.1 + */ +public TableTreeItem getItem (int index) { + checkWidget(); + int count = items.length; + if (!(0 <= index && index < count)) SWT.error (SWT.ERROR_INVALID_RANGE); + return items [index]; +} + +/** + * Returns the item at the given point in the receiver + * or null if no such item exists. The point is in the + * coordinate system of the receiver. + * + * @param point the point used to locate the item + * @return the item at the given point + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the point is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public TableTreeItem getItem(Point point) { + checkWidget(); + TableItem item = table.getItem(point); + if (item == null) return null; + return getItem(item); + +} +TableTreeItem getItem(TableItem tableItem) { + if (tableItem == null) return null; + for (int i = 0; i < items.length; i++) { + TableTreeItem item = items[i].getItem(tableItem); + if (item != null) return item; + } + return null; +} +void onFocusIn (Event e) { + table.setFocus(); +} + +void onKeyDown (Event e) { + TableTreeItem[] selection = getSelection(); + if (selection.length == 0) return; + TableTreeItem item = selection[0]; + int type = 0; + if (e.keyCode == SWT.ARROW_RIGHT || e.keyCode == SWT.ARROW_LEFT) { + int trailKey = (getStyle() & SWT.MIRRORED) != 0 ? SWT.ARROW_LEFT : SWT.ARROW_RIGHT; + if (e.keyCode == trailKey) { + if (item.getItemCount() == 0) return; + if (item.getExpanded()) { + TableTreeItem newSelection = item.getItems()[0]; + table.setSelection(new TableItem[]{newSelection.tableItem}); + showItem(newSelection); + type = SWT.Selection; + } else { + item.setExpanded(true); + type = SWT.Expand; + } + } else { + if (item.getExpanded()) { + item.setExpanded(false); + type = SWT.Collapse; + } else { + TableTreeItem parent = item.getParentItem(); + if (parent != null) { + int index = parent.indexOf(item); + if (index != 0) return; + table.setSelection(new TableItem[]{parent.tableItem}); + type = SWT.Selection; + } + } + } + } + if (e.character == '*') { + item.expandAll(true); + } + if (e.character == '-') { + if (item.getExpanded()) { + item.setExpanded(false); + type = SWT.Collapse; + } + } + if (e.character == '+') { + if (item.getItemCount() > 0 && !item.getExpanded()) { + item.setExpanded(true); + type = SWT.Expand; + } + } + if (type == 0) return; + Event event = new Event(); + event.item = item; + notifyListeners(type, event); +} +void onMouseDown(Event event) { + /* If user clicked on the [+] or [-], expand or collapse the tree. */ + TableItem[] items = table.getItems(); + for (int i = 0; i < items.length; i++) { + Rectangle rect = items[i].getImageBounds(0); + if (rect.contains(event.x, event.y)) { + TableTreeItem item = (TableTreeItem) items[i].getData(ITEMID); + event = new Event(); + event.item = item; + item.setExpanded(!item.getExpanded()); + if (item.getExpanded()) { + notifyListeners(SWT.Expand, event); + } else { + notifyListeners(SWT.Collapse, event); + } + return; + } + } +} + +/** + * Removes all items. + * <p> + * This operation will fail when an item + * could not be removed in the OS. + * + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread + * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed + * </ul> + */ +public void removeAll () { + checkWidget(); + setRedraw(false); + for (int i = items.length - 1; i >= 0; i--) { + items[i].dispose(); + } + items = EMPTY_ITEMS; + setRedraw(true); +} + +void removeItem(TableTreeItem item) { + int index = 0; + while (index < items.length && items[index] != item) index++; + if (index == items.length) return; + TableTreeItem[] newItems = new TableTreeItem[items.length - 1]; + System.arraycopy(items, 0, newItems, 0, index); + System.arraycopy(items, index + 1, newItems, index, items.length - index - 1); + items = newItems; +} + +/** + * Removes the listener from the collection of listeners who will + * be notified when the user changes the receiver's selection. + * + * @param listener the listener which should no longer be notified + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the listener is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see SelectionListener + * @see #addSelectionListener + */ +public void removeSelectionListener (SelectionListener listener) { + checkWidget(); + if (listener == null) SWT.error (SWT.ERROR_NULL_ARGUMENT); + removeListener(SWT.Selection, listener); + removeListener(SWT.DefaultSelection, listener); +} + +/** + * Removes the listener from the collection of listeners who will + * be notified when items in the receiver are expanded or collapsed. + * + * @param listener the listener which should no longer be notified + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the listener is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see TreeListener + * @see #addTreeListener + */ +public void removeTreeListener (TreeListener listener) { + checkWidget(); + if (listener == null) SWT.error (SWT.ERROR_NULL_ARGUMENT); + removeListener(SWT.Expand, listener); + removeListener(SWT.Collapse, listener); +} + +/** + * Selects all of the items in the receiver. + * <p> + * If the receiver is single-select, do nothing. + * + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread + * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed + * </ul> + */ +public void selectAll () { + checkWidget(); + table.selectAll(); +} +public void setBackground (Color color) { + super.setBackground(color); + table.setBackground(color); + if (sizeImage != null) { + GC gc = new GC (sizeImage); + gc.setBackground(getBackground()); + Rectangle size = sizeImage.getBounds(); + gc.fillRectangle(size); + gc.dispose(); + } +} +public void setEnabled (boolean enabled) { + super.setEnabled(enabled); + table.setEnabled(enabled); +} +public void setFont (Font font) { + super.setFont(font); + table.setFont(font); +} +public void setForeground (Color color) { + super.setForeground(color); + table.setForeground(color); +} +public void setMenu (Menu menu) { + super.setMenu(menu); + table.setMenu(menu); +} + +/** + * Sets the receiver's selection to be the given array of items. + * The current selection is cleared before the new items are selected. + * <p> + * Items that are not in the receiver are ignored. + * If the receiver is single-select and multiple items are specified, + * then all items are ignored. + * + * @param items the array of items + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the array of items is null</li> + * <li>ERROR_INVALID_ARGUMENT - if one of the item has been disposed</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see TableTree#deselectAll() + */ +public void setSelection (TableTreeItem[] items) { + checkWidget (); + if (items == null) SWT.error (SWT.ERROR_NULL_ARGUMENT); + int length = items.length; + if (length == 0 || ((table.getStyle() & SWT.SINGLE) != 0 && length > 1)) { + deselectAll(); + return; + } + TableItem[] tableItems = new TableItem[length]; + for (int i = 0; i < length; i++) { + if (items[i] == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + if (!items[i].getVisible()) expandItem (items[i]); + tableItems[i] = items[i].tableItem; + } + table.setSelection(tableItems); +} +public void setToolTipText (String string) { + super.setToolTipText(string); + table.setToolTipText(string); +} + +/** + * Shows the item. If the item is already showing in the receiver, + * this method simply returns. Otherwise, the items are scrolled + * and expanded until the item is visible. + * + * @param item the item to be shown + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the item is null</li> + * <li>ERROR_INVALID_ARGUMENT - if the item has been disposed</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @see TableTree#showSelection() + */ +public void showItem (TableTreeItem item) { + checkWidget(); + if (item == null) SWT.error (SWT.ERROR_NULL_ARGUMENT); + if (!item.getVisible()) expandItem (item); + TableItem tableItem = item.tableItem; + table.showItem(tableItem); +} + +/** + * Shows the selection. + * <p> + * If there is no selection or the selection + * is already visible, this method does nothing. + * If the selection is scrolled out of view, + * the top index of the widget is changed such + * that selection becomes visible. + * + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread + * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed + * </ul> + */ +public void showSelection () { + checkWidget(); + table.showSelection(); +} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TableTreeEditor.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TableTreeEditor.java new file mode 100755 index 0000000000..792527b1cf --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TableTreeEditor.java @@ -0,0 +1,259 @@ +/******************************************************************************* + * Copyright (c) 2000, 2007 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.custom; + + +import org.eclipse.swt.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.widgets.*; +import org.eclipse.swt.events.*; +/** +* +* A TableTreeEditor is a manager for a Control that appears above a cell in a TableTree +* and tracks with the moving and resizing of that cell. It can be used to display a +* text widget above a cell in a TableTree so that the user can edit the contents of +* that cell. It can also be used to display a button that can launch a dialog for +* modifying the contents of the associated cell. +* +* <p> Here is an example of using a TableTreeEditor: +* <code><pre> +* final TableTree tableTree = new TableTree(shell, SWT.FULL_SELECTION | SWT.HIDE_SELECTION); +* final Table table = tableTree.getTable(); +* TableColumn column1 = new TableColumn(table, SWT.NONE); +* TableColumn column2 = new TableColumn(table, SWT.NONE); +* for (int i = 0; i < 10; i++) { +* TableTreeItem item = new TableTreeItem(tableTree, SWT.NONE); +* item.setText(0, "item " + i); +* item.setText(1, "edit this value"); +* for (int j = 0; j < 3; j++) { +* TableTreeItem subitem = new TableTreeItem(item, SWT.NONE); +* subitem.setText(0, "subitem " + i + " " + j); +* subitem.setText(1, "edit this value"); +* } +* } +* column1.setWidth(100); +* column2.pack(); +* +* final TableTreeEditor editor = new TableTreeEditor(tableTree); +* //The editor must have the same size as the cell and must +* //not be any smaller than 50 pixels. +* editor.horizontalAlignment = SWT.LEFT; +* editor.grabHorizontal = true; +* editor.minimumWidth = 50; +* // editing the second column +* final int EDITABLECOLUMN = 1; +* +* tableTree.addSelectionListener(new SelectionAdapter() { +* public void widgetSelected(SelectionEvent e) { +* // Clean up any previous editor control +* Control oldEditor = editor.getEditor(); +* if (oldEditor != null) oldEditor.dispose(); +* +* // Identify the selected row +* TableTreeItem item = (TableTreeItem)e.item; +* if (item == null) return; +* +* // The control that will be the editor must be a child of the Table +* Text newEditor = new Text(table, SWT.NONE); +* newEditor.setText(item.getText(EDITABLECOLUMN)); +* newEditor.addModifyListener(new ModifyListener() { +* public void modifyText(ModifyEvent e) { +* Text text = (Text)editor.getEditor(); +* editor.getItem().setText(EDITABLECOLUMN, text.getText()); +* } +* }); +* newEditor.selectAll(); +* newEditor.setFocus(); +* editor.setEditor(newEditor, item, EDITABLECOLUMN); +* } +* }); +* </pre></code> +* +* @deprecated As of 3.1 use TreeEditor with Tree, TreeItem and TreeColumn +*/ +public class TableTreeEditor extends ControlEditor { + + TableTree tableTree; + TableTreeItem item; + int column = -1; + ControlListener columnListener; + TreeListener treeListener; +/** +* Creates a TableTreeEditor for the specified TableTree. +* +* @param tableTree the TableTree Control above which this editor will be displayed +* +*/ +public TableTreeEditor (TableTree tableTree) { + super(tableTree.getTable()); + this.tableTree = tableTree; + + treeListener = new TreeListener () { + final Runnable runnable = new Runnable() { + public void run() { + if (editor == null || editor.isDisposed()) return; + if (TableTreeEditor.this.tableTree.isDisposed()) return; + layout(); + editor.setVisible(true); + } + }; + public void treeCollapsed(TreeEvent e) { + if (editor == null || editor.isDisposed ()) return; + editor.setVisible(false); + e.display.asyncExec(runnable); + } + public void treeExpanded(TreeEvent e) { + if (editor == null || editor.isDisposed ()) return; + editor.setVisible(false); + e.display.asyncExec(runnable); + } + }; + tableTree.addTreeListener(treeListener); + + columnListener = new ControlListener() { + public void controlMoved(ControlEvent e){ + layout (); + } + public void controlResized(ControlEvent e){ + layout (); + } + }; + + // To be consistent with older versions of SWT, grabVertical defaults to true + grabVertical = true; +} +Rectangle computeBounds () { + if (item == null || column == -1 || item.isDisposed() || item.tableItem == null) return new Rectangle(0, 0, 0, 0); + Rectangle cell = item.getBounds(column); + Rectangle area = tableTree.getClientArea(); + if (cell.x < area.x + area.width) { + if (cell.x + cell.width > area.x + area.width) { + cell.width = area.x + area.width - cell.x; + } + } + Rectangle editorRect = new Rectangle(cell.x, cell.y, minimumWidth, minimumHeight); + + if (grabHorizontal) { + editorRect.width = Math.max(cell.width, minimumWidth); + } + + if (grabVertical) { + editorRect.height = Math.max(cell.height, minimumHeight); + } + + if (horizontalAlignment == SWT.RIGHT) { + editorRect.x += cell.width - editorRect.width; + } else if (horizontalAlignment == SWT.LEFT) { + // do nothing - cell.x is the right answer + } else { // default is CENTER + editorRect.x += (cell.width - editorRect.width)/2; + } + + if (verticalAlignment == SWT.BOTTOM) { + editorRect.y += cell.height - editorRect.height; + } else if (verticalAlignment == SWT.TOP) { + // do nothing - cell.y is the right answer + } else { // default is CENTER + editorRect.y += (cell.height - editorRect.height)/2; + } + return editorRect; +} +/** + * Removes all associations between the TableTreeEditor and the cell in the table tree. The + * TableTree and the editor Control are <b>not</b> disposed. + */ +public void dispose () { + if (tableTree != null && !tableTree.isDisposed()) { + Table table = tableTree.getTable(); + if (table != null && !table.isDisposed()) { + if (this.column > -1 && this.column < table.getColumnCount()){ + TableColumn tableColumn = table.getColumn(this.column); + tableColumn.removeControlListener(columnListener); + } + } + if (treeListener != null) tableTree.removeTreeListener(treeListener); + } + treeListener = null; + columnListener = null; + tableTree = null; + item = null; + column = -1; + super.dispose(); +} +/** +* Returns the zero based index of the column of the cell being tracked by this editor. +* +* @return the zero based index of the column of the cell being tracked by this editor +*/ +public int getColumn () { + return column; +} +/** +* Returns the TableTreeItem for the row of the cell being tracked by this editor. +* +* @return the TableTreeItem for the row of the cell being tracked by this editor +*/ +public TableTreeItem getItem () { + return item; +} +public void setColumn(int column) { + Table table = tableTree.getTable(); + int columnCount = table.getColumnCount(); + // Separately handle the case where the table has no TableColumns. + // In this situation, there is a single default column. + if (columnCount == 0) { + this.column = (column == 0) ? 0 : -1; + layout(); + return; + } + if (this.column > -1 && this.column < columnCount){ + TableColumn tableColumn = table.getColumn(this.column); + tableColumn.removeControlListener(columnListener); + this.column = -1; + } + + if (column < 0 || column >= table.getColumnCount()) return; + + this.column = column; + TableColumn tableColumn = table.getColumn(this.column); + tableColumn.addControlListener(columnListener); + layout(); +} +public void setItem (TableTreeItem item) { + this.item = item; + layout(); +} + +/** +* Specify the Control that is to be displayed and the cell in the table that it is to be positioned above. +* +* <p>Note: The Control provided as the editor <b>must</b> be created with its parent being the Table control +* specified in the TableEditor constructor. +* +* @param editor the Control that is displayed above the cell being edited +* @param item the TableItem for the row of the cell being tracked by this editor +* @param column the zero based index of the column of the cell being tracked by this editor +*/ +public void setEditor (Control editor, TableTreeItem item, int column) { + setItem(item); + setColumn(column); + setEditor(editor); +} +public void layout () { + if (tableTree == null || tableTree.isDisposed()) return; + if (item == null || item.isDisposed()) return; + Table table = tableTree.getTable(); + int columnCount = table.getColumnCount(); + if (columnCount == 0 && column != 0) return; + if (columnCount > 0 && (column < 0 || column >= columnCount)) return; + super.layout(); +} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TableTreeItem.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TableTreeItem.java new file mode 100755 index 0000000000..83c2fc8422 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TableTreeItem.java @@ -0,0 +1,876 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 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.custom; + + +import org.eclipse.swt.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.widgets.*; + +/** + * A TableTreeItem is a selectable user interface object + * that represents an item in a hierarchy of items in a + * TableTree. + * + * @deprecated As of 3.1 use Tree, TreeItem and TreeColumn + */ +public class TableTreeItem extends Item { + TableItem tableItem; + TableTree parent; + TableTreeItem parentItem; + TableTreeItem [] items = TableTree.EMPTY_ITEMS; + String[] texts = TableTree.EMPTY_TEXTS; + Image[] images = TableTree.EMPTY_IMAGES; + Color background; + Color foreground; + Font font; + boolean expanded; + boolean checked; + boolean grayed; + +/** + * Constructs a new instance of this class given its parent + * (which must be a <code>TableTree</code>) + * and a style value describing its behavior and appearance. + * The item is added to the end of the items maintained by its parent. + * <p> + * The style value is either one of the style constants defined in + * class <code>SWT</code> which is applicable to instances of this + * class, or must be built by <em>bitwise OR</em>'ing together + * (that is, using the <code>int</code> "|" operator) two or more + * of those <code>SWT</code> style constants. The class description + * lists the style constants that are applicable to the class. + * Style bits are also inherited from superclasses. + * </p> + * + * @param parent a composite control which will be the parent of the new instance (cannot be null) + * @param style the style of control to construct + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the parent is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li> + * </ul> + * + * @see SWT + * @see Widget#getStyle() + */ +public TableTreeItem(TableTree parent, int style) { + this (parent, style, parent.getItemCount()); +} + +/** + * Constructs a new instance of this class given its parent + * (which must be a <code>TableTree</code>, + * a style value describing its behavior and appearance, and the index + * at which to place it in the items maintained by its parent. + * <p> + * The style value is either one of the style constants defined in + * class <code>SWT</code> which is applicable to instances of this + * class, or must be built by <em>bitwise OR</em>'ing together + * (that is, using the <code>int</code> "|" operator) two or more + * of those <code>SWT</code> style constants. The class description + * lists the style constants that are applicable to the class. + * Style bits are also inherited from superclasses. + * </p> + * + * @param parent a composite control which will be the parent of the new instance (cannot be null) + * @param style the style of control to construct + * @param index the index to store the receiver in its parent + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the parent is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li> + * </ul> + * + * @see SWT + * @see Widget#getStyle() + */ +public TableTreeItem(TableTree parent, int style, int index) { + this (parent, null, style, index); +} + +/** + * Constructs a new instance of this class given its parent + * (which must be a <code>TableTreeItem</code>) + * and a style value describing its behavior and appearance. + * The item is added to the end of the items maintained by its parent. + * <p> + * The style value is either one of the style constants defined in + * class <code>SWT</code> which is applicable to instances of this + * class, or must be built by <em>bitwise OR</em>'ing together + * (that is, using the <code>int</code> "|" operator) two or more + * of those <code>SWT</code> style constants. The class description + * lists the style constants that are applicable to the class. + * Style bits are also inherited from superclasses. + * </p> + * + * @param parent a composite control which will be the parent of the new instance (cannot be null) + * @param style the style of control to construct + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the parent is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li> + * </ul> + * + * @see SWT + * @see Widget#getStyle() + */ +public TableTreeItem(TableTreeItem parent, int style) { + this (parent, style, parent.getItemCount()); +} + +/** + * Constructs a new instance of this class given its parent + * (which must be a <code>TableTreeItem</code>), + * a style value describing its behavior and appearance, and the index + * at which to place it in the items maintained by its parent. + * <p> + * The style value is either one of the style constants defined in + * class <code>SWT</code> which is applicable to instances of this + * class, or must be built by <em>bitwise OR</em>'ing together + * (that is, using the <code>int</code> "|" operator) two or more + * of those <code>SWT</code> style constants. The class description + * lists the style constants that are applicable to the class. + * Style bits are also inherited from superclasses. + * </p> + * + * @param parent a composite control which will be the parent of the new instance (cannot be null) + * @param style the style of control to construct + * @param index the index to store the receiver in its parent + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the parent is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li> + * </ul> + * + * @see SWT + * @see Widget#getStyle() + */ +public TableTreeItem(TableTreeItem parent, int style, int index) { + this (parent.getParent(), parent, style, index); +} + +TableTreeItem(TableTree parent, TableTreeItem parentItem, int style, int index) { + super(parent, style); + this.parent = parent; + this.parentItem = parentItem; + if (parentItem == null) { + + /* Root items are visible immediately */ + int tableIndex = parent.addItem(this, index); + tableItem = new TableItem(parent.getTable(), style, tableIndex); + tableItem.setData(TableTree.ITEMID, this); + addCheck(); + /* + * Feature in the Table. The table uses the first image that + * is inserted into the table to size the table rows. If the + * user is allowed to insert the first image, this will cause + * the +/- images to be scaled. The fix is to insert a dummy + * image to force the size. + */ + if (parent.sizeImage == null) { + int itemHeight = parent.getItemHeight(); + parent.sizeImage = new Image(parent.getDisplay(), itemHeight, itemHeight); + GC gc = new GC (parent.sizeImage); + gc.setBackground(parent.getBackground()); + gc.fillRectangle(0, 0, itemHeight, itemHeight); + gc.dispose(); + tableItem.setImage(0, parent.sizeImage); + } + } else { + parentItem.addItem(this, index); + } +} +void addCheck() { + Table table = parent.getTable(); + if ((table.getStyle() & SWT.CHECK) == 0) return; + tableItem.setChecked(checked); + tableItem.setGrayed(grayed); +} +void addItem(TableTreeItem item, int index) { + if (item == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); + if (index < 0 || index > items.length) SWT.error(SWT.ERROR_INVALID_ARGUMENT); + + /* Now that item has a sub-node it must indicate that it can be expanded */ + if (items.length == 0 && index == 0) { + if (tableItem != null) { + Image image = expanded ? parent.getMinusImage() : parent.getPlusImage(); + tableItem.setImage(0, image); + } + } + + /* Put the item in the items list */ + TableTreeItem[] newItems = new TableTreeItem[items.length + 1]; + System.arraycopy(items, 0, newItems, 0, index); + newItems[index] = item; + System.arraycopy(items, index, newItems, index + 1, items.length - index); + items = newItems; + if (expanded) item.setVisible(true); +} + +/** + * Returns the receiver's background color. + * + * @return the background color + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 2.0 + * + */ +public Color getBackground () { + checkWidget (); + return (background == null) ? parent.getBackground() : background; +} + +/** + * Returns a rectangle describing the receiver's size and location + * relative to its parent. + * + * @return the receiver's bounding rectangle + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public Rectangle getBounds (int index) { + checkWidget(); + if (tableItem != null) { + return tableItem.getBounds(index); + } else { + return new Rectangle(0, 0, 0, 0); + } +} +/** + * Returns <code>true</code> if the receiver is checked, + * and false otherwise. When the parent does not have + * the <code>CHECK style, return false. + * + * @return the checked state of the checkbox + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public boolean getChecked () { + checkWidget(); + if (tableItem == null) return checked; + return tableItem.getChecked(); +} + +/** + * Returns <code>true</code> if the receiver is grayed, + * and false otherwise. When the parent does not have + * the <code>CHECK</code> style, return false. + * + * @return the grayed state of the checkbox + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 2.1 + */ +public boolean getGrayed () { + checkWidget(); + if (tableItem == null) return grayed; + return tableItem.getGrayed(); +} + +/** + * Returns <code>true</code> if the receiver is expanded, + * and false otherwise. + * <p> + * + * @return the expanded state + */ +public boolean getExpanded () { + //checkWidget(); + return expanded; +} + +/** + * Returns the font that the receiver will use to paint textual information for this item. + * + * @return the receiver's font + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.0 + */ +public Font getFont () { + checkWidget (); + return (font == null) ? parent.getFont() : font; +} +/** + * Returns the foreground color that the receiver will use to draw. + * + * @return the receiver's foreground color + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 2.0 + * + */ +public Color getForeground () { + checkWidget (); + return (foreground == null) ? parent.getForeground() : foreground; +} +/** + * Gets the first image. + * <p> + * The image in column 0 is reserved for the [+] and [-] + * images of the tree, therefore getImage(0) will return null. + * + * @return the image at index 0 + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public Image getImage () { + checkWidget(); + return getImage(0); +} + +/** + * Gets the image at the specified index. + * <p> + * Indexing is zero based. The image can be null. + * The image in column 0 is reserved for the [+] and [-] + * images of the tree, therefore getImage(0) will return null. + * Return null if the index is out of range. + * + * @param index the index of the image + * @return the image at the specified index or null + */ +public Image getImage (int index) { + //checkWidget(); + if (0 < index && index < images.length) return images[index]; + return null; +} + +int getIndent() { + if (parentItem == null) return 0; + return parentItem.getIndent() + 1; +} + +/** + * Returns the item at the given, zero-relative index in the + * receiver. Throws an exception if the index is out of range. + * + * @param index the index of the item to return + * @return the item at the given index + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list minus 1 (inclusive)</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.1 + */ +public TableTreeItem getItem (int index) { + checkWidget(); + int count = items.length; + if (!(0 <= index && index < count)) SWT.error (SWT.ERROR_INVALID_RANGE); + return items [index]; +} + +/** + * Returns the number of items contained in the receiver + * that are direct item children of the receiver. + * + * @return the number of items + */ +public int getItemCount () { + //checkWidget(); + return items.length; +} + +/** + * Returns an array of <code>TableTreeItem</code>s which are the + * direct item children of the receiver. + * <p> + * Note: This is not the actual structure used by the receiver + * to maintain its list of items, so modifying the array will + * not affect the receiver. + * </p> + * + * @return the receiver's items + */ +public TableTreeItem[] getItems () { + //checkWidget(); + TableTreeItem[] newItems = new TableTreeItem[items.length]; + System.arraycopy(items, 0, newItems, 0, items.length); + return newItems; +} + +TableTreeItem getItem(TableItem tableItem) { + if (tableItem == null) return null; + if (this.tableItem == tableItem) return this; + for (int i = 0; i < items.length; i++) { + TableTreeItem item = items[i].getItem(tableItem); + if (item != null) return item; + } + return null; +} + +/** + * Returns the receiver's parent, which must be a <code>TableTree</code>. + * + * @return the receiver's parent + */ +public TableTree getParent () { + //checkWidget(); + return parent; +} + +/** + * Returns the receiver's parent item, which must be a + * <code>TableTreeItem</code> or null when the receiver is a + * root. + * + * @return the receiver's parent item + */ +public TableTreeItem getParentItem () { + //checkWidget(); + return parentItem; +} +public String getText () { + checkWidget(); + return getText(0); +} + +/** + * Gets the item text at the specified index. + * <p> + * Indexing is zero based. + * + * This operation will fail when the index is out + * of range or an item could not be queried from + * the OS. + * + * @param index the index of the item + * @return the item text at the specified index, which can be null + */ +public String getText(int index) { + //checkWidget(); + if (0 <= index && index < texts.length) return texts[index]; + return null; +} + +boolean getVisible () { + return tableItem != null; +} + +/** + * Gets the index of the specified item. + * + * <p>The widget is searched starting at 0 until an + * item is found that is equal to the search item. + * If no item is found, -1 is returned. Indexing + * is zero based. This index is relative to the parent only. + * + * @param item the search item + * @return the index of the item or -1 if the item is not found + * + */ +public int indexOf (TableTreeItem item) { + //checkWidget(); + for (int i = 0; i < items.length; i++) { + if (items[i] == item) return i; + } + return -1; +} + +void expandAll(boolean notify) { + if (items.length == 0) return; + if (!expanded) { + setExpanded(true); + if (notify) { + Event event = new Event(); + event.item = this; + parent.notifyListeners(SWT.Expand, event); + } + } + for (int i = 0; i < items.length; i++) { + items[i].expandAll(notify); + } +} +int expandedIndexOf (TableTreeItem item) { + int index = 0; + for (int i = 0; i < items.length; i++) { + if (items[i] == item) return index; + if (items[i].expanded) index += items[i].visibleChildrenCount (); + index++; + } + return -1; +} + +int visibleChildrenCount () { + int count = 0; + for (int i = 0; i < items.length; i++) { + if (items[i].getVisible ()) { + count += 1 + items[i].visibleChildrenCount (); + } + } + return count; +} + +public void dispose () { + if (isDisposed()) return; + for (int i = items.length - 1; i >= 0; i--) { + items[i].dispose(); + } + super.dispose(); + if (!parent.inDispose) { + if (parentItem != null) { + parentItem.removeItem(this); + } else { + parent.removeItem(this); + } + if (tableItem != null) tableItem.dispose(); + } + items = null; + parentItem = null; + parent = null; + images = null; + texts = null; + tableItem = null; + foreground = null; + background = null; + font = null; +} + +void removeItem(TableTreeItem item) { + int index = 0; + while (index < items.length && items[index] != item) index++; + if (index == items.length) return; + TableTreeItem[] newItems = new TableTreeItem[items.length - 1]; + System.arraycopy(items, 0, newItems, 0, index); + System.arraycopy(items, index + 1, newItems, index, items.length - index - 1); + items = newItems; + if (items.length == 0) { + if (tableItem != null) tableItem.setImage(0, null); + } +} + +/** + * Sets the receiver's background color to the color specified + * by the argument, or to the default system color for the item + * if the argument is null. + * + * @param color the new color (or null) + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 2.0 + * + */ +public void setBackground (Color color) { + checkWidget (); + if (color != null && color.isDisposed ()) { + SWT.error (SWT.ERROR_INVALID_ARGUMENT); + } + if (tableItem != null) { + tableItem.setBackground(color); + } + background = color; +} + +/** + * Sets the checked state of the checkbox for this item. This state change + * only applies if the Table was created with the SWT.CHECK style. + * + * @param checked the new checked state of the checkbox + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void setChecked (boolean checked) { + checkWidget(); + Table table = parent.getTable(); + if ((table.getStyle() & SWT.CHECK) == 0) return; + if (tableItem != null) { + tableItem.setChecked(checked); + } + this.checked = checked; +} + +/** + * Sets the grayed state of the checkbox for this item. This state change + * only applies if the Table was created with the SWT.CHECK style. + * + * @param grayed the new grayed state of the checkbox; + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 2.1 + */ +public void setGrayed (boolean grayed) { + checkWidget(); + Table table = parent.getTable(); + if ((table.getStyle() & SWT.CHECK) == 0) return; + if (tableItem != null) { + tableItem.setGrayed(grayed); + } + this.grayed = grayed; +} + +/** + * Sets the expanded state. + * <p> + * @param expanded the new expanded state. + * + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li> + * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li> + * </ul> + */ +public void setExpanded (boolean expanded) { + checkWidget(); + if (items.length == 0) return; + if (this.expanded == expanded) return; + this.expanded = expanded; + if (tableItem == null) return; + parent.setRedraw(false); + for (int i = 0; i < items.length; i++) { + items[i].setVisible(expanded); + } + Image image = expanded ? parent.getMinusImage() : parent.getPlusImage(); + tableItem.setImage(0, image); + parent.setRedraw(true); +} + +/** + * Sets the font that the receiver will use to paint textual information + * for this item to the font specified by the argument, or to the default font + * for that kind of control if the argument is null. + * + * @param font the new font (or null) + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 3.0 + */ +public void setFont (Font font){ + checkWidget (); + if (font != null && font.isDisposed ()) { + SWT.error (SWT.ERROR_INVALID_ARGUMENT); + } + if (tableItem != null) { + tableItem.setFont(font); + } + this.font = font; +} +/** + * Sets the receiver's foreground color to the color specified + * by the argument, or to the default system color for the item + * if the argument is null. + * + * @param color the new color (or null) + * + * @since 2.0 + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + * + * @since 2.0 + * + */ +public void setForeground (Color color) { + checkWidget (); + if (color != null && color.isDisposed ()) { + SWT.error (SWT.ERROR_INVALID_ARGUMENT); + } + if (tableItem != null) { + tableItem.setForeground(color); + } + foreground = color; +} + +/** + * Sets the image at an index. + * <p> + * The image can be null. + * The image in column 0 is reserved for the [+] and [-] + * images of the tree, therefore do nothing if index is 0. + * + * @param image the new image or null + * + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li> + * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li> + * </ul> + */ +public void setImage (int index, Image image) { + checkWidget(); + int columnCount = Math.max(parent.getTable().getColumnCount(), 1); + if (index <= 0 || index >= columnCount) return; + if (images.length < columnCount) { + Image[] newImages = new Image[columnCount]; + System.arraycopy(images, 0, newImages, 0, images.length); + images = newImages; + } + images[index] = image; + if (tableItem != null) tableItem.setImage(index, image); +} + +/** + * Sets the first image. + * <p> + * The image can be null. + * The image in column 0 is reserved for the [+] and [-] + * images of the tree, therefore do nothing. + * + * @param image the new image or null + * + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li> + * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li> + * </ul> + */ +public void setImage (Image image) { + setImage(0, image); +} + +/** + * Sets the widget text. + * <p> + * + * The widget text for an item is the label of the + * item or the label of the text specified by a column + * number. + * + * @param index the column number + * @param text the new text + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the text is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li> + * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li> + * </ul> + */ +public void setText(int index, String text) { + checkWidget(); + if (text == null) SWT.error (SWT.ERROR_NULL_ARGUMENT); + int columnCount = Math.max(parent.getTable().getColumnCount(), 1); + if (index < 0 || index >= columnCount) return; + if (texts.length < columnCount) { + String[] newTexts = new String[columnCount]; + System.arraycopy(texts, 0, newTexts, 0, texts.length); + texts = newTexts; + } + texts[index] = text; + if (tableItem != null) tableItem.setText(index, text); +} +public void setText (String string) { + setText(0, string); +} + +void setVisible (boolean show) { + if (parentItem == null) return; // this is a root and can not be toggled between visible and hidden + if (getVisible() == show) return; + + if (show) { + if (!parentItem.getVisible()) return; // parentItem must already be visible + // create underlying table item and set data in table item to stored data + Table table = parent.getTable(); + int parentIndex = table.indexOf(parentItem.tableItem); + int index = parentItem.expandedIndexOf(this) + parentIndex + 1; + if (index < 0) return; + tableItem = new TableItem(table, getStyle(), index); + tableItem.setData(TableTree.ITEMID, this); + tableItem.setImageIndent(getIndent()); + if (background != null) tableItem.setBackground(background); + if (foreground != null) tableItem.setForeground(foreground); + if (font != null) tableItem.setFont(font); + addCheck(); + + // restore fields to item + // ignore any images in the first column + int columnCount = Math.max(table.getColumnCount(), 1); + for (int i = 0; i < columnCount; i++) { + if (i < texts.length && texts[i] != null) setText(i, texts[i]); + if (i < images.length && images[i] != null) setImage(i, images[i]); + } + + // display the children and the appropriate [+]/[-] symbol as required + if (items.length != 0) { + if (expanded) { + tableItem.setImage(0, parent.getMinusImage()); + for (int i = 0, length = items.length; i < length; i++) { + items[i].setVisible(true); + } + } else { + tableItem.setImage(0, parent.getPlusImage()); + } + } + + } else { + + for (int i = 0, length = items.length; i < length; i++) { + items[i].setVisible(false); + } + // remove row from table + tableItem.dispose(); + tableItem = null; + } +} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TextChangeListener.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TextChangeListener.java new file mode 100755 index 0000000000..a7c53a855c --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TextChangeListener.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 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.custom; + + +import org.eclipse.swt.internal.SWTEventListener; + +/** + * The StyledText widget implements this listener to receive + * notifications when changes to the model occur. + * It is not intended to be implemented by clients or by + * implementors of StyledTextContent. + * Clients should listen to the ModifyEvent or ExtendedModifyEvent + * that is sent by the StyledText widget to receive text change + * notifications. + * Implementors of StyledTextContent should call the textChanging + * and textChanged methods when text changes occur as described + * below. If the entire text is replaced the textSet method + * should be called instead. + */ +public interface TextChangeListener extends SWTEventListener { + +/** + * This method is called when the content is about to be changed. + * Callers also need to call the textChanged method after the + * content change has been applied. The widget only updates the + * screen properly when it receives both events. + * + * @param event the text changing event. All event fields need + * to be set by the sender. + * @see TextChangingEvent + */ +public void textChanging(TextChangingEvent event); +/** + * This method is called when the content has changed. + * Callers need to have called the textChanging method prior to + * applying the content change and calling this method. The widget + * only updates the screen properly when it receives both events. + * + * @param event the text changed event + */ +public void textChanged(TextChangedEvent event); +/** + * This method is called instead of the textChanging/textChanged + * combination when the entire old content has been replaced + * (e.g., by a call to StyledTextContent.setText()). + * + * @param event the text changed event + */ +public void textSet(TextChangedEvent event); +} + + diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TextChangedEvent.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TextChangedEvent.java new file mode 100755 index 0000000000..0cacf8a6f7 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TextChangedEvent.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 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.custom; + +import org.eclipse.swt.events.*; + +/** + * This event is sent by the StyledTextContent implementor when a change to + * the text occurs. + * + * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> + */ +public class TextChangedEvent extends TypedEvent { + + static final long serialVersionUID = 3258696524257835065L; + +/** + * Create the TextChangedEvent to be used by the StyledTextContent implementor. + * <p> + * + * @param source the object that will be sending the TextChangedEvent, + * cannot be null + */ +public TextChangedEvent(StyledTextContent source) { + super(source); +} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TextChangingEvent.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TextChangingEvent.java new file mode 100755 index 0000000000..cf109a27dc --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TextChangingEvent.java @@ -0,0 +1,71 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 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.custom; + + +import org.eclipse.swt.events.*; + +/** + * This event is sent by the StyledTextContent implementor when a change + * to the text is about to occur. + * + * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> + */ +public class TextChangingEvent extends TypedEvent { + /** + * Start offset of the text that is going to be replaced + */ + public int start; + /** + * Text that is going to be inserted or empty string + * if no text will be inserted + */ + public String newText; + /** + * Length of text that is going to be replaced + */ + public int replaceCharCount; + /** + * Length of text that is going to be inserted + */ + public int newCharCount; + /** + * Number of lines that are going to be replaced + */ + public int replaceLineCount; + /** + * Number of new lines that are going to be inserted + */ + public int newLineCount; + + static final long serialVersionUID = 3257290210114352439L; + +/** + * Create the TextChangedEvent to be used by the StyledTextContent implementor. + * <p> + * + * @param source the object that will be sending the new TextChangingEvent, + * cannot be null + */ +public TextChangingEvent(StyledTextContent source) { + super(source); +} +TextChangingEvent(StyledTextContent source, StyledTextEvent e) { + super(source); + start = e.start; + replaceCharCount = e.replaceCharCount; + newCharCount = e.newCharCount; + replaceLineCount = e.replaceLineCount; + newLineCount = e.newLineCount; + newText = e.text; +} + +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TreeEditor.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TreeEditor.java new file mode 100755 index 0000000000..de4f7b2f4f --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TreeEditor.java @@ -0,0 +1,319 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 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.custom; + + +import org.eclipse.swt.*; +import org.eclipse.swt.events.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.widgets.*; + +/** +* +* A TreeEditor is a manager for a Control that appears above a cell in a Tree and tracks with the +* moving and resizing of that cell. It can be used to display a text widget above a cell +* in a Tree so that the user can edit the contents of that cell. It can also be used to display +* a button that can launch a dialog for modifying the contents of the associated cell. +* +* <p> Here is an example of using a TreeEditor: +* <code><pre> +* final Tree tree = new Tree(shell, SWT.BORDER); +* for (int i = 0; i < 3; i++) { +* TreeItem item = new TreeItem(tree, SWT.NONE); +* item.setText("item " + i); +* for (int j = 0; j < 3; j++) { +* TreeItem subItem = new TreeItem(item, SWT.NONE); +* subItem.setText("item " + i + " " + j); +* } +* } +* +* final TreeEditor editor = new TreeEditor(tree); +* //The editor must have the same size as the cell and must +* //not be any smaller than 50 pixels. +* editor.horizontalAlignment = SWT.LEFT; +* editor.grabHorizontal = true; +* editor.minimumWidth = 50; +* +* tree.addSelectionListener(new SelectionAdapter() { +* public void widgetSelected(SelectionEvent e) { +* // Clean up any previous editor control +* Control oldEditor = editor.getEditor(); +* if (oldEditor != null) oldEditor.dispose(); +* +* // Identify the selected row +* TreeItem item = (TreeItem)e.item; +* if (item == null) return; +* +* // The control that will be the editor must be a child of the Tree +* Text newEditor = new Text(tree, SWT.NONE); +* newEditor.setText(item.getText()); +* newEditor.addModifyListener(new ModifyListener() { +* public void modifyText(ModifyEvent e) { +* Text text = (Text)editor.getEditor(); +* editor.getItem().setText(text.getText()); +* } +* }); +* newEditor.selectAll(); +* newEditor.setFocus(); +* editor.setEditor(newEditor, item); +* } +* }); +* </pre></code> +* +* @see <a href="http://www.eclipse.org/swt/snippets/#treeeditor">TreeEditor snippets</a> +* @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> +*/ +public class TreeEditor extends ControlEditor { + Tree tree; + TreeItem item; + int column = 0; + ControlListener columnListener; + TreeListener treeListener; + Runnable timer; + static final int TIMEOUT = 1500; + +/** +* Creates a TreeEditor for the specified Tree. +* +* @param tree the Tree Control above which this editor will be displayed +* +*/ +public TreeEditor (Tree tree) { + super(tree); + this.tree = tree; + + columnListener = new ControlListener() { + public void controlMoved(ControlEvent e){ + layout(); + } + public void controlResized(ControlEvent e){ + layout(); + } + }; + timer = new Runnable () { + public void run() { + layout (); + } + }; + treeListener = new TreeListener () { + final Runnable runnable = new Runnable() { + public void run() { + if (editor == null || editor.isDisposed()) return; + if (TreeEditor.this.tree.isDisposed()) return; + layout(); + editor.setVisible(true); + } + }; + public void treeCollapsed(TreeEvent e) { + if (editor == null || editor.isDisposed ()) return; + editor.setVisible(false); + e.display.asyncExec(runnable); + } + public void treeExpanded(TreeEvent e) { + if (editor == null || editor.isDisposed ()) return; + editor.setVisible(false); + e.display.asyncExec(runnable); + } + }; + tree.addTreeListener(treeListener); + + // To be consistent with older versions of SWT, grabVertical defaults to true + grabVertical = true; +} + +Rectangle computeBounds () { + if (item == null || column == -1 || item.isDisposed()) return new Rectangle(0, 0, 0, 0); + Rectangle cell = item.getBounds(column); + Rectangle rect = item.getImageBounds(column); + cell.x = rect.x + rect.width; + cell.width -= rect.width; + Rectangle area = tree.getClientArea(); + if (cell.x < area.x + area.width) { + if (cell.x + cell.width > area.x + area.width) { + cell.width = area.x + area.width - cell.x; + } + } + Rectangle editorRect = new Rectangle(cell.x, cell.y, minimumWidth, minimumHeight); + + if (grabHorizontal) { + if (tree.getColumnCount() == 0) { + // Bounds of tree item only include the text area - stretch out to include + // entire client area + cell.width = area.x + area.width - cell.x; + } + editorRect.width = Math.max(cell.width, minimumWidth); + } + + if (grabVertical) { + editorRect.height = Math.max(cell.height, minimumHeight); + } + + if (horizontalAlignment == SWT.RIGHT) { + editorRect.x += cell.width - editorRect.width; + } else if (horizontalAlignment == SWT.LEFT) { + // do nothing - cell.x is the right answer + } else { // default is CENTER + editorRect.x += (cell.width - editorRect.width)/2; + } + // don't let the editor overlap with the +/- of the tree + editorRect.x = Math.max(cell.x, editorRect.x); + + if (verticalAlignment == SWT.BOTTOM) { + editorRect.y += cell.height - editorRect.height; + } else if (verticalAlignment == SWT.TOP) { + // do nothing - cell.y is the right answer + } else { // default is CENTER + editorRect.y += (cell.height - editorRect.height)/2; + } + return editorRect; +} + +/** + * Removes all associations between the TreeEditor and the row in the tree. The + * tree and the editor Control are <b>not</b> disposed. + */ +public void dispose () { + if (tree != null && !tree.isDisposed()) { + if (this.column > -1 && this.column < tree.getColumnCount()){ + TreeColumn treeColumn = tree.getColumn(this.column); + treeColumn.removeControlListener(columnListener); + } + if (treeListener != null) tree.removeTreeListener(treeListener); + } + columnListener = null; + treeListener = null; + tree = null; + item = null; + column = 0; + timer = null; + super.dispose(); +} + +/** +* Returns the zero based index of the column of the cell being tracked by this editor. +* +* @return the zero based index of the column of the cell being tracked by this editor +* +* @since 3.1 +*/ +public int getColumn () { + return column; +} + +/** +* Returns the TreeItem for the row of the cell being tracked by this editor. +* +* @return the TreeItem for the row of the cell being tracked by this editor +*/ +public TreeItem getItem () { + return item; +} + +void resize () { + layout(); + /* + * On some platforms, the table scrolls when an item that + * is partially visible at the bottom of the table is + * selected. Ensure that the correct row is edited by + * laying out one more time in a timerExec(). + */ + if (tree != null) { + Display display = tree.getDisplay(); + display.timerExec(-1, timer); + display.timerExec(TIMEOUT, timer); + } +} + +/** +* Sets the zero based index of the column of the cell being tracked by this editor. +* +* @param column the zero based index of the column of the cell being tracked by this editor +* +* @since 3.1 +*/ +public void setColumn(int column) { + int columnCount = tree.getColumnCount(); + // Separately handle the case where the tree has no TreeColumns. + // In this situation, there is a single default column. + if (columnCount == 0) { + this.column = (column == 0) ? 0 : -1; + resize(); + return; + } + if (this.column > -1 && this.column < columnCount){ + TreeColumn treeColumn = tree.getColumn(this.column); + treeColumn.removeControlListener(columnListener); + this.column = -1; + } + + if (column < 0 || column >= tree.getColumnCount()) return; + + this.column = column; + TreeColumn treeColumn = tree.getColumn(this.column); + treeColumn.addControlListener(columnListener); + resize(); +} + +/** +* Specifies the <code>TreeItem</code> that is to be edited. +* +* @param item the item to be edited +*/ +public void setItem (TreeItem item) { + this.item = item; + resize(); +} + +/** +* Specify the Control that is to be displayed and the cell in the tree that it is to be positioned above. +* +* <p>Note: The Control provided as the editor <b>must</b> be created with its parent being the Tree control +* specified in the TreeEditor constructor. +* +* @param editor the Control that is displayed above the cell being edited +* @param item the TreeItem for the row of the cell being tracked by this editor +* @param column the zero based index of the column of the cell being tracked by this editor +* +* @since 3.1 +*/ +public void setEditor (Control editor, TreeItem item, int column) { + setItem(item); + setColumn(column); + setEditor(editor); +} +public void setEditor (Control editor) { + super.setEditor(editor); + resize(); +} + +/** +* Specify the Control that is to be displayed and the cell in the tree that it is to be positioned above. +* +* <p>Note: The Control provided as the editor <b>must</b> be created with its parent being the Tree control +* specified in the TreeEditor constructor. +* +* @param editor the Control that is displayed above the cell being edited +* @param item the TreeItem for the row of the cell being tracked by this editor +*/ +public void setEditor (Control editor, TreeItem item) { + setItem(item); + setEditor(editor); +} + +public void layout () { + if (tree == null || tree.isDisposed()) return; + if (item == null || item.isDisposed()) return; + int columnCount = tree.getColumnCount(); + if (columnCount == 0 && column != 0) return; + if (columnCount > 0 && (column < 0 || column >= columnCount)) return; + super.layout(); +} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/VerifyKeyListener.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/VerifyKeyListener.java new file mode 100755 index 0000000000..6d308aecf0 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/VerifyKeyListener.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2000, 2008 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.custom; + +import org.eclipse.swt.events.*; +import org.eclipse.swt.internal.SWTEventListener; + +/** + * Classes which implement this interface provide a method + * that deals with the event that is generated when a + * key is pressed. + * + * @see VerifyEvent + * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> + */ +public interface VerifyKeyListener extends SWTEventListener { +/** + * The following event fields are used:<ul> + * <li>event.character is the character that was typed (input)</li> + * <li>event.keyCode is the key code that was typed (input)</li> + * <li>event.stateMask is the state of the keyboard (input)</li> + * <li>event.doit is processed or not (output)</li> + * </ul> + * @param event the verify event + * @see VerifyEvent + */ +public void verifyKey (VerifyEvent event); +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/ViewForm.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/ViewForm.java new file mode 100755 index 0000000000..55c1915ff3 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/ViewForm.java @@ -0,0 +1,476 @@ +/******************************************************************************* + * 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.custom; + + +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.widgets.*; +import org.eclipse.swt.*; + +/** + * Instances of this class implement a Composite that positions and sizes + * children and allows programmatic control of layout and border parameters. + * ViewForm is used in the workbench to lay out a view's label/menu/toolbar + * local bar. + * <p> + * Note that although this class is a subclass of <code>Composite</code>, + * it does not make sense to set a layout on it. + * </p><p> + * <dl> + * <dt><b>Styles:</b></dt> + * <dd>BORDER, FLAT</dd> + * <dt><b>Events:</b></dt> + * <dd>(None)</dd> + * </dl> + * <p> + * IMPORTANT: This class is <em>not</em> intended to be subclassed. + * </p> + * + * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> + * @noextend This class is not intended to be subclassed by clients. + */ + +public class ViewForm extends Composite { + + /** + * marginWidth specifies the number of pixels of horizontal margin + * that will be placed along the left and right edges of the form. + * + * The default value is 0. + */ + public int marginWidth = 0; + /** + * marginHeight specifies the number of pixels of vertical margin + * that will be placed along the top and bottom edges of the form. + * + * The default value is 0. + */ + public int marginHeight = 0; + /** + * horizontalSpacing specifies the number of pixels between the right + * edge of one cell and the left edge of its neighbouring cell to + * the right. + * + * The default value is 1. + */ + public int horizontalSpacing = 1; + /** + * verticalSpacing specifies the number of pixels between the bottom + * edge of one cell and the top edge of its neighbouring cell underneath. + * + * The default value is 1. + */ + public int verticalSpacing = 1; + + /** + * Color of innermost line of drop shadow border. + * + * NOTE This field is badly named and can not be fixed for backwards compatibility. + * It should be capitalized. + * + * @deprecated + */ + public static RGB borderInsideRGB = new RGB (132, 130, 132); + /** + * Color of middle line of drop shadow border. + * + * NOTE This field is badly named and can not be fixed for backwards compatibility. + * It should be capitalized. + * + * @deprecated + */ + public static RGB borderMiddleRGB = new RGB (143, 141, 138); + /** + * Color of outermost line of drop shadow border. + * + * NOTE This field is badly named and can not be fixed for backwards compatibility. + * It should be capitalized. + * + * @deprecated + */ + public static RGB borderOutsideRGB = new RGB (171, 168, 165); + + // SWT widgets + Control topLeft; + Control topCenter; + Control topRight; + Control content; + + // Configuration and state info + boolean separateTopCenter = false; + boolean showBorder = false; + + int separator = -1; + int borderTop = 0; + int borderBottom = 0; + int borderLeft = 0; + int borderRight = 0; + int highlight = 0; + Point oldSize; + + Color selectionBackground; + Listener listener; + + static final int OFFSCREEN = -200; + static final int BORDER1_COLOR = SWT.COLOR_WIDGET_NORMAL_SHADOW; + static final int SELECTION_BACKGROUND = SWT.COLOR_LIST_BACKGROUND; +/** + * Constructs a new instance of this class given its parent + * and a style value describing its behavior and appearance. + * <p> + * The style value is either one of the style constants defined in + * class <code>SWT</code> which is applicable to instances of this + * class, or must be built by <em>bitwise OR</em>'ing together + * (that is, using the <code>int</code> "|" operator) two or more + * of those <code>SWT</code> style constants. The class description + * lists the style constants that are applicable to the class. + * Style bits are also inherited from superclasses. + * </p> + * + * @param parent a widget which will be the parent of the new instance (cannot be null) + * @param style the style of widget to construct + * + * @exception IllegalArgumentException <ul> + * <li>ERROR_NULL_ARGUMENT - if the parent is null</li> + * </ul> + * @exception SWTException <ul> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li> + * </ul> + * + * @see SWT#BORDER + * @see SWT#FLAT + * @see #getStyle() + */ +public ViewForm(Composite parent, int style) { + super(parent, checkStyle(style)); + super.setLayout(new ViewFormLayout()); + + setBorderVisible((style & SWT.BORDER) != 0); + + listener = new Listener() { + public void handleEvent(Event e) { + switch (e.type) { + case SWT.Dispose: onDispose(e); break; + case SWT.Paint: onPaint(e.gc); break; + case SWT.Resize: onResize(); break; + } + } + }; + + int[] events = new int[] {SWT.Dispose, SWT.Paint, SWT.Resize}; + + for (int i = 0; i < events.length; i++) { + addListener(events[i], listener); + } +} + +static int checkStyle (int style) { + int mask = SWT.FLAT | SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT; + return style & mask | SWT.NO_REDRAW_RESIZE; +} + +//protected void checkSubclass () { +// String name = getClass().getName (); +// String validName = ViewForm.class.getName(); +// if (!validName.equals(name)) { +// SWT.error (SWT.ERROR_INVALID_SUBCLASS); +// } +//} + +public Rectangle computeTrim (int x, int y, int width, int height) { + checkWidget (); + int trimX = x - borderLeft - highlight; + int trimY = y - borderTop - highlight; + int trimWidth = width + borderLeft + borderRight + 2*highlight; + int trimHeight = height + borderTop + borderBottom + 2*highlight; + return new Rectangle(trimX, trimY, trimWidth, trimHeight); +} +public Rectangle getClientArea() { + checkWidget(); + Rectangle clientArea = super.getClientArea(); + clientArea.x += borderLeft; + clientArea.y += borderTop; + clientArea.width -= borderLeft + borderRight; + clientArea.height -= borderTop + borderBottom; + return clientArea; +} +/** +* Returns the content area. +* +* @return the control in the content area of the pane or null +*/ +public Control getContent() { + //checkWidget(); + return content; +} +/** +* Returns Control that appears in the top center of the pane. +* Typically this is a toolbar. +* +* @return the control in the top center of the pane or null +*/ +public Control getTopCenter() { + //checkWidget(); + return topCenter; +} +/** +* Returns the Control that appears in the top left corner of the pane. +* Typically this is a label such as CLabel. +* +* @return the control in the top left corner of the pane or null +*/ +public Control getTopLeft() { + //checkWidget(); + return topLeft; +} +/** +* Returns the control in the top right corner of the pane. +* Typically this is a Close button or a composite with a Menu and Close button. +* +* @return the control in the top right corner of the pane or null +*/ +public Control getTopRight() { + //checkWidget(); + return topRight; +} +void onDispose(Event event) { + removeListener(SWT.Dispose, listener); + notifyListeners(SWT.Dispose, event); + event.type = SWT.None; + + topLeft = null; + topCenter = null; + topRight = null; + content = null; + oldSize = null; + selectionBackground = null; +} +void onPaint(GC gc) { + Color gcForeground = gc.getForeground(); + Point size = getSize(); + Color border = getDisplay().getSystemColor(BORDER1_COLOR); + if (showBorder) { + gc.setForeground(border); + gc.drawRectangle(0, 0, size.x - 1, size.y - 1); + if (highlight > 0) { + int x1 = 1; + int y1 = 1; + int x2 = size.x - 1; + int y2 = size.y - 1; + int[] shape = new int[] {x1,y1, x2,y1, x2,y2, x1,y2, x1,y1+highlight, + x1+highlight,y1+highlight, x1+highlight,y2-highlight, + x2-highlight,y2-highlight, x2-highlight,y1+highlight, x1,y1+highlight}; + Color highlightColor = getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION); + gc.setBackground(highlightColor); + gc.fillPolygon(shape); + } + } + if (separator > -1) { + gc.setForeground(border); + gc.drawLine(borderLeft + highlight, separator, size.x - borderLeft - borderRight - highlight, separator); + } + gc.setForeground(gcForeground); +} +void onResize() { + Point size = getSize(); + if (oldSize == null || oldSize.x == 0 || oldSize.y == 0) { + redraw(); + } else { + int width = 0; + if (oldSize.x < size.x) { + width = size.x - oldSize.x + borderRight + highlight; + } else if (oldSize.x > size.x) { + width = borderRight + highlight; + } + redraw(size.x - width, 0, width, size.y, false); + + int height = 0; + if (oldSize.y < size.y) { + height = size.y - oldSize.y + borderBottom + highlight; + } + if (oldSize.y > size.y) { + height = borderBottom + highlight; + } + redraw(0, size.y - height, size.x, height, false); + } + oldSize = size; +} +/** +* Sets the content. +* Setting the content to null will remove it from +* the pane - however, the creator of the content must dispose of the content. +* +* @param content the control to be displayed in the content area or null +* +* @exception SWTException <ul> +* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> +* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> +* <li>ERROR_INVALID_ARGUMENT - if the control is not a child of this ViewForm</li> +* </ul> +*/ +public void setContent(Control content) { + checkWidget(); + if (content != null && content.getParent() != this) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + if (this.content != null && !this.content.isDisposed()) { + this.content.setBounds(OFFSCREEN, OFFSCREEN, 0, 0); + } + this.content = content; + layout(false); +} +/** + * Sets the layout which is associated with the receiver to be + * the argument which may be null. + * <p> + * Note: No Layout can be set on this Control because it already + * manages the size and position of its children. + * </p> + * + * @param layout the receiver's new layout or null + * + * @exception SWTException <ul> + * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> + * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> + * </ul> + */ +public void setLayout (Layout layout) { + checkWidget(); + return; +} +void setSelectionBackground (Color color) { + checkWidget(); + if (selectionBackground == color) return; + if (color == null) color = getDisplay().getSystemColor(SELECTION_BACKGROUND); + selectionBackground = color; + redraw(); +} +/** +* Set the control that appears in the top center of the pane. +* Typically this is a toolbar. +* The topCenter is optional. Setting the topCenter to null will remove it from +* the pane - however, the creator of the topCenter must dispose of the topCenter. +* +* @param topCenter the control to be displayed in the top center or null +* +* @exception SWTException <ul> +* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> +* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> +* <li>ERROR_INVALID_ARGUMENT - if the control is not a child of this ViewForm</li> +* </ul> +*/ +public void setTopCenter(Control topCenter) { + checkWidget(); + if (topCenter != null && topCenter.getParent() != this) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + if (this.topCenter != null && !this.topCenter.isDisposed()) { + Point size = this.topCenter.getSize(); + this.topCenter.setLocation(OFFSCREEN - size.x, OFFSCREEN - size.y); + } + this.topCenter = topCenter; + layout(false); +} +/** +* Set the control that appears in the top left corner of the pane. +* Typically this is a label such as CLabel. +* The topLeft is optional. Setting the top left control to null will remove it from +* the pane - however, the creator of the control must dispose of the control. +* +* @param c the control to be displayed in the top left corner or null +* +* @exception SWTException <ul> +* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> +* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> +* <li>ERROR_INVALID_ARGUMENT - if the control is not a child of this ViewForm</li> +* </ul> +*/ +public void setTopLeft(Control c) { + checkWidget(); + if (c != null && c.getParent() != this) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + if (this.topLeft != null && !this.topLeft.isDisposed()) { + Point size = this.topLeft.getSize(); + this.topLeft.setLocation(OFFSCREEN - size.x, OFFSCREEN - size.y); + } + this.topLeft = c; + layout(false); +} +/** +* Set the control that appears in the top right corner of the pane. +* Typically this is a Close button or a composite with a Menu and Close button. +* The topRight is optional. Setting the top right control to null will remove it from +* the pane - however, the creator of the control must dispose of the control. +* +* @param c the control to be displayed in the top right corner or null +* +* @exception SWTException <ul> +* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> +* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> +* <li>ERROR_INVALID_ARGUMENT - if the control is not a child of this ViewForm</li> +* </ul> +*/ +public void setTopRight(Control c) { + checkWidget(); + if (c != null && c.getParent() != this) { + SWT.error(SWT.ERROR_INVALID_ARGUMENT); + } + if (this.topRight != null && !this.topRight.isDisposed()) { + Point size = this.topRight.getSize(); + this.topRight.setLocation(OFFSCREEN - size.x, OFFSCREEN - size.y); + } + this.topRight = c; + layout(false); +} +/** +* Specify whether the border should be displayed or not. +* +* @param show true if the border should be displayed +* +* @exception SWTException <ul> +* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> +* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> +* </ul> +*/ +public void setBorderVisible(boolean show) { + checkWidget(); + if (showBorder == show) return; + + showBorder = show; + if (showBorder) { + borderLeft = borderTop = borderRight = borderBottom = 1; + if ((getStyle() & SWT.FLAT)== 0) highlight = 2; + } else { + borderBottom = borderTop = borderLeft = borderRight = 0; + highlight = 0; + } + layout(false); + redraw(); +} +/** +* If true, the topCenter will always appear on a separate line by itself, otherwise the +* topCenter will appear in the top row if there is room and will be moved to the second row if +* required. +* +* @param show true if the topCenter will always appear on a separate line by itself +* +* @exception SWTException <ul> +* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> +* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> +* </ul> +*/ +public void setTopCenterSeparate(boolean show) { + checkWidget(); + separateTopCenter = show; + layout(false); +} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/ViewFormLayout.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/ViewFormLayout.java new file mode 100644 index 0000000000..18d4b6d421 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/ViewFormLayout.java @@ -0,0 +1,211 @@ +/******************************************************************************* + * 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.custom; + +import org.eclipse.swt.*; +import org.eclipse.swt.graphics.*; +import org.eclipse.swt.widgets.*; + +/** + * This class provides the layout for ViewForm + * + * @see ViewForm + */ +class ViewFormLayout extends Layout { + +protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) { + ViewForm form = (ViewForm)composite; + Control left = form.topLeft; + Control center = form.topCenter; + Control right = form.topRight; + Control content = form.content; + + Point leftSize = new Point(0, 0); + if (left != null) { + leftSize = computeChildSize(left, SWT.DEFAULT, SWT.DEFAULT, flushCache); + } + Point centerSize = new Point(0, 0); + if (center != null) { + centerSize = computeChildSize(center, SWT.DEFAULT, SWT.DEFAULT, flushCache); + } + Point rightSize = new Point(0, 0); + if (right != null) { + rightSize = computeChildSize(right, SWT.DEFAULT, SWT.DEFAULT, flushCache); + } + Point size = new Point(0, 0); + // calculate width of title bar + if (form.separateTopCenter || + (wHint != SWT.DEFAULT && leftSize.x + centerSize.x + rightSize.x > wHint)) { + size.x = leftSize.x + rightSize.x; + if (leftSize.x > 0 && rightSize.x > 0) size.x += form.horizontalSpacing; + size.x = Math.max(centerSize.x, size.x); + size.y = Math.max(leftSize.y, rightSize.y); + if (center != null){ + size.y += centerSize.y; + if (left != null ||right != null)size.y += form.verticalSpacing; + } + } else { + size.x = leftSize.x + centerSize.x + rightSize.x; + int count = -1; + if (leftSize.x > 0) count++; + if (centerSize.x > 0) count++; + if (rightSize.x > 0) count++; + if (count > 0) size.x += count * form.horizontalSpacing; + size.y = Math.max(leftSize.y, Math.max(centerSize.y, rightSize.y)); + } + + if (content != null) { + if (left != null || right != null || center != null) size.y += 1; // allow space for a vertical separator + Point contentSize = new Point(0, 0); + contentSize = computeChildSize(content, SWT.DEFAULT, SWT.DEFAULT, flushCache); + size.x = Math.max (size.x, contentSize.x); + size.y += contentSize.y; + if (size.y > contentSize.y) size.y += form.verticalSpacing; + } + + size.x += 2*form.marginWidth; + size.y += 2*form.marginHeight; + + if (wHint != SWT.DEFAULT) size.x = wHint; + if (hHint != SWT.DEFAULT) size.y = hHint; + + return size; +} + +Point computeChildSize(Control control, int wHint, int hHint, boolean flushCache) { + Object data = control.getLayoutData(); + if (data == null || !(data instanceof CLayoutData)) { + data = new CLayoutData(); + control.setLayoutData(data); + } + return ((CLayoutData)data).computeSize(control, wHint, hHint, flushCache); +} + +int computeTrim(Control c) { + if (c instanceof Scrollable) { + Rectangle rect = ((Scrollable) c).computeTrim (0, 0, 0, 0); + return rect.width; + } + return c.getBorderWidth () * 2; +} + +protected boolean flushCache(Control control) { + Object data = control.getLayoutData(); + if (data != null && data instanceof CLayoutData) ((CLayoutData)data).flushCache(); + return true; +} + +protected void layout(Composite composite, boolean flushCache) { + ViewForm form = (ViewForm)composite; + Control left = form.topLeft; + Control center = form.topCenter; + Control right = form.topRight; + Control content = form.content; + + Rectangle rect = composite.getClientArea(); + + Point leftSize = new Point(0, 0); + if (left != null && !left.isDisposed()) { + leftSize = computeChildSize(left, SWT.DEFAULT, SWT.DEFAULT, flushCache); + } + Point centerSize = new Point(0, 0); + if (center != null && !center.isDisposed()) { + centerSize = computeChildSize(center, SWT.DEFAULT, SWT.DEFAULT, flushCache); + } + Point rightSize = new Point(0, 0); + if (right != null && !right.isDisposed()) { + rightSize = computeChildSize(right, SWT.DEFAULT, SWT.DEFAULT, flushCache); + } + + int minTopWidth = leftSize.x + centerSize.x + rightSize.x + 2*form.marginWidth + 2*form.highlight; + int count = -1; + if (leftSize.x > 0) count++; + if (centerSize.x > 0) count++; + if (rightSize.x > 0) count++; + if (count > 0) minTopWidth += count * form.horizontalSpacing; + + int x = rect.x + rect.width - form.marginWidth - form.highlight; + int y = rect.y + form.marginHeight + form.highlight; + + boolean top = false; + if (form.separateTopCenter || minTopWidth > rect.width) { + int topHeight = Math.max(rightSize.y, leftSize.y); + if (right != null && !right.isDisposed()) { + top = true; + x -= rightSize.x; + right.setBounds(x, y, rightSize.x, topHeight); + x -= form.horizontalSpacing; + } + if (left != null && !left.isDisposed()) { + top = true; + int trim = computeTrim(left); + int leftW = x - rect.x - form.marginWidth - form.highlight - trim; + leftSize = computeChildSize(left, leftW, SWT.DEFAULT, false); + left.setBounds(rect.x + form.marginWidth + form.highlight, y, leftSize.x, topHeight); + } + if (top) y += topHeight + form.verticalSpacing; + if (center != null && !center.isDisposed()) { + top = true; + int trim = computeTrim(center); + int w = rect.width - 2*form.marginWidth - 2*form.highlight - trim; + centerSize = computeChildSize(center, w, SWT.DEFAULT, false); + center.setBounds(rect.x + rect.width - form.marginWidth - form.highlight - centerSize.x, y, centerSize.x, centerSize.y); + y += centerSize.y + form.verticalSpacing; + } + } else { + int topHeight = Math.max(rightSize.y, Math.max(centerSize.y, leftSize.y)); + if (right != null && !right.isDisposed()) { + top = true; + x -= rightSize.x; + right.setBounds(x, y, rightSize.x, topHeight); + x -= form.horizontalSpacing; + } + if (center != null && !center.isDisposed()) { + top = true; + x -= centerSize.x; + center.setBounds(x, y, centerSize.x, topHeight); + x -= form.horizontalSpacing; + } + if (left != null && !left.isDisposed()) { + top = true; + Rectangle trim = left instanceof Composite ? ((Composite)left).computeTrim(0, 0, 0, 0) : new Rectangle(0, 0, 0, 0); + int w = x - rect.x - form.marginWidth - form.highlight - trim.width; + int h = topHeight - trim.height; + leftSize = computeChildSize(left, w, h, false); + left.setBounds(rect.x + form.marginWidth + form.highlight, y, leftSize.x, topHeight); + } + if (top)y += topHeight + form.verticalSpacing; + } + int oldSeperator = form.separator; + form.separator = -1; + if (content != null && !content.isDisposed()) { + if (left != null || right!= null || center != null){ + form.separator = y; + y++; + } + content.setBounds(rect.x + form.marginWidth + form.highlight, y, rect.width - 2 * form.marginWidth - 2*form.highlight, rect.y + rect.height - y - form.marginHeight - form.highlight); + } + if (oldSeperator != form.separator) { + int t, b; + if (oldSeperator == -1) { + t = form.separator; + b = form.separator + 1; + } else if (form.separator == -1) { + t = oldSeperator; + b = oldSeperator + 1; + } else { + t = Math.min(form.separator, oldSeperator); + b = Math.max(form.separator, oldSeperator); + } + form.redraw(form.borderLeft, t, form.getSize().x - form.borderLeft - form.borderRight, b - t, false); + } +} +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/package.html b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/package.html new file mode 100755 index 0000000000..3011a3feef --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/package.html @@ -0,0 +1,15 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> + <meta name="Author" content="IBM"> + <title>Package-level Javadoc</title> +</head> +<body> +SWT Custom widgets. +<h2> +Package Specification</h2> +This package contains the custom widgets which were written to provide the +standard look and feel of the Eclipse platform. +</body> +</html> |