summaryrefslogtreecommitdiffstats
path: root/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org
diff options
context:
space:
mode:
Diffstat (limited to 'bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org')
-rwxr-xr-xbundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/AnimatedProgress.java168
-rwxr-xr-xbundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/BusyIndicator.java111
-rwxr-xr-xbundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CCombo.java950
-rwxr-xr-xbundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CLabel.java462
-rwxr-xr-xbundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabFolder.java1535
-rwxr-xr-xbundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabFolderAdapter.java10
-rwxr-xr-xbundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabFolderEvent.java18
-rwxr-xr-xbundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabFolderListener.java12
-rwxr-xr-xbundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabItem.java406
-rwxr-xr-xbundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/ControlEditor.java238
-rwxr-xr-xbundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/DefaultContent.java797
-rwxr-xr-xbundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/DefaultLineStyler.java599
-rwxr-xr-xbundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/ExtendedModifyEvent.java24
-rwxr-xr-xbundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/ExtendedModifyListener.java22
-rwxr-xr-xbundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/LineBackgroundEvent.java26
-rwxr-xr-xbundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/LineBackgroundListener.java24
-rwxr-xr-xbundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/LineStyleEvent.java25
-rwxr-xr-xbundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/LineStyleListener.java21
-rwxr-xr-xbundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/PopupList.java249
-rwxr-xr-xbundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/ST.java57
-rwxr-xr-xbundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/SashForm.java384
-rwxr-xr-xbundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/ScrolledComposite.java202
-rwxr-xr-xbundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/StackLayout.java74
-rwxr-xr-xbundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/StyleRange.java147
-rwxr-xr-xbundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/StyledText.java5426
-rwxr-xr-xbundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/StyledTextContent.java177
-rwxr-xr-xbundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/StyledTextEvent.java31
-rwxr-xr-xbundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/StyledTextListener.java62
-rwxr-xr-xbundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/StyledTextPrinter.java344
-rwxr-xr-xbundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TableEditor.java179
-rwxr-xr-xbundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TableTree.java701
-rwxr-xr-xbundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TableTreeEditor.java100
-rwxr-xr-xbundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TableTreeItem.java561
-rwxr-xr-xbundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TextChangedEvent.java41
-rwxr-xr-xbundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TextChangedListener.java41
-rwxr-xr-xbundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TreeEditor.java163
-rwxr-xr-xbundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/VerifyKeyListener.java19
-rwxr-xr-xbundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/ViewForm.java477
-rwxr-xr-xbundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/package.html13
39 files changed, 14896 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..af654766de
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/AnimatedProgress.java
@@ -0,0 +1,168 @@
+package org.eclipse.swt.custom;
+
+/*
+ * Licensed Materials - Property of IBM,
+ * (c) Copyright IBM Corp. 1998, 2001 All Rights Reserved
+ */
+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.
+ */
+public class AnimatedProgress extends Canvas {
+
+ private static final int SLEEP = 70;
+ private static final int DEFAULT_WIDTH = 160;
+ private static final int DEFAULT_HEIGHT = 18;
+ private boolean active = false;
+ private boolean showStripes = false;
+ private int value;
+ private int orientation = SWT.HORIZONTAL;
+ private boolean showBorder = false;
+
+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.
+ */
+public synchronized void clear(){
+ if (active)
+ stop();
+ showStripes = false;
+ redraw();
+}
+public Point computeSize(int wHint, int hHint, boolean changed) {
+ 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);
+}
+private 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);
+}
+private 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.
+*/
+public synchronized void start() {
+
+ 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() {
+ active = false;
+}
+}
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..d97f8be1f7
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/BusyIndicator.java
@@ -0,0 +1,111 @@
+package org.eclipse.swt.custom;
+
+/*
+ * Licensed Materials - Property of IBM,
+ * (c) Copyright IBM Corp. 1998, 2001 All Rights Reserved
+ */
+
+import org.eclipse.swt.*;
+import org.eclipse.swt.events.*;
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.widgets.*;
+
+/**
+ * Support for showing a Busy Cursor during a long running process.
+ */
+public class BusyIndicator {
+
+ static int[] counts = new int[0];
+ static Display[] displays = new Display[0];
+ static Cursor[] cursors = new Cursor[0];
+
+/**
+ * Runs the given <code>Runnable</code> while providing
+ * busy feedback using this busy indicator.
+ *
+ * @param 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 the runnable for which busy feedback is to be shown
+ * @see #showWhile
+ */
+
+public static void showWhile(Display display, Runnable runnable) {
+ if (display == null) {
+ display = Display.getCurrent();
+ if (display == null) {
+ runnable.run();
+ return;
+ }
+ }
+
+ int index = 0;
+ while (index < displays.length) {
+ if (displays[index] == display) break;
+ index++;
+ }
+ if (index == displays.length) {
+ Display[] newDisplays = new Display[displays.length + 1];
+ System.arraycopy(displays, 0, newDisplays, 0, displays.length);
+ displays = newDisplays;
+ displays[index] = display;
+ final Display d = display;
+ display.disposeExec( new Runnable() {
+ public void run() {
+ clear (d);
+ }
+ });
+
+ int[] newCounts = new int[counts.length + 1];
+ System.arraycopy(counts, 0, newCounts, 0, counts.length);
+ counts = newCounts;
+
+ Cursor[] newCursors = new Cursor[cursors.length + 1];
+ System.arraycopy(cursors, 0, newCursors, 0, cursors.length);
+ cursors = newCursors;
+ }
+
+ if (counts[index] == 0) {
+ cursors[index] = new Cursor(display, SWT.CURSOR_WAIT);
+ }
+
+ counts[index]++;
+
+ Shell[] shells = display.getShells();
+ for (int i = 0; i < shells.length; i++) {
+ shells[i].setCursor(cursors[index]);
+ }
+
+ try {
+ runnable.run();
+ } finally {
+ counts[index]--;
+ if (counts[index] == 0) {
+ shells = display.getShells();
+ for (int i = 0; i < shells.length; i++) {
+ shells[i].setCursor(null);
+ }
+ cursors[index].dispose();
+ cursors[index] = null;
+ }
+ }
+}
+
+static void clear(Display display) {
+ int index = 0;
+ while (index < displays.length) {
+ if (displays[index] == display) break;
+ index++;
+ }
+
+ if (index == displays.length) return;
+
+ if (cursors[index] != null) {
+ cursors[index].dispose();
+ }
+ cursors[index] = null;
+ counts[index] = 0;
+ displays[index] = null;
+}
+} \ No newline at end of file
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..f263e2b2fd
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CCombo.java
@@ -0,0 +1,950 @@
+package org.eclipse.swt.custom;
+
+/*
+ * Licensed Materials - Property of IBM,
+ * (c) Copyright IBM Corp. 1998, 2001 All Rights Reserved
+ */
+
+import org.eclipse.swt.*;
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.events.*;
+import org.eclipse.swt.widgets.*;
+
+/**
+ * The CCombo class represents a selectable user interface object
+ * that combines a text field and a list and issues notificiation
+ * when an item is selected from the list.
+ * <p>
+ * <dl>
+ * <dt><b>Styles:</b>
+ * <dd>BORDER, READ_ONLY</dd>
+ * <dt><b>Events:</b>
+ * <dd>Selection</dd>
+ * </dl>
+ */
+public final class CCombo extends Composite {
+
+ static final int ITEMS_SHOWING = 5;
+
+ Text text;
+ List list;
+ Shell popup;
+ Button arrow;
+
+public CCombo (Composite parent, int style) {
+ super (parent, checkStyle (style));
+
+ style = getStyle();
+
+ 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);
+
+ popup = new Shell (getShell (), SWT.ON_TOP | SWT.NO_TRIM);
+
+ int listStyle = SWT.SINGLE | SWT.V_SCROLL;
+ if ((style & SWT.FLAT) != 0) listStyle |= SWT.FLAT;
+ list = new List (popup, listStyle);
+
+ int arrowStyle = SWT.ARROW | SWT.DOWN;
+ if ((style & SWT.FLAT) != 0) arrowStyle |= SWT.FLAT;
+ arrow = new Button (this, arrowStyle);
+
+ Listener listener = new Listener () {
+ public void handleEvent (Event event) {
+ 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;
+ }
+
+ }
+ };
+
+ int [] popupEvents = {SWT.Close, SWT.Paint, SWT.Deactivate};
+ for (int i=0; i<popupEvents.length; i++) popup.addListener (popupEvents [i], listener);
+
+ int [] textEvents = {SWT.KeyDown, SWT.KeyUp, SWT.Modify, SWT.MouseDown, SWT.MouseUp, SWT.FocusIn, SWT.FocusOut, SWT.Traverse};
+ for (int i=0; i<textEvents.length; i++) text.addListener (textEvents [i], listener);
+
+ int [] listEvents = {SWT.MouseUp, SWT.FocusIn, SWT.FocusOut, SWT.Selection};
+ for (int i=0; i<listEvents.length; i++) list.addListener (listEvents [i], listener);
+
+ int [] comboEvents = {SWT.Dispose, SWT.Move, SWT.Resize};
+ for (int i=0; i<comboEvents.length; i++) this.addListener (comboEvents [i], listener);
+
+ int [] arrowEvents = {SWT.MouseDown};
+ for (int i=0; i<arrowEvents.length; i++) arrow.addListener (arrowEvents [i], listener);
+
+}
+static int checkStyle (int style) {
+ int mask = SWT.BORDER | SWT.READ_ONLY | SWT.FLAT;
+ return style & mask;
+}
+/**
+* Adds an item.
+* <p>
+* The item is placed at the end of the list.
+* Indexing is zero based.
+*
+* @param string the new item
+*
+* @exception SWTError(ERROR_THREAD_INVALID_ACCESS)
+* when called from the wrong thread
+* @exception SWTError(ERROR_WIDGET_DISPOSED)
+* when the widget has been disposed
+* @exception SWTError(ERROR_NULL_ARGUMENT)
+* when the string is null
+* @exception SWTError(ERROR_ITEM_NOT_ADDED)
+* when the item cannot be added
+*/
+public void add (String string) {
+ if (string == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
+ list.add (string);
+}
+/**
+* Adds an item at an index.
+* <p>
+* The item is placed at an index in the list.
+* Indexing is zero based.
+*
+* This operation will fail when the index is
+* out of range.
+*
+* @param string the new item
+* @param index the index for the item
+*
+* @exception SWTError(ERROR_THREAD_INVALID_ACCESS)
+* when called from the wrong thread
+* @exception SWTError(ERROR_WIDGET_DISPOSED)
+* when the widget has been disposed
+* @exception SWTError(ERROR_NULL_ARGUMENT)
+* when the string is null
+* @exception SWTError(ERROR_ITEM_NOT_ADDED)
+* when the item cannot be added
+*/
+public void add (String string, int index) {
+ if (string == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
+ list.add (string, index);
+}
+/**
+* Adds the listener to receive events.
+* <p>
+*
+* @param listener the listener
+*
+* @exception SWTError(ERROR_THREAD_INVALID_ACCESS)
+* when called from the wrong thread
+* @exception SWTError(ERROR_WIDGET_DISPOSED)
+* when the widget has been disposed
+* @exception SWTError(ERROR_NULL_ARGUMENT)
+* when listener is null
+*/
+public void addModifyListener (ModifyListener listener) {;
+ if (listener == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
+ TypedListener typedListener = new TypedListener (listener);
+ addListener (SWT.Modify, typedListener);
+}
+/**
+* Adds the listener to receive events.
+* <p>
+*
+* @param listener the listener
+*
+* @exception SWTError(ERROR_THREAD_INVALID_ACCESS)
+* when called from the wrong thread
+* @exception SWTError(ERROR_WIDGET_DISPOSED)
+* when the widget has been disposed
+* @exception SWTError(ERROR_NULL_ARGUMENT)
+* when listener is null
+*/
+public void addSelectionListener(SelectionListener listener) {
+ if (listener == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
+ TypedListener typedListener = new TypedListener (listener);
+ addListener (SWT.Selection,typedListener);
+ addListener (SWT.DefaultSelection,typedListener);
+}
+void arrowEvent (Event event) {
+ if (event.type != SWT.MouseDown || event.button != 1) return;
+ dropDown (!isDropped ());
+}
+/**
+* Clears the current selection.
+* <p>
+*
+* @exception SWTError(ERROR_THREAD_INVALID_ACCESS)
+* when called from the wrong thread
+* @exception SWTError(ERROR_WIDGET_DISPOSED)
+* when the widget has been disposed
+*/
+public void clearSelection () {
+ text.clearSelection ();
+ list.deselectAll ();
+}
+void comboEvent (Event event) {
+ switch (event.type) {
+ case SWT.Dispose:
+ if (popup != null && !popup.isDisposed ())
+ popup.dispose ();
+ popup = null;
+ text = null;
+ list = null;
+ arrow = null;
+ break;
+
+ case SWT.Move:
+ dropDown(false);
+ break;
+ case SWT.Resize:
+ internalLayout();
+ break;
+ }
+}
+
+public Point computeSize (int wHint, int hHint, boolean changed) {
+ int width = 0, height = 0;
+ Point textSize = text.computeSize (wHint, SWT.DEFAULT, changed);
+ Point arrowSize = arrow.computeSize(SWT.DEFAULT, SWT.DEFAULT, changed);
+ Point listSize = list.computeSize (wHint, SWT.DEFAULT, changed);
+ Point barSize = list.getVerticalBar().getSize();
+ int borderWidth = getBorderWidth();
+
+ height = Math.max (hHint, Math.max(textSize.y, arrowSize.y) + 2*borderWidth);
+ width = Math.max (wHint, Math.max(textSize.x + arrowSize.x + 2*borderWidth, listSize.x + 2) );
+ return new Point (width, height);
+}
+/**
+* Deselects an item.
+* <p>
+* If the item at an index is selected, it is
+* deselected. If the item at an index is not
+* selected, it remains deselected. Indices
+* that are out of range are ignored. Indexing
+* is zero based.
+*
+* @param index the index of the item
+*
+* @exception SWTError(ERROR_THREAD_INVALID_ACCESS)
+* when called from the wrong thread
+* @exception SWTError(ERROR_WIDGET_DISPOSED)
+* when the widget has been disposed
+*/
+public void deselect (int index) {
+ list.deselect (index);
+}
+/**
+* Deselects all items.
+* <p>
+*
+* If an item is selected, it is deselected.
+* If an item is not selected, it remains unselected.
+*
+* @exception SWTError(ERROR_THREAD_INVALID_ACCESS)
+* when called from the wrong thread
+* @exception SWTError(ERROR_WIDGET_DISPOSED)
+* when the widget has been disposed
+*/
+public void deselectAll () {
+ list.deselectAll ();
+}
+void dropDown (boolean drop) {
+ if (drop == isDropped ()) return;
+ if (!drop) {
+ popup.setVisible (false);
+ return;
+ }
+ int index = list.getSelectionIndex ();
+ if (index != -1) list.setTopIndex (index);
+ Rectangle listRect = list.getBounds ();
+ int borderWidth = getBorderWidth();
+ Point point = toDisplay (new Point (0 - borderWidth, 0 - borderWidth));
+ Point comboSize = getSize();
+ popup.setBounds (point.x, point.y + comboSize.y, comboSize.x, listRect.height + 2);
+ popup.setVisible (true);
+ list.setFocus();
+}
+public Control [] getChildren () {
+ return new Control [0];
+}
+boolean getEditable () {
+ return text.getEditable ();
+}
+/**
+* Gets an item at an 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
+*
+* @exception SWTError(ERROR_THREAD_INVALID_ACCESS)
+* when called from the wrong thread
+* @exception SWTError(ERROR_WIDGET_DISPOSED)
+* when the widget has been disposed
+* @exception SWTError(ERROR_CANNOT_GET_ITEM)
+* when the operation fails
+*/
+public String getItem (int index) {
+ return list.getItem (index);
+}
+/**
+* Gets the number of items.
+* <p>
+* This operation will fail if the number of
+* items could not be queried from the OS.
+*
+* @return the number of items in the widget
+*
+* @exception SWTError(ERROR_THREAD_INVALID_ACCESS)
+* when called from the wrong thread
+* @exception SWTError(ERROR_WIDGET_DISPOSED)
+* when the widget has been disposed
+* @exception SWTError(ERROR_CANNOT_GET_COUNT)
+* when the operation fails
+*/
+public int getItemCount () {
+ return list.getItemCount ();
+}
+/**
+* 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 SWTError(ERROR_THREAD_INVALID_ACCESS)
+* when called from the wrong thread
+* @exception SWTError(ERROR_WIDGET_DISPOSED)
+* when the widget has been disposed
+* @exception SWTError(ERROR_CANNOT_GET_ITEM_HEIGHT)
+* when the operation fails
+*/
+public int getItemHeight () {
+ return list.getItemHeight ();
+}
+/**
+* Gets the items.
+* <p>
+* This operation will fail if the items cannot
+* be queried from the OS.
+*
+* @return the items in the widget
+*
+* @exception SWTError(ERROR_THREAD_INVALID_ACCESS)
+* when called from the wrong thread
+* @exception SWTError(ERROR_WIDGET_DISPOSED)
+* when the widget has been disposed
+* @exception SWTError(ERROR_CANNOT_GET_ITEM)
+* when the operation fails
+*/
+public String [] getItems () {
+ return list.getItems ();
+}
+/**
+* Gets the selection.
+* <p>
+* @return a point representing the selection start and end
+*
+* @exception SWTError(ERROR_THREAD_INVALID_ACCESS)
+* when called from the wrong thread
+* @exception SWTError(ERROR_WIDGET_DISPOSED)
+* when the widget has been disposed
+*/
+public Point getSelection () {
+ return text.getSelection ();
+}
+/**
+* Gets the index of the selected item.
+* <p>
+* Indexing is zero based.
+* If no item is selected -1 is returned.
+*
+* @return the index of the selected item.
+*
+* @exception SWTError(ERROR_THREAD_INVALID_ACCESS)
+* when called from the wrong thread
+* @exception SWTError(ERROR_WIDGET_DISPOSED)
+* when the widget has been disposed
+*/
+public int getSelectionIndex () {
+ return list.getSelectionIndex ();
+}
+/**
+* Gets the widget text.
+* <p>
+* If the widget has no text, an empty string is returned.
+*
+* @return the widget text
+*
+* @exception SWTError(ERROR_THREAD_INVALID_ACCESS)
+* when called from the wrong thread
+* @exception SWTError(ERROR_WIDGET_DISPOSED)
+* when the widget has been disposed
+*/
+public String getText () {
+ return text.getText ();
+}
+/**
+* Gets the height of the combo's text field.
+* <p>
+* The operation will fail if the height cannot
+* be queried from the OS.
+
+* @return the height of the combo's text field.
+*
+* @exception SWTError(ERROR_THREAD_INVALID_ACCESS)
+* when called from the wrong thread
+* @exception SWTError(ERROR_WIDGET_DISPOSED)
+* when the widget has been disposed
+* @exception SWTError(ERROR_ERROR_CANNOT_GET_ITEM_HEIGHT)
+* when the operation fails
+*/
+public int getTextHeight () {
+ return text.getLineHeight();
+}
+/**
+* Gets the text limit.
+* <p>
+* @return the text limit
+*
+* @exception SWTError(ERROR_THREAD_INVALID_ACCESS)
+* when called from the wrong thread
+* @exception SWTError(ERROR_WIDGET_DISPOSED)
+* when the widget has been disposed
+*/
+public int getTextLimit () {
+ return text.getTextLimit ();
+}
+/**
+* Gets the index of an item.
+* <p>
+* The list 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.
+*
+* @param string the search item
+* @return the index of the item
+*
+* @exception SWTError(ERROR_THREAD_INVALID_ACCESS)
+* when called from the wrong thread
+* @exception SWTError(ERROR_WIDGET_DISPOSED)
+* when the widget has been disposed
+* @exception SWTError(ERROR_NULL_ARGUMENT)
+* when string is null
+*/
+public int indexOf (String string) {
+ if (string == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
+ return list.indexOf (string);
+}
+/**
+* Gets the index of an item.
+* <p>
+* The widget is searched starting at start including
+* the end position until an item is found that
+* is equal to the search itenm. If no item is
+* found, -1 is returned. Indexing is zero based.
+*
+* @param string the search item
+* @param index the starting position
+* @return the index of the item
+*
+* @exception SWTError(ERROR_THREAD_INVALID_ACCESS)
+* when called from the wrong thread
+* @exception SWTError(ERROR_WIDGET_DISPOSED)
+* when the widget has been disposed
+* @exception SWTError(ERROR_NULL_ARGUMENT)
+* when string is null
+*/
+public int indexOf (String string, int start) {
+ if (string == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
+ return list.indexOf (string, start);
+}
+boolean isDropped () {
+ return popup.getVisible ();
+}
+void internalLayout () {
+ Rectangle rect = getClientArea();
+ int width = rect.width;
+ int height = rect.height;
+
+ if (isDropped ()) dropDown (false);
+ Point arrowSize = arrow.computeSize(SWT.DEFAULT, height);
+ text.setBounds (0, 0, width - arrowSize.x, height);
+ arrow.setBounds (width - arrowSize.x, 0, arrowSize.x, arrowSize.y);
+
+ Point size = getSize();
+ int listHeight = list.getItemHeight () * ITEMS_SHOWING;
+ Rectangle trim = list.computeTrim (0, 0, size.x - 2, listHeight);
+ list.setBounds (1, 1, size.x - 2, trim.height);
+}
+void listEvent (Event event) {
+ switch (event.type) {
+ 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.x = event.x;
+ e.y = event.y;
+ e.width = event.width;
+ e.height = event.height;
+ e.detail = event.detail;
+ e.doit = event.doit;
+ notifyListeners(SWT.Selection, e);
+ event.doit = e.doit;
+ break;
+ }
+ case SWT.FocusOut: {
+ Control control = getDisplay ().getFocusControl ();
+ if (control == text) return;
+ Event e = new Event();
+ e.time = event.time;
+ notifyListeners(SWT.FocusOut, e);
+ break;
+ }
+ case SWT.FocusIn: {
+ Control control = getDisplay ().getFocusControl ();
+ if (control == text) return;
+ Event e = new Event();
+ e.time = event.time;
+ notifyListeners(SWT.FocusIn, e);
+ break;
+ }
+ }
+}
+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:
+ dropDown (false);
+ break;
+ }
+}
+public void redraw (int x, int y, int width, int height, boolean all) {
+ if (!all) return;
+ Point location = text.getLocation();
+ text.redraw(x - location.x, y - location.y, width, height, all);
+ location = list.getLocation();
+ list.redraw(x - location.x, y - location.y, width, height, all);
+ if (arrow != null) {
+ location = arrow.getLocation();
+ arrow.redraw(x - location.x, y - location.y, width, height, all);
+ }
+}
+
+/**
+* Removes an item at an index.
+* <p>
+* Indexing is zero based.
+*
+* This operation will fail when the index is out
+* of range or an item could not be removed from
+* the OS.
+*
+* @param index the index of the item
+* @return the selection state
+*
+* @exception SWTError(ERROR_THREAD_INVALID_ACCESS)
+* when called from the wrong thread
+* @exception SWTError(ERROR_WIDGET_DISPOSED)
+* when the widget has been disposed
+* @exception SWTError(ERROR_ITEM_NOT_REMOVED)
+* when the operation fails
+*/
+public void remove (int index) {
+ list.remove (index);
+}
+/**
+* Removes a range of items.
+* <p>
+* Indexing is zero based. The range of items
+* is from the start index up to and including
+* the end index.
+*
+* This operation will fail when the index is out
+* of range or an item could not be removed from
+* the OS.
+*
+* @param start the start of the range
+* @param end the end of the range
+*
+* @exception SWTError(ERROR_THREAD_INVALID_ACCESS)
+* when called from the wrong thread
+* @exception SWTError(ERROR_WIDGET_DISPOSED)
+* when the widget has been disposed
+* @exception SWTError(ERROR_ITEM_NOT_REMOVED)
+* when the operation fails
+*/
+public void remove (int start, int end) {
+ list.remove (start, end);
+}
+/**
+* Removes an item.
+* <p>
+* This operation will fail when the item
+* could not be removed from the OS.
+*
+* @param string the search item
+*
+* @exception SWTError(ERROR_THREAD_INVALID_ACCESS)
+* when called from the wrong thread
+* @exception SWTError(ERROR_WIDGET_DISPOSED)
+* when the widget has been disposed
+* @exception SWTError(ERROR_NULL_ARGUMENT)
+* when string is null
+* @exception SWTError(ERROR_ITEM_NOT_REMOVED)
+* when the operation fails
+*/
+public void remove (String string) {
+ if (string == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
+ list.remove (string);
+}
+/**
+* Removes all items.
+* <p>
+* @exception SWTError(ERROR_THREAD_INVALID_ACCESS)
+* when called from the wrong thread
+* @exception SWTError(ERROR_WIDGET_DISPOSED)
+* when the widget has been disposed
+*/
+public void removeAll () {
+ text.setText ("");
+ list.removeAll ();
+}
+/**
+* Removes the listener.
+* <p>
+*
+* @param listener the listener
+*
+* @exception SWTError(ERROR_THREAD_INVALID_ACCESS)
+* when called from the wrong thread
+* @exception SWTError(ERROR_WIDGET_DISPOSED)
+* when the widget has been disposed
+* @exception SWTError(ERROR_NULL_ARGUMENT)
+* when listener is null
+*/
+public void removeModifyListener (ModifyListener listener) {
+ if (listener == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
+ removeListener(SWT.Modify, listener);
+}
+/**
+* Removes the listener.
+* <p>
+*
+* @param listener the listener
+*
+* @exception SWTError(ERROR_THREAD_INVALID_ACCESS)
+* when called from the wrong thread
+* @exception SWTError(ERROR_WIDGET_DISPOSED)
+* when the widget has been disposed
+* @exception SWTError(ERROR_NULL_ARGUMENT)
+* when listener is null
+*/
+public void removeSelectionListener (SelectionListener listener) {
+ if (listener == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
+ removeListener(SWT.Selection, listener);
+ removeListener(SWT.DefaultSelection,listener);
+}
+/**
+* Selects an item.
+* <p>
+* If the item at an index is not selected, it is
+* selected. Indices that are out of
+* range are ignored. Indexing is zero based.
+*
+* @param index the index of the item
+*
+* @exception SWTError(ERROR_THREAD_INVALID_ACCESS)
+* when called from the wrong thread
+* @exception SWTError(ERROR_WIDGET_DISPOSED)
+* when the widget has been disposed
+*/
+public void select (int index) {
+ if (index == -1) {
+ list.deselectAll ();
+ text.setText ("");
+ 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);
+ if (text != null) text.setBackground(color);
+ if (list != null) list.setBackground(color);
+ if (arrow != null) arrow.setBackground(color);
+}
+
+
+/**
+* Sets the focus.
+*/
+public boolean setFocus () {
+ return text.setFocus ();
+}
+/**
+* Sets the widget font.
+*/
+public void setFont (Font font) {
+ super.setFont (font);
+ text.setFont (font);
+ list.setFont (font);
+ internalLayout ();
+}
+public void setForeground (Color color) {
+ super.setForeground(color);
+ if (text != null) text.setForeground(color);
+ if (list != null) list.setForeground(color);
+ if (arrow != null) arrow.setForeground(color);
+}
+/**
+* Sets the text of an item.
+* <p>
+* Indexing is zero based.
+*
+* This operation will fail when the index is out
+* of range or an item could not be changed in
+* the OS.
+*
+* @param index the index for the item
+* @param string the item
+*
+* @exception SWTError(ERROR_THREAD_INVALID_ACCESS)
+* when called from the wrong thread
+* @exception SWTError(ERROR_WIDGET_DISPOSED)
+* when the widget has been disposed
+* @exception SWTError(ERROR_NULL_ARGUMENT)
+* when items is null
+* @exception SWTError(ERROR_ITEM_NOT_MODIFIED)
+* when the operation fails
+*/
+public void setItem (int index, String string) {
+ if (string == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
+ list.setItem (index, string);
+}
+/**
+* Sets all items.
+* <p>
+* @param items the array of items
+*
+* @exception SWTError(ERROR_THREAD_INVALID_ACCESS)
+* when called from the wrong thread
+* @exception SWTError(ERROR_WIDGET_DISPOSED)
+* when the widget has been disposed
+* @exception SWTError(ERROR_NULL_ARGUMENT)
+* when items is null
+* @exception SWTError(ERROR_ITEM_NOT_ADDED)
+* when the operation fails
+*/
+public void setItems (String [] items) {
+ if (items == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
+ int style = getStyle();
+ if ((style & SWT.READ_ONLY) != 0) text.setText ("");
+ list.setItems (items);
+}
+/**
+* Sets the new selection.
+* <p>
+* @param selection point representing the start and the end of the new selection
+*
+* @exception SWTError(ERROR_THREAD_INVALID_ACCESS)
+* when called from the wrong thread
+* @exception SWTError(ERROR_WIDGET_DISPOSED)
+* when the widget has been disposed
+* @exception SWTError(ERROR_NULL_ARGUMENT)
+* when selection is null
+*/
+public void setSelection (Point selection) {
+ if (selection == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
+ text.setSelection (selection.x, selection.y);
+}
+
+
+/**
+* Sets the widget text
+* <p>
+* @param string the widget text
+*
+* @exception SWTError(ERROR_THREAD_INVALID_ACCESS)
+* when called from the wrong thread
+* @exception SWTError(ERROR_WIDGET_DISPOSED)
+* when the widget has been disposed
+* @exception SWTError(ERROR_NULL_ARGUMENT)
+* when string is null
+*/
+public void setText (String string) {
+ 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 text limit
+* <p>
+* @param limit new text limit
+*
+* @exception SWTError(ERROR_THREAD_INVALID_ACCESS)
+* when called from the wrong thread
+* @exception SWTError(ERROR_WIDGET_DISPOSED)
+* when the widget has been disposed
+* @exception SWTError(ERROR_CANNOT_BE_ZERO)
+* when limit is 0
+*/
+public void setTextLimit (int limit) {
+ text.setTextLimit (limit);
+}
+void textEvent (Event event) {
+ switch (event.type) {
+ case SWT.KeyDown: {
+
+ if (event.character == SWT.ESC) { // escape key cancels popup list
+ dropDown (false);
+ return;
+ }
+ if (event.keyCode == SWT.ARROW_UP || event.keyCode == SWT.ARROW_DOWN) {
+ 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;
+ notifyListeners(SWT.Selection, e);
+ }
+ }
+
+ // Further work : Need to add support for incremental search in pop up list as characters typed in text widget
+
+ 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;
+ }
+ 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.Modify: {
+ list.deselectAll ();
+ Event e = new Event();
+ e.time = event.time;
+ notifyListeners(SWT.Modify, e);
+ break;
+ }
+ case SWT.MouseDown: {
+ if (event.button != 1) return;
+ if (text.getEditable ()) return;
+ boolean dropped = isDropped ();
+ text.selectAll ();
+ if (!dropped) setFocus ();
+ dropDown (!dropped);
+ break;
+ }
+ case SWT.MouseUp: {
+ if (event.button != 1) return;
+ if (text.getEditable ()) return;
+ text.selectAll ();
+ break;
+ }
+ case SWT.FocusIn: {
+ if (getEditable ()) text.selectAll ();
+ Event e = new Event();
+ e.time = event.time;
+ notifyListeners(SWT.FocusIn, e);
+ break;
+ }
+ case SWT.FocusOut: {
+ Display display = getDisplay ();
+ Control focusControl = display.getFocusControl ();
+ if (isDropped ()) {
+ Control cursorControl = display.getCursorControl ();
+ if (cursorControl != list) dropDown (false);
+ if (focusControl != text) dropDown (false);
+ }
+ if (focusControl == list) return;
+ Event e = new Event();
+ e.time = event.time;
+ notifyListeners(SWT.FocusOut, e);
+ break;
+ }
+ case SWT.Traverse: {
+ if (event.detail == SWT.TRAVERSE_ARROW_PREVIOUS || event.detail == SWT.TRAVERSE_ARROW_NEXT) {
+ event.doit = false;
+ }
+ Event e = new Event();
+ e.time = event.time;
+ e.detail = event.detail;
+ e.doit = event.doit;
+ e.keyCode = event.keyCode;
+ notifyListeners(SWT.Traverse, e);
+ event.doit = e.doit;
+ break;
+ }
+ }
+}
+} \ No newline at end of file
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..c2c372db5f
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CLabel.java
@@ -0,0 +1,462 @@
+package org.eclipse.swt.custom;
+
+/*
+ * Licensed Materials - Property of IBM,
+ * (c) Copyright IBM Corp. 1998, 2001 All Rights Reserved
+ */
+
+import org.eclipse.swt.*;
+import org.eclipse.swt.widgets.*;
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.events.*;
+
+/**
+ * A Label which supports aligned text and/or an image and different border styles.
+ * <p>
+ * If there is not enough space a SmartLabel 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>SHADOW_IN, SHADOW_OUT, SHADOW_NONE</dd>
+ * <dt><b>Events:</b>
+ * <dd></dd>
+ * </dl>
+ */
+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 = "...";
+ /** 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 Image gradientImage;
+ private Color[] gradientColors;
+ private int[] gradientPercents;
+
+
+/**
+ * Create a CLabel with the given border style as a child of parent.
+ */
+public CLabel(Composite parent, int style) {
+ super(parent, checkStyle(style));
+
+ 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);
+ }
+ });
+
+ addDisposeListener(new DisposeListener(){
+ public void widgetDisposed(DisposeEvent event) {
+ onDispose(event);
+ }
+ });
+
+}
+/**
+ * Check the style bits to ensure that no invalid styles are applied.
+ */
+private static int checkStyle (int style) {
+ int mask = SWT.SHADOW_IN | SWT.SHADOW_OUT | SWT.SHADOW_NONE;
+ style = style & mask;
+ style |= SWT.NO_FOCUS | SWT.NO_BACKGROUND;
+ return style;
+}
+public Point computeSize(int wHint, int hHint, boolean changed) {
+ 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);
+}
+/**
+ * Returns the alignment.
+ * The alignment style (LEFT, CENTER or RIGHT) is returned.
+ */
+public int getAlignment() {
+ return align;
+}
+/**
+ * Return the CLabel's image or <code>null</code>.
+ */
+public Image getImage() {
+ 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);
+ 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 void setToolTipText (String string) {
+ super.setToolTipText (string);
+ appToolTipText = super.getToolTipText();
+}
+/**
+ * Return the Label's text.
+ */
+public String getText() {
+ return text;
+}
+public String getToolTipText () {
+ return appToolTipText;
+}
+/**
+ * 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);
+ }
+}
+private void onDispose(DisposeEvent event) {
+ if (gradientImage != null) {
+ gradientImage.dispose();
+ }
+ gradientImage = null;
+ gradientColors = null;
+ gradientPercents = null;
+ backgroundImage = null;
+}
+/*
+ * Process the paint event
+ */
+private void onPaint(PaintEvent event) {
+ Rectangle rect = getClientArea();
+ if (rect.width == 0 || rect.height == 0) return;
+
+ boolean shortenText = false;
+ String t = text;
+ Image i = image;
+ int availableWidth = rect.width - 2*hIndent;
+ Point extent = getTotalSize(i, t);
+ if (extent.x > availableWidth) {
+ i = null;
+ extent = getTotalSize(i, t);
+ if (extent.x > availableWidth) {
+ shortenText = true;
+ }
+ }
+
+ GC gc = event.gc;
+
+ // shorten the text
+ if (shortenText) {
+ t = shortenText(gc, text, availableWidth);
+ extent = getTotalSize(i, t);
+ 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-extent.x - hIndent;
+ }
+
+ // draw a background image behind the text
+ if (backgroundImage != null) {
+ Rectangle imageRect = backgroundImage.getBounds();
+ gc.drawImage(backgroundImage, 0, 0, imageRect.width, imageRect.height,
+ 0, 0, rect.width, rect.height);
+ } else {
+ 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 (i != null) {
+ Rectangle imageRect = i.getBounds();
+ gc.drawImage(i, 0, 0, imageRect.width, imageRect.height,
+ x, (rect.height-imageRect.height)/2, imageRect.width, imageRect.height);
+ x += imageRect.width + GAP;
+ }
+ // draw the text
+ if (t != null) {
+ int textHeight = gc.getFontMetrics().getHeight();
+ gc.setForeground(getForeground());
+ gc.drawText(t, x, rect.y + (rect.height-textHeight)/2, true);
+ }
+}
+/**
+ * Set the alignment of the CLabel.
+ * Use the values LEFT, CENTER and RIGHT to align image and text within the available space.
+ */
+public void setAlignment(int align) {
+ if (align != SWT.LEFT && align != SWT.RIGHT && align != SWT.CENTER) {
+ SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ }
+ if (this.align != align) {
+ this.align = align;
+ redraw();
+ }
+}
+/**
+ * Specify a gradiant of colours to be draw in the background of the CLabel.
+ * For example to draw a gradiant that varies from dark blue to blue and then to
+ * white, use the following call to setBackground:
+ * <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 gradiant
+ * in order of appearance left to right. The value <code>null</code> clears the
+ * background gradiant. 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.
+ */
+public void setBackground(Color[] colors, int[] percents) {
+ 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);
+ }
+ }
+
+ // Are these settings the same as before?
+ if (gradientImage == null && gradientColors == null && colors == null) {
+ if (backgroundImage != null) {
+ backgroundImage = null;
+ redraw();
+ }
+ return;
+ }
+ if (gradientColors != null && colors != null
+ && gradientColors.length == colors.length) {
+ boolean same = false;
+ for (int i = 0; i < gradientColors.length; i++) {
+ 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) return;
+ }
+
+ // Cleanup
+ if (gradientImage != null) {
+ gradientImage.dispose();
+ }
+ gradientImage = null;
+ gradientColors = null;
+ gradientPercents = null;
+ backgroundImage = null;
+
+ // Draw gradient onto an image
+ if (colors != null) {
+ int x = 0; int y = 0;
+ int width = 100; int height = 10;
+
+ Display display = getDisplay();
+ Image temp = new Image(display, width, height);
+ GC gc = new GC(temp);
+ if (colors.length == 1) {
+ gc.setBackground(colors[0]);
+ gc.fillRectangle(temp.getBounds());
+ }
+ int start = 0;
+ int end = 0;
+ for (int j = 0; j < colors.length - 1; j++) {
+ Color startColor = colors[j];
+ if (startColor == null) startColor = getBackground();
+ RGB rgb1 = startColor.getRGB();
+ Color endColor = colors[j+1];
+ if (endColor == null) endColor = getBackground();
+ RGB rgb2 = endColor.getRGB();
+ start = end;
+ end = (width) * percents[j] / 100;
+ int range = Math.max(1, end - start);
+ for (int k = 0; k < (end - start); k++) {
+ int r = rgb1.red + k*(rgb2.red - rgb1.red)/range;
+ r = (rgb2.red > rgb1.red) ? Math.min(r, rgb2.red) : Math.max(r, rgb2.red);
+ int g = rgb1.green + k*(rgb2.green - rgb1.green)/range;
+ g = (rgb2.green > rgb1.green) ? Math.min(g, rgb2.green) : Math.max(g, rgb2.green);
+ int b = rgb1.blue + k*(rgb2.blue - rgb1.blue)/range;
+ b = (rgb2.blue > rgb1.blue) ? Math.min(b, rgb2.blue) : Math.max(b, rgb2.blue);
+ Color color = new Color(display, r, g, b);
+ gc.setBackground(color);
+ gc.fillRectangle(start + k,y,1,height);
+ gc.setBackground(getBackground());
+ color.dispose();
+ }
+ }
+ gc.dispose();
+ gradientImage = temp;
+ gradientColors = colors;
+ gradientPercents = percents;
+ backgroundImage = temp;
+ }
+
+ redraw();
+}
+public void setBackground(Image image) {
+ if (image == backgroundImage) return;
+
+ if (gradientImage != null) {
+ gradientImage.dispose();
+ gradientImage = 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.
+ */
+public void setImage(Image image) {
+ if (image != this.image) {
+ this.image = image;
+ redraw();
+ }
+}
+/**
+ * Set the label's text.
+ * The value <code>null</code> clears it.
+ */
+public void setText(String text) {
+ if (text == null) text = "";
+ if (! text.equals(this.text)) {
+ this.text = text;
+ redraw();
+ }
+}
+/**
+ * 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.
+ */
+protected String shortenText(GC gc, String t, int width) {
+ int w = gc.textExtent(ellipsis).x;
+ int l = t.length();
+ int pivot = l/2;
+ int s = pivot;
+ int e = pivot+1;
+ while (s >= 0 && e < l) {
+ String s1 = t.substring(0, s);
+ String s2 = t.substring(e, l);
+ int l1 = gc.textExtent(s1).x;
+ int l2 = gc.textExtent(s2).x;
+ if (l1+w+l2 < width) {
+ t = s1 + ellipsis + s2;
+ break;
+ }
+ s--;
+ e++;
+ }
+ return t;
+}
+}
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..4c4f90cbb8
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabFolder.java
@@ -0,0 +1,1535 @@
+package org.eclipse.swt.custom;
+
+/*
+ * Licensed Materials - Property of IBM,
+ * (c) Copyright IBM Corp. 1998, 2001 All Rights Reserved
+ */
+
+import org.eclipse.swt.*;
+import org.eclipse.swt.events.*;
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.widgets.*;
+import org.eclipse.swt.layout.*;
+
+
+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;
+
+ /**
+ * Color of innermost line of drop shadow border.
+ */
+ public static RGB borderInsideRGB = new RGB (132, 130, 132);
+ /**
+ * Color of middle line of drop shadow border.
+ */
+ public static RGB borderMiddleRGB = new RGB (143, 141, 138);
+ /**
+ * Color of outermost line of drop shadow border.
+ */
+ public static RGB borderOutsideRGB = new RGB (171, 168, 165);
+
+ /*
+ * 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.
+ */
+ public static int MIN_TAB_WIDTH = 3;
+
+ /* sizing, positioning */
+ int xClient, yClient;
+ boolean onBottom = false;
+ int fixedTabHeight = -1;
+ int imageHeight = -1;
+
+ /* item management */
+ private CTabItem items[] = new CTabItem[0];
+ private int selectedIndex = -1;
+ int topTabIndex = -1; // index of the left most visible tab.
+
+ /* External Listener management */
+ private CTabFolderListener[] tabListeners = new CTabFolderListener[0];
+
+ /* Color appearance */
+ Image backgroundImage;
+ private Image gradientImage;
+ private Color[] gradientColors;
+ private int[] gradientPercents;
+ Color selectionForeground;
+
+ // internal constants
+ private static final int DEFAULT_WIDTH = 64;
+ private static final int DEFAULT_HEIGHT = 64;
+
+ // scrolling arrows
+ private ToolBar scrollBar;
+ private ToolItem scrollLeft;
+ private ToolItem scrollRight;
+ private Image arrowLeftImage;
+ private Image arrowRightImage;
+ private Image arrowLeftDisabledImage;
+ private Image arrowRightDisabledImage;
+
+ // close button
+ boolean showClose = false;
+ ToolBar closeBar;
+ private ToolItem closeItem;
+ private Image closeImage;
+ private ToolBar inactiveCloseBar;
+ private ToolItem inactiveCloseItem;
+ private Image inactiveCloseImage;
+ private CTabItem inactiveItem;
+
+ private boolean shortenedTabs = false;
+
+ // borders
+ boolean showBorders = false;
+ private int BORDER_BOTTOM = 0;
+ private int BORDER_LEFT = 0;
+ private int BORDER_RIGHT = 0;
+ private int BORDER_TOP = 0;
+ private Color borderColor1;
+ private Color borderColor2;
+ private Color borderColor3;
+
+ // when disposing CTabFolder, don't try to layout the items or
+ // change the selection as each child is destroyed.
+ private boolean inDispose = false;
+
+ // keep track of size changes in order to redraw only affected area
+ // on Resize
+ private Rectangle oldArea;
+
+ // insertion marker
+ int insertionIndex = -2; // Index of insert marker. Marker always shown after index.
+ // -2 means no insert marker
+
+ // tool tip
+ private Shell tip;
+
+/**
+ * Construct a CTabFolder with the specified parent and style.
+ * @param parent org.eclipse.swt.widgets.Composite
+ * @param swtStyle int
+ */
+public CTabFolder(Composite parent, int style) {
+ super(parent, checkStyle (style));
+
+ onBottom = (getStyle() & SWT.BOTTOM) != 0;
+
+ borderColor1 = new Color(getDisplay(), borderInsideRGB);
+ borderColor2 = new Color(getDisplay(), borderMiddleRGB);
+ borderColor3 = new Color(getDisplay(), borderOutsideRGB);
+ Color foreground = getForeground();
+ Color background = getBackground();
+
+ // create scrolling arrow buttons
+ scrollBar = new ToolBar(this, SWT.FLAT);
+ scrollBar.setVisible(false);
+ scrollLeft = new ToolItem(scrollBar, SWT.PUSH);
+ scrollLeft.setEnabled(false);
+ scrollRight = new ToolItem(scrollBar, SWT.PUSH);
+ scrollRight.setEnabled(false);
+ initScrollButtons(foreground, background);
+
+ // create close buttons
+ closeBar = new ToolBar(this, SWT.FLAT);
+ closeBar.setVisible(false);
+ closeItem = new ToolItem(closeBar, SWT.PUSH);
+ initCloseButton(foreground, background);
+
+ inactiveCloseBar = new ToolBar(this, SWT.FLAT);
+ inactiveCloseBar.setVisible(false);
+ inactiveCloseItem = new ToolItem(inactiveCloseBar, SWT.PUSH);
+ initInactiveCloseButton(foreground, background);
+
+ Listener listener = new Listener() {
+ public void handleEvent(Event event) {
+ handleEvents(event);
+ }
+ };
+ addListener(SWT.Dispose, listener);
+ addListener(SWT.MouseUp, listener);
+ addListener(SWT.MouseMove, listener);
+ addListener(SWT.MouseExit, listener);
+ addListener(SWT.Paint, listener);
+ addListener(SWT.Resize, listener);
+ scrollLeft.addListener(SWT.Selection, listener);
+ scrollRight.addListener(SWT.Selection, listener);
+ closeItem.addListener(SWT.Selection, listener);
+ inactiveCloseItem.addListener(SWT.Selection, listener);
+ inactiveCloseBar.addListener (SWT.MouseExit, listener);
+
+ setBorderVisible((style & SWT.BORDER) != 0);
+
+ // tool tip support
+ Display display = getDisplay();
+ tip = new Shell (getShell(), SWT.NONE);
+ tip.setLayout(new FillLayout());
+ Label label = new Label (tip, SWT.NONE);
+ label.setForeground (display.getSystemColor (SWT.COLOR_INFO_FOREGROUND));
+ label.setBackground (display.getSystemColor (SWT.COLOR_INFO_BACKGROUND));
+ addMouseTrackListener (new MouseTrackAdapter () {
+ public void mouseExit(MouseEvent e) {
+ if (tip.isVisible()) tip.setVisible(false);
+ }
+ public void mouseHover(MouseEvent e) {
+ Point pt = new Point (e.x, e.y);
+ CTabItem item = getItem(pt);
+ if (item != null) {
+ String tooltip = item.getToolTipText();
+ if (tooltip != null) {
+ pt.y = (onBottom) ? pt.y - 26 : pt.y + 26;
+ pt = toDisplay(pt);
+ tip.setLocation(pt);
+ Label label = (Label) (tip.getChildren() [0]);
+ label.setText(tooltip);
+ tip.pack();
+ tip.setVisible(true);
+ return;
+ }
+ }
+
+ tip.setVisible(false);
+ }
+ });
+
+}
+private static int checkStyle (int style) {
+ int mask = SWT.TOP | SWT.BOTTOM | SWT.FLAT;
+ style = style & mask;
+ // TOP and BOTTOM are mutually exlusive.
+ // TOP is the default
+ if ((style & SWT.TOP) != 0)
+ style = style & ~(SWT.TOP | SWT.BOTTOM) | SWT.TOP;
+ // reduce the flash by not redrawing the entire area on a Resize event
+ style |= SWT.NO_REDRAW_RESIZE;
+ return style;
+}
+/**
+* Adds the listener to receive events.
+* <p>
+*
+* @param listener the listener
+*
+* @exception SWTError <ul>
+* <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
+* <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
+* <li>ERROR_NULL_ARGUMENT when listener is null</li>
+* </ul>
+*/
+public void addSelectionListener(SelectionListener listener) {
+ 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 receive events.
+* <p>
+*
+* @param listener the listener
+*
+* @exception SWTError <ul>
+* <li>ERROR_NULL_ARGUMENT when listener is null</li>
+* </ul>
+*/
+public void addCTabFolderListener(CTabFolderListener listener) {
+ 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;
+ showClose = true;
+}
+private void closeNotify(CTabItem item, int time) {
+ CTabFolderEvent event = new CTabFolderEvent(this);
+ event.widget = this;
+ event.time = time;
+ event.item = item;
+ event.doit = true;
+ if (tabListeners != null) {
+ for (int i = 0; i < tabListeners.length; i++) {
+ tabListeners[i].itemClosed(event);
+ }
+ }
+ if (event.doit) {
+ item.dispose();
+ }
+}
+public Point computeSize (int wHint, int hHint, boolean changed) {
+ int minWidth = 0;
+ int minHeight = 0;
+
+ // tab width
+ if (items.length > 0) {
+ CTabItem lastItem = items[items.length-1];
+ minWidth = lastItem.x + lastItem.width;
+ }
+
+ // get max preferred size of 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);
+ minWidth = Math.max (minWidth, size.x);
+ minHeight = Math.max (minHeight, size.y);
+ }
+ }
+ if (minWidth == 0) minWidth = DEFAULT_WIDTH;
+ if (minHeight == 0) minHeight = DEFAULT_HEIGHT;
+
+ if (wHint != SWT.DEFAULT) minWidth = wHint;
+ if (hHint != SWT.DEFAULT) minHeight = hHint;
+
+ Rectangle trim = computeTrim(0, 0, minWidth, minHeight);
+ return new Point (trim.width, trim.height);
+}
+public Rectangle computeTrim (int x, int y, int width, int height) {
+ int tabHeight = getTabHeight();
+ int trimX = x - marginWidth - BORDER_LEFT;
+ int trimY = y - marginHeight - tabHeight - BORDER_TOP;
+ if (onBottom) {
+ trimY = y - marginHeight - BORDER_TOP;
+ }
+ int trimWidth = width + BORDER_LEFT + BORDER_RIGHT + 2*marginWidth;
+ int trimHeight = height + BORDER_TOP + BORDER_BOTTOM + 2*marginHeight + tabHeight;
+ return new Rectangle (trimX, trimY, trimWidth, trimHeight);
+}
+/**
+ * Create the specified item at 'index'.
+ */
+void createItem (CTabItem item, int index) {
+ if (0 > index || index > getItemCount ()){
+ SWT.error (SWT.ERROR_INVALID_RANGE);
+ }
+ // grow by one and rearrange the array.
+ 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;
+
+ item.parent = this;
+
+ if (selectedIndex >= index) {
+ selectedIndex ++;
+ }
+ if (items.length == 1) {
+ topTabIndex = 0;
+ }
+
+ layoutItems();
+ if (items.length == 1) {
+ redraw();
+ } else {
+ redrawTabArea(-1);
+ }
+}
+/**
+ * Destroy the specified item.
+ */
+void destroyItem (CTabItem item) {
+ if (inDispose) return;
+
+ int index = indexOf(item);
+ if (index == -1) return; // should this trigger an error?
+
+ insertionIndex = -2;
+
+ if (items.length == 1) {
+ items = new CTabItem[0];
+ selectedIndex = -1;
+ topTabIndex = 0;
+
+ Control control = item.getControl();
+ if (control != null && !control.isDisposed()) {
+ control.setVisible(false);
+ }
+ closeBar.setVisible(false);
+ redraw();
+ return;
+ }
+
+ // shrink by one and rearrange the array.
+ 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;
+
+ if (topTabIndex == items.length) {
+ --topTabIndex;
+ }
+
+ // move the selection if this item is selected
+ if (selectedIndex == index) {
+ Control control = item.getControl();
+ if (control != null && !control.isDisposed()) {
+ control.setVisible(false);
+ }
+ selectedIndex = -1;
+ setSelectionNotify(Math.max(0, index - 1));
+ } else if (selectedIndex > index) {
+ selectedIndex --;
+ }
+
+ layoutItems();
+ redrawTabArea(-1);
+}
+/**
+ * Dispose the items of the receiver
+ */
+private void onDispose() {
+ inDispose = true;
+
+ // items array is resized during CTabItem.dispose
+ // it is set to null if the last item is removed
+ int length = items.length;
+ for (int i = 0; i < length; i++) {
+ if (items[i] != null) {
+ items[i].dispose();
+ }
+ }
+
+ // clean up resources
+ if (tip != null && !tip.isDisposed()) {
+ tip.dispose();
+ tip = null;
+ }
+
+ if (gradientImage != null){
+ gradientImage.dispose();
+ gradientImage = null;
+ }
+ gradientColors = null;
+ gradientPercents = null;
+ backgroundImage = null;
+
+ if (arrowLeftImage != null){
+ arrowLeftImage.dispose();
+ arrowLeftImage = null;
+ }
+
+ if (arrowRightImage != null){
+ arrowRightImage.dispose();
+ arrowRightImage = null;
+ }
+
+ if (arrowLeftDisabledImage != null) {
+ arrowLeftDisabledImage.dispose();
+ arrowLeftDisabledImage = null;
+ }
+
+ if (arrowRightDisabledImage != null) {
+ arrowRightDisabledImage.dispose();
+ arrowRightDisabledImage = null;
+ }
+
+ if (closeImage != null) {
+ closeImage.dispose();
+ closeImage = null;
+ }
+
+ if (inactiveCloseImage != null) {
+ inactiveCloseImage.dispose();
+ inactiveCloseImage = null;
+ }
+
+ if (borderColor1 != null) {
+ borderColor1.dispose();
+ borderColor1 = null;
+ }
+
+ if (borderColor2 != null) {
+ borderColor2.dispose();
+ borderColor2 = null;
+ }
+
+ if (borderColor3 != null) {
+ borderColor3.dispose();
+ borderColor3 = null;
+ }
+}
+/**
+ * Draw a border around the receiver.
+ */
+private void drawBorder(GC gc) {
+
+ Rectangle d = super.getClientArea();
+
+ if (showBorders) {
+ if ((getStyle() & SWT.FLAT) != 0) {
+ gc.setForeground(borderColor1);
+ gc.drawRectangle(d.x, d.y, d.x + d.width - 1, d.y + d.height - 1);
+ } else {
+ gc.setForeground(borderColor1);
+ gc.drawRectangle(d.x, d.y, d.x + d.width - 3, d.y + d.height - 3);
+
+ gc.setForeground(borderColor2);
+ gc.drawLine(d.x + 1, d.y + d.height - 2, d.x + d.width - 1, d.y + d.height - 2);
+ gc.drawLine(d.x + d.width - 2, d.y + 1, d.x + d.width - 2, d.y + d.height - 1);
+
+ gc.setForeground(borderColor3);
+ gc.drawLine(d.x + 2, d.y + d.height - 1, d.x + d.width - 2, d.y + d.height - 1);
+ gc.drawLine(d.x + d.width - 1, d.y + 2, d.x + d.width - 1, d.y + d.height - 2);
+
+ // fill in corners with parent's background
+ gc.setForeground(getParent().getBackground());
+ gc.drawLine(d.x + d.width - 2, d.y, d.x + d.width - 1, d.y);
+ gc.drawLine(d.x + d.width - 1, d.y + 1, d.x + d.width - 1, d.y + 1);
+
+ gc.drawLine(d.x, d.y + d.height - 2, d.x, d.y + d.height - 2);
+ gc.drawLine(d.x, d.y + d.height - 1, d.x + 1, d.y + d.height - 1);
+
+ gc.drawLine(d.x + d.width - 1, d.y + d.height - 1, d.x + d.width - 1, d.y + d.height - 1);
+ }
+
+ }
+
+ // draw a separator line
+ if (items.length > 0) {
+ int tabHeight = getTabHeight();
+ int lineY = d.y + BORDER_TOP + tabHeight;
+ if (onBottom) {
+ lineY = d.y + d.height - BORDER_BOTTOM - tabHeight - 1;
+ }
+ gc.setForeground(borderColor1);
+ gc.drawLine(d.x + BORDER_LEFT, lineY, d.x + d.width - BORDER_RIGHT, lineY);
+ }
+
+ gc.setForeground(getForeground());
+}
+public Rectangle getClientArea() {
+ Rectangle clientArea = super.getClientArea();
+ clientArea.x = xClient;
+ clientArea.y = yClient;
+ clientArea.width -= 2*marginWidth + BORDER_LEFT + BORDER_RIGHT;
+ clientArea.height -= 2*marginHeight + BORDER_TOP + BORDER_BOTTOM + getTabHeight() + 1;
+ return clientArea;
+}
+/**
+ * Return the height of item images. All images are scaled to
+ * the height of the first image.
+ */
+int getImageHeight() {
+ return imageHeight;
+}
+public int getTabHeight(){
+ if (fixedTabHeight > 0) return fixedTabHeight;
+
+ if (isDisposed()) return 0;
+
+ int tempHeight = 0;
+ GC gc = new GC(this);
+ for (int i=0; i < items.length; i++) {
+ int height = items[i].preferredHeight(gc);
+ tempHeight = Math.max(tempHeight, height);
+ }
+ gc.dispose();
+ return tempHeight;
+}
+/**
+ * Return the tab that is located at the specified index.
+ */
+public CTabItem getItem (int index) {
+ if (index < 0 || index > items.length)
+ SWT.error(SWT.ERROR_INVALID_RANGE);
+ return items [index];
+}
+/**
+* Gets the item at a point in the widget.
+* <p>
+*
+* @return the item at a point
+*
+* @exception SWTError <ul>
+* <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
+* <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
+* <li>ERROR_CANNOT_GET_COUNT when the operation fails</li>
+* </ul>
+*/
+public CTabItem getItem (Point pt) {
+ for (int i = 0; i < items.length; i++) {
+ Rectangle bounds = items[i].getBounds();
+ if (bounds.contains(pt)) return items[i];
+ }
+ return null;
+}
+/**
+ * Return the number of tabs in the folder.
+ */
+public int getItemCount(){
+ return items.length;
+}
+/**
+ * Return the tab items.
+ */
+public CTabItem [] getItems() {
+ CTabItem[] tabItems = new CTabItem [items.length];
+ System.arraycopy(items, 0, tabItems, 0, items.length);
+ return tabItems;
+}
+/**
+ * Return the selected tab item, or an empty array if there
+ * is no selection.
+ */
+public CTabItem getSelection() {
+ if (selectedIndex == -1) return null;
+ return items[selectedIndex];
+}
+/**
+ * Return the index of the selected tab item, or -1 if there
+ * is no selection.
+ */
+public int getSelectionIndex() {
+ return selectedIndex;
+}
+
+private void handleEvents (Event event){
+ switch (event.type) {
+ case SWT.Dispose:
+ onDispose();
+ break;
+ case SWT.Paint:
+ onPaint(event);
+ break;
+ case SWT.Resize:
+ onResize();
+ break;
+ case SWT.MouseUp:
+ onMouseUp(event);
+ break;
+ case SWT.MouseExit:
+ if (event.widget == this) {
+ Rectangle inactiveBounds = inactiveCloseBar.getBounds();
+ if (inactiveBounds.contains(event.x, event.y)) return;
+ inactiveCloseBar.setVisible(false);
+ inactiveItem = null;
+ }
+ if (event.widget == inactiveCloseBar) {
+ Rectangle bounds = getBounds();
+ if (bounds.contains(event.x, event.y)) return;
+ inactiveCloseBar.setVisible(false);
+ inactiveItem = null;
+ }
+ break;
+ case SWT.MouseMove:
+ onMouseMove(event);
+ break;
+ case SWT.Selection:
+ if (event.widget == scrollLeft) {
+ scroll_scrollLeft();
+ }
+ if (event.widget == scrollRight) {
+ scroll_scrollRight();
+ }
+ if (event.widget == closeItem) {
+ closeNotify(getSelection(), event.time);
+ }
+ if (event.widget == inactiveCloseItem) {
+ closeNotify(inactiveItem, event.time);
+ inactiveCloseBar.setVisible(false);
+ inactiveItem = null;
+ }
+ break;
+ default:
+ break;
+ }
+}
+/**
+ * Return the index of the specified tab or -1 if the tab is not
+ * in the receiver.
+ */
+public int indexOf(CTabItem item) {
+ 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;
+}
+/**
+ * 'item' has changed. Store the image size if this is the
+ * first item with an image.
+ */
+void itemChanged(CTabItem item) {
+ Image itemImage = item.getImage();
+ if (imageHeight == -1 && itemImage != null) {
+ imageHeight = itemImage.getBounds().height;
+ }
+
+ int height = getTabHeight();
+ Point size = scrollBar.computeSize(SWT.DEFAULT, height);
+ scrollBar.setSize(size);
+ height = (onBottom) ? height - 2 : height - 1;
+ size = closeBar.computeSize(SWT.DEFAULT, SWT.DEFAULT);
+ closeBar.setSize(size);
+ inactiveCloseBar.setSize(size);
+
+ layoutItems();
+ redrawTabArea(-1);
+}
+private void layoutButtons() {
+
+ boolean leftVisible = scroll_leftVisible();
+ boolean rightVisible = scroll_rightVisible();
+
+ if (leftVisible || rightVisible) {
+ Point size = scrollBar.getSize();
+ Rectangle area = super.getClientArea();
+ int x = area.x + area.width - size.x - BORDER_RIGHT;
+ int y = 0;
+ if (!onBottom) {
+ y = area.y + BORDER_TOP;
+ } else {
+ y = area.y + area.height - BORDER_BOTTOM - size.y;
+ }
+ scrollBar.setLocation(x, y);
+ scrollLeft.setEnabled(leftVisible);
+ scrollRight.setEnabled(rightVisible);
+ scrollBar.setVisible(true);
+ } else {
+ scrollBar.setVisible(false);
+ }
+
+ // When the close button is right at the edge of the Tab folder, hide it because
+ // otherwise it may block off a part of the border on the right
+ if (showClose) {
+ Point size = closeBar.getSize();
+ CTabItem item = getSelection();
+ if (item == null) {
+ closeBar.setVisible(false);
+ } else {
+ int x = item.x + item.width - size.x - 2;
+ int y = item.y + 1 + (item.height - 2 - size.y) / 2; // +1 for white line across top of tab
+ closeBar.setLocation(x, y);
+ if (scrollBar.isVisible()) {
+ Rectangle scrollRect = scrollBar.getBounds();
+ scrollRect.width += BORDER_RIGHT;
+ closeBar.setVisible(!scrollRect.contains(x, y));
+ } else {
+ closeBar.setVisible(true);
+ }
+ }
+ }
+}
+/**
+ * Layout the items and store the client area size.
+ */
+private void layoutItems() {
+ if (isDisposed()) return;
+
+ Rectangle area = super.getClientArea();
+ if (area.height == 0) return;
+
+ int tabHeight = getTabHeight();
+
+ shortenedTabs = false;
+ if (items.length > 0) {
+ int[] widths = new int[items.length];
+ int totalWidth = 0;
+ GC gc = new GC(this);
+ for (int i = 0; i < items.length; i++) {
+ widths[i] = items[i].preferredWidth(gc);
+ totalWidth += widths[i];
+ }
+ gc.dispose();
+ if (totalWidth < (area.width - BORDER_LEFT - BORDER_RIGHT) ) {
+ topTabIndex = 0;
+ } else {
+
+ int oldAverageWidth = 0;
+ int averageWidth = (area.width - BORDER_LEFT - BORDER_RIGHT) / items.length;
+ while (averageWidth > oldAverageWidth) {
+ int width = area.width - BORDER_LEFT - BORDER_RIGHT;
+ int count = items.length;
+ for (int i = 0; i < items.length; i++) {
+ if (widths[i] < averageWidth) {
+ width -= widths[i];
+ count--;
+ }
+ }
+ oldAverageWidth = averageWidth;
+ if (count > 0) {
+ averageWidth = width / count;
+ }
+ }
+ if (averageWidth > MIN_TAB_WIDTH * tabHeight) {
+ for (int i = 0; i < items.length; i++) {
+ if (widths[i] > averageWidth) {
+ widths[i] = averageWidth;
+ }
+ }
+ topTabIndex = 0;
+ shortenedTabs = true;
+ }
+ }
+ int x = area.x;
+ int y = area.y + BORDER_TOP;
+ if (onBottom) {
+ y = Math.max(0, area.y + area.height - BORDER_BOTTOM - tabHeight);
+ }
+ for (int i = topTabIndex - 1; i>=0; i--) {
+ // if the first visible tab is not the first tab
+ CTabItem tab = items[i];
+ tab.width = widths[i];
+ tab.height = getTabHeight();
+ x -= tab.width;
+ // layout tab items from right to left thus making them invisible
+ tab.x = x;
+ tab.y = y;
+ }
+
+ x = area.x + BORDER_LEFT;
+ for (int i=topTabIndex; i<items.length; i++) {
+ // continue laying out remaining, visible items left to right
+ CTabItem tab = items[i];
+ tab.x = x;
+ tab.y = y;
+ tab.height = tabHeight;
+ tab.width = widths[i];
+ x = x + tab.width;
+ }
+ }
+
+ xClient = area.x + BORDER_LEFT + marginWidth;
+ if (onBottom) {
+ yClient = area.y + BORDER_TOP + marginHeight;
+ } else {
+ yClient = area.y + BORDER_TOP + tabHeight + 1 + marginHeight;
+ // +1 is for the line at the bottom of the tabs
+ }
+
+ // resize the scrollbar and close butotns
+ layoutButtons();
+}
+/**
+ * Paint the receiver.
+ */
+private void onPaint(Event event) {
+ GC gc = event.gc;
+ Rectangle rect = super.getClientArea();
+ if (items.length == 0) {
+
+ if (showBorders) {
+ gc.setForeground(borderColor1);
+ gc.drawRectangle(rect.x + BORDER_RIGHT,
+ rect.y + BORDER_BOTTOM,
+ rect.x + rect.width - BORDER_RIGHT - 1,
+ rect.y + rect.height - BORDER_BOTTOM - 1);
+ // fill in top and left edge with parent's background color
+ gc.setBackground(getParent().getBackground());
+ gc.fillRectangle(rect.x, rect.y, BORDER_RIGHT, rect.height);
+ gc.fillRectangle(rect.x, rect.y, rect.width, BORDER_BOTTOM);
+
+ }
+ if (fixedTabHeight > 0) {
+ int y = rect.y + BORDER_BOTTOM + fixedTabHeight;
+ if (onBottom) {
+ y = rect.y + rect.height - fixedTabHeight - 1;
+ }
+ gc.setForeground(borderColor1);
+ gc.drawLine(rect.x + BORDER_RIGHT, y, rect.x + rect.width, y);
+ }
+ gc.setForeground(getForeground());
+ gc.setBackground(getBackground());
+ return;
+ }
+
+ // redraw the Border
+ drawBorder(gc);
+
+ rect.x += BORDER_LEFT;
+ rect.y += BORDER_TOP;
+ rect.width -= BORDER_LEFT + BORDER_RIGHT;
+ rect.height -= BORDER_TOP + BORDER_BOTTOM;
+ gc.setClipping(rect);
+
+ // Draw the unselected tabs first.
+ for (int i=0; i < items.length; i++) {
+ if (i != selectedIndex && event.getBounds().intersects(items[i].getBounds())) {
+ items[i].onPaint(gc, false);
+ }
+ }
+ // Selected tab comes last
+ if (selectedIndex != -1) {
+ items[selectedIndex].onPaint(gc, true);
+ }
+
+ // draw insertion mark
+ if (insertionIndex > -2) {
+ gc.setForeground(getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION));
+ if (insertionIndex == -1) {
+ Rectangle bounds = items[0].getBounds();
+ gc.drawLine(bounds.x, bounds.y, bounds.x, bounds.y + bounds.height - 1);
+ gc.drawLine(bounds.x - 2, bounds.y, bounds.x + 2, bounds.y);
+ gc.drawLine(bounds.x - 1, bounds.y + 1, bounds.x + 1, bounds.y + 1);
+ gc.drawLine(bounds.x - 1, bounds.y + bounds.height - 2, bounds.x + 1, bounds.y + bounds.height - 2);
+ gc.drawLine(bounds.x - 2, bounds.y + bounds.height - 1, bounds.x + 2, bounds.y + bounds.height - 1);
+
+ } else {
+ Rectangle bounds = items[insertionIndex].getBounds();
+ gc.drawLine(bounds.x + bounds.width, bounds.y, bounds.x + bounds.width, bounds.y + bounds.height - 1);
+ gc.drawLine(bounds.x + bounds.width - 2, bounds.y, bounds.x + bounds.width + 2, bounds.y);
+ gc.drawLine(bounds.x + bounds.width - 1, bounds.y + 1, bounds.x + bounds.width + 1, bounds.y + 1);
+ gc.drawLine(bounds.x + bounds.width - 1, bounds.y + bounds.height - 2, bounds.x + bounds.width + 1, bounds.y + bounds.height - 2);
+ gc.drawLine(bounds.x + bounds.width - 2, bounds.y + bounds.height - 1, bounds.x + bounds.width + 2, bounds.y + bounds.height - 1);
+ }
+ }
+
+ gc.setForeground(getForeground());
+ gc.setBackground(getBackground());
+}
+private void redrawTabArea(int index) {
+ int x = 0, y = 0, width = 0, height = 0;
+ if (index == -1) {
+ Rectangle area = super.getClientArea();
+ if (area.width == 0 || area.height == 0) return;
+ width = area.x + area.width - BORDER_LEFT - BORDER_RIGHT;
+ height = getTabHeight() + 1; // +1 causes top line between content and tabs to be redrawn
+ x = area.x + BORDER_LEFT;
+ y = area.y + BORDER_TOP;
+ if (onBottom) {
+ y = Math.max(0, area.y + area.height - BORDER_BOTTOM - height);
+ }
+ } else {
+ CTabItem item = items[index];
+ x = item.x;
+ y = item.y;
+ width = item.width;
+ height = item.height;
+ }
+ redraw(x, y, width, height, false);
+}
+
+/**
+ * Removes the listener.
+ *
+ * @param listener the listener
+ *
+ * @exception SWTError
+ * <ul><li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
+ * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
+ * <li>ERROR_NULL_ARGUMENT when listener is null</li></ul>
+ */
+public void removeSelectionListener(SelectionListener listener) {
+ if (listener == null) {
+ SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ }
+ removeListener(SWT.Selection, listener);
+ removeListener(SWT.DefaultSelection, listener);
+}
+/**
+ * Removes the listener.
+ *
+ * @param listener the listener
+ *
+ * @exception SWTError
+ * <ul><li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
+ * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
+ * <li>ERROR_NULL_ARGUMENT when listener is null</li></ul>
+ */
+public void removeCTabFolderListener(CTabFolderListener listener) {
+ 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];
+ showClose = false;
+ 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;
+}
+
+/**
+ * The widget was resized. Adjust the size of the currently selected page.
+ */
+private void onResize() {
+
+ if (items.length == 0) {
+ redraw();
+ return;
+ }
+
+ Rectangle area = super.getClientArea();
+ if (oldArea == null || oldArea.width == 0 || oldArea.height == 0) {
+ layoutItems();
+ redraw();
+ } else {
+ if (onBottom && oldArea.height != area.height) {
+ // move tabs up or down if tabs on bottom
+ layoutItems();
+ redraw();
+ } else {
+ int width = 0;
+ if (oldArea.width < area.width) {
+ width = area.width - oldArea.width + BORDER_RIGHT;
+ } else if (oldArea.width > area.width) {
+ width = BORDER_RIGHT;
+ }
+ redraw(area.x + area.width - width, area.y, width, area.height, false);
+
+ int height = 0;
+ if (oldArea.height < area.height) {
+ height = area.height - oldArea.height + BORDER_BOTTOM;
+ }
+ if (oldArea.height > area.height) {
+ height = BORDER_BOTTOM;
+ }
+ redraw(area.x, area.y + area.height - height, area.width, height, false);
+
+ if (oldArea.width != area.width) {
+ // resize the widths so that all tabs are visible
+ boolean wasShortened = shortenedTabs;
+ layoutItems();
+ if (wasShortened || shortenedTabs) {
+ redrawTabArea(-1);
+ }
+ }
+ }
+ }
+ oldArea = area;
+
+ // resize content
+ if (selectedIndex != -1) {
+ Control control = items[selectedIndex].getControl();
+ if (control != null && !control.isDisposed()) {
+ control.setBounds(getClientArea());
+ }
+ }
+}
+public void setBackground (Color color) {
+ super.setBackground(color);
+ color = getBackground();
+ Color foreground = getForeground();
+
+ // init inactive close button
+ initInactiveCloseButton(foreground, color);
+
+ // init scroll buttons
+ initScrollButtons(foreground, color);
+
+ // init close button
+ if (gradientColors == null) {
+ if (selectionForeground != null) {
+ foreground = selectionForeground;
+ }
+ initCloseButton(foreground, color);
+ }
+}
+/**
+ * Specify a gradiant of colours to be draw in the background of the selected tab.
+ * For example to draw a gradiant 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 gradiant
+ * in order of appearance left to right. The value <code>null</code> clears the
+ * background gradiant. 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.
+ */
+
+public void setSelectionBackground(Color[] colors, int[] percents) {
+ 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);
+ }
+ }
+ }
+
+ // Are these settings the same as before?
+ if (gradientImage == null && gradientColors == null && colors == null) {
+ if (backgroundImage != null) {
+ backgroundImage = null;
+ redrawTabArea(selectedIndex);
+ }
+ return;
+ }
+ 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]);
+ if (!same) break;
+ }
+ if (same) {
+ for (int i = 0; i < gradientPercents.length; i++) {
+ same = gradientPercents[i] == percents[i];
+ if (!same) break;
+ }
+ }
+ if (same) return;
+ }
+
+ // Cleanup
+ if (gradientImage != null) {
+ gradientImage.dispose();
+ gradientImage = null;
+ }
+ gradientColors = null;
+ gradientPercents = null;
+ backgroundImage = null;
+
+ // Draw gradient onto an image
+ if (colors != null) {
+ int x = 0; int y = 0;
+ int width = 100; int height = 10;
+
+ Display display = getDisplay();
+ Image temp = new Image(display, width, height);
+ GC gc = new GC(temp);
+ int start = 0;
+ int end = 0;
+ Color background = getBackground();
+ if (colors.length == 1) {
+ gc.setBackground(colors[0]);
+ gc.fillRectangle(temp.getBounds());
+ }
+ for (int j = 0; j < colors.length - 1; j++) {
+ Color startColor = colors[j];
+ if (startColor == null) startColor = getBackground();
+ RGB rgb1 = startColor.getRGB();
+ Color endColor = colors[j+1];
+ if (endColor == null) endColor = getBackground();
+ RGB rgb2 = endColor.getRGB();
+ start = end;
+ end = (width) * percents[j] / 100;
+ int range = Math.max(1, end - start);
+ for (int k = 0; k < (end - start); k++) {
+ int r = rgb1.red + k*(rgb2.red - rgb1.red)/range;
+ r = (rgb2.red > rgb1.red) ? Math.min(r, rgb2.red) : Math.max(r, rgb2.red);
+ int g = rgb1.green + k*(rgb2.green - rgb1.green)/range;
+ g = (rgb2.green > rgb1.green) ? Math.min(g, rgb2.green) : Math.max(g, rgb2.green);
+ int b = rgb1.blue + k*(rgb2.blue - rgb1.blue)/range;
+ b = (rgb2.blue > rgb1.blue) ? Math.min(b, rgb2.blue) : Math.max(b, rgb2.blue);
+ Color color = new Color(display, r, g, b);
+ gc.setBackground(color);
+ gc.fillRectangle(start + k,y,1,height);
+ gc.setBackground(background);
+ color.dispose();
+ }
+ }
+ gc.dispose();
+ gradientImage = temp;
+ gradientColors = colors;
+ gradientPercents = percents;
+ backgroundImage = temp;
+
+ Color closeBackground = colors[colors.length - 1];
+ if (closeBackground == null){
+ closeBackground = background;
+ }
+ Color closeForeground = getForeground();
+ initCloseButton(closeForeground, closeBackground);
+ } else {
+ Color closeBackground = getBackground();
+ Color closeForeground = getForeground();
+ initCloseButton(closeForeground, closeBackground);
+ }
+ if (selectedIndex > -1) redrawTabArea(selectedIndex);
+}
+public void setSelectionBackground(Image image) {
+ if (image == backgroundImage) return;
+ if (gradientImage != null) {
+ gradientImage.dispose();
+ gradientImage = null;
+ }
+ gradientColors = null;
+ gradientPercents = null;
+ backgroundImage = image;
+ redrawTabArea(selectedIndex);
+}
+public void setBorderVisible(boolean show) {
+ if (showBorders == show) return;
+
+ showBorders = show;
+ if (showBorders) {
+ if ((getStyle() & SWT.FLAT) != 0) {
+ BORDER_BOTTOM = BORDER_TOP = BORDER_LEFT = BORDER_RIGHT = 1;
+ } else {
+ BORDER_LEFT = BORDER_TOP = 1;
+ BORDER_RIGHT = BORDER_BOTTOM = 3;
+ }
+ } else {
+ BORDER_BOTTOM = BORDER_TOP = BORDER_LEFT = BORDER_RIGHT = 0;
+ }
+ layoutItems();
+ redraw();
+}
+public void setFont(Font font) {
+ if (font != null && font.equals(getFont())) return;
+ super.setFont(font);
+ layoutItems();
+ redrawTabArea(-1);
+}
+public void setForeground (Color color) {
+ super.setForeground(color);
+ color = getForeground();
+ Color background = getBackground();
+
+ initInactiveCloseButton(color, background);
+ initScrollButtons(color, background);
+
+ if (gradientColors != null) {
+ background = gradientColors[gradientColors.length - 1];
+ }
+ initCloseButton(color, background);
+}
+public void setSelectionForeground (Color color) {
+ if (selectionForeground == color) return;
+ if (color == null) color = getForeground();
+ selectionForeground = color;
+ if (selectedIndex > -1) {
+ redrawTabArea(selectedIndex);
+ }
+}
+public void setInsertMark(CTabItem item, boolean after) {
+ int index = -1;
+ if (item != null) {
+ index = indexOf(item);
+ }
+ setInsertMark(index, after);
+}
+public void setInsertMark(int index, boolean after) {
+ if (index < -1 || index >= getItemCount()) {
+ SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ }
+
+ if (index == -1) {
+ index = -2;
+ } else {
+ index = after ? index : --index;
+ }
+
+ if (insertionIndex == index) return;
+ int oldIndex = insertionIndex;
+ insertionIndex = index;
+ if (index > -1) redrawTabArea(index);
+ if (oldIndex > 1) redrawTabArea(oldIndex);
+}
+
+/**
+ * Set the selection to the tab at the specified index.
+ */
+public void setSelection(int index) {
+ if (index < 0 || index >= items.length)
+ SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+
+ if (selectedIndex == index) return;
+
+ if (showClose) {
+ inactiveCloseBar.setVisible(false);
+ inactiveItem = null;
+ }
+
+ int oldIndex = selectedIndex;
+ selectedIndex = index;
+
+ Control control = items[index].control;
+ if (control != null && !control.isDisposed()) {
+ control.setBounds(getClientArea());
+ control.setVisible(true);
+ }
+
+ if (oldIndex != -1) {
+ control = items[oldIndex].control;
+ if (control != null && !control.isDisposed()) {
+ control.setVisible(false);
+ }
+ }
+ ensureVisible();
+
+ redrawTabArea(-1);
+}
+private void ensureVisible() {
+ // make sure item is visible
+ Rectangle area = super.getClientArea();
+ if (area.width == 0) return;
+ int areaWidth = area.x + area.width - BORDER_RIGHT;
+
+ CTabItem tabItem = items[selectedIndex];
+ if (selectedIndex < topTabIndex) {
+ topTabIndex = selectedIndex;
+ }
+ layoutItems();
+
+ int scrollWidth = scrollBar.getSize().x;
+ int width = areaWidth;
+ if (scroll_leftVisible() || scroll_rightVisible()) {
+ width -= scrollWidth;
+ }
+ while (tabItem.x + tabItem.width > width && selectedIndex != topTabIndex) {
+ topTabIndex++;
+ layoutItems();
+ width = areaWidth;
+ if (scroll_leftVisible() || scroll_rightVisible()) {
+ width -= scrollWidth;
+ }
+ }
+}
+/**
+ * Set the selection to the specified item.
+ */
+public void setSelection(CTabItem item) {
+ 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.
+ */
+private void setSelectionNotify(int index) {
+ int oldSelectedIndex = selectedIndex;
+ setSelection(index);
+ if (selectedIndex != oldSelectedIndex && selectedIndex != -1) {
+ Event event = new Event();
+ event.item = getItem(selectedIndex);
+ notifyListeners(SWT.Selection, event);
+ }
+}
+
+private void initCloseButton(Color foreground, Color background) {
+ if (closeImage != null) {
+ closeImage.dispose();
+ closeImage = null;
+ }
+ closeImage = drawCloseImage(foreground, background);
+ closeBar.setBackground(background);
+ closeBar.setForeground(foreground);
+ closeItem.setImage(closeImage);
+}
+private void initInactiveCloseButton(Color foreground, Color background) {
+ if (inactiveCloseImage != null) {
+ inactiveCloseImage.dispose();
+ inactiveCloseImage = null;
+ }
+ inactiveCloseImage = drawCloseImage(foreground, background);
+ inactiveCloseBar.setBackground(background);
+ inactiveCloseBar.setForeground(foreground);
+ inactiveCloseItem.setImage(inactiveCloseImage);
+}
+private Image drawCloseImage(Color foreground, Color background) {
+ Image image = new Image(getDisplay(), 9, 9);
+ GC gc = new GC(image);
+ gc.setBackground(background);
+ gc.fillRectangle(0, 0, 9, 9);
+ gc.setForeground(foreground);
+ for (int i = 0; i < 8; i++) {
+ gc.drawLine(i, i, i + 1, i);
+ gc.drawLine(7 - i, i, 8 - i, i);
+ }
+ gc.dispose();
+ return image;
+}
+private void initScrollButtons(Color foreground, Color background) {
+
+ scrollBar.setBackground(background);
+
+ Display display = getDisplay();
+ Color shadow = display.getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW);
+ if (arrowLeftImage != null) {
+ arrowLeftImage.dispose();
+ arrowLeftImage = null;
+ }
+ if (arrowLeftDisabledImage != null) {
+ arrowLeftDisabledImage.dispose();
+ arrowLeftDisabledImage = null;
+ }
+ arrowLeftImage = drawArrowImage(foreground, background, true);
+ arrowLeftDisabledImage = drawArrowImage(shadow, background, true);
+ scrollLeft.setImage(arrowLeftImage);
+ scrollLeft.setDisabledImage(arrowLeftDisabledImage);
+ scrollLeft.setHotImage(arrowLeftImage);
+
+ if (arrowRightImage != null) {
+ arrowRightImage.dispose();
+ arrowRightImage = null;
+ }
+ if (arrowRightDisabledImage != null) {
+ arrowRightDisabledImage.dispose();
+ arrowRightDisabledImage = null;
+ }
+ arrowRightImage = drawArrowImage(foreground, background, false);
+ arrowRightDisabledImage = drawArrowImage(shadow, background, false);
+ scrollRight.setImage(arrowRightImage);
+ scrollRight.setDisabledImage(arrowRightDisabledImage);
+ scrollRight.setHotImage(arrowRightImage);
+}
+
+private Image drawArrowImage (Color foreground, Color background, boolean left) {
+ // create image for left button
+ int arrow[] = new int[6];
+ if (left) {
+ arrow[0] = 4; arrow[1] = 0;
+ arrow[2] = 0; arrow[3] = 4;
+ arrow[4] = 4; arrow[5] = 8;
+ } else {
+ arrow[0] = 0; arrow[1] = 0;
+ arrow[2] = 4; arrow[3] = 4;
+ arrow[4] = 0; arrow[5] = 8;
+ }
+
+ Image image = new Image(getDisplay(), 5, 9);
+ GC gc = new GC(image);
+ gc.setBackground(background);
+ gc.fillRectangle(0, 0, 5, 9);
+ gc.setBackground(foreground);
+ gc.fillPolygon(arrow);
+ gc.setBackground(background);
+ gc.dispose();
+
+ return image;
+}
+/**
+ * A mouse button was pressed down.
+ * If one of the tab scroll buttons was hit, scroll in the appropriate
+ * direction.
+ * If a tab was hit select the tab.
+ */
+private void onMouseUp(Event event) {
+ for (int i=0; i<items.length; i++) {
+ if (items[i].getBounds().contains(new Point(event.x, event.y))) {
+ setSelectionNotify(i);
+ return;
+ }
+ }
+}
+private void onMouseMove(Event event) {
+ if (!showClose) return;
+
+ CTabItem item = null;
+ Rectangle itemRect = null;
+ for (int i=0; i<items.length; i++) {
+ itemRect = items[i].getBounds();
+ if (itemRect.contains(new Point(event.x, event.y))) {
+ item = items[i];
+ break;
+ }
+ }
+ if (item == inactiveItem) return;
+
+ inactiveCloseBar.setVisible(false);
+ inactiveItem = null;
+
+ if (item == null || item == getSelection()) return;
+
+ Point closeRect = inactiveCloseBar.getSize();
+ int x = itemRect.x + itemRect.width - closeRect.x - 2;
+ int y = itemRect.y + 1 + (itemRect.height - 2 - closeRect.y)/2;
+ if (scrollBar.isVisible()) {
+ Rectangle scrollArea = scrollBar.getBounds();
+ scrollArea.width += BORDER_RIGHT;
+ if (scrollArea.contains(x, y)) return;
+ }
+
+ inactiveCloseBar.setLocation(x, y);
+ inactiveCloseBar.setVisible(true);
+ inactiveItem = item;
+}
+/**
+ * Answer the area where the left scroll button is drawn.
+ */
+private Rectangle scroll_getBounds() {
+ if (scrollBar != null)
+ return scrollBar.getBounds();
+ return new Rectangle(0, 0, 0, 0);
+}
+
+/**
+ * Answer true if not all tabs can be visible in the receive
+ * thus requiring the scroll buttons to be visible.
+ */
+private boolean scroll_leftVisible() {
+ return topTabIndex > 0;
+}
+
+/**
+ * Answer true if not all tabs can be visible in the receive
+ * thus requiring the scroll buttons to be visible.
+ */
+private boolean scroll_rightVisible() {
+ if (topTabIndex < items.length - 1) {
+ // only show Scroll buttons if there is more than one item
+ // and if we are not alread at the last item
+ CTabItem tabItem = items[items.length-1];
+ int tabStopX = tabItem.x + tabItem.width;
+ Rectangle area = super.getClientArea();
+ if (tabStopX > area.x + area.width - BORDER_RIGHT) {
+ return true; // not all tabs fit in the client area
+ }
+ }
+ return false;
+}
+
+/**
+ * Scroll the tab items to the left.
+ */
+private void scroll_scrollLeft() {
+ if (scroll_leftVisible()) {
+ --topTabIndex;
+ layoutItems();
+ redrawTabArea(-1);
+ }
+}
+
+/**
+ * Scroll the tab items to the right.
+ */
+private void scroll_scrollRight() {
+ if (scroll_rightVisible()) {
+ topTabIndex++;
+ layoutItems();
+ redrawTabArea(-1);
+ }
+}
+public void setTabHeight(int height) {
+ if (height < 0) {
+ SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ }
+ fixedTabHeight = height + CTabItem.TOP_MARGIN + CTabItem.BOTTOM_MARGIN;
+ layoutItems();
+}
+} \ No newline at end of file
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..4c269a551e
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabFolderAdapter.java
@@ -0,0 +1,10 @@
+package org.eclipse.swt.custom;
+
+/*
+ * Licensed Materials - Property of IBM,
+ * (c) Copyright IBM Corp. 1998, 2001 All Rights Reserved
+ */
+
+public class CTabFolderAdapter implements CTabFolderListener {
+ public void itemClosed(CTabFolderEvent event){};
+} \ No newline at end of file
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..825d848700
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabFolderEvent.java
@@ -0,0 +1,18 @@
+package org.eclipse.swt.custom;
+
+/*
+ * Licensed Materials - Property of IBM,
+ * (c) Copyright IBM Corp. 1998, 2001 All Rights Reserved
+ */
+
+import org.eclipse.swt.events.TypedEvent;
+import org.eclipse.swt.widgets.*;
+
+public class CTabFolderEvent extends TypedEvent {
+ public Widget item;
+ public boolean doit;
+
+CTabFolderEvent(Widget w) {
+ super(w);
+}
+} \ No newline at end of file
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..fd3938d351
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabFolderListener.java
@@ -0,0 +1,12 @@
+package org.eclipse.swt.custom;
+
+/*
+ * Licensed Materials - Property of IBM,
+ * (c) Copyright IBM Corp. 1998, 2001 All Rights Reserved
+ */
+
+import java.util.EventListener;
+
+public interface CTabFolderListener {
+ public void itemClosed(CTabFolderEvent event);
+} \ No newline at end of file
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..fdcba65fc6
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/CTabItem.java
@@ -0,0 +1,406 @@
+package org.eclipse.swt.custom;
+
+/*
+ * Licensed Materials - Property of IBM,
+ * (c) Copyright IBM Corp. 1998, 2001 All Rights Reserved
+ */
+
+import org.eclipse.swt.*;
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.widgets.*;
+
+public class CTabItem extends Item {
+ CTabFolder parent;
+ int x,y,width,height = 0;
+ String toolTipText;
+ Control control; // the tab page
+
+ private Image disabledImage;
+
+ // internal constants
+ private static final int LEFT_MARGIN = 3;
+ private static final int RIGHT_MARGIN = 3;
+ static final int TOP_MARGIN = 2;
+ static final int BOTTOM_MARGIN = 2;
+ private static final int INTERNAL_SPACING = 2;
+
+ private static final String ellipsis = "...";
+
+/**
+ * Construct a CTabItem with the specified parent and style.
+ */
+public CTabItem (CTabFolder parent, int style) {
+ this(parent, style, parent.getItemCount());
+}
+/**
+ * Construct a CTabItem with the specified parent and style, inserted at
+ * the specified index.
+ */
+public CTabItem (CTabFolder parent, int style, int index) {
+ super (parent, checkStyle(style));
+ parent.createItem (this, index);
+ addListener(SWT.Dispose, new Listener() {
+ public void handleEvent(Event event) {
+ onDispose();
+ }
+ });
+}
+private static int checkStyle(int style) {
+ return SWT.NONE;
+}
+/**
+ * Dispose the receiver.
+ */
+private void onDispose() {
+ parent.destroyItem(this);
+ parent = null;
+ control = null;
+ toolTipText = null;
+}
+/**
+ * Return the bounds of the CTabItem.
+ */
+public Rectangle getBounds () {
+ return new Rectangle(x, y, width, height);
+}
+/**
+* Gets the control.
+* <p>
+* @return the control
+*
+* @exception SWTError(ERROR_THREAD_INVALID_ACCESS)
+* when called from the wrong thread
+* @exception SWTError(ERROR_WIDGET_DISPOSED)
+* when the widget has been disposed
+*/
+public Control getControl () {
+ return control;
+}
+/**
+ * Answer the display of the receiver's parent widget.
+ */
+public Display getDisplay() {
+ if (parent == null) SWT.error(SWT.ERROR_WIDGET_DISPOSED);
+ return parent.getDisplay();
+}
+public Image getDisabledImage(){
+ return disabledImage;
+}
+/**
+ * Return the parent of the CTabItem.
+ */
+public CTabFolder getParent () {
+ return parent;
+}
+/**
+ * Gets the tool tip text.
+ * <p>
+ * @return the tool tip text.
+ *
+ * @exception SWTError(ERROR_THREAD_INVALID_ACCESS)
+ * when called from the wrong thread
+ * @exception SWTError(ERROR_WIDGET_DISPOSED)
+ * when the widget has been disposed
+ */
+public String getToolTipText () {
+ return toolTipText;
+}
+private int imageHeight() {
+ int imageHeight = 0;
+ if (parent.getImageHeight() != -1) {
+ imageHeight = parent.getImageHeight();
+ } else {
+ Image image = getImage();
+ if (image != null) {
+ imageHeight = image.getBounds().height;
+ }
+ }
+ return imageHeight;
+}
+private int imageWidth() {
+ int imageWidth = 0;
+ Image image = getImage();
+ if (image != null) {
+ imageWidth = image.getBounds().width;
+ }
+ return imageWidth;
+}
+/**
+ * Paint the receiver.
+ */
+void onPaint(GC gc, boolean isSelected) {
+
+ if (width == 0 || height == 0) return;
+
+ Display display = getDisplay();
+ Color highlightShadow = display.getSystemColor(SWT.COLOR_WIDGET_HIGHLIGHT_SHADOW);
+ Color normalShadow = display.getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW);
+
+ int index = parent.indexOf(this);
+
+ if (isSelected) {
+ // draw a background image behind the text
+ if (parent.backgroundImage != null) {
+ if (index == parent.topTabIndex){
+ Rectangle imageRect = parent.backgroundImage.getBounds();
+ gc.drawImage(parent.backgroundImage, 0, 0, imageRect.width, imageRect.height,
+ x + 1, y, width - 3, height);
+ } else {
+ Rectangle imageRect = parent.backgroundImage.getBounds();
+ gc.drawImage(parent.backgroundImage, 0, 0, imageRect.width, imageRect.height,
+ x + 2, y, width - 4, height);
+ }
+ }
+
+ // draw tab lines
+ if (!parent.onBottom) {
+ gc.setForeground(normalShadow);
+ if (index != parent.topTabIndex) {
+ gc.drawLine(x + 1, y, x + 1, y);
+ gc.drawLine(x, y + 1, x, y + height - 2);
+ gc.drawLine(x, y + height - 1, x, y + height - 1);
+ }
+ gc.drawLine(x + width - 2, y, x + width - 2, y);
+ gc.drawLine(x + width - 1, y + 1, x + width - 1, y + height - 2);
+ gc.drawLine(x + width - 1, y + height - 1, x + width - 1, y + height - 1);
+
+ gc.setForeground(highlightShadow);
+ if (index != parent.topTabIndex) {
+ gc.drawLine(x + 2, y, x + 2, y);
+ gc.drawLine(x + 1, y + 1, x + 1, y + height - 2);
+ gc.drawLine(x + 1, y + height - 1, x + 1, y + height - 1);
+ } else {
+ gc.drawLine(x, y, x, y + height - 1);
+ }
+
+ gc.drawLine(x + width - 3, y, x + width - 3, y);
+ gc.drawLine(x + width - 2, y + 1, x + width - 2, y + height - 2);
+ gc.drawLine(x + width - 2, y + height - 1, x + width - 2, y + height - 1);
+
+ // light line across top
+ if (index != parent.topTabIndex) {
+ gc.drawLine(x + 3, y, x + width - 4, y);
+ } else {
+ gc.drawLine(x + 1, y, x + width - 4, y);
+ }
+ } else {
+ gc.setForeground(normalShadow);
+ if (index != parent.topTabIndex) {
+ gc.drawLine(x, y, x, y);
+ gc.drawLine(x, y + 1, x, y + height - 2);
+ gc.drawLine(x + 1, y + height - 1, x + 1, y + height - 1);
+ }
+ gc.drawLine(x + width - 1, y, x + width - 1, y);
+ gc.drawLine(x + width - 1, y + 1, x + width - 1, y + height - 2);
+ gc.drawLine(x + width - 2, y + height - 1, x + width - 2, y + height - 1);
+
+ gc.setForeground(highlightShadow);
+ if (index != parent.topTabIndex) {
+ gc.drawLine(x + 1, y, x + 1, y);
+ gc.drawLine(x + 1, y + 1, x + 1, y + height - 2);
+ gc.drawLine(x + 2, y + height - 1, x + 2, y + height - 1);
+ } else {
+ gc.drawLine(x, y, x, y + height - 1);
+ }
+
+ gc.drawLine(x + width - 2, y, x + width - 2, y);
+ gc.drawLine(x + width - 2, y + 1, x + width - 2, y + height - 2);
+ gc.drawLine(x + width - 3, y + height - 1, x + width - 3, y + height - 1);
+
+ // light line across top and bottom
+ if (index != parent.topTabIndex) {
+ gc.drawLine(x + 1, y, x + width - 2, y);
+ gc.drawLine(x + 2, y + height - 1, x + width - 4, y + height - 1);
+ } else {
+ gc.drawLine(x + 1, y, x + width - 2, y);
+ gc.drawLine(x + 1, y + height - 1, x + width - 4, y + height - 1);
+ }
+ }
+ }
+
+ // draw Image
+ int xDraw = x + LEFT_MARGIN;
+
+ Image image = getImage();
+ if (!isSelected && image != null) {
+ Image temp = getDisabledImage();
+ if (temp != null){
+ image = temp;
+ }
+ }
+ if (image != null) {
+ Rectangle imageBounds = image.getBounds();
+ int imageHeight = imageHeight();
+ int imageY = y + (height - parent.getImageHeight()) / 2;
+ gc.drawImage(image,
+ imageBounds.x, imageBounds.y, imageBounds.width, imageBounds.height,
+ xDraw, imageY, imageBounds.width, imageHeight);
+ xDraw += imageBounds.width + INTERNAL_SPACING;
+ }
+
+ // draw Text
+ int textWidth = x + width - xDraw - RIGHT_MARGIN;
+ if (isSelected && parent.showClose) {
+ textWidth = x + width - xDraw - parent.closeBar.getSize().x - RIGHT_MARGIN;
+ }
+ String text = shortenText(gc, getText(), textWidth);
+
+ if (isSelected && parent.selectionForeground != null) {
+ gc.setForeground(parent.selectionForeground);
+ } else {
+ gc.setForeground(parent.getForeground());
+ }
+ int textY = y + (height - textHeight(gc)) / 2;
+ gc.drawString(text, xDraw, textY, true);
+
+ gc.setForeground(parent.getForeground());
+}
+private String shortenText(GC gc, String text, int width) {
+ if (gc.textExtent(text).x <= width) return text;
+
+ int ellipseWidth = gc.textExtent(ellipsis).x;
+ int length = text.length();
+ int end = length - 1;
+ while (end > 0) {
+ text = text.substring(0, end);
+ int l1 = gc.textExtent(text).x;
+ if (l1 + ellipseWidth <= width) {
+ return text + ellipsis;
+ }
+ end--;
+ }
+ return "";
+}
+/**
+ * Answer the preferred height of the receiver for the GC.
+ */
+int preferredHeight(GC gc) {
+ return Math.max(textHeight(gc), imageHeight()) + TOP_MARGIN + BOTTOM_MARGIN;
+}
+/**
+ * Answer the preferred width of the receiver for the GC.
+ */
+int preferredWidth(GC gc) {
+ int tabWidth = LEFT_MARGIN + RIGHT_MARGIN;
+ Image image = getImage();
+ if (image != null) tabWidth += imageWidth() + INTERNAL_SPACING;
+ tabWidth += textWidth(gc);
+ if (parent.showClose) tabWidth += parent.closeBar.getSize().x;
+ return tabWidth;
+}
+/**
+ * Sets the control.
+ * <p>
+ * @param control the new control
+ *
+ * @exception SWTError(ERROR_THREAD_INVALID_ACCESS)
+ * when called from the wrong thread
+ * @exception SWTError(ERROR_WIDGET_DISPOSED)
+ * when the widget has been disposed
+ */
+public void setControl (Control control) {
+ if (control != null && 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.
+ * <p>
+ * @param image the new image (or null)
+ *
+ * @exception SWTError(ERROR_THREAD_INVALID_ACCESS)
+ * when called from the wrong thread
+ * @exception SWTError(ERROR_WIDGET_DISPOSED)
+ * when the widget has been disposed
+ */
+public void setImage (Image image) {
+ Image oldImage = getImage();
+
+ super.setImage(image);
+ if (image == null || !image.equals(oldImage)) {
+ parent.itemChanged(this);
+ }
+}
+public void setDisabledImage (Image image) {
+ Image oldImage = getDisabledImage();
+
+ disabledImage = image;
+ if (disabledImage == null || !disabledImage.equals(oldImage)) {
+ parent.itemChanged(this);
+ }
+}
+
+/**
+ * Set the widget text.
+ * <p>
+ * This method sets the widget label. The label may include
+ * the mnemonic characters but must not contain line delimiters.
+ *
+ * @param string the new label for the widget
+ *
+ */
+public void setText (String string) {
+ String oldText = getText();
+
+ super.setText(string);
+ if (!string.equals(oldText)) {
+ parent.itemChanged(this);
+ }
+}
+
+/**
+ * Sets the tool tip text.
+ * <p>
+ * @param string the new tool tip text (or null)
+ *
+ * @exception SWTError(ERROR_THREAD_INVALID_ACCESS)
+ * when called from the wrong thread
+ * @exception SWTError(ERROR_WIDGET_DISPOSED)
+ * when the widget has been disposed
+ */
+public void setToolTipText (String string) {
+ toolTipText = string;
+}
+/**
+ * Answer the text height.
+ */
+private int textHeight(GC gc) {
+ int textHeight = 0;
+
+ if (isDisposed()) return textHeight;
+
+ String text = getText();
+ if (text != null) {
+ textHeight = gc.stringExtent(text).y;
+ }
+ return textHeight;
+}
+/**
+ * Answer the text width.
+ */
+private int textWidth(GC gc) {
+ int textWidth = 0;
+
+ if (isDisposed()) return 0;
+
+ String text = getText();
+ if (text != null) {
+ textWidth = gc.stringExtent(text).x;
+ }
+ return textWidth;
+}
+} \ No newline at end of file
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..eb99ff6e53
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/ControlEditor.java
@@ -0,0 +1,238 @@
+package org.eclipse.swt.custom;
+
+/*
+ * Licensed Materials - Property of IBM,
+ * (c) Copyright IBM Corp. 1998, 2001 All Rights Reserved
+ */
+
+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>
+*/
+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 minumum 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 minumum height value. The default value is 0.
+ */
+ public int minimumHeight = 0;
+
+ Composite parent;
+ Control editor;
+ private boolean hadFocus;
+ private Listener internalListener;
+/**
+* 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;
+
+ internalListener = new Listener() {
+ public void handleEvent(Event e) {
+ if (e.widget instanceof ScrollBar && e.type == SWT.Selection)
+ scroll (e);
+ else if (e.type == SWT.Resize)
+ resize ();
+ }
+ };
+
+ parent.addListener (SWT.Resize, internalListener);
+
+ ScrollBar hBar = parent.getHorizontalBar ();
+ if (hBar != null) hBar.addListener (SWT.Selection, internalListener);
+ ScrollBar vBar = parent.getVerticalBar ();
+ if (vBar != null) vBar.addListener (SWT.Selection, internalListener);
+}
+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.isDisposed()) {
+ parent.removeListener (SWT.Resize, internalListener);
+ ScrollBar hBar = parent.getHorizontalBar ();
+ if (hBar != null) hBar.removeListener (SWT.Selection, internalListener);
+ ScrollBar vBar = parent.getVerticalBar ();
+ if (vBar != null) vBar.removeListener (SWT.Selection, internalListener);
+ }
+
+ parent = null;
+ editor = null;
+ hadFocus = false;
+ internalListener = 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;
+}
+void resize () {
+ 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) editor.setFocus ();
+}
+void scroll (Event e) {
+ if (editor == null || editor.isDisposed()) return;
+ if (editor.getVisible ()) {
+ hadFocus = editor.isFocusControl();
+ }
+ boolean visible = e.detail != SWT.DRAG;
+ if (visible) {
+ editor.setBounds (computeBounds ());
+ }
+ editor.setVisible (visible);
+ if (visible && hadFocus) editor.setFocus ();
+}
+/**
+* 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;
+
+ editor.setVisible (false);
+ editor.setBounds (computeBounds ());
+ editor.setVisible (true);
+}
+} \ No newline at end of file
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..0469aa577c
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/DefaultContent.java
@@ -0,0 +1,797 @@
+package org.eclipse.swt.custom;
+/*
+ * Licensed Materials - Property of IBM,
+ * (c) Copyright IBM Corp 2000, 2001
+ */
+
+/* Imports */
+import org.eclipse.swt.*;
+import org.eclipse.swt.events.*;
+import org.eclipse.swt.widgets.*;
+import java.io.*;
+import java.util.*;
+
+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+(int)Math.pow(2, 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+(int)Math.pow(2, replaceExpandExp)][2];
+ replaceExpandExp++;
+ System.arraycopy(linesArray, 0, newLines, 0, size);
+ }
+ int[] range = new int[] {start, length};
+ newLines[count] = range;
+ return newLines;
+}
+/**
+ * Adds <code>TextChangedEvent</code> listener. A <code>TextChangedEvent</code> is sent
+ * when changes to the textStore occur.
+ * <p>
+ *
+ * @param listener the listener
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_NULL_ARGUMENT when listener is null</li>
+ * </ul>
+ */
+public void addTextChangedListener(TextChangedListener listener) {
+ if (listener == null) error(SWT.ERROR_NULL_ARGUMENT);
+ StyledTextListener typedListener = new StyledTextListener(listener);
+ textListeners.addElement(typedListener);
+}
+/**
+ * Adjusts the gap to accomodate 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;
+}
+/**
+ * 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 lineCnt = 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, lineCnt);
+ lineCnt++;
+ start = i + 1;
+ } else if (ch == SWT.LF) {
+ indexedLines = addLineIndex(start, i - start + 1, indexedLines, lineCnt);
+ lineCnt++;
+ start = i + 1;
+ }
+ }
+ }
+ int[][] newLines = new int[lineCnt+1][2];
+ System.arraycopy(indexedLines, 0, newLines, 0, lineCnt);
+ int[] range = new int[] {start, i - start};
+ newLines[lineCnt]=range;
+ return newLines;
+}
+/**
+ * Inserts text.
+ * <p>
+ *
+ * @param position the position at which to insert the text
+ * @param length 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 sizeHint the size of the change
+ * @param line 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 lineCnt = 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++;
+ }
+ }
+ lineCnt++;
+ } else if (ch == SWT.LF) {
+ lineCnt++;
+ }
+ count++;
+ }
+ i++;
+ }
+ return lineCnt;
+}
+/**
+ * @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.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 buf = new StringBuffer();
+ int gapLength = gapEnd - gapStart;
+ buf.append(textStore, start, gapStart - start);
+ buf.append(textStore, gapEnd, length - gapLength - (gapStart - start));
+ return buf.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){
+ int position;
+ if ((charPosition > getCharCount()) || (charPosition < 0)) error(SWT.ERROR_INVALID_ARGUMENT);
+ 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 accomodate 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 continous 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 text changed listener.
+ * <p>
+ *
+ * @param listener the listener
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_NULL_ARGUMENT when listener is null</li>
+ * </ul>
+ */
+public void removeTextChangedListener(TextChangedListener 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 TextChangedEvent, <code>numNewLines</code> is the number of
+ * inserted lines and <code>numReplacedLines</code> is the number of deleted lines based
+ * on the change that occurs visually. For example:
+ * <ul>
+ * <li>(replacedText,newText) ==> (numReplacedLines,numNewLines)
+ * <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
+ */
+public void replaceTextRange(int start, int replaceLength, String newText){
+ StyledTextEvent event = new StyledTextEvent(this);
+ event.type = StyledText.TextReplaced;
+ event.start = start;
+ event.replacedLineCount = lineCount(start, replaceLength);
+ event.text = getTextRange(start, replaceLength);
+
+ // first delete the text to be replaced
+ delete(start, replaceLength, event.replacedLineCount + 1);
+ // then insert the new text
+ insert(start, newText);
+
+ // inform listeners
+ event.newLineCount = lineCount(start, newText.length());
+ event.replacedCharCount = replaceLength;
+ event.newCharCount = newText.length();
+ sendTextEvent(event);
+ // printLines();
+}
+/**
+ * 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 delmiter 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);
+}
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/DefaultLineStyler.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/DefaultLineStyler.java
new file mode 100755
index 0000000000..d6eaac6976
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/DefaultLineStyler.java
@@ -0,0 +1,599 @@
+package org.eclipse.swt.custom;
+/*
+ * Licensed Materials - Property of IBM,
+ * (c) Copyright IBM Corp 2000, 2001
+ */
+
+/* Imports */
+import org.eclipse.swt.events.*;
+import org.eclipse.swt.graphics.*;
+import java.util.*;
+
+class DefaultLineStyler implements LineStyleListener, LineBackgroundListener {
+ StyledTextContent content;
+ StyleRange styles[] = new StyleRange[0];
+ int styleCount = 0; // the number of styles
+ int styleExpandExp = 1; // the expansion exponent, used to increase the styles array exponentially
+ int lineExpandExp = 1; // the expansion exponent, used to increase the lines array exponentially
+ int lineCount = 0;
+ Color lineBackgrounds[];
+
+/**
+ * Creates a new default line styler.
+ * <p>
+ *
+ * @param content the text to which the styles apply
+ */
+public DefaultLineStyler(StyledTextContent content) {
+ this.content = content;
+ lineCount = content.getLineCount();
+ lineBackgrounds = new Color[lineCount];
+}
+/**
+ * Inserts a style at the given location.
+ * <p>
+ *
+ * @param style the new style
+ * @param index the index at which to insert the style (the new style
+ * will reside at this index)
+ *
+ */
+void insertStyle(StyleRange style, int index) {
+ int size = styles.length;
+ if (styleCount == size) {
+ // expand the styles array by powers of 2
+ StyleRange[] newStyles = new StyleRange[size+(int)Math.pow(2, styleExpandExp)];
+ System.arraycopy(styles, 0, newStyles, 0, size);
+ styles = newStyles;
+ styleExpandExp++;
+ }
+ // shift the styles down to make room for the new style
+ for (int i=styleCount-1; i>=index; i--) {
+ styles[i+1]=styles[i];
+ }
+ styles[index] = style;
+ styleCount++;
+}
+/**
+ * Inserts a style, merging it with adjacent styles if possible.
+ * <p>
+ *
+ * @param style the new style
+ * @param index the index at which to insert the style (the new style
+ * will reside at this index)
+ * @return true if the style was inserted, false if the style was merged with an adjacent
+ * style
+ */
+boolean insertMergeStyle(StyleRange style, int index) {
+ if (mergeStyleBefore(style, index)) return false;
+ if (mergeStyleAfter(style, index)) return false;
+ insertStyle(style, index);
+ return true;
+}
+/**
+ * Merges the style with the style before it if possible.
+ * <p>
+ *
+ * @param style the new style
+ * @param index the index at which to attempt the merge.
+ * @return true if the style was merged, false otherwise
+ */
+boolean mergeStyleBefore(StyleRange style, int index) {
+ // see if the style is similar to the style before it and merge the
+ // styles if possible
+ if (index > 0) {
+ StyleRange previous = styles[index-1];
+ if (style.similarTo(previous)) {
+ // the start of style needs to be in the range of the previous style
+ // and the end of style needs to be < the start of the next style
+ int previousEnd = previous.start + previous.length;
+ if ((style.start <= previousEnd) && (style.start >= previous.start)) {
+ int styleEnd = style.start + style.length;
+ if ((index == styleCount) || (styleEnd <= styles[index].start)) {
+ previous.length = style.start + style.length - previous.start;
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+/**
+ * Merges the style with the style after it if possible.
+ * <p>
+ *
+ * @param style the new style
+ * @param index the index at which to attempt the merge.
+ * @return true if the style was merged, false otherwise
+ */
+boolean mergeStyleAfter(StyleRange style, int index) {
+ // see if the style is similar to the style that will be after it and
+ // merge the styles if possible
+ if (index < styleCount) {
+ StyleRange next = styles[index];
+ if (style.similarTo(next)) {
+ // the end of style needs to be in the range of the next style and
+ // the start of style needs to be > the end of the previous style
+ int styleEnd = style.start + style.length;
+ int nextEnd = next.start + next.length;
+ if ((styleEnd <= nextEnd) && (styleEnd >= next.start)) {
+ if ((index == 0) || (style.start >= styles[index-1].start + styles[index-1].length)) {
+ next.length = next.start + next.length - style.start;
+ next.start = style.start;
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+/**
+ * Removes style information that is defined for the range of text in <code>clearStyle</code>.
+ * <p>
+ *
+ * @param clearStyle the style information to use for clearing
+ */
+void clearStyle(StyleRange clearStyle) {
+ Point pt = getOverlappingStyles(clearStyle.start, clearStyle.length);
+ int clearStyleEnd = clearStyle.start + clearStyle.length - 1;
+
+ // no overlapped styles exist
+ if ((pt == null) || (pt.y == 0)) return;
+
+ // the newStyle overlaps one or more of the existing styles
+ // pt.x is the index of the first overlapped style, pt.y is the number of overlapped
+ // styles
+ int count = 0;
+ for (int i=pt.x; count<pt.y; i++) {
+ StyleRange overlap = styles[i];
+ int overlapEnd = overlap.start + overlap.length - 1;
+ if (overlap.start < clearStyle.start) {
+ if (overlapEnd <= clearStyleEnd) {
+ // the end of overlap needs to be cleared
+ overlap.length=clearStyle.start - overlap.start;
+ } else {
+ // middle of overlap needs to be cleared, this will
+ // cause overlap to be broken into two
+ StyleRange endStyle = (StyleRange)overlap.clone();
+ endStyle.start = clearStyleEnd + 1;
+ endStyle.length = overlapEnd - clearStyleEnd;
+ overlap.length = clearStyle.start - overlap.start;
+ insertStyle(endStyle, i+1);
+ break;
+ }
+ } else {
+ if (overlapEnd <= clearStyleEnd) {
+ // entire overlap needs to be cleared
+ deleteStyle(i);
+ i--;
+ } else {
+ // beginning of overlap needs to be cleared
+ overlap.start=clearStyleEnd + 1;
+ overlap.length=overlapEnd - overlap.start + 1;
+ break;
+ }
+ }
+ count++;
+ }
+}
+/**
+ * Increases the <code>linebackgrounds</code> array to accomodate new line background
+ * information.
+ * <p>
+ *
+ * @param numLines the number to increase the array by
+ */
+void expandLinesBy(int numLines) {
+ int size = lineBackgrounds.length;
+ if (size - lineCount >= numLines) {
+ return;
+ }
+ Color[] newLines = new Color[size+Math.max((int)Math.pow(2, lineExpandExp), numLines)];
+ System.arraycopy(lineBackgrounds, 0, newLines, 0, size);
+ lineBackgrounds = newLines;
+ lineExpandExp++;
+}
+/**
+ * Deletes the style at <code>index</code>.
+ * <p>
+ *
+ * @param index the index of the style to be deleted
+ */
+void deleteStyle(int index) {
+ // move the styles up
+ for (int i=index+1; i<styleCount; i++) {
+ styles[i-1] = styles[i];
+ }
+ styles[styleCount-1]=null;
+ styleCount--;
+}
+/**
+ * Returns the styles that are defined.
+ * <p>
+ *
+ * @return the copied array of styles
+ */
+StyleRange [] getStyleRanges() {
+ StyleRange[] newStyles = new StyleRange[styleCount];
+ System.arraycopy(styles, 0, newStyles, 0, styleCount);
+ return newStyles;
+}
+/**
+ * Handles the get line background color callback.
+ * <p>
+ *
+ * @param event.lineOffset line number (input)
+ * @param event.lineText line text (input)
+ * @param event.background line background color (output)
+ */
+public void lineGetBackground(LineBackgroundEvent event) {
+ event.lineBackground = lineBackgrounds[content.getLineAtOffset(event.lineOffset)];
+}
+/**
+ * Handles the get line style information callback.
+ * <p>
+ *
+ * @param event.lineOffset line number (input)
+ * @param event.lineText line text (input)
+ * @param event.styles array of StyleRanges, need to be in order (output)
+ */
+public void lineGetStyle(LineStyleEvent event) {
+ int lineStart = event.lineOffset;
+ int lineEnd = lineStart + event.lineText.length();
+
+ int high = searchForStyle(lineStart, lineEnd);
+ StyleRange style = null;
+ Vector lineStyles = new Vector();
+
+ // index will represent a style that
+ // -- starts after the line (end processing)
+ // -- ends before the line (continue processing)
+ // -- starts before the line, ends in the line (add range)
+ // -- starts in the line, ends in the line (add range)
+ // -- starts in the line, ends after the line (add range)
+ // -- starts before the line, ends after the line (add range)
+ for (int index = high; index < styleCount; index++) {
+ style = styles[index];
+ if (style.start > lineEnd)
+ // style starts after the line, end looping
+ break;
+ int styleEnd = style.start + style.length - 1;
+ if (styleEnd >= lineStart) lineStyles.add(style);
+ }
+ event.styles = new StyleRange[lineStyles.size()];
+ lineStyles.copyInto(event.styles);
+}
+/**
+ * Searches for the first style in the <code>start</code> - <code>end</code> range.
+ * <p>
+ *
+ * @return the index of the first style that overlaps the input range
+ */
+int searchForStyle(int start, int end) {
+ int high = styleCount;
+ int low = -1;
+ int index = high;
+ // find the index of the first style for the given range, use a binary search
+ while (high - low > 1) {
+ index = (high + low) / 2;
+ StyleRange style = styles[index];
+ int styleEnd = style.start + style.length - 1;
+ if (start <= style.start || end <= styleEnd || (start > style.start && styleEnd >= start && styleEnd < end)) {
+ high = index;
+ }
+ else {
+ low = index;
+ }
+ }
+ return high;
+}
+/**
+ * Updates the line background colors to reflect a new color. Called by StyledText.
+ * <p>
+ *
+ * @param startLine index of the first line to color
+ * @param lineCount number of lines to color starting at startLine
+ * @param background the background color for the lines
+ */
+void setLineBackground(int startLine, int count, Color background) {
+ for (int i=startLine; i<startLine + count; i++) {
+ lineBackgrounds[i]=background;
+ }
+}
+/**
+ * Update the styles to reflect the new style. <code>newStyle</code> will
+ * replace any old style for the range. When this method is called, the
+ * DefaultLineStyler may merge the new style with an existing style (if possible).
+ * Called by StyledText when a style is added. Called by StyledText.
+ * <p>
+ *
+ * @param newStyle the new style information.
+ */
+void setStyleRange(StyleRange newStyle) {
+ if (newStyle == null) {
+ styles = new StyleRange[0];
+ styleExpandExp = 1;
+ styleCount = 0;
+ return;
+ }
+ if (newStyle.length ==0) return;
+ if (newStyle.isUnstyled()) {
+ clearStyle(newStyle);
+ return;
+ }
+
+ Point pt = getOverlappingStyles(newStyle.start, newStyle.length);
+ int newStyleEnd = newStyle.start + newStyle.length - 1;
+
+ // no styles exist
+ if (pt == null) {
+ insertStyle(newStyle, 0);
+ return;
+ }
+
+ // newStyle does not overlap any other styles
+ if (pt.y == 0) {
+ insertMergeStyle(newStyle, pt.x);
+ return;
+ }
+
+ // the newStyle overlaps one or more of the existing styles
+ boolean added = false; // indicates whether or not the new style has been added
+ int count = 0;
+ // pt.x is the index of the first overlapped style, pt.y is the number of overlapped
+ // styles
+ for (int i=pt.x; count<pt.y; i++) {
+ StyleRange overlap = styles[i];
+ int overlapEnd = overlap.start + overlap.length - 1;
+ if (overlap.start < newStyle.start) {
+ if (overlapEnd <= newStyleEnd) {
+ // the end of overlap needs to be replaced by newStyle
+ if (newStyle.similarTo(overlap)) {
+ // update overlap to accomodate the new style
+ overlap.length = newStyle.start + newStyle.length - overlap.start;
+ } else {
+ overlap.length=newStyle.start - overlap.start;
+ // see if newStyle can be merged with the style after overlap, if so,
+ // processing is done
+ if (mergeStyleAfter(newStyle, i+1)) break;
+ // otherwise, insert the newStyle, newStyle may still overlap other
+ // styles after it so continue processing
+ insertStyle(newStyle, i+1);
+ i++;
+ }
+ added = true;
+ } else {
+ // middle of overlap needs to be replaced by newStyle, this will
+ // cause overlap to be broken into two
+ if (newStyle.similarTo(overlap)) break;
+ StyleRange endStyle = (StyleRange)overlap.clone();
+ endStyle.start = newStyleEnd + 1;
+ endStyle.length = overlapEnd - newStyleEnd;
+ overlap.length = newStyle.start - overlap.start;
+ insertStyle(newStyle, i+1);
+ i++;
+ insertStyle(endStyle, i+1);
+ // when newStyle overlaps the middle of a style, this implies that
+ // processing is done (no more overlapped styles)
+ break;
+ }
+ } else {
+ if (overlapEnd <= newStyleEnd) {
+ // overlap will be replaced by the newStyle, make sure newStyle
+ // hasn't already been added, if it has just delete overlap
+ if (!added) {
+ styles[i] = newStyle;
+ added = true;
+ } else {
+ deleteStyle(i);
+ i--;
+ }
+ } else {
+ // beginning of overlap needs to be replaced by newStyle
+ overlap.start=newStyleEnd + 1;
+ overlap.length=overlapEnd - overlap.start + 1;
+ if (!added) {
+ insertMergeStyle(newStyle, i);
+ }
+ // when newStyle overlaps only the beginning of a style, this implies
+ // that processing is done (no more overlapped styles)
+ break;
+ }
+ }
+ count++;
+ }
+}
+/**
+ * Sets the array of styles and discards old styles. Called by StyledText.
+ * <p>
+ *
+ * @param styles the new styles, must be in order and non-overlapping
+ */
+void setStyleRanges(StyleRange[] styles) {
+ this.styles = new StyleRange[styles.length];
+ System.arraycopy(styles, 0, this.styles, 0, styles.length);
+ styleCount = styles.length;
+ styleExpandExp = 1;
+}
+/**
+ * Updates the style ranges and line backgrounds to reflect a text change.
+ * Called by StyledText when a TextChangedEvent is received.
+ * <p>
+ *
+ * @param event the event with the text change information
+ */
+public void textChanged(TextChangedEvent event) {
+ int startLine = content.getLineAtOffset(event.start);
+ int startLineOffset = content.getOffsetAtLine(startLine);
+
+ textChanged(event.start, -event.replacedCharCount);
+ textChanged(event.start, event.newCharCount);
+
+ if (event.newCharCount == content.getCharCount()) {
+ // all text replaced, clear line backgrounds
+ linesChanged(0, -lineCount);
+ linesChanged(0, content.getLineCount());
+ return;
+ }
+
+ if (event.start != startLineOffset) {
+ startLine = startLine + 1;
+ }
+
+ linesChanged(startLine, -event.replacedLineCount);
+ linesChanged(startLine, event.newLineCount);
+}
+/*
+ * Updates the line backgrounds to reflect a text change.
+ * <p>
+ *
+ * @param start the starting line of the change that took place
+ * @param delta the number of lines in the change, > 0 indicates lines inserted,
+ * < 0 indicates lines deleted
+ */
+void linesChanged(int start, int delta) {
+ if (delta == 0) return;
+ boolean inserting = delta > 0;
+ if (inserting) {
+ // shift the lines down to make room for new lines
+ expandLinesBy(delta);
+ for (int i = lineCount-1; i >= start; i--) {
+ lineBackgrounds[i + delta]=lineBackgrounds[i];
+ }
+ for (int i=start; i<start + delta; i++) {
+ lineBackgrounds[i]=null;
+ }
+ } else {
+ // shift up the lines
+ for (int i = start - delta; i < lineCount; i++) {
+ lineBackgrounds[i+delta]=lineBackgrounds[i];
+ }
+ }
+ lineCount += delta;
+}
+/*
+ * Updates the style ranges to reflect a text change.
+ * <p>
+ *
+ * @param start the starting offset of the change that took place
+ * @param delta the length of the change, > 0 indicates text inserted,
+ * < 0 indicates text deleted
+ */
+void textChanged(int start, int delta) {
+ if (delta == 0) return;
+ StyleRange style;
+ // find the index of the first style for the given offset, use a binary search
+ // to find the index
+ int end;
+ boolean inserting = delta > 0;
+ if (inserting) {
+ end = (start + delta) - 1;
+ } else {
+ end = (start - delta) - 1;
+ }
+ int high = searchForStyle(start, end);
+ int index;
+ // update the styles that are in the affected range
+ for (index = high; index < styleCount; index++) {
+ style = styles[index];
+ if (inserting) {
+ if (style.start >= start) break;
+ // in the insert case only one style range will be directly affected,
+ // it will need to be split into two and then the newStyle inserted
+ StyleRange beforeStyle = (StyleRange)style.clone();
+ beforeStyle.length = start - style.start;
+ style.start = start;
+ style.length = style.length - beforeStyle.length;
+ if (beforeStyle.length != 0) insertStyle(beforeStyle, index);
+ index++;
+ break;
+ } else {
+ int styleEnd = style.start + style.length - 1;
+ if (style.start > end) break;
+ // in the delete case, any style that overlaps the change range will be
+ // affected
+ if (style.start < start) {
+ if (styleEnd <= end) {
+ // style starts before change range, ends in change range
+ style.length = start - style.start;
+ } else {
+ // style starts before change range, ends after change range
+ style.length = style.length + delta;
+ index++;
+ break;
+ }
+ } else {
+ if (styleEnd <= end) {
+ // style starts in change range, ends in change range
+ deleteStyle(index);
+ index--;
+ } else {
+ // style starts in change range, ends after change range
+ style.start = start;
+ style.length = styleEnd - end;
+ index++;
+ break;
+ }
+ }
+ }
+ }
+ // change the offsets of the styles after the affected styles
+ for (int i = index ; i < styleCount; i++) {
+ style = styles[i];
+ style.start = style.start + delta;
+ }
+}
+/**
+ * Returns the indexes of the styles that overlap the given range. Styles that partially
+ * or fully overlap the range will be returned.
+ * <p>
+ *
+ * @return Point where x is the index of the starting overlap style, y is the number of
+ * styles that overlap the range
+ */
+Point getOverlappingStyles(int start, int length) {
+ StyleRange style;
+ if (styleCount == 0) return null;
+ // find the index of the first style for the given offset, use a binary search
+ // to find the index
+ int end = start + length - 1;
+ int high = searchForStyle(start, end);
+ int count = 0;
+ for (int index = high; index < styleCount; index++) {
+ style = styles[index];
+ int styleEnd = style.start + style.length - 1;
+ if (style.start > end) break;
+ if (styleEnd >= start) count++;
+ }
+ return new Point(high, count);
+}
+/**
+ * Returns the background color of a line. Called by StyledText. It is safe to return
+ * the existing Color object since the colors are set and managed by the client.
+ * <p>
+ *
+ * @param index the line index
+ * @return the background color of the line at the given index
+ */
+Color getLineBackground(int index) {
+ return lineBackgrounds[index];
+}
+/**
+ * Returns the style for the character at <code>offset</code>. Called by StyledText.
+ * Returns a new style. Does not return the existing style.
+ * <p>
+ *
+ * @param offset the character position in the text
+ * @return a cloned StyleRange with start == offset and length == 1 if a style is
+ * specified or null if no style is specified
+ */
+StyleRange getStyleRangeAtOffset(int offset) {
+ if (styleCount == 0) return null;
+ Point pt = getOverlappingStyles(offset, 1);
+ if (pt == null || pt.y == 0) return null;
+ StyleRange newStyle = (StyleRange)styles[pt.x].clone();
+ newStyle.start = offset;
+ newStyle.length = 1;
+ return newStyle;
+}
+} \ No newline at end of file
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..26fd9c9473
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/ExtendedModifyEvent.java
@@ -0,0 +1,24 @@
+package org.eclipse.swt.custom;
+/*
+ * Licensed Materials - Property of IBM,
+ * (c) Copyright IBM Corp 2000, 2001
+ */
+
+/* Imports */
+import org.eclipse.swt.events.*;
+
+/**
+ * This event is sent after a text change occurs.
+ */
+public final class ExtendedModifyEvent extends TypedEvent {
+ public int start; // start offset of the new text
+ public int length; // length of the new text
+ public String replacedText; // replaced text or empty string if no text was replaced
+
+public ExtendedModifyEvent(StyledTextEvent e) {
+ super(e);
+ start = e.start;
+ length = e.end - e.start;
+ replacedText = e.text;
+}
+} \ No newline at end of file
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..5300f6da2d
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/ExtendedModifyListener.java
@@ -0,0 +1,22 @@
+package org.eclipse.swt.custom;
+/*
+ * Licensed Materials - Property of IBM,
+ * (c) Copyright IBM Corp 2000, 2001
+ */
+
+/* Imports */
+import java.util.EventListener;
+
+public interface ExtendedModifyListener extends EventListener {
+/**
+ * This method is called after a text change occurs.
+ * <p>
+ *
+ * @param event.start the start offset of the new text (input)
+ * @param event.length the length of the new text (input)
+ * @param event.replacedText the replaced text (input)
+ */
+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..fecd8742eb
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/LineBackgroundEvent.java
@@ -0,0 +1,26 @@
+package org.eclipse.swt.custom;
+/*
+ * Licensed Materials - Property of IBM,
+ * (c) Copyright IBM Corp 2000, 2001
+ */
+
+/* Imports */
+import org.eclipse.swt.events.*;
+import org.eclipse.swt.graphics.*;
+
+/**
+ * This event is sent when a line is about to be drawn.
+ */
+public class LineBackgroundEvent extends TypedEvent {
+ public int lineOffset; // line start offset
+ public String lineText; // line text
+ public Color lineBackground; // line background color
+
+public LineBackgroundEvent(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/LineBackgroundListener.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/LineBackgroundListener.java
new file mode 100755
index 0000000000..7ea0deeb24
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/LineBackgroundListener.java
@@ -0,0 +1,24 @@
+package org.eclipse.swt.custom;
+/*
+ * Licensed Materials - Property of IBM,
+ * (c) Copyright IBM Corp 2000, 2001
+ */
+
+
+/* Imports */
+import java.util.*;
+
+
+public interface LineBackgroundListener extends EventListener {
+
+/**
+ * This method is called when a line is about to be drawn in order to get its
+ * background color.
+ * <p>
+ *
+ * @param event.lineOffset line start offset (input)
+ * @param event.lineText line text (input)
+ * @param event.lineBackground line background color (output)
+ */
+public void lineGetBackground(LineBackgroundEvent event);
+} \ No newline at end of file
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..0bac2326a3
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/LineStyleEvent.java
@@ -0,0 +1,25 @@
+package org.eclipse.swt.custom;
+/*
+ * Licensed Materials - Property of IBM,
+ * (c) Copyright IBM Corp 2000, 2001
+ */
+
+/* Imports */
+import org.eclipse.swt.events.*;
+import org.eclipse.swt.graphics.*;
+
+/**
+ * This event is sent when a line is about to be drawn.
+ */
+public class LineStyleEvent extends TypedEvent {
+ public int lineOffset; // line start offset
+ public String lineText; // line text
+ public StyleRange[] styles; // array of StyleRanges
+
+public LineStyleEvent(StyledTextEvent e) {
+ super(e);
+ lineOffset = e.detail;
+ lineText = e.text;
+ styles = e.styles;
+}
+} \ No newline at end of file
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..6ece0586e5
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/LineStyleListener.java
@@ -0,0 +1,21 @@
+package org.eclipse.swt.custom;
+/*
+ * Licensed Materials - Property of IBM,
+ * (c) Copyright IBM Corp 2000, 2001
+ */
+
+/* Imports */
+import java.util.*;
+
+public interface LineStyleListener extends EventListener {
+/**
+ * This method is called when a line is about to be drawn in order to get the
+ * line's style information.
+ * <p>
+ *
+ * @param event.lineOffset line start offset (input)
+ * @param event.lineText line text (input)
+ * @param event.styles array of StyleRanges, need to be in order (output)
+ */
+public void lineGetStyle(LineStyleEvent event);
+} \ No newline at end of file
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..f03c817c1c
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/PopupList.java
@@ -0,0 +1,249 @@
+package org.eclipse.swt.custom;
+
+/*
+ * Licensed Materials - Property of IBM,
+ * WebSphere Studio Workbench
+ * (c) Copyright IBM Corp 2000
+ */
+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 it 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 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.
+*/
+public class PopupList {
+ private Shell shell;
+ private List list;
+ private int minimumWidth;
+/**
+* Creates a PopupList above the specified shell.
+*/
+public PopupList(Shell parent) {
+
+ shell = new Shell(parent, 0);
+
+ 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);
+ }
+ };
+ });
+
+}
+/**
+* Gets the widget font.
+* <p>
+* @return the widget font
+*
+* @exception SWTError <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 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 SWTError <ul>
+* <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
+* <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
+* <li>ERROR_CANNOT_GET_ITEM when the operation fails</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 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);
+ 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 les 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 SWTError <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 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 SWTError <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 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 items the array of items
+*
+* This operation will fail when an item is null
+* or could not be added in the OS.
+*
+* @exception SWTError <ul>
+* <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
+* <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
+* <li>ERROR_NULL_ARGUMENT when items is null</li>
+* <li>ERROR_ITEM_NOT_ADDED when the operation fails</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)
+ throw new SWTError(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..533f6f2e82
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/ST.java
@@ -0,0 +1,57 @@
+package org.eclipse.swt.custom;
+/*
+ * Licensed Materials - Property of IBM,
+ * (c) Copyright IBM Corp 2000, 2001
+ */
+
+/* Imports */
+import org.eclipse.swt.*;
+
+/**
+ * This class provides access to the public constants provided by <code>StyledText</code>.
+ */
+public class ST {
+
+ /* StyledText key action constants */
+ /* Navigation */
+ public static final int LINE_UP = SWT.ARROW_UP;
+ public static final int LINE_DOWN = SWT.ARROW_DOWN;
+ public static final int LINE_START = SWT.HOME;
+ public static final int LINE_END = SWT.END;
+ public static final int COLUMN_PREVIOUS = SWT.ARROW_LEFT;
+ public static final int COLUMN_NEXT = SWT.ARROW_RIGHT;
+ public static final int PAGE_UP = SWT.PAGE_UP;
+ public static final int PAGE_DOWN = SWT.PAGE_DOWN;
+ public static final int WORD_PREVIOUS = SWT.ARROW_LEFT | SWT.CTRL;
+ public static final int WORD_NEXT = SWT.ARROW_RIGHT | SWT.CTRL;
+ public static final int TEXT_START = SWT.HOME | SWT.CTRL;
+ public static final int TEXT_END = SWT.END | SWT.CTRL;
+ public static final int WINDOW_START = SWT.PAGE_UP | SWT.CTRL;
+ public static final int WINDOW_END = SWT.PAGE_DOWN | SWT.CTRL;
+
+ /* Selection */
+ public static final int SELECT_LINE_UP = SWT.ARROW_UP | SWT.SHIFT;
+ public static final int SELECT_LINE_DOWN = SWT.ARROW_DOWN | SWT.SHIFT;
+ public static final int SELECT_LINE_START = SWT.HOME | SWT.SHIFT;
+ public static final int SELECT_LINE_END = SWT.END | SWT.SHIFT;
+ public static final int SELECT_COLUMN_PREVIOUS = SWT.ARROW_LEFT | SWT.SHIFT;
+ public static final int SELECT_COLUMN_NEXT = SWT.ARROW_RIGHT | SWT.SHIFT;
+ public static final int SELECT_PAGE_UP = SWT.PAGE_UP | SWT.SHIFT;
+ public static final int SELECT_PAGE_DOWN = SWT.PAGE_DOWN | SWT.SHIFT;
+ public static final int SELECT_WORD_PREVIOUS = SWT.ARROW_LEFT | SWT.CTRL | SWT.SHIFT;
+ public static final int SELECT_WORD_NEXT = SWT.ARROW_RIGHT | SWT.CTRL | SWT.SHIFT;
+ public static final int SELECT_TEXT_START = SWT.HOME | SWT.CTRL | SWT.SHIFT;
+ public static final int SELECT_TEXT_END = SWT.END | SWT.CTRL | SWT.SHIFT;
+ public static final int SELECT_WINDOW_START = SWT.PAGE_UP | SWT.CTRL| SWT.SHIFT;
+ public static final int SELECT_WINDOW_END = SWT.PAGE_DOWN | SWT.CTRL | SWT.SHIFT;
+
+ /* Modification */
+ public static final int CUT = SWT.DEL | SWT.SHIFT;
+ public static final int COPY = SWT.INSERT | SWT.CTRL;
+ public static final int PASTE = SWT.INSERT | SWT.SHIFT;
+ public static final int DELETE_PREVIOUS = SWT.BS;
+ public static final int DELETE_NEXT = SWT.DEL;
+
+ /* Miscellaneous */
+ public static final int TOGGLE_OVERWRITE = SWT.INSERT;
+}
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..27bc05ae52
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/SashForm.java
@@ -0,0 +1,384 @@
+package org.eclipse.swt.custom;
+
+/*
+ * Licensed Materials - Property of IBM,
+ * (c) Copyright IBM Corp. 1998, 2001 All Rights Reserved
+ */
+
+import org.eclipse.swt.*;
+import org.eclipse.swt.widgets.*;
+import org.eclipse.swt.graphics.*;
+
+/**
+ * The SashForm lays out its children in a Row or Column arrangement (as specified
+ * by the orientation) and places a Sash between the children.
+ * One child may be maximized to occupy the entire size of the SashForm.
+ * The relative sizes of the children may be specfied using weights.
+ *
+ * <p>
+ * <dl>
+ * <dt><b>Styles:</b><dd>HORIZONTAL, VERTICAL
+ * </dl>
+ */
+public class SashForm extends Composite {
+
+ public int SASH_WIDTH = 3;
+
+ private static final int DRAG_MINIMUM = 20;
+
+ private int orientation = SWT.HORIZONTAL;
+ private Sash[] sashes = new Sash[0];
+ private Control[] controls = new Control[0];
+ private Control maxControl = null;
+ private Listener sashListener;
+
+public SashForm(Composite parent, int style) {
+ super(parent, checkStyle(style));
+ if ((style & SWT.VERTICAL) != 0){
+ orientation = SWT.VERTICAL;
+ }
+
+ this.addListener(SWT.Resize, new Listener() {
+ public void handleEvent(Event e) {
+ layout(true);
+ }
+ });
+
+ sashListener = new Listener() {
+ public void handleEvent(Event e) {
+ onDragSash(e);
+ }
+ };
+}
+private static int checkStyle (int style) {
+ int mask = SWT.BORDER;
+ return style & mask;
+}
+public Point computeSize (int wHint, int hHint, boolean changed) {
+
+ Control[] controls = getControls(true);
+ if (controls.length == 0) return new Point(wHint, hHint);
+
+ int width = 0;
+ int height = 0;
+ boolean vertical = (getStyle() & SWT.VERTICAL) != 0;
+ if (vertical) {
+ width = wHint;
+ height += (controls.length - 1) * SASH_WIDTH;
+ } else {
+ height = hHint;
+ width += controls.length *SASH_WIDTH;
+ }
+ for (int i = 0; i < controls.length; i++) {
+ if (vertical) {
+ Point size = controls[i].computeSize(wHint, SWT.DEFAULT);
+ height += size.y;
+ } else {
+ Point size = controls[i].computeSize(SWT.DEFAULT, hHint);
+ width += size.x;
+ }
+ }
+ if (wHint != SWT.DEFAULT) width = wHint;
+ if (hHint != SWT.DEFAULT) height = hHint;
+
+ return new Point(width, height);
+}
+/**
+ * Answer SWT.HORIZONTAL if the controls in the SashForm are laid out side by side.
+ * Answer SWT.VERTICAL if the controls in the SashForm are laid out top to bottom.
+ */
+public int getOrientation() {
+ return orientation;
+}
+/**
+ * Answer the control that currently is maximized in the SashForm. This value may be null.
+ */
+public Control getMaximizedControl(){
+ 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).
+ */
+
+public int[] getWeights() {
+ Control[] cArray = getControls(false);
+ float[] ratios = new float[cArray.length];
+ for (int i = 0; i < cArray.length; i++) {
+ Float ratio = (Float)cArray[i].getData("layout ratio");
+ if (ratio != null) {
+ ratios[i] = ratio.floatValue();
+ } else {
+ ratios[i] = (float)0.2;
+ }
+ }
+
+ int[] weights = new int[cArray.length];
+ for (int i = 0; i < weights.length; i++) {
+ weights[i] = (int)(ratios[i] * 1000);
+ }
+ return weights;
+}
+private Control[] getControls(boolean onlyVisible) {
+ Control[] children = getChildren();
+ Control[] controls = new Control[0];
+ for (int i = 0; i < children.length; i++) {
+ if (children[i] instanceof Sash) continue;
+ if (onlyVisible && !children[i].getVisible()) continue;
+
+ Control[] newControls = new Control[controls.length + 1];
+ System.arraycopy(controls, 0, newControls, 0, controls.length);
+ newControls[controls.length] = children[i];
+ controls = newControls;
+ }
+ return controls;
+}
+public void layout(boolean changed) {
+ Rectangle area = getClientArea();
+ if (area.width == 0 || area.height == 0) return;
+
+ Control[] newControls = getControls(true);
+ if (controls.length == 0 && newControls.length == 0) return;
+ controls = newControls;
+
+ if (maxControl != null && !maxControl.isDisposed()) {
+ for (int i= 0; i < controls.length; i++){
+ if (controls[i] != maxControl) {
+ controls[i].setBounds(-200, -200, 0, 0);
+ } else {
+ controls[i].setBounds(area);
+ }
+ }
+ return;
+ }
+
+ // keep just the right number of sashes
+ if (sashes.length < controls.length - 1) {
+ Sash[] newSashes = new Sash[controls.length - 1];
+ System.arraycopy(sashes, 0, newSashes, 0, sashes.length);
+ int sashOrientation = (orientation == SWT.HORIZONTAL) ? SWT.VERTICAL : SWT.HORIZONTAL;
+ for (int i = sashes.length; i < newSashes.length; i++) {
+ newSashes[i] = new Sash(this, sashOrientation);
+ newSashes[i].addListener(SWT.Selection, sashListener);
+ }
+ sashes = newSashes;
+ }
+ if (sashes.length > controls.length - 1) {
+ if (controls.length == 0) {
+ for (int i = 0; i < sashes.length; i++) {
+ sashes[i].dispose();
+ }
+ sashes = new Sash[0];
+ } else {
+ Sash[] newSashes = new Sash[controls.length - 1];
+ System.arraycopy(sashes, 0, newSashes, 0, newSashes.length);
+ for (int i = controls.length - 1; i < sashes.length; i++) {
+ sashes[i].dispose();
+ }
+ sashes = newSashes;
+ }
+ }
+
+ if (controls.length == 0) return;
+
+ // get the ratios
+ float[] ratios = new float[controls.length];
+ float total = 0;
+ for (int i = 0; i < controls.length; i++) {
+ Float ratio = (Float)controls[i].getData("layout ratio");
+ if (ratio != null) {
+ ratios[i] = ratio.floatValue();
+ } else {
+ ratios[i] = (float)0.2;
+ }
+ total += ratios[i];
+ }
+
+ if (orientation == SWT.HORIZONTAL) {
+ total += (float)sashes.length * ((float)SASH_WIDTH / (float)area.width);
+ } else {
+ total += (float)sashes.length * ((float)SASH_WIDTH / (float)area.height);
+ }
+
+ if (orientation == SWT.HORIZONTAL) {
+ int width = (int)((ratios[0] / total) * (float)area.width);
+ 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, SASH_WIDTH, area.height);
+ x += SASH_WIDTH;
+ width = (int)((ratios[i] / total) * (float)area.width);
+ controls[i].setBounds(x, area.y, width, area.height);
+ x += width;
+ }
+ if (controls.length > 1) {
+ sashes[sashes.length - 1].setBounds(x, area.y, SASH_WIDTH, area.height);
+ x += SASH_WIDTH;
+ width = area.width - x;
+ controls[controls.length - 1].setBounds(x, area.y, width, area.height);
+ }
+ } else {
+ int height = (int)((ratios[0] / total) * (float)area.height);
+ 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, SASH_WIDTH);
+ y += SASH_WIDTH;
+ height = (int)((ratios[i] / total) * (float)area.height);
+ 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, SASH_WIDTH);
+ y += SASH_WIDTH;
+ height = area.height - y;
+ controls[controls.length - 1].setBounds(area.x, y, area.width, height);
+ }
+
+ }
+}
+private void onDragSash(Event event) {
+ if (event.detail == SWT.DRAG) {
+ // constrain feedback
+ Rectangle area = getClientArea();
+ if (orientation == SWT.HORIZONTAL) {
+ event.x = Math.min(Math.max(DRAG_MINIMUM, event.x), area.width - DRAG_MINIMUM);
+ } else {
+ event.y = Math.min(Math.max(DRAG_MINIMUM, event.y), area.height - DRAG_MINIMUM);
+ }
+ return;
+ }
+
+ 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();
+ if (orientation == SWT.HORIZONTAL) {
+ int shift = event.x - sashBounds.x;
+ b1.width += shift;
+ b2.x += shift;
+ b2.width -= shift;
+ if (b1.width < DRAG_MINIMUM || b2.width < DRAG_MINIMUM) {
+ return;
+ }
+ c1.setData("layout ratio", new Float((float)b1.width / (float)area.width));
+ c2.setData("layout ratio", new Float((float)b2.width / (float)area.width));
+ } else {
+ int shift = event.y - sashBounds.y;
+ b1.height += shift;
+ b2.y += shift;
+ b2.height -= shift;
+ if (b1.height < DRAG_MINIMUM || b2.height < DRAG_MINIMUM) {
+ return;
+ }
+ c1.setData("layout ratio", new Float((float)b1.height / (float)area.height));
+ c2.setData("layout ratio", new Float((float)b2.height / (float)area.height));
+ }
+
+ 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.
+ */
+public void setOrientation(int orientation) {
+ if (this.orientation == orientation) return;
+ if (orientation != SWT.HORIZONTAL && orientation != SWT.VERTICAL) {
+ SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ }
+ this.orientation = orientation;
+
+ int sashOrientation = (orientation == SWT.HORIZONTAL) ? SWT.VERTICAL : SWT.HORIZONTAL;
+ for (int i = 0; i < sashes.length; i++) {
+ sashes[i].dispose();
+ sashes[i] = new Sash(this, sashOrientation);
+ sashes[i].addListener(SWT.Selection, sashListener);
+ }
+ layout();
+}
+public void setLayout (Layout layout) {
+ // SashForm does not use Layout
+}
+/**
+ * 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.
+ */
+public void setMaximizedControl(Control control){
+ if (control == null) {
+ if (maxControl != null) {
+ this.maxControl = null;
+ layout();
+ 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();
+
+// // walk up
+// w= getParent();
+// if (w instanceof SplitForm)
+// ((SplitForm) w).internalMaximize(this);
+// else
+// layout(true);
+}
+
+/**
+ * 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.
+ */
+public void setWeights(int[] weights) {
+ 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++) {
+ cArray[i].setData("layout ratio", new Float((float)weights[i] / (float)total));
+ }
+
+ layout();
+}
+} \ No newline at end of file
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..cd9ac851a2
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/ScrolledComposite.java
@@ -0,0 +1,202 @@
+package org.eclipse.swt.custom;
+
+/*
+ * Licensed Materials - Property of IBM,
+ * (c) Copyright IBM Corp. 1998, 2001 All Rights Reserved
+ */
+
+import org.eclipse.swt.*;
+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>If the ScrolledComposite can be configured to stretch the content to be as big as the
+ * ScrolledComposite when the composite is resized to a size larger than the minimum width
+ * or minimum height and to provide scrolls when the ScrolledComposite is smaller than the
+ * minimum width and minimum height. Refer to the methods setExpandHorizontal,
+ * setExpandVertical, setMinWidth and setMinHeight.
+ *
+ * <p>
+ * <dl>
+ * <dt><b>Styles:</b><dd>H_SCROLL, V_SCROLL
+ * </dl>
+ */
+public class ScrolledComposite extends Composite {
+
+ private Control content;
+ private Listener contentListener;
+
+ private int minHeight = 0;
+ private int minWidth = 0;
+ private boolean expandHorizontal = false;
+ private boolean expandVertical = false;
+
+public ScrolledComposite(Composite parent, int style) {
+ super(parent, checkStyle(style));
+
+ ScrollBar hBar = getHorizontalBar ();
+ if (hBar != null) {
+ hBar.addListener (SWT.Selection, new Listener () {
+ public void handleEvent (Event e) {
+ hScroll();
+ }
+ });
+ }
+
+ ScrollBar vBar = getVerticalBar ();
+ if (vBar != null) {
+ vBar.addListener (SWT.Selection, new Listener () {
+ public void handleEvent (Event e) {
+ vScroll();
+ }
+ });
+ }
+
+ addListener (SWT.Resize, new Listener () {
+ public void handleEvent (Event e) {
+ resize();
+ }
+ });
+
+ contentListener = new Listener() {
+ public void handleEvent(Event e) {
+ if (e.type != SWT.Resize) return;
+ resize();
+ }
+ };
+}
+
+private static int checkStyle (int style) {
+ int mask = SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER;
+ return style & mask;
+}
+/**
+ * Get the content that is being scrolled.
+ */
+public Control getContent() {
+ return content;
+}
+private void hScroll() {
+ if (content == null) return;
+ Point location = content.getLocation ();
+ ScrollBar hBar = getHorizontalBar ();
+ int hSelection = hBar.getSelection ();
+ content.setLocation (-hSelection, location.y);
+}
+private void resize() {
+ if (content == null) return;
+ Rectangle contentRect = content.getBounds();
+ Rectangle hostRect = getClientArea();
+ if (expandHorizontal) {
+ contentRect.width = Math.max(minWidth, hostRect.width);
+ }
+ if (expandVertical) {
+ contentRect.height = Math.max(minHeight, hostRect.height);
+ }
+
+ ScrollBar hBar = getHorizontalBar ();
+ 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;
+ contentRect.x = -hSelection;
+ }
+ }
+
+ ScrollBar vBar = getVerticalBar ();
+ 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;
+ contentRect.y = -vSelection;
+ }
+ }
+
+ content.setBounds (contentRect);
+}
+/**
+ * Set the content that will be scrolled.
+ */
+public void setContent(Control content) {
+ if (this.content != null && !this.content.isDisposed()) {
+ this.content.removeListener(SWT.Resize, contentListener);
+ content.setBounds(new Rectangle(-200, -200, 0, 0));
+ }
+
+ this.content = content;
+ if (this.content != null) {
+ this.content.addListener(SWT.Resize, contentListener);
+ resize();
+ }
+}
+/**
+ * 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 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.
+ */
+public void setExpandHorizontal(boolean expand) {
+ if (expand == expandHorizontal) return;
+ expandHorizontal = expand;
+ resize();
+}
+/**
+ * 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 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.
+ */
+public void setExpandVertical(boolean expand) {
+ if (expand == expandVertical) return;
+ expandVertical = expand;
+ resize();
+}
+public void setLayout (Layout layout) {
+ // do not allow a layout to be set on this class because layout is being handled by the resize listener
+ 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.
+ */
+public void setMinHeight(int height) {
+ if (height == minHeight) return;
+ minHeight = Math.max(0, height);
+ resize();
+}
+/**
+ * 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.
+ */
+
+public void setMinWidth(int width) {
+ if (width == minWidth) return;
+ minWidth = Math.max(0, width);
+ resize();
+}
+
+private void vScroll() {
+ Control[] children = getChildren();
+ if (children.length == 0) return;
+ Control content = children[0];
+ Point location = content.getLocation ();
+ ScrollBar vBar = getVerticalBar ();
+ int vSelection = vBar.getSelection ();
+ content.setLocation (location.x, -vSelection);
+}
+} \ No newline at end of file
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..a193ad5a24
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/StackLayout.java
@@ -0,0 +1,74 @@
+package org.eclipse.swt.custom;
+
+/*
+ * Licensed Materials - Property of IBM,
+ * (c) Copyright IBM Corp. 1998, 2001 All Rights Reserved
+ */
+
+import org.eclipse.swt.*;
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.widgets.*;
+import org.eclipse.swt.layout.*;
+
+/**
+ * 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 the call
+ * layout() on the composite which has the StackLayout.
+ */
+
+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 = wHint, height = hHint;
+ if (wHint == SWT.DEFAULT) width = maxWidth;
+ if (hHint == SWT.DEFAULT) height = maxHeight;
+ return new Point(width + 2 * marginWidth, height + 2 * marginHeight);
+}
+
+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);
+
+ }
+}
+}
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..12623a5b22
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/StyleRange.java
@@ -0,0 +1,147 @@
+package org.eclipse.swt.custom;
+/*
+ * Licensed Materials - Property of IBM,
+ * (c) Copyright IBM Corp 2000, 2001
+ */
+
+/* Imports */
+import org.eclipse.swt.*;
+import org.eclipse.swt.graphics.*;
+
+public class StyleRange implements Cloneable {
+ public int start; // style start offset. 0 based from the document start
+ public int length; // style length.
+ public Color foreground;
+ public Color background;
+ public int fontStyle = SWT.NORMAL; // may be SWT.NORMAL or SWT.BOLD
+
+public StyleRange() {
+}
+/**
+ * Create a new style range.
+ * <p>
+ *
+ * @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) {
+ this.start = start;
+ this.length = length;
+ this.foreground = foreground;
+ this.background = background;
+}
+
+/**
+ * Create a new style range.
+ * <p>
+ *
+ * @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 or SWT.BOLD
+ */
+public StyleRange(int start, int length, Color foreground, Color background, int fontStyle) {
+ this.start = start;
+ this.length = length;
+ this.foreground = foreground;
+ this.background = background;
+ this.fontStyle = fontStyle;
+}
+
+/**
+ * Compare the specified object to this StyleRange and answer if the two
+ * are equal. The object must be an instance of StyleRange and have the
+ * same field values.
+ * <p>
+ *
+ * @param object the object to compare with this object
+ * @return true if the objects are equal, false otherwise
+ */
+public boolean equals(Object object) {
+ StyleRange style;
+ if (object == this) return true;
+ if (object instanceof StyleRange) style = (StyleRange)object;
+ else return false;
+ if (this.start != style.start) return false;
+ if (this.length != style.length) return false;
+ if (this.foreground != null) {
+ if (!this.foreground.equals(style.foreground)) return false;
+ } else if (style.foreground != null) return false;
+ if (this.background != null) {
+ if (!this.background.equals(style.background)) return false;
+ } else if (style.background != null) return false;
+ if (this.fontStyle != style.fontStyle) return false;
+ return true;
+}
+/**
+ * Returns an integer hash code for the receiver. Objects which are
+ * equal answer the same value for this method.
+ * <p>
+ *
+ * @return the receiver's hash
+ */
+public int hashCode() {
+ return start + length + foreground.hashCode() + background.hashCode() + fontStyle;
+}
+/**
+ * Returns whether or not the receiver is unstyled (i.e., does not have any
+ * style attributes specified).
+ * <p>
+ *
+ * @return true if the receiver is unstyled, false otherwise.
+ */
+public boolean isUnstyled() {
+ if (this.foreground != null) return false;
+ if (this.background != null) return false;
+ if (this.fontStyle != SWT.NORMAL) 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.
+ * <p>
+ *
+ * @param object the object to compare with this object
+ * @return true if the objects are similar, false otherwise
+ */
+public boolean similarTo(StyleRange style) {
+ if (this.foreground != null) {
+ if (!this.foreground.equals(style.foreground)) return false;
+ } else if (style.foreground != null) return false;
+ if (this.background != null) {
+ if (!this.background.equals(style.background)) return false;
+ } else if (style.background != null) return false;
+ if (this.fontStyle != style.fontStyle) return false;
+ return true;
+}
+/**
+ * Answers a new StyleRange with the same values as this StyleRange.
+ * <p>
+ *
+ * @return a shallow copy of this StyleRange
+ */
+public Object clone() {
+ StyleRange style = new StyleRange(start, length, foreground, background, fontStyle);
+ return style;
+}
+/**
+ * Answers a string description of the receiver.
+ * <p>
+ *
+ * @return a printable representation for the receiver.
+ */
+public String toString() {
+ StringBuffer buf = new StringBuffer();
+ buf.append(start + "," + length + " fg:" + foreground + " bg:" + background + " fStyle:");
+ if (fontStyle == SWT.NORMAL) {
+ buf.append("normal");
+ } else if (fontStyle == SWT.BOLD) {
+ buf.append("bold");
+ }
+ return buf.toString();
+}
+} \ No newline at end of file
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..b6f2c2ecda
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/StyledText.java
@@ -0,0 +1,5426 @@
+package org.eclipse.swt.custom;
+/*
+ * Licensed Materials - Property of IBM,
+ * (c) Copyright IBM Corp 2000, 2001
+ */
+
+/* Imports */
+import org.eclipse.swt.*;
+import org.eclipse.swt.dnd.*;
+import org.eclipse.swt.events.*;
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.widgets.*;
+import java.util.*;
+
+/**
+ * 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, regular)
+ * </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>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>
+ * IMPORTANT: This class is <em>not</em> intended to be subclassed.
+ * </p>
+ * <dl>
+ * <dt><b>Styles:</b><dd>FULL_SELECTION, MULTI, SINGLE, READ_ONLY
+ * <dt><b>Events:</b><dd>ExtendedModify, LineGetBackground, LineGetStyle, Modify, Selection, Verify, VerifyKey
+ * </dl>
+ */
+public class StyledText extends Canvas {
+ static final int CARET_WIDTH = 1; // width of the caret. Used during content width calculation since
+ // Caret.getSize() does not return its width until it is made visible.
+ static final char TAB = '\t';
+
+ private final static String PlatformLineDelimiter = System.getProperty("line.separator");
+
+ StyledTextContent content;
+ TextChangedListener textChangedListener; // listener for TextReplaced and TextSet events from StyledTextContent
+ DefaultLineStyler defaultLineStyler;// used for setStyles API when no LineStyleListener is registered
+ boolean userLineStyle = false; // true=widget is using a user defined line style listener for line styles. false=widget is using the default line styler to store line styles
+ boolean userLineBackground = false;// true=widget is using a user defined line background listener for line backgrounds. false=widget is using the default line styler to store line backgrounds
+ int verticalScrollOffset = 0; // pixel based
+ int horizontalScrollOffset = 0; // pixel based
+ int topIndex = 0; // top visible line
+ int clientAreaHeight = 0; // the client area height. Needed to calculate content width for new
+ // visible lines during Resize callback
+ int contentWidth = 0; // width of widest known (already visible) line
+ int contentWidthIndex = 0; // line up to which the content width has been calculated
+ int lineHeight; // line height=font height
+ int tabLength = 4; // number of characters in a tab
+ int tabWidth; // width of a tab character in the current GC
+ Caret caret; // caret SWT widget
+ Cursor ibeamCursor;
+ int caretOffset = 0;
+ Point selection = new Point(0, 0); // x is character offset, y is length
+ int selectionAnchor; // position of selection anchor. 0 based offset from beginning of text
+ boolean editable = true;
+ 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.
+ boolean selectionVisible = true;
+ Hashtable keyActionMap = new Hashtable();
+ Font boldFont;
+ Font regularFont;
+ Clipboard clipboard;
+
+ boolean mouseDoubleClick = false; // true=a double click ocurred. Don't do mouse swipe selection.
+ int autoScrollDirection = SWT.NULL; // the direction of autoscrolling (up, down, right, left)
+
+ static final int DEFAULT_WIDTH = 64;
+ static final int DEFAULT_HEIGHT = 64;
+
+ static final int ExtendedModify = 3000;
+ static final int LineGetStyle = 3002;
+ static final int LineGetBackground = 3001;
+ static final int TextReplaced = 3003;
+ static final int TextSet = 3004;
+ static final int VerifyKey = 3005;
+ /**
+ * 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 {
+ final int DEFAULT_FOREGROUND = 0;
+ final int DEFAULT_BACKGROUND = 1;
+ Vector colorTable = new Vector();
+
+ /**
+ * 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.
+ * <p>
+ *
+ * @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.addElement(getForeground());
+ colorTable.addElement(getBackground());
+ }
+ /**
+ * 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() == false) {
+ writeHeader();
+ write("\n}}\0");
+ super.close();
+ }
+ }
+ /**
+ * Returns the index of the specified color in the RTF color table.
+ * <p>
+ *
+ * @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) {
+ int index;
+
+ if (color == null) {
+ index = defaultIndex;
+ }
+ else {
+ index = colorTable.indexOf(color);
+ if (index == -1) {
+ index = colorTable.size();
+ colorTable.addElement(color);
+ }
+ }
+ return index;
+ }
+ /**
+ * 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\\deff0{\\fonttbl{\\f0\\fnil ");
+ header.append(fontData.getName());
+ 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.
+ * <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.
+ * @exception SWTException <ul>
+ * <li>ERROR_IO when the writer is closed. @see close()</li>
+ * </ul>
+ */
+ public void writeLine(String line, int lineOffset) {
+ StyleRange[] styles = new StyleRange[0];
+ Color lineBackground = null;
+ StyledTextEvent event;
+
+ if (isClosed()) {
+ SWT.error(SWT.ERROR_IO);
+ }
+ event = getLineStyleData(lineOffset, line);
+ if (event != null) {
+ styles = event.styles;
+ }
+ event = getLineBackgroundData(lineOffset, line);
+ if (event != null) {
+ lineBackground = event.lineBackground;
+ }
+ if (lineBackground == null) {
+ lineBackground = getBackground();
+ }
+ writeStyledLine(line, lineOffset, styles, lineBackground);
+ }
+ /**
+ * Appends the specified line delmimiter to the RTF data.
+ * <p>
+ *
+ * @param lineDelimiter line delimiter to write as RTF.
+ * @exception SWTException <ul>
+ * <li>ERROR_IO when the writer is closed. @see close()</li>
+ * </ul>
+ */
+ public void writeLineDelimiter(String lineDelimiter) {
+ if (isClosed()) {
+ SWT.error(SWT.ERROR_IO);
+ }
+ write(lineDelimiter, 0, lineDelimiter.length());
+ write("\\par ");
+ }
+ /**
+ * Appends the specified segment of "string" to the RTF data.
+ * Copy from <code>start</code> up to, but excluding, <code>end</code>.
+ * <p>
+ *
+ * @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) {
+ int index;
+
+ for (index = start; index < end; index++) {
+ char c = string.charAt(index);
+ if (c == '}' || c == '{' || c == '\\') {
+ break;
+ }
+ }
+ if (index == end) {
+ write(string.substring(start, end)); // string doesn't contain RTF formatting characters, write as is
+ }
+ else { // string needs to be transformed
+ char[] text = new char[end - start];
+
+ string.getChars(start, end, text, 0);
+ for (index = 0; index < text.length; index++) {
+ switch (text[index]) {
+ case '}':
+ case '{':
+ case '\\':
+ write("\\");
+ default:
+ write(text[index]);
+ }
+ }
+ }
+ }
+ /**
+ * Appends the specified line text to the RTF data.
+ * 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, StyleRange[] styles, Color lineBackground) {
+ int lineLength = line.length();
+ int lineIndex;
+ int copyEnd;
+ int startOffset = getStart();
+ int endOffset = startOffset + super.getCharCount();
+ int writeOffset = startOffset - lineOffset;
+
+ if (writeOffset >= line.length()) {
+ return; // whole line is outside write range
+ }
+ else
+ if (writeOffset > 0) {
+ lineIndex = writeOffset; // line starts before RTF write start
+ }
+ else {
+ lineIndex = 0;
+ }
+ if (lineBackground != null) {
+ write("{\\highlight");
+ write(getColorIndex(lineBackground, DEFAULT_BACKGROUND));
+ write(" ");
+ }
+ for (int i = 0; i < styles.length; i++) {
+ StyleRange style = styles[i];
+ int start = style.start - lineOffset;
+ int end = start + style.length;
+ int colorIndex;
+ // skip over partial first line
+ if (end < writeOffset) {
+ continue;
+ }
+ // break on partial last line
+ if (style.start > endOffset) {
+ break;
+ }
+ // write any unstyled text
+ if (lineIndex < start) {
+ // copy to start of style or end of write range (specified
+ // during object creation) or end of line
+ copyEnd = Math.min(start, endOffset - lineOffset);
+ copyEnd = Math.min(copyEnd, lineLength);
+ write(line, lineIndex, copyEnd);
+ lineIndex = copyEnd;
+ if (copyEnd != start) {
+ break;
+ }
+ }
+ // write styled text
+ colorIndex = getColorIndex(style.background, DEFAULT_BACKGROUND);
+ write("{\\cf");
+ write(getColorIndex(style.foreground, DEFAULT_FOREGROUND));
+ if (colorIndex != DEFAULT_BACKGROUND) {
+ write("\\highlight");
+ write(colorIndex);
+ }
+ if (style.fontStyle == SWT.BOLD) {
+ write("\\b");
+ }
+ write(" ");
+ // copy to end of style or end of write range (specified
+ // during object creation) or end of line
+ copyEnd = Math.min(end, endOffset - lineOffset);
+ copyEnd = Math.min(copyEnd, lineLength);
+ write(line, lineIndex, copyEnd);
+ if (style.fontStyle == SWT.BOLD) {
+ write("\\b0");
+ }
+ write("}");
+ lineIndex = copyEnd;
+ if (copyEnd != end) {
+ break;
+ }
+ }
+ copyEnd = Math.min(lineLength, endOffset - lineOffset);
+ if (lineIndex < copyEnd) {
+ write(line, lineIndex, copyEnd);
+ }
+ 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.
+ */
+ 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.
+ * <p>
+ *
+ * @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 == false) {
+ isClosed = true;
+ }
+ }
+ /**
+ * Returns the 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.
+ */
+ public int getStart() {
+ return startOffset;
+ }
+ /**
+ * Returns whether 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.
+ * <p>
+ *
+ * @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.
+ * 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.
+ * <p>
+ *
+ * @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. @see close()</li>
+ * </ul>
+ */
+ public void writeLine(String line, int lineOffset) {
+ int lineLength = line.length();
+ int lineIndex;
+ int copyEnd;
+ int writeOffset = startOffset - lineOffset;
+
+ if (isClosed) {
+ SWT.error(SWT.ERROR_IO);
+ }
+ if (writeOffset >= lineLength) {
+ return; // whole line is outside write range
+ }
+ else
+ if (writeOffset > 0) {
+ lineIndex = writeOffset; // line starts before write start
+ }
+ else {
+ lineIndex = 0;
+ }
+ copyEnd = Math.min(lineLength, endOffset - lineOffset);
+ if (lineIndex < copyEnd) {
+ write(line.substring(lineIndex, copyEnd));
+ }
+ }
+ /**
+ * Appends the specified line delmimiter to the data.
+ * <p>
+ *
+ * @param lineDelimiter line delimiter to write
+ * @exception SWTException <ul>
+ * <li>ERROR_IO when the writer is closed. @see close()</li>
+ * </ul>
+ */
+ public void writeLineDelimiter(String lineDelimiter) {
+ if (isClosed) {
+ SWT.error(SWT.ERROR_IO);
+ }
+ write(lineDelimiter);
+ }
+ }
+
+public StyledText(Composite parent, int style) {
+ // use NO_BACKGROUND style when implemented by SWT.
+ // always need to draw background in drawLine when using NO_BACKGROUND!
+ super(parent, checkStyle(style | SWT.NO_REDRAW_RESIZE | SWT.NO_BACKGROUND));
+ Display display = getDisplay();
+
+ if ((style & SWT.READ_ONLY) != 0) {
+ setEditable(false);
+ }
+ clipboard = new Clipboard(display);
+ calculateLineHeight();
+ calculateTabWidth();
+ caret = new Caret(this, SWT.NULL);
+ caret.setBounds(0, 0, CARET_WIDTH, lineHeight);
+ installDefaultContent();
+ calculateScrollBars();
+ createKeyBindings();
+ ibeamCursor = new Cursor(display, SWT.CURSOR_IBEAM);
+ setCursor(ibeamCursor);
+
+ setForeground(display.getSystemColor(SWT.COLOR_LIST_FOREGROUND));
+ setBackground(display.getSystemColor(SWT.COLOR_LIST_BACKGROUND));
+ initializeFonts();
+ installListeners();
+ installDefaultLineStyler();
+}
+
+/**
+ * Adds an extended modify listener. An ExtendedModify event is sent by the
+ * widget when the widget text has changed.
+ * <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>
+ */
+public void addExtendedModifyListener(ExtendedModifyListener extendedModifyListener) {
+ checkWidget();
+ if (extendedModifyListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ StyledTextListener typedListener = new StyledTextListener(extendedModifyListener);
+ addListener(ExtendedModify, typedListener);
+}
+
+/**
+ * Maps a key to an action.
+ * 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 (one or more of SWT.CTRL, SWT.SHIFT, SWT.ALT)
+ * @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>
+ * @see org.eclipse.swt.SWT
+ * @see org.eclipse.swt.ST
+ */
+public void setKeyBinding(int key, int action) {
+ checkWidget();
+ if (action == SWT.NULL) {
+ keyActionMap.remove(new Integer(key));
+ }
+ else {
+ keyActionMap.put(new Integer(key), new Integer(action));
+ }
+}
+/**
+ * Adds a line background listener. A LineGetBackground event is sent by the
+ * widget to determine the background color for a line.
+ * <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>
+ */
+public void addLineBackgroundListener(LineBackgroundListener listener) {
+ checkWidget();
+ if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ if (userLineBackground == false) {
+ removeLineBackgroundListener(defaultLineStyler);
+ defaultLineStyler.setLineBackground(0, content.getLineCount(), null);
+ userLineBackground = true;
+ }
+ StyledTextListener typedListener = new StyledTextListener(listener);
+ addListener(LineGetBackground, typedListener);
+}
+/**
+ * Adds a line style listener. A LineGetStyle event is sent by the widget to
+ * determine the styles for a line.
+ * <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>
+ */
+public void addLineStyleListener(LineStyleListener listener) {
+ checkWidget();
+ if (listener == null) {
+ SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ }
+ if (userLineStyle == false) {
+ removeLineStyleListener(defaultLineStyler);
+ defaultLineStyler.setStyleRange(null);
+ userLineStyle = true;
+ }
+ StyledTextListener typedListener = new StyledTextListener(listener);
+ addListener(LineGetStyle, typedListener);
+}
+/**
+ * Adds a modify listener. A Modify event is sent by the widget when the widget text
+ * has changed.
+ * <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>
+ */
+public void addModifyListener(ModifyListener modifyListener) {
+ checkWidget();
+ if (modifyListener == null) {
+ SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ }
+ TypedListener typedListener = new TypedListener(modifyListener);
+ addListener(SWT.Modify, typedListener);
+}
+
+/**
+ * Adds a selection listener. A Selection event is sent by the widget when the
+ * selection has changed.
+ * <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>
+ */
+public void addSelectionListener(SelectionListener listener) {
+ checkWidget();
+ if (listener == null) {
+ SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ }
+ TypedListener typedListener = new TypedListener(listener);
+ addListener(SWT.Selection, typedListener);
+}
+
+/**
+ * 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.
+ * <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>
+ */
+public void addVerifyKeyListener(VerifyKeyListener listener) {
+ checkWidget();
+ if (listener == null) {
+ SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ }
+ StyledTextListener typedListener = new StyledTextListener(listener);
+ addListener(VerifyKey, typedListener);
+}
+
+/**
+ * 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.
+ * <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>
+ */
+public void addVerifyListener(VerifyListener verifyListener) {
+ checkWidget();
+ if (verifyListener == null) {
+ SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ }
+ TypedListener typedListener = new TypedListener(verifyListener);
+ addListener(SWT.Verify, typedListener);
+}
+/**
+ * Appends a string to the text at the end of the widget.
+ * <p>
+ *
+ * @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 width of the widest visible line.
+ */
+void calculateContentWidth() {
+ if (lineHeight != 0) {
+ int itemCount = (int) Math.ceil((float) getClientArea().height / lineHeight);
+ calculateContentWidth(topIndex, Math.min(itemCount, content.getLineCount() - topIndex));
+ }
+}
+
+/**
+ * Calculates the width of the widget text in the specified line range.
+ * <p>
+ *
+ * @param startline the first line
+ * @param lineCount number of lines to consider for the calculation
+ * @param skipCalculated true=don't recalculated lines that have already been calculated
+ */
+void calculateContentWidth(int startLine, int lineCount) {
+ String line;
+ GC gc = new GC(this);
+ FontData fontData = gc.getFont().getFontData()[0];
+ int stopLine;
+
+ if (lineCount < 0) {
+ startLine += lineCount;
+ lineCount *= -1;
+ }
+ stopLine = startLine + lineCount;
+ setLineFont(gc, fontData, SWT.BOLD);
+ for (int i = startLine; i < stopLine; i++) {
+ line = content.getLine(i);
+ contentWidth = Math.max(contentWidth(line, i, gc) + CARET_WIDTH, contentWidth);
+ }
+ gc.dispose();
+}
+
+/**
+ * Calculates the line height
+ */
+void calculateLineHeight() {
+ GC gc = new GC(this);
+ lineHeight = gc.getFontMetrics().getHeight();
+ gc.dispose();
+}
+
+/**
+ * Calculates the width in pixel of a tab character
+ */
+void calculateTabWidth() {
+ StringBuffer tabBuffer = new StringBuffer(tabLength);
+ GC gc = new GC(this);
+
+ for (int i = 0; i < tabLength; i++) {
+ tabBuffer.append(' ');
+ }
+ tabWidth = gc.stringExtent(tabBuffer.toString()).x;
+ gc.dispose();
+}
+
+/**
+ * Calculates the scroll bars
+ */
+void calculateScrollBars() {
+ ScrollBar horizontalBar = getHorizontalBar();
+ ScrollBar verticalBar = getVerticalBar();
+
+ setScrollBars();
+ if (verticalBar != null) {
+ verticalBar.setIncrement(getVerticalIncrement());
+ }
+ if (horizontalBar != null) {
+ horizontalBar.setIncrement(getHorizontalIncrement());
+ }
+}
+
+/**
+ * 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);
+ }
+ return style;
+}
+
+/**
+ * Scrolls down the text to use new space made available by a resize or by
+ * deleted lines.
+ */
+void claimBottomFreeSpace() {
+ int clientAreaItemCount = getLineCountWhole();
+ int topIndex = getTopIndex();
+ int newTopIndex;
+ int lastItemIndex = content.getLineCount() - topIndex;
+
+ if (topIndex > 0 &&
+ lastItemIndex > 0 &&
+ lastItemIndex < clientAreaItemCount) {
+ newTopIndex = Math.max(0, topIndex - (clientAreaItemCount - lastItemIndex));
+ setTopIndex(newTopIndex);
+ }
+}
+
+/**
+ * Scrolls text to the right to use new space made available by a resize.
+ */
+void claimRightFreeSpace() {
+ int newHorizontalOffset = Math.max(0, contentWidth - getClientArea().width);
+
+ 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).
+ scrollHorizontalBar(newHorizontalOffset - horizontalScrollOffset);
+ }
+}
+
+/**
+ * Removes the widget selection.
+ * <p>
+ *
+ * @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;
+ int length = content.getCharCount();
+
+ resetSelection();
+ // redraw old selection, if any
+ if (selectionEnd - selectionStart > 0) {
+ // 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) {
+ redrawRange(redrawStart, redrawEnd - redrawStart, true);
+ }
+ if (sendEvent == true) {
+ sendSelectionEvent();
+ }
+ }
+}
+
+/**
+ * Computes the preferred 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 Point computeSize (int wHint, int hHint, boolean changed) {
+ checkWidget();
+ int count, width, height, lineHeight;
+ boolean singleLine = (getStyle() & SWT.SINGLE) != 0;
+ lineHeight = getLineHeight();
+ count = content.getLineCount();
+
+ // If a height or width has been specified (via hHint and wHint),
+ // use those values. Otherwise calculate the size based on the
+ // text that is defined.
+ if (hHint != SWT.DEFAULT) {
+ height = hHint;
+ } else {
+ if (singleLine) count = 1;
+ height = count * lineHeight;
+ }
+ if (wHint != SWT.DEFAULT) {
+ width = wHint;
+ } else {
+ // Only calculate what can actually be displayed.
+ // Do this because measuring each text line is a
+ // time-consuming process.
+ int visibleCount = Math.min (count, getDisplay().getBounds().width / lineHeight);
+ calculateContentWidth(0, visibleCount);
+ width = contentWidth;
+ }
+
+ // Use default values if no text is defined.
+ if (width == 0) width = DEFAULT_WIDTH;
+ if (height == 0) {
+ if (singleLine) height = getLineHeight();
+ else height = DEFAULT_HEIGHT;
+ }
+
+ // Hardcode the inset margins. Assume text is inset
+ // 1 pixel on each side.
+ width += 2;
+ height += 2;
+
+ Rectangle rect = computeTrim(0,0,width,height);
+ return new Point (rect.width, rect.height);
+}
+/**
+ * Returns the width of the specified text. Expand tabs to tab stops using
+ * the widget tab width.
+ * This is a quick and inaccurate measurement. Text styles are not taken
+ * into consideration. The gc should be setup to reflect the widest
+ * possible font style.
+ * <p>
+ *
+ * @param text text to be measured.
+ * @param lineIndex index of the line.
+ * @param gc GC to use for measuring text
+ * @return width of the text with tabs expanded to tab stops
+ */
+int contentWidth(String text, int lineIndex, GC gc) {
+ int paintX = 0;
+ int textLength = text.length();
+
+ for (int i = 0; i < textLength; i++) {
+ int tabIndex = text.indexOf(TAB, i);
+ // is tab not present or past the rendering range?
+ if (tabIndex == -1 || tabIndex > textLength) {
+ tabIndex = textLength;
+ }
+ if (tabIndex != i) {
+ String tabSegment = text.substring(i, tabIndex);
+ paintX += gc.stringExtent(tabSegment).x;
+ if (tabIndex != textLength && tabWidth > 0) {
+ paintX += tabWidth;
+ paintX -= paintX % tabWidth;
+ }
+ i = tabIndex;
+ }
+ else
+ if (tabWidth > 0) {
+ paintX += tabWidth;
+ paintX -= paintX % tabWidth;
+ }
+ }
+ return paintX;
+}
+/**
+ * Copies the selected text to the clipboard. The text will be put in the
+ * clipboard in plain text format and RTF format.
+ * <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();
+ int length = selection.y - selection.x;
+ if (length > 0) {
+ RTFTransfer rtfTransfer = RTFTransfer.getInstance();
+ TextTransfer plainTextTransfer = TextTransfer.getInstance();
+ RTFWriter rtfWriter = new RTFWriter(selection.x, length);
+ TextWriter plainTextWriter = new TextWriter(selection.x, length);
+ String rtfText;
+ String plainText;
+ rtfText = getPlatformDelimitedText(rtfWriter);
+ plainText = getPlatformDelimitedText(plainTextWriter);
+ clipboard.setContents(
+ new String[]{rtfText, plainText},
+ new Transfer[]{rtfTransfer, plainTextTransfer});
+ }
+}
+
+/**
+ * Returns a string that uses only the line delimiter specified by the
+ * StyledTextContent implementation.
+ * 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) {
+ StringBuffer convertedText;
+ String delimiter = getLineDelimiter();
+ int length = text.length();
+ int crIndex = 0;
+ int lfIndex = 0;
+ int i = 0;
+
+ if (length == 0) {
+ return text;
+ }
+ convertedText = new StringBuffer(length);
+ 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 // CR occurs before LF or no LF present?
+ 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() == false || convertedText.length() == 0)) {
+ convertedText.append(text.substring(i));
+ }
+ return convertedText.toString();
+}
+
+/**
+ * Creates default key bindings.
+ */
+void createKeyBindings() {
+ // Navigation
+ setKeyBinding(SWT.ARROW_UP, ST.LINE_UP);
+ setKeyBinding(SWT.ARROW_DOWN, ST.LINE_DOWN);
+ setKeyBinding(SWT.HOME, ST.LINE_START);
+ setKeyBinding(SWT.END, ST.LINE_END);
+ setKeyBinding(SWT.ARROW_LEFT, ST.COLUMN_PREVIOUS);
+ setKeyBinding(SWT.ARROW_RIGHT, ST.COLUMN_NEXT);
+ setKeyBinding(SWT.PAGE_UP, ST.PAGE_UP);
+ setKeyBinding(SWT.PAGE_DOWN, ST.PAGE_DOWN);
+ setKeyBinding(SWT.ARROW_LEFT | SWT.CTRL, ST.WORD_PREVIOUS);
+ setKeyBinding(SWT.ARROW_RIGHT | SWT.CTRL, ST.WORD_NEXT);
+ setKeyBinding(SWT.HOME | SWT.CTRL, ST.TEXT_START);
+ setKeyBinding(SWT.END | SWT.CTRL, ST.TEXT_END);
+ setKeyBinding(SWT.PAGE_UP | SWT.CTRL, ST.WINDOW_START);
+ setKeyBinding(SWT.PAGE_DOWN | SWT.CTRL, ST.WINDOW_END);
+
+ // Selection
+ setKeyBinding(SWT.ARROW_UP | SWT.SHIFT, ST.SELECT_LINE_UP);
+ setKeyBinding(SWT.ARROW_DOWN | SWT.SHIFT, ST.SELECT_LINE_DOWN);
+ setKeyBinding(SWT.HOME | SWT.SHIFT, ST.SELECT_LINE_START);
+ setKeyBinding(SWT.END | SWT.SHIFT, ST.SELECT_LINE_END);
+ setKeyBinding(SWT.ARROW_LEFT | SWT.SHIFT, ST.SELECT_COLUMN_PREVIOUS);
+ setKeyBinding(SWT.ARROW_RIGHT | SWT.SHIFT, ST.SELECT_COLUMN_NEXT);
+ setKeyBinding(SWT.PAGE_UP | SWT.SHIFT, ST.SELECT_PAGE_UP);
+ setKeyBinding(SWT.PAGE_DOWN | SWT.SHIFT, ST.SELECT_PAGE_DOWN);
+ setKeyBinding(SWT.ARROW_LEFT | SWT.CTRL | SWT.SHIFT, ST.SELECT_WORD_PREVIOUS);
+ setKeyBinding(SWT.ARROW_RIGHT | SWT.CTRL | SWT.SHIFT, ST.SELECT_WORD_NEXT);
+ setKeyBinding(SWT.HOME | SWT.CTRL | SWT.SHIFT, ST.SELECT_TEXT_START);
+ setKeyBinding(SWT.END | SWT.CTRL | SWT.SHIFT, ST.SELECT_TEXT_END);
+ setKeyBinding(SWT.PAGE_UP | SWT.CTRL | SWT.SHIFT, ST.SELECT_WINDOW_START);
+ setKeyBinding(SWT.PAGE_DOWN | SWT.CTRL | SWT.SHIFT, ST.SELECT_WINDOW_END);
+
+ // Modification
+ // Cut, Copy, Paste
+ // CUA style
+ setKeyBinding('\u0018' | SWT.CTRL, ST.CUT);
+ setKeyBinding('\u0003' | SWT.CTRL, ST.COPY);
+ setKeyBinding('\u0016' | SWT.CTRL, ST.PASTE);
+ // Wordstar style
+ setKeyBinding(SWT.DEL | SWT.SHIFT, ST.CUT);
+ setKeyBinding(SWT.INSERT | SWT.CTRL, ST.COPY);
+ setKeyBinding(SWT.INSERT | SWT.SHIFT, ST.PASTE);
+
+ setKeyBinding(SWT.BS, ST.DELETE_PREVIOUS);
+ setKeyBinding(SWT.DEL, ST.DELETE_NEXT);
+
+ // Miscellaneous
+ setKeyBinding(SWT.INSERT, ST.TOGGLE_OVERWRITE);
+}
+/**
+ * Moves the selected text to the clipboard. The text will be put in the
+ * clipboard in plain text format and RTF format.
+ * <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 cut(){
+ checkWidget();
+ if (selection.y > selection.x) {
+ copy();
+ 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) {
+ Rectangle area = getClientArea();
+ if (event.y > area.height) doAutoScroll(SWT.DOWN);
+ else if (event.y < 0) doAutoScroll(SWT.UP);
+ else if (event.x < 0) doAutoScroll(SWT.LEFT);
+ else if (event.x > area.width) doAutoScroll(SWT.RIGHT);
+ else endAutoScroll();
+}
+/**
+ * Initiates autoscrolling.
+ * <p>
+ *
+ * @param direction SWT.UP, SWT.DOWN, SWT.RIGHT, SWT.LEFT
+ */
+void doAutoScroll(int direction) {
+ Runnable timer = null;
+ final int TIMER_INTERVAL = 5;
+
+ // If we're already autoscrolling in the given direction do nothing
+ if (autoScrollDirection == direction) {
+ return;
+ }
+
+ 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) {
+ doLineUp();
+ doSelection(SWT.LEFT);
+ display.timerExec(TIMER_INTERVAL, this);
+ }
+ }
+ };
+ } else if (direction == SWT.DOWN) {
+ timer = new Runnable() {
+ public void run() {
+ if (autoScrollDirection == SWT.DOWN) {
+ doLineDown();
+ doSelection(SWT.RIGHT);
+ display.timerExec(TIMER_INTERVAL, this);
+ }
+ }
+ };
+ } else if (direction == SWT.RIGHT) {
+ timer = new Runnable() {
+ public void run() {
+ if (autoScrollDirection == SWT.RIGHT) {
+ doColumnRight();
+ doSelection(SWT.RIGHT);
+ display.timerExec(TIMER_INTERVAL, this);
+ }
+ }
+ };
+ } else if (direction == SWT.LEFT) {
+ timer = new Runnable() {
+ public void run() {
+ if (autoScrollDirection == SWT.LEFT) {
+ doColumnLeft();
+ doSelection(SWT.LEFT);
+ display.timerExec(TIMER_INTERVAL, this);
+ }
+ }
+ };
+ }
+ if (timer != null) {
+ autoScrollDirection = direction;
+ display.timerExec(TIMER_INTERVAL, timer);
+ }
+}
+
+/**
+ * Deletes the character to the left of the caret. 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 line = content.getLineAtOffset(caretOffset);
+ int lineOffset = content.getOffsetAtLine(line);
+
+ if (caretOffset == lineOffset) {
+ lineOffset = content.getOffsetAtLine(line - 1);
+ event.start = lineOffset + content.getLine(line - 1).length();
+ event.end = caretOffset;
+ }
+ else {
+ event.start = caretOffset - 1;
+ event.end = caretOffset;
+ }
+ sendKeyEvent(event);
+ }
+ claimBottomFreeSpace();
+}
+
+/**
+ * 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.
+ * <p>
+ *
+ * @see #doSelectionCursorLeft
+ */
+void doCursorLeft() {
+ if (selection.y - selection.x > 0) {
+ caretOffset = selection.x;
+ showCaret();
+ }
+ else {
+ doSelectionCursorLeft();
+ }
+}
+
+/**
+ * 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.
+ * <p>
+ *
+ * @see #doSelectionCursorRight
+ */
+void doCursorRight() {
+ if (selection.y - selection.x > 0) {
+ caretOffset = selection.y;
+ showCaret();
+ }
+ else {
+ doSelectionCursorRight();
+ }
+}
+/**
+ * Moves the caret one character to the left. Do not go to the previous line.
+ */
+void doColumnLeft() {
+ int line = content.getLineAtOffset(caretOffset);
+ int lineOffset = content.getOffsetAtLine(line);
+ int offsetInLine = caretOffset - lineOffset;
+
+ if (offsetInLine > 0) {
+ caretOffset--;
+ showCaret();
+ }
+}
+/**
+ * Moves the caret one character to the right. Do not go to the next line.
+ */
+void doColumnRight() {
+ int line = content.getLineAtOffset(caretOffset);
+ int lineOffset = content.getOffsetAtLine(line);
+ int offsetInLine = caretOffset - lineOffset;
+
+ if (offsetInLine < content.getLine(line).length()) {
+ caretOffset++;
+ showCaret();
+ }
+}
+
+/**
+ * Replaces the selection with the character or insert the character at the
+ * current caret position if no selection exists.
+ * 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) {
+ Event event;
+
+ if (textLimit > 0 && content.getCharCount() - (selection.y - selection.x) >= textLimit) {
+ return;
+ }
+ 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() == false) {
+ event.text = getLineDelimiter();
+ }
+ }
+ // no selection and overwrite mode is on and the typed key is not a
+ // tab character (tabs are always inserted without overwriting)?
+ else
+ if (selection.x == selection.y && overwrite == true && key != TAB) {
+ 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) {
+ sendKeyEvent(event);
+ }
+}
+
+/**
+ * Moves the caret after the last character of the widget content.
+ */
+void doContentEnd() {
+ int length = content.getCharCount();
+
+ if (caretOffset < length) {
+ caretOffset = length;
+ showCaret();
+ }
+}
+
+/**
+ * Moves the caret in front of the first character of the widget content.
+ */
+void doContentStart() {
+ if (caretOffset > 0) {
+ caretOffset = 0;
+ showCaret();
+ }
+}
+
+/**
+ * Deletes the character to the right of the caret. 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 = caretOffset + 1;
+ }
+ sendKeyEvent(event);
+ }
+ claimBottomFreeSpace();
+}
+
+/**
+ * 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.
+ * Make the new caret position visible.
+ */
+void doLineDown() {
+ doSelectionLineDown();
+ showCaret();
+}
+
+/**
+ * Moves the caret to the end of the line.
+ */
+void doLineEnd() {
+ int line = content.getLineAtOffset(caretOffset);
+ int lineOffset = content.getOffsetAtLine(line);
+ int lineLength = content.getLine(line).length();
+ int lineEndOffset = lineOffset + lineLength;
+
+ if (caretOffset < lineEndOffset) {
+ caretOffset = lineEndOffset;
+ showCaret();
+ }
+}
+
+/**
+ * Moves the caret to the beginning of the line.
+ */
+void doLineStart() {
+ int line = content.getLineAtOffset(caretOffset);
+ int lineOffset = content.getOffsetAtLine(line);
+
+ if (caretOffset > lineOffset) {
+ caretOffset = lineOffset;
+ showCaret();
+ }
+ else // move up one line to remove line selection
+ if (line > 0 && selection.x < caretOffset) {
+ line--;
+ caretOffset = content.getOffsetAtLine(line);
+ 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() {
+ int line = content.getLineAtOffset(caretOffset);
+ int lineOffset = content.getOffsetAtLine(line);
+ int offsetInLine = caretOffset - lineOffset;
+
+ if (line > 0) {
+ line--;
+ lineOffset = content.getOffsetAtLine(line);
+ int lineLength = content.getLine(line).length();
+ if (offsetInLine > lineLength) {
+ offsetInLine = lineLength;
+ }
+ caretOffset = lineOffset + offsetInLine;
+ showCaret();
+ }
+}
+
+/**
+ * Moves the caret to the specified location.
+ * <p>
+ *
+ * @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 = (y + verticalScrollOffset) / getLineHeight();
+ int lineCount = content.getLineCount();
+
+ if (line > lineCount - 1) {
+ line = lineCount - 1;
+ }
+ if (line >= 0) {
+ String lineText = content.getLine(line);
+ int offsetInLine = -1;
+ int newCaretOffset;
+ int lineOffset = content.getOffsetAtLine(line);
+
+ if (select && selection.x == lineOffset && line < lineCount - 1) {
+ GC gc = new GC(this);
+ int lineLength = lineText.length();
+ // represent the line break as one additional white space
+ // if the selection mouse event occurs beyond the line end
+ if (x + horizontalScrollOffset > textWidth(lineText, line, lineLength, gc)) {
+ line++;
+ offsetInLine = 0;
+ lineOffset = content.getOffsetAtLine(line);
+ }
+ gc.dispose();
+ }
+ if (offsetInLine == -1) {
+ offsetInLine = getOffsetAtX(lineText, lineOffset, x);
+ }
+ newCaretOffset = lineOffset + offsetInLine;
+ if (newCaretOffset != caretOffset) {
+ caretOffset = newCaretOffset;
+ if (select) {
+ doMouseSelection();
+ }
+ showCaret();
+ }
+ if (select == false) {
+ 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(SWT.LEFT);
+ }
+ else {
+ doSelection(SWT.RIGHT);
+ }
+}
+
+/**
+ * Scrolls one page down so that the last line (truncated or whole)
+ * of the current page becomes the fully visible top 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 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 line = content.getLineAtOffset(caretOffset);
+ int lineCount = content.getLineCount();
+
+ if (line < lineCount) {
+ int lineOffset = content.getOffsetAtLine(line);
+ int offsetInLine = caretOffset - lineOffset;
+ int lineLength;
+ int verticalMaximum = content.getLineCount() * getVerticalIncrement();
+ int pageSize = getClientArea().height;
+ int scrollLines = Math.min(lineCount - line - 1, getLineCountWhole() - 1);
+ int scrollOffset;
+
+ line += scrollLines;
+ lineOffset = content.getOffsetAtLine(line);
+ lineLength = content.getLine(line).length();
+ // set cursor to end of line if cursor would be beyond the end
+ // of line or if page down goes to last line
+ if (offsetInLine > lineLength || line == lineCount - 1) {
+ offsetInLine = lineLength;
+ }
+ caretOffset = lineOffset + offsetInLine;
+ if (select) {
+ doSelection(SWT.RIGHT);
+ }
+ // scroll one page down or to the bottom
+ scrollOffset = verticalScrollOffset + scrollLines * getVerticalIncrement();
+ if (scrollOffset + pageSize > verticalMaximum) {
+ scrollOffset = verticalMaximum - pageSize;
+ }
+ if (scrollOffset > verticalScrollOffset) {
+ setVerticalScrollOffset(scrollOffset, true);
+ }
+ else {
+ showCaret();
+ }
+ }
+}
+/**
+ * Moves the cursor to the end of the last fully visible line.
+ */
+void doPageEnd() {
+ int line = content.getLineAtOffset(caretOffset);
+ int lineCount = content.getLineCount();
+
+ if (line < lineCount) {
+ line = getBottomIndex();
+ caretOffset = content.getOffsetAtLine(line) + content.getLine(line).length();
+ showCaret();
+ }
+}
+/**
+ * Moves the cursor to the beginning of the first fully visible line.
+ */
+void doPageStart() {
+ if (content.getLineAtOffset(caretOffset) > topIndex) {
+ caretOffset = content.getOffsetAtLine(topIndex);
+ 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() {
+ int line = content.getLineAtOffset(caretOffset);
+
+ if (line > 0) {
+ int lineOffset = content.getOffsetAtLine(line);
+ int offsetInLine = caretOffset - lineOffset;
+ int lineLength;
+ int scrollLines = Math.min(line, getLineCountWhole() - 1);
+ int scrollOffset;
+
+ line -= scrollLines;
+ lineOffset = content.getOffsetAtLine(line);
+ lineLength = content.getLine(line).length();
+ // set cursor to start of line if page up goes to first line
+ if (line == 0) {
+ offsetInLine = 0;
+ }
+ else
+ if (offsetInLine > lineLength) {
+ offsetInLine = lineLength;
+ }
+ caretOffset = lineOffset + offsetInLine;
+ // scroll one page up or to the top
+ scrollOffset = Math.max(0, verticalScrollOffset - scrollLines * getVerticalIncrement());
+ if (scrollOffset < verticalScrollOffset) {
+ setVerticalScrollOffset(scrollOffset, true);
+ }
+ else {
+ showCaret();
+ }
+ }
+}
+/**
+ * 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 doSelectionLineDown() {
+ int line = content.getLineAtOffset(caretOffset);
+ int lineOffset = content.getOffsetAtLine(line);
+ int offsetInLine = caretOffset - lineOffset;
+
+ if (line < content.getLineCount() - 1) {
+ line++;
+ lineOffset = content.getOffsetAtLine(line);
+ int lineLength = content.getLine(line).length();
+ if (offsetInLine > lineLength) {
+ offsetInLine = lineLength;
+ }
+ caretOffset = lineOffset + offsetInLine;
+ }
+}
+/**
+ * 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 == SWT.LEFT) {
+ 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;
+ }
+ }
+ else // test whether selection actually changed. Fixes 1G71EO1
+ 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;
+ }
+ }
+ else // test whether selection actually changed. Fixes 1G71EO1
+ 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) {
+ redrawRange(redrawStart, redrawEnd - redrawStart, true);
+ sendSelectionEvent();
+ }
+}
+
+/**
+ * Moves the caret one character to the left or to the end of the previous
+ * line if the cursor is at the beginning of a line.
+ */
+void doSelectionCursorLeft() {
+ int line = content.getLineAtOffset(caretOffset);
+ int lineOffset = content.getOffsetAtLine(line);
+ int offsetInLine = caretOffset - lineOffset;
+
+ if (offsetInLine > 0) {
+ caretOffset--;
+ showCaret();
+ }
+ else
+ if (line > 0) {
+ line--;
+ lineOffset = content.getOffsetAtLine(line);
+ caretOffset = lineOffset + content.getLine(line).length();
+ showCaret();
+ }
+}
+
+/**
+ * Moves the caret one character to the right or to the beginning of the
+ * next line if the cursor is at the end of a line.
+ */
+void doSelectionCursorRight() {
+ int line = content.getLineAtOffset(caretOffset);
+ int lineOffset = content.getOffsetAtLine(line);
+ int offsetInLine = caretOffset - lineOffset;
+
+ if (offsetInLine < content.getLine(line).length()) {
+ caretOffset++;
+ showCaret();
+ }
+ else
+ if (line < content.getLineCount() - 1) {
+ line++;
+ caretOffset = content.getOffsetAtLine(line);
+ showCaret();
+ }
+}
+
+/**
+ * Moves the caret to the beginning of the next line, selecting to the end
+ * of the line plus one whitespace to represent the line break.
+ * Do the regular line selection, if the selection does not start at the
+ * line start or if the last line is being selected.
+ */
+void doSelectionLineEnd() {
+ int line = content.getLineAtOffset(caretOffset);
+ int lineOffset = content.getOffsetAtLine(line);
+
+ if (selection.x == lineOffset && line < content.getLineCount() - 1) {
+ line++;
+ caretOffset = content.getOffsetAtLine(line);
+ showCaret();
+ }
+ else {
+ doLineEnd();
+ }
+}
+/**
+ * Moves the caret to the start of the word that is to the left of
+ * the current caret position.
+ * If a selection exists, move the caret to the start of the selection
+ * and remove the selection.
+ */
+void doWordLeft() {
+ if (selection.y - selection.x > 0) {
+ caretOffset = selection.x;
+ showCaret();
+ }
+ else {
+ doSelectionWordLeft();
+ }
+}
+
+/**
+ * Moves the caret to the end of the word that is to the right of
+ * the current caret position.
+ * If a selection exists, move the caret to the end of the selection
+ * and remove the selection.
+ */
+void doWordRight() {
+ if (selection.y - selection.x > 0) {
+ caretOffset = selection.y;
+ showCaret();
+ }
+ else {
+ doSelectionWordRight();
+ }
+}
+
+/**
+ * Moves the caret to the start of the word that is to the left of
+ * the current caret position.
+ */
+void doSelectionWordLeft() {
+ caretOffset = getWordStart(caretOffset);
+ showCaret();
+}
+
+/**
+ * Moves the caret to the end of the word that is to the right of
+ * the current caret position.
+ */
+void doSelectionWordRight() {
+ caretOffset = getWordEnd(caretOffset);
+ showCaret();
+}
+
+/**
+ * Draws the specified rectangle.
+ * Draw directly without invalidating the affected area when a gc is
+ * specified.
+ * <p>
+ *
+ * @param x the x position
+ * @param y the y position
+ * @param width the width
+ * @param height the height
+ * @param gc GC to use for direct drawing. If null the specified area
+ * is invalidated only.
+ * @param clearBackground true=clear the background, false=only draw the foregorund
+ */
+void draw(int x, int y, int width, int height, GC gc, boolean clearBackground) {
+ if (gc == null) {
+ redraw(x, y, width, height, false);
+ }
+ else {
+ int lineHeight = getLineHeight();
+ int startLine = (y + verticalScrollOffset) / lineHeight;
+ int endY = y + height;
+ int paintYFromTopLine = (startLine - topIndex) * lineHeight;
+ int topLineOffset = (topIndex * lineHeight - verticalScrollOffset);
+ int paintY = paintYFromTopLine + topLineOffset; // adjust y position for pixel based scrolling
+ int lineCount = content.getLineCount();
+ Color background = getBackground();
+ Color foreground = getForeground();
+
+ if (isSingleLine()) {
+ lineCount = 1;
+ if (startLine > 1) {
+ startLine = 1;
+ }
+ }
+ for (int i = startLine; paintY < endY && i < lineCount; i++, paintY += lineHeight) {
+ String line = content.getLine(i);
+ drawLine(line, i, paintY, gc, background, foreground, clearBackground);
+ }
+ if (clearBackground && paintY < endY) {
+ gc.setBackground(background);
+ gc.setForeground(background);
+ gc.fillRectangle(0, paintY, getClientArea().width, endY - paintY);
+ }
+ }
+}
+/**
+ * Draws a line of text at the specified location.
+ * <p>
+ *
+ * @param line the line to draw
+ * @param lineIndex index of the line to draw
+ * @param paintY y location to draw at
+ * @param gc GC to draw on
+ * @param widgetBackground the widget background color. Used as the default rendering color.
+ * @param widgetForeground the widget foreground color. Used as the default rendering color.
+ */
+void drawLine(String line, int lineIndex, int paintY, GC gc, Color widgetBackground, Color widgetForeground, boolean clearBackground) {
+ int lineOffset = content.getOffsetAtLine(lineIndex);
+ int lineLength = line.length();
+ int selectionEndX = 0;
+ int clientAreaWidth = getClientArea().width;
+ int selectionStartOffset;
+ int selectionEndOffset;
+ int selectionLength;
+ boolean lineSelection;
+ StyleRange[] styles = new StyleRange[0];
+ Color lineBackground = null;
+ StyledTextEvent event = getLineStyleData(lineOffset, line);
+
+ if (event != null) {
+ styles = event.styles;
+ }
+ event = getLineBackgroundData(lineOffset, line);
+ if (event != null) {
+ lineBackground = event.lineBackground;
+ }
+ if (lineBackground == null) {
+ lineBackground = widgetBackground;
+ }
+ // calculate offset of selection start and end relative to the line start
+ selectionStartOffset = Math.max(0, selection.x - lineOffset);
+ selectionEndOffset = selection.y - lineOffset;
+ selectionLength = selectionEndOffset - selectionStartOffset;
+ if (selectionLength == 0 || selectionVisible == false) {
+ selectionStartOffset = 0;
+ selectionEndOffset = 0;
+ }
+ lineSelection = selectionEndOffset > 0 && selectionStartOffset <= lineLength;
+ if (lineSelection == false && clearBackground) {
+ // draw background for completely unselected line
+ gc.setBackground(lineBackground);
+ gc.setForeground(lineBackground);
+ gc.fillRectangle(0, paintY, clientAreaWidth, lineHeight);
+ }
+ if (selectionStartOffset > 0) {
+ String unselectedLine;
+ if (selectionStartOffset > lineLength) {
+ unselectedLine = line;
+ }
+ else {
+ if (clearBackground) {
+ // draw background for partially unselected line
+ int selectionStartX = textWidth(line, lineOffset, 0, selectionStartOffset, styles, 0, gc);
+ gc.setBackground(lineBackground);
+ gc.setForeground(lineBackground);
+ gc.fillRectangle(0, paintY, selectionStartX - horizontalScrollOffset, lineHeight);
+ }
+ unselectedLine = line.substring(0, selectionStartOffset);
+ }
+ drawStyledLine(unselectedLine, lineOffset, 0, styles, 0, paintY, gc, lineBackground, widgetForeground);
+ }
+ if (selectionLength > 0 && lineSelection) {
+ // draw selected text
+ selectionEndX = drawLineSelection(line, lineOffset, selectionStartOffset, selectionLength, styles, paintY, gc) - horizontalScrollOffset;
+ if (selectionEndX < clientAreaWidth && clearBackground) {
+ // draw background after selection
+ gc.setBackground(lineBackground);
+ gc.setForeground(lineBackground);
+ gc.fillRectangle(selectionEndX, paintY, clientAreaWidth - selectionEndX, lineHeight);
+ }
+ selectionEndX += horizontalScrollOffset;
+ }
+ if (selectionEndOffset < lineLength) {
+ selectionEndOffset = Math.max(0, selectionEndOffset);
+ drawStyledLine(line, lineOffset, selectionEndOffset, styles, selectionEndX, paintY, gc, lineBackground, widgetForeground);
+ }
+}
+/**
+ * Draws the selected part of a line at the specified location.
+ * Draws selected text on top of unselected text.
+ * <p>
+ *
+ * @param line the line to draw
+ * @param lineOffset offset of the first character in the line.
+ * Relative to the start of the document.
+ * @param selectionStartOffset offset of the first selected character.
+ * Relative to the start of the line.
+ * @param selectionLength length, in characters, of the selection.
+ * @param styles line styles
+ * @param paintY y location to draw at
+ * @param gc GC to draw on
+ * @return the x location of the selection end relative to first character location in the line
+ */
+int drawLineSelection(String line, int lineOffset, int selectionStartOffset, int selectionLength, StyleRange[] styles, int paintY, GC gc) {
+ int lineLength = line.length();
+ int paintX;
+ int selectionBackgroundWidth;
+
+ styles = filterLineStyles(styles);
+ paintX = textWidth(line, lineOffset, 0, selectionStartOffset, styles, 0, gc);
+ if (selectionStartOffset + selectionLength > lineLength) {
+ selectionLength = lineLength - selectionStartOffset;
+ if ((getStyle() & SWT.FULL_SELECTION) != 0) {
+ // use the greater of the client area width and the content width
+ // fixes 1G8IYRD
+ selectionBackgroundWidth = Math.max(getClientArea().width, contentWidth);
+ }
+ else {
+ // if the selection extends beyond this line, render an
+ // additional whitespace to represent the selected line break
+ selectionBackgroundWidth = textWidth(line + " ", lineOffset, selectionStartOffset, selectionLength + 1, styles, paintX, gc);
+ }
+ }
+ else {
+ selectionBackgroundWidth = textWidth(line, lineOffset, selectionStartOffset, selectionLength, styles, paintX, gc);
+ }
+ gc.setBackground(getSelectionBackground());
+ gc.setForeground(getSelectionForeground());
+ // fill the background first since expanded tabs are not
+ // drawn as spaces. tabs just move the draw position.
+ gc.fillRectangle(paintX - horizontalScrollOffset, paintY, selectionBackgroundWidth, lineHeight);
+ drawText(line, lineOffset, selectionStartOffset, selectionLength, styles, paintX, paintY, gc);
+ return paintX + selectionBackgroundWidth;
+}
+/**
+ * Draws the line at the specified location.
+ * <p>
+ *
+ * @param line the line to draw
+ * @param lineOffset offset of the first character in the line.
+ * Relative to the start of the document.
+ * @param renderOffset offset of the first character that should be rendered.
+ * Relative to the start of the line.
+ * @param styles the styles to use for rendering line segments. May be empty but not null.
+ * @param paintX x location to draw at
+ * @param paintY y location to draw at
+ * @param gc GC to draw on
+ * @param lineBackground line background color, used when no style is specified for a line segment.
+ * @param lineForeground line foreground color, used when no style is specified for a line segment.
+ */
+void drawStyledLine(String line, int lineOffset, int renderOffset, StyleRange[] styles, int paintX, int paintY, GC gc, Color lineBackground, Color lineForeground) {
+ int lineLength = line.length();
+ Color background = gc.getBackground();
+ Color foreground = gc.getForeground();
+ StyleRange style = null;
+ StyleRange[] filteredStyles = filterLineStyles(styles);
+ int renderStopX = getClientArea().width + horizontalScrollOffset;
+
+ for (int i = 0; i < styles.length && paintX < renderStopX; i++) {
+ int styleLineLength;
+ int styleLineStart;
+ style = styles[i];
+ styleLineStart = Math.max(style.start - lineOffset, 0);
+ // render unstyled text between the start of the current
+ // style range and the end of the previously rendered
+ // style range
+ if (styleLineStart > renderOffset) {
+ background = setLineBackground(gc, background, lineBackground);
+ foreground = setLineForeground(gc, foreground, lineForeground);
+ // don't try to render more text than requested
+ styleLineStart = Math.min(lineLength, styleLineStart);
+ paintX = drawText(line, lineOffset, renderOffset, styleLineStart - renderOffset, filteredStyles, paintX, paintY, gc);
+ renderOffset = styleLineStart;
+ }
+ styleLineLength = Math.min(style.start - lineOffset + style.length, lineLength) - renderOffset;
+ if (styleLineLength == 0) {
+ // there are line styles but no text for those styles
+ // possible when called with partial line text
+ break;
+ }
+ else
+ if (styleLineLength < 0) {
+ // style ends before render start offset
+ continue;
+ }
+ // set style background color if specified
+ if (style.background != null) {
+ background = setLineBackground(gc, background, style.background);
+ foreground = setLineForeground(gc, foreground, style.background);
+ int fillWidth = textWidth(line, lineOffset, renderOffset, styleLineLength, filteredStyles, paintX, gc);
+ gc.fillRectangle(paintX - horizontalScrollOffset, paintY, fillWidth, lineHeight);
+ }
+ else {
+ background = setLineBackground(gc, background, lineBackground);
+ }
+ // set style foreground color if specified
+ if (style.foreground != null) {
+ foreground = setLineForeground(gc, foreground, style.foreground);
+ }
+ else {
+ foreground = setLineForeground(gc, foreground, lineForeground);
+ }
+ paintX = drawText(line, lineOffset, renderOffset, styleLineLength, filteredStyles, paintX, paintY, gc);
+ renderOffset += styleLineLength;
+ }
+ // render unstyled text at the end of the line
+ if ((style == null || renderOffset < lineLength) && paintX < renderStopX) {
+ setLineBackground(gc, background, lineBackground);
+ setLineForeground(gc, foreground, lineForeground);
+ drawText(line, lineOffset, renderOffset, lineLength - renderOffset, filteredStyles, paintX, paintY, gc);
+ }
+}
+
+/**
+ * Draws the text at the specified location. Expands tabs to tab stops using
+ * the widget tab width.
+ * <p>
+ *
+ * @param text text to draw
+ * @param lineOffset offset of the first character in the line.
+ * Relative to the start of the document.
+ * @param startOffset offset of the first character in text to draw
+ * @param length number of characters to draw
+ * @param styles line styles
+ * @param paintX x location to start drawing at
+ * @param paintY y location to draw at
+ * @param gc GC to draw on
+ * @return x location where drawing stopped or 0 if the startOffset or
+ * length is outside the specified text.
+ */
+int drawText(String text, int lineOffset, int startOffset, int length, StyleRange[] lineStyles, int paintX, int paintY, GC gc) {
+ int endOffset = startOffset + length;
+ int textLength = text.length();
+ StyleRange[] styles;
+ FontData fontData = gc.getFont().getFontData()[0];
+
+ if (startOffset < 0 || startOffset >= textLength || startOffset + length > textLength) {
+ return paintX;
+ }
+ for (int i = startOffset; i < endOffset; i++) {
+ int tabIndex = text.indexOf(TAB, i);
+ // is tab not present or past the rendering range?
+ if (tabIndex == -1 || tabIndex > endOffset) {
+ tabIndex = endOffset;
+ }
+ if (tabIndex != i) {
+ String tabSegment = text.substring(i, tabIndex);
+ if (lineStyles != null) {
+ paintX = styledTextWidth(tabSegment, lineOffset + i, lineStyles, paintX, paintY, gc, true);
+ }
+ else {
+ fontData = setLineFont(gc, fontData, SWT.NORMAL);
+ gc.drawString(tabSegment, paintX - horizontalScrollOffset, paintY, true);
+ paintX += gc.stringExtent(tabSegment).x;
+ }
+ if (tabIndex != endOffset && tabWidth > 0) {
+ paintX += tabWidth;
+ paintX -= paintX % tabWidth;
+ }
+ i = tabIndex;
+ }
+ else // is tab at current rendering offset?
+ if (tabWidth > 0) {
+ paintX += tabWidth;
+ paintX -= paintX % tabWidth;
+ }
+ }
+ return paintX;
+}
+
+/**
+ * Ends the autoscroll process.
+ */
+void endAutoScroll() {
+ autoScrollDirection = SWT.NULL;
+}
+
+/**
+ * @param styles styles that may contain font styles.
+ * @return null if the styles contain only regular font styles, the
+ * unchanged styles otherwise.
+ */
+StyleRange[] filterLineStyles(StyleRange[] styles) {
+ if (styles != null) {
+ int styleIndex = 0;
+ while (styleIndex < styles.length && styles[styleIndex].fontStyle == SWT.NORMAL) {
+ styleIndex++;
+ }
+ if (styleIndex == styles.length) {
+ styles = null;
+ }
+ }
+ return styles;
+}
+/**
+ * Returns the index of the last fully visible line.
+ * <p>
+ *
+ * @return index of the last fully visible line.
+ */
+int getBottomIndex() {
+ return Math.min(content.getLineCount(), topIndex + getLineCountWhole()) - 1;
+}
+
+
+/**
+ * Returns the caret position relative to the start of the text.
+ * <p>
+ *
+ * @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 content implementation that is used for text storage
+ * or null if no user defined content implementation has been set.
+ * <p>
+ *
+ * @return content implementation that is used for text storage or null
+ * 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;
+}
+
+/**
+ * Returns whether the widget implements double click mouse behavior.
+ * <p>
+ *
+ * @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.
+ * <p>
+ *
+ * @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;
+}
+
+/**
+ * Returns the horizontal scroll increment.
+ * <p>
+ *
+ * @return horizontal scroll increment.
+ */
+int getHorizontalIncrement() {
+ GC gc = new GC(this);
+ int increment = gc.getFontMetrics().getAverageCharWidth();
+
+ gc.dispose();
+ return increment;
+}
+
+/**
+ * Returns the horizontal scroll offset relative to the start of the line.
+ * <p>
+ *
+ * @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.
+ * <p>
+ *
+ * @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 action assigned to the key.
+ * Returns SWT.NULL if there is no action associated with the key.
+ * <p>
+ *
+ * @param key a key code defined in SWT.java or a character.
+ * Optionally ORd with a state mask (one or more of SWT.CTRL, SWT.SHIFT, SWT.ALT)
+ * @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>
+ * @see org.eclipse.swt.SWT
+ * @see org.eclipse.swt.ST
+ */
+public int getKeyBinding(int key) {
+ checkWidget();
+ Integer action = (Integer) keyActionMap.get(new Integer(key));
+ int intAction;
+
+ if (action == null) {
+ intAction = SWT.NULL;
+ }
+ else {
+ intAction = action.intValue();
+ }
+ return intAction;
+}
+
+/**
+ * Gets the number of characters.
+ * <p>
+ *
+ * @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 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.
+ * <p>
+ *
+ * @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();
+ Color lineBackground = null;
+
+ if (index < 0 || index > content.getLineCount()) {
+ SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ }
+ if (userLineBackground == false) {
+ lineBackground = defaultLineStyler.getLineBackground(index);
+ }
+ return lineBackground;
+}
+/**
+ * Gets the number of text lines.
+ * <p>
+ *
+ * @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 getLineAtOffset(getCharCount()) + 1;
+}
+/**
+ * Returns the number of lines that are at least partially displayed in the widget client area.
+ * <p>
+ *
+ * @return number of lines that are at least partially displayed in the widget client area.
+ */
+int getLineCountTruncated() {
+ int lineCount;
+ int lineHeight = getLineHeight();
+
+ if (lineHeight != 0) {
+ lineCount = (int) Math.ceil((float) getClientArea().height / lineHeight);
+ }
+ else {
+ lineCount = 1;
+ }
+ return lineCount;
+}
+
+/**
+ * Returns the number of lines that are completely displayed in the widget client area.
+ * <p>
+ *
+ * @return number of lines that are completely displayed in the widget client area.
+ */
+int getLineCountWhole() {
+ int lineCount;
+ int lineHeight = getLineHeight();
+
+ if (lineHeight != 0) {
+ lineCount = getClientArea().height / lineHeight;
+ }
+ else {
+ lineCount = 1;
+ }
+ return lineCount;
+}
+
+/**
+ * Returns the line at the specified offset in the text.
+ * 0 <= offset <= getCharCount() so that getLineAtOffset(getCharCount())
+ * returns the line of the insert location.
+ * <p>
+ *
+ * @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 line delimiter used for entering new lines by key down
+ * or paste operation.
+ * <p>
+ *
+ * @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>
+ *
+ * @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>
+ */
+public int getLineHeight() {
+ checkWidget();
+
+ if (lineHeight == 0) {
+ calculateLineHeight();
+ }
+ return lineHeight;
+}
+/**
+ * Returns the line style data for the given line or null if there is none.
+ * If there is a LineStyleListener but it does not set any styles, the
+ * StyledTextEvent.styles field will be initialized to an empty array.
+ */
+StyledTextEvent getLineStyleData(int lineOffset, String line) {
+ if (isListening(LineGetStyle)) {
+ StyledTextEvent event = new StyledTextEvent(content);
+ event.detail = lineOffset;
+ event.text = line;
+ notifyListeners(LineGetStyle, event);
+ if (event.styles == null) {
+ event.styles = new StyleRange[0];
+ }
+ return event;
+ }
+ return null;
+}
+/**
+ * Returns the line background data for the given line or null if there is none.
+ */
+StyledTextEvent getLineBackgroundData(int lineOffset, String line) {
+ if (isListening(LineGetBackground)) {
+ StyledTextEvent event = new StyledTextEvent(content);
+ event.detail = lineOffset;
+ event.text = line;
+ notifyListeners(LineGetBackground, event);
+ return event;
+ }
+ return null;
+}
+/**
+ * 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.
+ * <p>
+ *
+ * @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);
+ }
+ int line = getLineAtOffset(offset);
+ int lineOffset = content.getOffsetAtLine(line);
+ String lineContent = content.getLine(line);
+ int x = getXAtOffset(lineContent, line, offset - lineOffset);
+ int y = line * lineHeight - verticalScrollOffset;
+
+ return new Point(x, y);
+}
+/**
+ * Returns the offset at the given x location in the line.
+ * Doesn't properly handle ligatures and other context dependent characters or
+ * bidi. Ligatures are handled properly as long as they don't occur at lineXOffset.
+ * <p>
+ *
+ * @param line text of the line to calculate the offset in
+ * @param lineOffset offset of the first character in the line.
+ * 0 based from the beginning of the document.
+ * @param lineXOffset x location in the line
+ * @return offset of the character at the x location relative to the start of the
+ * line.
+ */
+int getOffsetAtX(String line, int lineOffset, int lineXOffset) {
+ int x = 0;
+ GC gc = new GC(this);
+ int low = -1;
+ int high = line.length();
+ int offset;
+ StyleRange[] styles = null;
+ StyledTextEvent event = getLineStyleData(lineOffset, line);
+
+ if (event != null) {
+ styles = filterLineStyles(event.styles);
+ }
+ lineXOffset += horizontalScrollOffset;
+ while (high - low > 1) {
+ offset = (high + low) / 2;
+ x = textWidth(line, lineOffset, 0, offset, styles, 0, gc);
+ int charWidth = textWidth(line, lineOffset, 0, offset + 1, styles, 0, gc) - x;
+ if (lineXOffset <= x + charWidth / 2) {
+ high = offset;
+ }
+ else {
+ low = offset;
+ }
+ }
+ offset = high;
+ gc.dispose();
+ return offset;
+}
+/**
+ * Returns the index of the last partially visible line.
+ *
+ * @return index of the last partially visible line.
+ */
+int getPartialBottomIndex() {
+ int partialLineCount = (int) Math.ceil((float) getClientArea().height / lineHeight);
+
+ return Math.min(content.getLineCount(), topIndex + partialLineCount) - 1;
+}
+/**
+ * Returns the content in the specified range using the platform line
+ * delimiter to separate lines.
+ * <p>
+ *
+ * @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 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
+ * @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.
+ * <p>
+ *
+ * @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.
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - 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 background color to be used for rendering selected text.
+ * <p>
+ *
+ * @return background color to be used for rendering selected text
+ */
+Color getSelectionBackground() {
+ return getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION);
+}
+/**
+ * Gets the number of selected characters.
+ * <p>
+ *
+ * @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();
+ return getSelectionRange().y;
+}
+/**
+ * Returns the foreground color to be used for rendering selected text.
+ * <p>
+ *
+ * @return foreground color to be used for rendering selected text
+ */
+Color getSelectionForeground() {
+ return getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT);
+}
+
+/**
+ * Returns the selected text.
+ * <p>
+ *
+ * @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();
+
+ return content.getTextRange(selection.x, selection.y - selection.x);
+}
+
+/**
+ * Returns the style range at the given offset.
+ * 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>
+ *
+ * @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 (userLineStyle == false) {
+ return defaultLineStyler.getStyleRangeAtOffset(offset);
+ }
+ return null;
+}
+/**
+ * Returns the styles.
+ * 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>
+ *
+ * @return the styles or null 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>
+ */
+public StyleRange [] getStyleRanges() {
+ checkWidget();
+ StyleRange styles[];
+
+ if (userLineStyle == false) {
+ styles = defaultLineStyler.getStyleRanges();
+ }
+ else {
+ styles = new StyleRange[0];
+ }
+ return styles;
+}
+/**
+ * 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.
+ * <p>
+ *
+ * @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.
+ * <p>
+ *
+ * @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 widget content starting at start for length characters.
+ * <p>
+ *
+ * @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 < 0 || start > contentLength || end < 0 || end > contentLength || start > end) {
+ SWT.error(SWT.ERROR_INVALID_RANGE);
+ }
+ return content.getTextRange(start, length);
+}
+
+/**
+ * Gets the text limit. The text limit specifies the amount of text that the user
+ * can type into 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>
+ */
+public int getTextLimit() {
+ checkWidget();
+
+ return textLimit;
+}
+
+/**
+ * Gets the top index. The top index is the index of the fully visible line that
+ * is currently at the top of the widget. 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;
+}
+
+/**
+ * Gets the top pixel. 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 verticalScrollOffset;
+}
+
+/**
+ * Returns the vertical scroll increment.
+ * <p>
+ *
+ * @return vertical scroll increment.
+ */
+int getVerticalIncrement() {
+ return getLineHeight();
+}
+
+/**
+ * Returns the offset of the character after the word at the specified
+ * offset.
+ * <p>
+ * There are two classes of words formed by a sequence of characters:
+ * <ul>
+ * <li>from 0-9 and A-z (ASCII 48-57 and 65-122)
+ * <li>every other character except line breaks
+ * </ul>
+ * </p>
+ * <p>
+ * Space characters ' ' (ASCII 20) are special as they are treated as
+ * part of the word leading up to the space character. Line breaks are
+ * treated as one word.
+ * </p>
+ */
+int getWordEnd(int offset) {
+ int line = content.getLineAtOffset(offset);
+ int lineOffset = content.getOffsetAtLine(line);
+ String lineText = content.getLine(line);
+ int lineLength = lineText.length();
+
+ if (offset >= getCharCount()) {
+ return offset;
+ }
+ if (offset == lineOffset + lineLength) {
+ line++;
+ offset = content.getOffsetAtLine(line);
+ }
+ else {
+ offset -= lineOffset;
+ char ch = lineText.charAt(offset);
+ boolean letterOrDigit = Character.isLetterOrDigit(ch);
+ while (offset < lineLength - 1 && Character.isLetterOrDigit(ch) == letterOrDigit) {
+ offset++;
+ ch = lineText.charAt(offset);
+ }
+ // skip over trailing whitespace
+ while (offset < lineLength - 1 && Character.isSpaceChar(ch)) {
+ offset++;
+ ch = lineText.charAt(offset);
+ }
+ if (offset == lineLength - 1 && (Character.isLetterOrDigit(ch) == letterOrDigit || Character.isSpaceChar(ch))) {
+ offset++;
+ }
+ offset += lineOffset;
+ }
+ return offset;
+}
+
+/**
+ * Returns the offset of the character after the word at the specified
+ * offset.
+ * <p>
+ * There are two classes of words formed by a sequence of characters:
+ * <ul>
+ * <li>from 0-9 and A-z (ASCII 48-57 and 65-122)
+ * <li>every other character except line breaks
+ * </ul>
+ * </p>
+ * <p>
+ * Spaces are ignored and do not represent a word. Line breaks are treated
+ * as one word.
+ * </p>
+ */
+int getWordEndNoSpaces(int offset) {
+ int line = content.getLineAtOffset(offset);
+ int lineOffset = content.getOffsetAtLine(line);
+ String lineText = content.getLine(line);
+ int lineLength = lineText.length();
+
+ if (offset >= getCharCount()) {
+ return offset;
+ }
+ if (offset == lineOffset + lineLength) {
+ line++;
+ offset = content.getOffsetAtLine(line);
+ }
+ else {
+ offset -= lineOffset;
+ char ch = lineText.charAt(offset);
+ boolean letterOrDigit = Character.isLetterOrDigit(ch);
+
+ while (offset < lineLength - 1 && Character.isLetterOrDigit(ch) == letterOrDigit && Character.isSpaceChar(ch) == false) {
+ offset++;
+ ch = lineText.charAt(offset);
+ }
+ if (offset == lineLength - 1 && Character.isLetterOrDigit(ch) == letterOrDigit && Character.isSpaceChar(ch) == false) {
+ offset++;
+ }
+ offset += lineOffset;
+ }
+ return offset;
+}
+
+/**
+ * Returns the start offset of the word at the specified offset.
+ * There are two classes of words formed by a sequence of characters:
+ * <p>
+ * <ul>
+ * <li>from 0-9 and A-z (ASCII 48-57 and 65-122)
+ * <li>every other character except line breaks
+ * </ul>
+ * </p>
+ * <p>
+ * Space characters ' ' (ASCII 20) are special as they are treated as
+ * part of the word leading up to the space character. Line breaks are treated
+ * as one word.
+ * </p>
+ */
+int getWordStart(int offset) {
+ int line = content.getLineAtOffset(offset);
+ int lineOffset = content.getOffsetAtLine(line);
+ String lineText = content.getLine(line);
+
+ if (offset <= 0) {
+ return offset;
+ }
+ if (offset == lineOffset) {
+ line--;
+ lineText = content.getLine(line);
+ offset = content.getOffsetAtLine(line) + lineText.length();
+ }
+ else {
+ char ch;
+ boolean letterOrDigit;
+
+ offset -= lineOffset;
+ // skip over trailing whitespace
+ do {
+ offset--;
+ ch = lineText.charAt(offset);
+ } while (offset > 0 && Character.isSpaceChar(ch));
+ letterOrDigit = Character.isLetterOrDigit(ch);
+ while (offset > 0 && Character.isLetterOrDigit(ch) == letterOrDigit && Character.isSpaceChar(ch) == false) {
+ offset--;
+ ch = lineText.charAt(offset);
+ }
+ if (offset > 0 || Character.isLetterOrDigit(ch) != letterOrDigit) {
+ offset++;
+ }
+ offset += lineOffset;
+ }
+ return offset;
+}
+
+/**
+ * Returns the x location of the character at the give offset in the line.
+ * <b>NOTE:</b> Does not return correct values for true italic fonts (vs. slanted fonts).
+ * <p>
+ *
+ * @return x location of the character at the give offset in the line.
+ */
+int getXAtOffset(String line, int lineIndex, int lineOffset) {
+ int x;
+
+ if (lineOffset == 0) {
+ x = 0;
+ }
+ else {
+ GC gc = new GC(this);
+ if (lineOffset > line.length()) {
+ // offset is not on the line. return an x location one character
+ // after the line to indicate the line delimiter.
+ line += " ";
+ lineOffset = line.length();
+ }
+ x = textWidth(line, lineIndex, lineOffset, gc);
+ gc.dispose();
+ }
+ return x - horizontalScrollOffset;
+}
+/**
+ * Inserts a string. The old selection is replaced with the new text.
+ * <p>
+ *
+ * @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 listener is null</li>
+ * </ul>
+ */
+public void insert(String string) {
+ checkWidget();
+ if (string == null) {
+ SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ }
+ Point sel = getSelectionRange();
+ replaceTextRange(sel.x, sel.y, string);
+}
+/**
+ * Creates content change listeners and set the default content model.
+ */
+void installDefaultContent() {
+ textChangedListener = new TextChangedListener() {
+ public void textReplaced(TextChangedEvent event) {
+ handleTextChanged(event);
+ }
+ public void textSet(TextChangedEvent event) {
+ handleTextSet(event);
+ }
+ };
+ content = new DefaultContent();
+ content.addTextChangedListener(textChangedListener);
+}
+/**
+ * Creates a default line style listener.
+ * Used to store line background colors and styles.
+ * Removed when the user sets a LineStyleListener.
+ * <p>
+ *
+ * @see addLineStyleListener
+ */
+void installDefaultLineStyler() {
+ defaultLineStyler = new DefaultLineStyler(content);
+ StyledTextListener typedListener = new StyledTextListener(defaultLineStyler);
+ if (userLineStyle == false) {
+ addListener(LineGetStyle, typedListener);
+ }
+ if (userLineBackground == false) {
+ addListener(LineGetBackground, typedListener);
+ }
+}
+/**
+ * Adds event listeners
+ */
+void installListeners() {
+ ScrollBar verticalBar = getVerticalBar();
+ ScrollBar horizontalBar = getHorizontalBar();
+
+ addListener(SWT.Dispose, new Listener() {
+ public void handleEvent(Event event) {
+ handleDispose();
+ }
+ });
+ addListener(SWT.KeyDown, new Listener() {
+ public void handleEvent(Event event) {
+ handleKeyDown(event);
+ }
+ });
+ addListener(SWT.MouseDown, new Listener() {
+ public void handleEvent(Event event) {
+ handleMouseDown(event);
+ }
+ });
+ addListener(SWT.MouseUp, new Listener() {
+ public void handleEvent(Event event) {
+ handleMouseUp(event);
+ }
+ });
+ addListener(SWT.MouseDoubleClick, new Listener() {
+ public void handleEvent(Event event) {
+ handleMouseDoubleClick(event);
+ }
+ });
+ addListener(SWT.MouseMove, new Listener() {
+ public void handleEvent(Event event) {
+ handleMouseMove(event);
+ }
+ });
+ addListener(SWT.Paint, new Listener() {
+ public void handleEvent(Event event) {
+ handlePaint(event);
+ }
+ });
+ addListener(SWT.Resize, new Listener() {
+ public void handleEvent(Event event) {
+ handleResize(event);
+ }
+ });
+ addListener(SWT.Traverse, new Listener() {
+ public void handleEvent(Event event) {
+ // do nothing
+ // hooked to disable automatic tab traversal on tab key press
+ }
+ });
+ 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);
+ }
+ });
+ }
+}
+/**
+ * Returns the widget text with style information encoded using RTF format
+ * specification version 1.5.
+ *
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+String getRtf(){
+ checkWidget();
+ String rtfText = null;
+ int length = getCharCount();
+
+ if (length > 0) {
+ RTFWriter rtfWriter = new RTFWriter(0, length);
+ rtfText = getPlatformDelimitedText(rtfWriter);
+ }
+ return rtfText;
+}
+/**
+ * Frees resources.
+ */
+void handleDispose() {
+ Enumeration colors;
+
+ clipboard.dispose();
+ ibeamCursor.dispose();
+ if (boldFont != null) {
+ boldFont.dispose();
+ }
+ if (content != null) {
+ content.removeTextChangedListener(textChangedListener);
+ }
+}
+
+/**
+ * Updates the caret location and selection if mouse button 1 has been
+ * pressed.
+ */
+void handleMouseDoubleClick(Event event) {
+ if (event.button != 1 || doubleClickEnabled == false) {
+ return;
+ }
+ mouseDoubleClick = true;
+ caretOffset = getWordEndNoSpaces(caretOffset);
+ resetSelection();
+ caretOffset = getWordStart(caretOffset);
+ showCaret();
+ doMouseSelection();
+}
+
+/**
+ * Updates the caret location and selection if mouse button 1 has been
+ * pressed.
+ */
+void handleMouseDown(Event event) {
+ if (event.button != 1) {
+ return;
+ }
+ mouseDoubleClick = false;
+ doMouseLocationChange(event.x, event.y, (event.stateMask & SWT.SHIFT) != 0);
+}
+/**
+ * Autoscrolling ends when the mouse button is released.
+ */
+void handleMouseUp(Event event) {
+ endAutoScroll();
+}
+/**
+ * Updates the caret location and selection if mouse button 1 is pressed
+ * during the mouse move.
+ */
+void handleMouseMove(Event event) {
+ if (mouseDoubleClick == true || (event.stateMask & SWT.BUTTON1) == 0) {
+ return;
+ }
+ doMouseLocationChange(event.x, event.y, true);
+ doAutoScroll(event);
+}
+
+/**
+ * Replaces the selection with the clipboard text or insert the text at
+ * the current caret offset if there is no selection.
+ * 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.
+ * <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 paste(){
+ checkWidget();
+ TextTransfer transfer = TextTransfer.getInstance();
+ String text;
+
+ text = (String) clipboard.getContents(transfer);
+ if (text != null && text.length() > 0) {
+ Event event = new Event();
+ event.start = selection.x;
+ event.end = selection.y;
+ event.text = getModelDelimitedText(text);
+ sendKeyEvent(event);
+ // fixes 1GBKN67
+ claimBottomFreeSpace();
+ }
+}
+/**
+ * Prints the widget's text to the default printer.
+ * <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 print() {
+ checkWidget();
+ new StyledTextPrinter(this).print();
+}
+
+/**
+ * Scrolls the widget horizontally.
+ */
+void handleHorizontalScroll(Event event) {
+ int scrollPixel = getHorizontalBar().getSelection() - horizontalScrollOffset;
+ scrollHorizontal(scrollPixel);
+}
+
+/**
+ * If a VerifyKey listener exists, verify that the key that was entered
+ * should be processed.
+ * <p>
+ *
+ * @param event keyboard event
+ */
+void handleKeyDown(Event event) {
+ 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 == true) {
+ handleKey(event);
+ }
+}
+
+/**
+ * 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.
+ * <p>
+ *
+ * @param event keyboard event
+ */
+void handleKey(Event event) {
+ int action;
+
+ if (event.keyCode != 0) {
+ action = getKeyBinding(event.keyCode | event.stateMask);
+ }
+ else {
+ action = getKeyBinding(event.character | event.stateMask);
+ }
+ if (action == SWT.NULL) {
+ // ignore anything below SPACE and ignore DEL
+ if (event.character > 31 && event.character != SWT.DEL ||
+ event.character == SWT.CR || event.character == SWT.LF ||
+ event.character == TAB) {
+ doContent(event.character);
+ }
+ }
+ else {
+ invokeAction(action);
+ }
+}
+/**
+ * Renders the invalidated area specified in the paint event.
+ * <p>
+ *
+ * @param event paint event
+ */
+void handlePaint(Event event) {
+ draw(event.x, event.y, event.width, event.height, event.gc, true);
+}
+/**
+ * Recalculates the scroll bars.
+ * <p>
+ *
+ * @param event resize event
+ */
+void handleResize(Event event) {
+ ScrollBar verticalBar = getVerticalBar();
+ int oldHeight = clientAreaHeight;
+
+ clientAreaHeight = getClientArea().height;
+ if (clientAreaHeight > oldHeight) {
+ int lineCount = content.getLineCount();
+ int oldBottomIndex = topIndex + oldHeight / lineHeight;
+ int newItemCount = (int) Math.ceil((float) (clientAreaHeight - oldHeight) / lineHeight);
+ oldBottomIndex = Math.min(oldBottomIndex, lineCount);
+ newItemCount = Math.min(newItemCount, lineCount - oldBottomIndex);
+ calculateContentWidth(oldBottomIndex, newItemCount);
+ }
+ setScrollBars();
+ claimBottomFreeSpace();
+ claimRightFreeSpace();
+}
+
+/**
+ * Updates the caret position and selection to reflect the content change.
+ * Redraws the changed area.
+ * <p>
+ *
+ * @param event.start the start offset of the change
+ * @param event.replacedCharCount the length of the replaced text
+ * @param event.newCharCount the length of the new text
+ * @param event.replacedLineCount number of replaced lines.
+ * @param event.newLineCount number of new lines.
+ */
+void handleTextChanged(TextChangedEvent event) {
+ int firstLine;
+ int firstLineOffset;
+ int offsetInLine;
+ String firstLineText;
+ int stopLine;
+ boolean isMultiLineChange = event.replacedLineCount > 0 || event.newLineCount > 0;
+ int textChangeX = -1;
+ int textChangeY;
+ int oldTabX = 0;
+ int textChangeStopX = -1;
+ int visibleItemCount = (int) Math.ceil((float) getClientArea().height / lineHeight);
+
+ if (event.replacedCharCount < 0) {
+ event.start += event.replacedCharCount;
+ event.replacedCharCount *= -1;
+ }
+ firstLine = content.getLineAtOffset(event.start);
+ firstLineText = content.getLine(firstLine);
+ firstLineOffset = content.getOffsetAtLine(firstLine);
+ offsetInLine = event.start - firstLineOffset;
+ if (isMultiLineChange == false) {
+ // get location of nearest tab and replace stop offset in old text
+ int oldTabIndex;
+ String oldLine;
+ StringBuffer oldLineText = new StringBuffer(firstLineText);
+ oldLineText.delete(offsetInLine, offsetInLine + event.newCharCount);
+ if (event.replacedText != null && event.replacedText.length() > 0) {
+ oldLineText.insert(offsetInLine, event.replacedText);
+ }
+ oldLine = oldLineText.toString();
+ oldTabIndex = oldLine.indexOf(TAB, offsetInLine + event.replacedCharCount);
+ oldTabX = getXAtOffset(oldLine, firstLine, oldTabIndex + 1);
+ if (event.newCharCount == 0) {
+ // characters were deleted. find out where the last deleted
+ // character stopped drawing
+ textChangeStopX = getXAtOffset(
+ oldLine,
+ firstLine,
+ offsetInLine + event.replacedCharCount);
+ }
+ else
+ if (event.replacedCharCount == 0) {
+ // characters were added. find out where before the styles are
+ // updated to reflect the text change
+ textChangeX = getXAtOffset(oldLine, firstLine, offsetInLine);
+ }
+ }
+ // notify default line styler about text change
+ if (defaultLineStyler != null) {
+ defaultLineStyler.textChanged(event);
+ }
+ // calculate width of visible changed lines
+ stopLine = firstLine + event.newLineCount + 1;
+ if (stopLine > topIndex && firstLine < topIndex + visibleItemCount) {
+ int startLine = Math.max(firstLine, topIndex);
+ calculateContentWidth(startLine, Math.min(stopLine, topIndex + visibleItemCount) - startLine);
+ }
+ setScrollBars();
+ textChangeY = firstLine * lineHeight - verticalScrollOffset;
+ if (textChangeX == -1) {
+ textChangeX = getXAtOffset(firstLineText, firstLine, offsetInLine);
+ }
+ if (isMultiLineChange) {
+ redrawMultiLineChange(textChangeX, textChangeY, event.newLineCount, event.replacedLineCount);
+ }
+ else {
+ int newTabIndex = firstLineText.indexOf(TAB, offsetInLine + event.newCharCount);
+ if (newTabIndex != -1) {
+ // there is at least one tab after the text change
+ int newTabX = getXAtOffset(firstLineText, firstLine, newTabIndex + 1);
+ redrawSingleLineTabChange(textChangeX, textChangeY, newTabX, oldTabX);
+ }
+ else {
+ if (textChangeStopX == -1) {
+ textChangeStopX = getXAtOffset(
+ firstLineText,
+ firstLine,
+ offsetInLine + event.newCharCount);
+ }
+ redrawSingleLineChange(
+ textChangeX,
+ textChangeY,
+ event.newCharCount,
+ event.replacedCharCount,
+ textChangeStopX);
+ }
+ }
+ // 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.
+ // fixes 1GB93QT
+ updateSelection(event.start, event.replacedCharCount, event.newCharCount);
+}
+/**
+ * Called when the widget content is set programatically, overwriting
+ * the old content. Resets the caret position, selection and scroll offsets.
+ * Recalculates the content width and scroll bars. Redraws the widget.
+ * <p>
+ *
+ * @param event text change event.
+ */
+void handleTextSet(TextChangedEvent event) {
+ reset();
+}
+/**
+ * Scrolls the widget vertically.
+ */
+void handleVerticalScroll(Event event) {
+ setVerticalScrollOffset(getVerticalBar().getSelection(), false);
+}
+/**
+ * Initializes the fonts used to render font styles.
+ * Presently only regular and bold fonts are supported.
+ */
+void initializeFonts() {
+ FontData fontData;
+
+ regularFont = getFont();
+ fontData = regularFont.getFontData()[0];
+ fontData.setStyle(fontData.getStyle() | SWT.BOLD);
+ boldFont = new Font(getDisplay(), fontData);
+}
+/**
+ * Executes the action.
+ * <p>
+ *
+ * @param action one of the actions defined in ST.java
+ * @see ST.java
+ */
+public void invokeAction(int action) {
+ switch (action) {
+ // Navigation
+ case ST.LINE_UP:
+ doLineUp();
+ clearSelection(true);
+ break;
+ case ST.LINE_DOWN:
+ doLineDown();
+ clearSelection(true);
+ break;
+ case ST.LINE_START:
+ doLineStart();
+ clearSelection(true);
+ break;
+ case ST.LINE_END:
+ doLineEnd();
+ clearSelection(true);
+ break;
+ case ST.COLUMN_PREVIOUS:
+ doCursorLeft();
+ clearSelection(true);
+ break;
+ case ST.COLUMN_NEXT:
+ doCursorRight();
+ clearSelection(true);
+ break;
+ case ST.PAGE_UP:
+ doPageUp();
+ clearSelection(true);
+ break;
+ case ST.PAGE_DOWN:
+ doPageDown(false);
+ clearSelection(true);
+ break;
+ case ST.WORD_PREVIOUS:
+ doWordLeft();
+ clearSelection(true);
+ break;
+ case ST.WORD_NEXT:
+ doWordRight();
+ 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:
+ doLineUp();
+ doSelection(SWT.LEFT);
+ break;
+ case ST.SELECT_LINE_DOWN:
+ doSelectionLineDown();
+ doSelection(SWT.RIGHT);
+ showCaret();
+ break;
+ case ST.SELECT_LINE_START:
+ doLineStart();
+ doSelection(SWT.LEFT);
+ break;
+ case ST.SELECT_LINE_END:
+ doSelectionLineEnd();
+ doSelection(SWT.RIGHT);
+ break;
+ case ST.SELECT_COLUMN_PREVIOUS:
+ doSelectionCursorLeft();
+ doSelection(SWT.LEFT);
+ break;
+ case ST.SELECT_COLUMN_NEXT:
+ doSelectionCursorRight();
+ doSelection(SWT.RIGHT);
+ break;
+ case ST.SELECT_PAGE_UP:
+ doPageUp();
+ doSelection(SWT.LEFT);
+ break;
+ case ST.SELECT_PAGE_DOWN:
+ doPageDown(true);
+ break;
+ case ST.SELECT_WORD_PREVIOUS:
+ doSelectionWordLeft();
+ doSelection(SWT.LEFT);
+ break;
+ case ST.SELECT_WORD_NEXT:
+ doSelectionWordRight();
+ doSelection(SWT.RIGHT);
+ break;
+ case ST.SELECT_TEXT_START:
+ doContentStart();
+ doSelection(SWT.LEFT);
+ break;
+ case ST.SELECT_TEXT_END:
+ doContentEnd();
+ doSelection(SWT.RIGHT);
+ break;
+ case ST.SELECT_WINDOW_START:
+ doPageStart();
+ doSelection(SWT.LEFT);
+ break;
+ case ST.SELECT_WINDOW_END:
+ doPageEnd();
+ doSelection(SWT.RIGHT);
+ break;
+ // Modification
+ case ST.CUT:
+ cut();
+ break;
+ case ST.COPY:
+ copy();
+ break;
+ case ST.PASTE:
+ paste();
+ break;
+ case ST.DELETE_PREVIOUS:
+ doBackspace();
+ clearSelection(true);
+ break;
+ case ST.DELETE_NEXT:
+ doDelete();
+ clearSelection(true);
+ break;
+ // Miscellaneous
+ case ST.TOGGLE_OVERWRITE:
+ overwrite = !overwrite; // toggle insert/overwrite mode
+ break;
+ }
+}
+/**
+ * Returns whether the font style in the given style range is changing
+ * from SWT.NORMAL to SWT.BOLD or vice versa.
+ * <p>
+ *
+ * @param range StyleRange to compare current font style with.
+ * @param start offset of the first font style to compare
+ * @param end offset behind the last font style to compare
+ * @return true if the font style is changing in the given style range,
+ * false if the font style is not changing in the given style range.
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
+ * </ul>
+ */
+boolean isStyleChanging(StyleRange range, int start, int end) {
+ checkWidget();
+ int styleChangeOffset = -1;
+
+ for (int i = start; i < end; i++) {
+ StyleRange style = defaultLineStyler.getStyleRangeAtOffset(i);
+ if ((style != null && style.fontStyle != range.fontStyle) ||
+ (style == null && range.fontStyle != SWT.NORMAL)) {
+ return true;
+ }
+ }
+ return false;
+}
+/**
+ * Sends the specified verify event, replace/insert text as defined by
+ * the event and send a modify event.
+ * <p>
+ *
+ * @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);
+ }
+ content.replaceTextRange(event.start, replacedLength, event.text);
+ // set the caret position prior to sending the modify event.
+ // fixes 1GBB8NJ
+ if (updateCaret) {
+ internalSetSelection(event.start + event.text.length(), 0);
+ // always update the caret location. fixes 1G8FODP
+ showCaret();
+ }
+ notifyListeners(SWT.Modify, event);
+ if (isListening(ExtendedModify)) {
+ notifyListeners(ExtendedModify, styledTextEvent);
+ }
+ }
+}
+/**
+ * Redraws the specified text range.
+ * <p>
+ *
+ * @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 area will be cleared before anything
+ * is redrawn. 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
+ * area have changed, clearBackground should be set to true. If only foreground colors
+ * have changed for the redraw area, clearBackground can be set to false.
+ */
+public void redrawRange(int start, int length, boolean clearBackground) {
+ Rectangle clientArea = getClientArea();
+ int lineHeight = getLineHeight();
+ int end = start + length;
+ int firstLine = content.getLineAtOffset(start);
+ int lastLine = content.getLineAtOffset(end);
+ int lineCount = lastLine - firstLine + 1;
+ int redrawX;
+ int redrawStopX;
+ int redrawY;
+ int firstLineOffset;
+ int offsetInFirstLine;
+ int partialBottomIndex = getPartialBottomIndex();
+ int partialTopIndex = verticalScrollOffset / lineHeight;
+ String line;
+ GC gc = null;
+
+ // do nothing if redraw range is completely invisible
+ if (firstLine > partialBottomIndex || lastLine < partialTopIndex) {
+ return;
+ }
+ if (clearBackground == false) {
+ gc = new GC(this);
+ }
+ // only redraw visible lines
+ if (partialTopIndex > firstLine) {
+ firstLine = partialTopIndex;
+ firstLineOffset = start = content.getOffsetAtLine(firstLine);
+ offsetInFirstLine = 0;
+ }
+ else {
+ firstLineOffset = content.getOffsetAtLine(firstLine);
+ offsetInFirstLine = start - firstLineOffset;
+ }
+ if (partialBottomIndex + 1 < lastLine) {
+ lastLine = partialBottomIndex + 1; // + 1 to redraw whole bottom line, including line break
+ end = content.getOffsetAtLine(lastLine);
+ }
+ // redraw first line
+ line = content.getLine(firstLine);
+ // calculate redraw start location
+ redrawX = getXAtOffset(line, firstLine, offsetInFirstLine);
+ redrawY = firstLine * lineHeight - verticalScrollOffset;
+ // calculate redraw stop location
+ if ((getStyle() & SWT.FULL_SELECTION) != 0 && lastLine > firstLine) {
+ redrawStopX = clientArea.width;
+ }
+ else {
+ redrawStopX = getXAtOffset(line, firstLine, end - firstLineOffset);
+ }
+ draw(redrawX, redrawY, redrawStopX - redrawX, lineHeight, gc, clearBackground);
+ firstLine++; // first line has been redrawn.
+
+ // redraw last line if more than line needs redrawing
+ if (lineCount > 1) {
+ int offsetInLastLine = end - content.getOffsetAtLine(lastLine);
+ // no redraw necessary if redraw offset is 0
+ if (offsetInLastLine > 0) {
+ line = content.getLine(lastLine);
+ redrawStopX = getXAtOffset(line, lastLine, offsetInLastLine);
+ redrawY = lastLine * lineHeight - verticalScrollOffset;
+ draw(0, redrawY, redrawStopX, lineHeight, gc, clearBackground);
+ }
+ lastLine--; // last line has been redrawn.
+ }
+ // redraw entire center lines if redraw range includes more than two lines
+ if (lastLine >= firstLine) {
+ int redrawStopY = (lastLine + 1) * lineHeight - verticalScrollOffset;
+ redrawY = firstLine * lineHeight - verticalScrollOffset;
+ draw(0, redrawY, clientArea.width, redrawStopY - redrawY, gc, clearBackground);
+ }
+ if (gc != null) {
+ gc.dispose();
+ }
+}
+/**
+ * Fixes the widget to display a text change.
+ * Bit blitting and redrawing is done as necessary.
+ * <p>
+ *
+ * @param x x location of the text change
+ * @param y y location of the text change
+ * @param newLineCount number of new lines.
+ * @param replacedLineCount number of replaced lines.
+ */
+void redrawMultiLineChange(int x, int y, int newLineCount, int replacedLineCount) {
+ Rectangle clientArea = getClientArea();
+ int lineHeight = getLineHeight();
+ int lineCount = newLineCount - replacedLineCount;
+ int sourceY;
+ int destinationY;
+
+ if (lineCount > 0) {
+ sourceY = y + lineHeight;
+ destinationY = y + lineCount * lineHeight + lineHeight;
+ }
+ else {
+ sourceY = y - lineCount * lineHeight + lineHeight;
+ destinationY = y + lineHeight;
+ }
+ scroll(
+ 0, destinationY, // destination x, y
+ 0, sourceY, // source x, y
+ clientArea.width, clientArea.height, true);
+ // Always redrawing causes the bottom line to flash when a line is
+ // deleted. This is because SWT merges the paint area of the scroll
+ // with the paint area of the redraw call below.
+ // To prevent this we could call update after the scroll. However,
+ // adding update can cause even more flash if the client does other
+ // redraw/update calls (ie. for syntax highlighting).
+ // We could also redraw only when a line has been added or when
+ // contents has been added to a line. This would require getting
+ // line index info from the content and is not worth the trouble
+ // (the flash is only on the bottom line and minor).
+ // Specifying the NO_MERGE_PAINTS style bit prevents the merged
+ // redraw but could cause flash/slowness elsewhere.
+ redraw(x, y, clientArea.width, lineHeight, true);
+ if (newLineCount > 0) {
+ redraw(0, y + lineHeight, clientArea.width, newLineCount * lineHeight, true);
+ }
+}
+/**
+ * Fixes the widget after a text change local to one line ocurred.
+ * Bit blitting and/or redrawing is done as necessary.
+ * <p>
+ *
+ * @param textChangeX x location of the text change
+ * @param textChangeY y location of the text change
+ * @param newCharCount number of new characters
+ * @param replacedCharCount number of replaced characters
+ * @param textChangeStopX the x location of the character after last
+ * changed character
+ */
+void redrawSingleLineChange(int textChangeX, int textChangeY, int newCharCount, int replacedCharCount, int textChangeStopX) {
+ Rectangle clientArea = getClientArea();
+ int lineHeight = getLineHeight();
+
+ if (newCharCount == 0) {
+ // characters were deleted
+ scroll(
+ textChangeX, textChangeY, // destination x, y
+ textChangeStopX, textChangeY, // source x, y
+ clientArea.width, lineHeight, true);
+ }
+ else
+ if (replacedCharCount == 0) {
+ // characters were added
+ scroll(
+ textChangeStopX, textChangeY, // destination x, y
+ textChangeX, textChangeY, // source x, y
+ clientArea.width, lineHeight, true);
+ }
+ else {
+ // simple redraw if text has been replaced with other text
+ redraw(textChangeX, textChangeY, clientArea.width, lineHeight, true);
+ }
+}
+/**
+ * Fixes the widget after a text change local to one line ocurred.
+ * The line has a tab after the text change offset. Bit blitting
+ * and/or redrawing is done as necessary.
+ * <p>
+ *
+ * @param textChangeX x location of the text change
+ * @param textChangeY y location of the text change
+ * @param newTabX the x location of the tab closest to the insert
+ * location as measured with the text change.
+ * @param oldTabX the x location of the tab closest to the insert
+ * location as measured without the text change.
+ */
+void redrawSingleLineTabChange(int textChangeX, int textChangeY, int newTabX, int oldTabX) {
+ Rectangle clientArea = getClientArea();
+ int lineHeight = getLineHeight();
+
+ if (newTabX > oldTabX) {
+ scroll(
+ newTabX, textChangeY, // destination x, y
+ oldTabX, textChangeY, // source x, y
+ clientArea.width, lineHeight, true);
+ redraw(textChangeX, textChangeY, oldTabX - textChangeX, lineHeight, true);
+ }
+ else
+ if (newTabX < oldTabX) {
+ scroll(
+ newTabX, textChangeY, // destination x, y
+ oldTabX, textChangeY, // source x, y
+ clientArea.width, lineHeight, true);
+ redraw(textChangeX, textChangeY, newTabX - textChangeX, lineHeight, true);
+ }
+ else {
+ // tab location unchanged. redraw text between change offset and tab
+ redraw(textChangeX, textChangeY, newTabX - textChangeX, lineHeight, true);
+ }
+}
+/**
+ * Removes the specified extended modify listener.
+ * <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>
+ */
+public void removeExtendedModifyListener(ExtendedModifyListener extendedModifyListener) {
+ checkWidget();
+ if (extendedModifyListener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ removeListener(ExtendedModify, extendedModifyListener);
+}
+/**
+ * Removes the specified line background listener.
+ * <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>
+ */
+public void removeLineBackgroundListener(LineBackgroundListener listener) {
+ checkWidget();
+ if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ removeListener(LineGetBackground, listener);
+ // use default line styler if last user line styler was removed.
+ if (isListening(LineGetBackground) == false && userLineBackground) {
+ StyledTextListener typedListener = new StyledTextListener(defaultLineStyler);
+ addListener(LineGetBackground, typedListener);
+ userLineBackground = false;
+ }
+}
+/**
+ * Removes the specified line style listener.
+ * <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>
+ */
+public void removeLineStyleListener(LineStyleListener listener) {
+ checkWidget();
+ if (listener == null) {
+ SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ }
+ removeListener(LineGetStyle, listener);
+ // use default line styler if last user line styler was removed. Fixes 1G7B1X2
+ if (isListening(LineGetStyle) == false && userLineStyle) {
+ StyledTextListener typedListener = new StyledTextListener(defaultLineStyler);
+ addListener(LineGetStyle, typedListener);
+ userLineStyle = false;
+ }
+}
+/**
+ * Removes the specified modify listener.
+ * <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>
+ */
+public void removeModifyListener(ModifyListener modifyListener) {
+ checkWidget();
+ if (modifyListener == null) {
+ SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ }
+ removeListener(SWT.Modify, modifyListener);
+}
+
+/**
+ * Removes the specified selection listener.
+ * <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>
+ */
+public void removeSelectionListener(SelectionListener listener) {
+ checkWidget();
+ if (listener == null) {
+ SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ }
+ removeListener(SWT.Selection, listener);
+}
+
+/**
+ * Removes the specified verify listener.
+ * <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>
+ */
+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.
+ * <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>
+ */
+public void removeVerifyKeyListener(VerifyKeyListener listener) {
+ if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ removeListener(VerifyKey, listener);
+}
+
+/**
+ * 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>
+ * </ul>
+ */
+public void replaceTextRange(int start, int length, String text) {
+ checkWidget();
+ int contentLength = getCharCount();
+ int end = start + length;
+ Event event = new Event();
+
+ if (start < 0 || start > contentLength || end < 0 || end > contentLength || start > end) {
+ SWT.error(SWT.ERROR_INVALID_RANGE);
+ }
+ 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();
+
+ caretOffset = 0;
+ topIndex = 0;
+ verticalScrollOffset = 0;
+ horizontalScrollOffset = 0;
+ contentWidth = 0;
+ contentWidthIndex = 0;
+ resetSelection();
+ // discard any styles that may have been set by creating a
+ // new default line styler
+ if (defaultLineStyler != null) {
+ removeLineBackgroundListener(defaultLineStyler);
+ removeLineStyleListener(defaultLineStyler);
+ installDefaultLineStyler();
+ }
+ calculateContentWidth();
+ if (verticalBar != null) {
+ verticalBar.setSelection(0);
+ }
+ if (horizontalBar != null) {
+ horizontalBar.setSelection(0);
+ }
+ setScrollBars();
+ setCaretLocation();
+ redraw();
+}
+
+/**
+ * Resets the selection.
+ */
+void resetSelection() {
+ selection.x = selection.y = caretOffset;
+ selectionAnchor = -1;
+}
+
+
+
+/**
+ * Scrolls the widget horizontally.
+ * <p>
+ *
+ * @param pixels number of pixels to scroll, > 0 = scroll left, < 0 scroll right
+ */
+void scrollHorizontal(int pixels) {
+ Rectangle clientArea;
+
+ if (pixels == 0) {
+ return;
+ }
+ clientArea = getClientArea();
+ scroll(
+ pixels * -1, 0, // destination x, y
+ 0, 0, // source x, y
+ clientArea.width, clientArea.height, true);
+ horizontalScrollOffset += pixels;
+ setCaretLocation();
+}
+
+/**
+ * Scrolls the widget horizontally and adjust the horizontal scroll bar to
+ * reflect the new horizontal offset..
+ * <p>
+ *
+ * @param pixels number of pixels to scroll, > 0 = scroll left, < 0 scroll right
+ */
+void scrollHorizontalBar(int pixels) {
+ if (pixels == 0) {
+ return;
+ }
+ ScrollBar horizontalBar = getHorizontalBar();
+ if (horizontalBar != null) {
+ horizontalBar.setSelection(horizontalScrollOffset + pixels);
+ }
+ scrollHorizontal(pixels);
+}
+/**
+ * Selects all the text.
+ * <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 selectAll() {
+ checkWidget();
+ setSelection(new Point(0, Math.max(getCharCount(),0)));
+}
+/**
+ * Replaces/inserts text as defined by the event.
+ * <p>
+ *
+ * @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 == false) {
+ return;
+ }
+ modifyContent(event, true);
+}
+
+/**
+ * Sends the specified selection event.
+ */
+void sendSelectionEvent() {
+ Event event = new Event();
+
+ event.x = selection.x;
+ event.y = selection.y;
+ notifyListeners(SWT.Selection, event);
+}
+
+/**
+ * Moves the Caret to the current caret offset.
+ */
+void setCaretLocation() {
+ int line = content.getLineAtOffset(caretOffset);
+ int lineStartOffset = content.getOffsetAtLine(line);
+ int lineX = getXAtOffset(content.getLine(line), line, caretOffset - lineStartOffset);
+ int lineHeight = getLineHeight();
+
+ // workaround for 1G3AKJO exposed by Leapfrog
+ // causes flashing but works
+ if (caret.isVisible() == false) {
+ setRedraw(true);
+ }
+ caret.setLocation(lineX, line * lineHeight - verticalScrollOffset);
+}
+/**
+ * Sets the caret offset.
+ * <p>
+ * <b>NOTE:</b> If offset is greater than the number of characters of text in the
+ * widget, the value will be ignored.
+ * </p>
+ *
+ * @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>
+ */
+public void setCaretOffset(int offset) {
+ checkWidget();
+ int length = getCharCount();
+
+ if (length > 0 && offset != caretOffset) {
+ if (offset < 0) {
+ caretOffset = 0;
+ }
+ else
+ if (offset > length) {
+ caretOffset = length;
+ }
+ else {
+ caretOffset = offset;
+ }
+ // clear the selection if the caret is moved.
+ // don't notify listeners about the selection change.
+ clearSelection(false);
+ }
+ // always update the caret location. fixes 1G8FODP
+ setCaretLocation();
+}
+
+/**
+ * Sets the content implementation to use for text storage.
+ * <p>
+ *
+ * @param content 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 content) {
+ checkWidget();
+ if (content == null) {
+ SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ }
+ if (this.content != null) {
+ this.content.removeTextChangedListener(textChangedListener);
+ }
+ this.content = content;
+ content.addTextChangedListener(textChangedListener);
+ reset();
+}
+
+
+/**
+ * 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;
+}
+/**
+ * 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();
+ super.setFont(font);
+ if (boldFont != null) {
+ boldFont.dispose();
+ }
+ initializeFonts();
+ lineHeight = 0;
+ contentWidth = 0;
+ contentWidthIndex = 0;
+ setTabs(getTabs());
+ calculateContentWidth();
+ calculateScrollBars();
+ setTabs(tabLength);
+ caret.setSize(0, getLineHeight());
+ 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();
+ int clientAreaWidth = getClientArea().width;
+
+ 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) {
+ // 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 > contentWidth - clientAreaWidth) {
+ offset = Math.max(0, contentWidth - clientAreaWidth);
+ }
+ }
+ scrollHorizontalBar(offset - horizontalScrollOffset);
+}
+/**
+ * Sets the background color of the specified lines.
+ * 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. Should not be called if a LineBackgroundListener
+ * has been set since the listener maintains the line backgrounds.
+ * <p>
+ * During text changes, when entire lines are inserted or removed, the line
+ * background colors that are associated with the lines after the change
+ * will "move" with their respective text. For all other text changes,
+ * line background colors 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();
+ int partialBottomIndex = getPartialBottomIndex();
+ int lineHeight = getLineHeight();
+
+ // this API can not be used if the client is providing the line background
+ if (userLineBackground) {
+ return;
+ }
+ if (startLine < 0 || startLine + lineCount > content.getLineCount()) {
+ SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ }
+ defaultLineStyler.setLineBackground(startLine, lineCount, background);
+ // do nothing if redraw range is completely invisible
+ if (startLine > partialBottomIndex || startLine + lineCount - 1 < topIndex) {
+ return;
+ }
+ // only redraw visible lines
+ if (startLine < topIndex) {
+ lineCount -= topIndex - startLine;
+ startLine = topIndex;
+ }
+ if (startLine + lineCount - 1 > partialBottomIndex) {
+ lineCount = partialBottomIndex - startLine + 1;
+ }
+ startLine -= topIndex;
+ redraw(
+ 0, startLine * lineHeight,
+ getClientArea().width, lineCount * lineHeight, true);
+}
+/**
+ * Sets the background of the specified GC for a line rendering operation,
+ * if it is not already set.
+ * </p>
+ *
+ * @param gc GC to set the background color in
+ * @param currentBackground background color currently set in gc
+ * @param newBackground new background color of gc
+ */
+Color setLineBackground(GC gc, Color currentBackground, Color newBackground) {
+ if (currentBackground.equals(newBackground) == false) {
+ gc.setBackground(newBackground);
+ }
+ return newBackground;
+}
+
+/**
+ * Sets the font of the specified GC if it is not already set.
+ * </p>
+ *
+ * @param gc GC to set the font in
+ * @param currentFont font data of font currently set in gc
+ * @param style desired style of the font in gc. Can be one of
+ * SWT.NORMAL, SWT.ITALIC, SWT.BOLD
+ * @return the font data of the font set in "gc". Same as "currentFont" if
+ * currentFont.getStyle() == "style".
+ */
+FontData setLineFont(GC gc, FontData currentFont, int style) {
+ if (currentFont.getStyle() != style) {
+ if (style == SWT.BOLD) {
+ currentFont.setStyle(style);
+ gc.setFont(boldFont);
+ }
+ else
+ if (style == SWT.NORMAL) {
+ currentFont.setStyle(style);
+ gc.setFont(regularFont);
+ }
+ }
+ return currentFont;
+}
+
+/**
+ * Sets the foreground of the specified GC for a line rendering operation,
+ * if it is not already set.
+ * </p>
+ *
+ * @param gc GC to set the foreground color in
+ * @param currentForeground foreground color currently set in gc
+ * @param newForeground new foreground color of gc
+ */
+Color setLineForeground(GC gc, Color currentForeground, Color newForeground) {
+ if (currentForeground.equals(newForeground) == false) {
+ gc.setForeground(newForeground);
+ }
+ return newForeground;
+}
+
+
+/**
+ * Adjusts the scroll bar maximum and page size to reflect content
+ * width/length changes.
+ */
+void setScrollBars() {
+ ScrollBar verticalBar = getVerticalBar();
+ ScrollBar horizontalBar = getHorizontalBar();
+ Rectangle clientArea = getClientArea();
+ final int INACTIVE = 1;
+
+ if (verticalBar != null) {
+ int maximum = content.getLineCount() * getVerticalIncrement();
+ // 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 (clientArea.height < maximum) {
+ verticalBar.setValues(
+ verticalBar.getSelection(),
+ verticalBar.getMinimum(),
+ maximum,
+ clientArea.height, // thumb size
+ verticalBar.getIncrement(),
+ clientArea.height); // page size
+ }
+ else
+ if (verticalBar.getThumb() != INACTIVE || verticalBar.getMaximum() != INACTIVE) {
+ verticalBar.setValues(
+ verticalBar.getSelection(),
+ verticalBar.getMinimum(),
+ INACTIVE,
+ INACTIVE,
+ verticalBar.getIncrement(),
+ INACTIVE);
+ }
+ }
+ if (horizontalBar != null) {
+ // 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 (clientArea.width < contentWidth) {
+ horizontalBar.setValues(
+ horizontalBar.getSelection(),
+ horizontalBar.getMinimum(),
+ contentWidth, // maximum
+ clientArea.width, // thumb size
+ horizontalBar.getIncrement(),
+ clientArea.width); // page size
+ }
+ 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).
+ * <p>
+ *
+ * @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_RANGE when start is outside the widget content
+ * </ul>
+ */
+public void setSelection (int start) {
+ 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
+ * @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_RANGE when start and end is outside the widget content
+ * </ul>
+ */
+public void setSelection(Point point) {
+ checkWidget();
+ if (point == null) SWT.error (SWT.ERROR_NULL_ARGUMENT);
+ setSelection(point.x, point.y);
+}
+/**
+ * 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
+ * @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_RANGE when start and end is outside the widget content
+ * </ul>
+ */
+public void setSelection(int start, int end) {
+ checkWidget();
+ int contentLength = getCharCount();
+
+ if (start > end || start < 0 || end > contentLength) {
+ SWT.error(SWT.ERROR_INVALID_RANGE);
+ }
+ internalSetSelection(start, end - start);
+ // always update the caret location. fixes 1G8FODP
+ setCaretLocation();
+ showSelection();
+}
+/**
+ * Sets the selection. 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, start <= start + length <= getCharCount()
+ * must be true.
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - 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 range specified by start and length is outside the widget content
+ * </ul>
+ */
+public void setSelectionRange(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);
+ }
+ internalSetSelection(start, length);
+ // always update the caret location. fixes 1G8FODP
+ setCaretLocation();
+}
+/**
+ * Sets the selection.
+ * 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, start <= start + length
+ * <= getCharCount() must be true.
+ */
+void internalSetSelection(int start, int length) {
+ int end = start + length;
+
+ if (selection.x != start || selection.y != end) {
+ clearSelection(false);
+ selectionAnchor = selection.x = start;
+ caretOffset = selection.y = end;
+ if (length > 0) {
+ redrawRange(selection.x, selection.y - selection.x, true);
+ }
+ }
+}
+/**
+ * Adds the specified style. 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>
+ * 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();
+ boolean redrawFirstLine = false;
+ boolean redrawLastLine = false;
+
+ // this API can not be used if the client is providing the line styles
+ if (userLineStyle) {
+ return;
+ }
+ // check the range, make sure it falls within the range of the
+ // text
+ if (range != null && range.start + range.length > content.getCharCount()) {
+ SWT.error(SWT.ERROR_INVALID_RANGE);
+ }
+ if (range != null) {
+ // the first and last line needs to be redrawn completely if the
+ // font style is changing from SWT.NORMAL to something else or
+ // vice versa. fixes 1G7M5WE.
+ int rangeEnd = range.start + range.length;
+ int firstLine = content.getLineAtOffset(range.start);
+ int lastLine = content.getLineAtOffset(rangeEnd);
+ int firstLineOffset = content.getOffsetAtLine(firstLine);
+ if (isStyleChanging(range, range.start, Math.min(rangeEnd, firstLineOffset + content.getLine(firstLine).length()))) {
+ redrawFirstLine = true;
+ }
+ if (lastLine != firstLine) {
+ int lastLineOffset = content.getOffsetAtLine(lastLine);
+ if (isStyleChanging(range, lastLineOffset, rangeEnd)) {
+ redrawLastLine = true;
+ }
+ }
+ }
+ defaultLineStyler.setStyleRange(range);
+ if (range != null) {
+ int lineHeight = getLineHeight();
+
+ redrawRange(range.start, range.length, true);
+ if (redrawFirstLine) {
+ // redraw starting at the style change start offset since
+ // single line text changes, followed by style changes will
+ // flash otherwise
+ int firstLine = content.getLineAtOffset(range.start);
+ int firstLineOffset = content.getOffsetAtLine(firstLine);
+ String firstLineText = content.getLine(firstLine);
+ int redrawX = getXAtOffset(firstLineText, firstLine, range.start - firstLineOffset);
+ int redrawY = firstLine * lineHeight - verticalScrollOffset;
+ redraw(redrawX, redrawY, getClientArea().width, lineHeight, true);
+ }
+ if (redrawLastLine) {
+ // redraw the whole line if the font style changed on the last line
+ int lastLine = content.getLineAtOffset(range.start + range.length);
+ int redrawY = lastLine * lineHeight - verticalScrollOffset;
+ redraw(0, redrawY, getClientArea().width, lineHeight, true);
+ }
+ }
+ else {
+ redraw();
+ }
+ // make sure that the caret is positioned correctly.
+ // caret location may change if font style changes.
+ // fixes 1G8FODP
+ setCaretLocation();
+}
+
+/**
+ * Sets styles to be used for rendering the widget content. All styles
+ * will be replaced with the given set of styles.
+ * <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.
+ * @exception SWTException <ul>
+ * <li>ERROR_WIDGET_DISPOSED - 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>
+ * <li>ERROR_INVALID_RANGE when the last of the style ranges is outside the valid range (> getCharCount())</li>
+ * </ul>
+ */
+public void setStyleRanges(StyleRange[] ranges) {
+ checkWidget();
+ // this API can not be used if the client is providing the line styles
+ if (userLineStyle) {
+ return;
+ }
+ if (ranges == null) {
+ SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ }
+ // check the last range, make sure it falls within the range of the
+ // current text
+ if (ranges.length != 0) {
+ StyleRange last = ranges[ranges.length-1];
+ if (last.start + last.length > content.getCharCount()) {
+ SWT.error(SWT.ERROR_INVALID_RANGE);
+ }
+ }
+ defaultLineStyler.setStyleRanges(ranges);
+ redraw(); // bogus! only redraw affected area to avoid flashing
+ // make sure that the caret is positioned correctly.
+ // caret location may change if font style changes.
+ // fixes 1G8FODP
+ setCaretLocation();
+}
+
+/**
+ * Sets the tab width.
+ * <p>
+ *
+ * @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;
+ calculateTabWidth();
+ if (caretOffset > 0) {
+ caretOffset = 0;
+ showCaret();
+ clearSelection(false);
+ }
+ 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>
+ */
+public void setText(String text) {
+ checkWidget();
+ Event event = new Event();
+
+ event.start = 0;
+ event.end = getCharCount();
+ event.text = text;
+ event.doit = true;
+ notifyListeners(SWT.Verify, event);
+ if (event.doit) {
+ String replacedText = content.getTextRange(event.start, event.end - event.start);
+ content.setText(event.text);
+ event.end = event.start + event.text.length();
+ event.text = replacedText;
+ notifyListeners(SWT.Modify, event);
+ }
+}
+
+/**
+ * Sets the text limit.
+ * <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 index new top index. Must be between 0 and getLineCount() -
+ * visible lines per page. 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();
+ int lineCount = content.getLineCount();
+ int pageSize = Math.min(lineCount, getLineCountWhole());
+
+ if (getCharCount() == 0) {
+ return;
+ }
+ if (topIndex < 0) {
+ topIndex = 0;
+ }
+ else
+ if (topIndex > lineCount - pageSize) {
+ topIndex = lineCount - pageSize;
+ }
+ setVerticalScrollOffset(topIndex * getVerticalIncrement(), true);
+ // set the top index directly in case setVerticalScrollOffset didn't
+ // (ie. because the widget is not yet visible)
+ this.topIndex = topIndex;
+}
+/**
+ * Scrolls the widget vertically.
+ * <p>
+ *
+ * @param pixels number of pixels to scroll. > 0 = scroll up, < 0 scroll down
+ */
+void setVerticalScrollOffset(int pixelOffset, boolean adjustScrollBar) {
+ Rectangle clientArea;
+ ScrollBar verticalBar = getVerticalBar();
+ int verticalIncrement = getVerticalIncrement();
+
+ if (pixelOffset == verticalScrollOffset) {
+ return;
+ }
+ if (verticalBar != null && adjustScrollBar) {
+ verticalBar.setSelection(pixelOffset);
+ }
+ clientArea = getClientArea();
+ scroll(
+ 0, 0, // destination x, y
+ 0, pixelOffset - verticalScrollOffset, // source x, y
+ clientArea.width, clientArea.height, true);
+ if (verticalIncrement != 0) {
+ int oldTopIndex = topIndex;
+
+ topIndex = (int) Math.ceil((float) pixelOffset / verticalIncrement);
+ if (topIndex != oldTopIndex) {
+ int lineCount = content.getLineCount();
+ int visibleItemCount = (int) Math.ceil((float) clientArea.height / verticalIncrement);
+ int oldBottomIndex = Math.min(oldTopIndex + visibleItemCount, lineCount);
+ int newItemCount = topIndex - oldTopIndex;
+
+ if (Math.abs(newItemCount) > visibleItemCount) {
+ calculateContentWidth();
+ }
+ else {
+ if (newItemCount > 0) {
+ newItemCount = Math.min(newItemCount, lineCount - oldBottomIndex);
+ calculateContentWidth(oldBottomIndex, newItemCount);
+ }
+ else
+ if (newItemCount < 0) {
+ // make sure calculation range does not exceed number of lines
+ // fixes 1GBKCLF
+ calculateContentWidth(topIndex, Math.min(newItemCount * -1, lineCount - topIndex));
+ }
+ }
+ setScrollBars();
+ }
+ }
+ verticalScrollOffset = pixelOffset;
+ setCaretLocation();
+}
+/**
+ * Sets the caret location and scrolls the caret offset into view.
+ */
+void showCaret() {
+ int line = content.getLineAtOffset(caretOffset);
+ int lineOffset = content.getOffsetAtLine(line);
+ int offsetInLine = caretOffset - lineOffset;
+ String lineText = content.getLine(line);
+ int xAtOffset = getXAtOffset(lineText, line, offsetInLine);
+ int clientAreaWidth = getClientArea().width;
+ int verticalIncrement = getVerticalIncrement();
+ int horizontalIncrement = clientAreaWidth / 4;
+ boolean scrolled = false;
+
+ if (xAtOffset < 0) {
+ // always make 1/4 of a page visible
+ xAtOffset = Math.max(horizontalScrollOffset * -1, xAtOffset - horizontalIncrement);
+ scrollHorizontalBar(xAtOffset);
+ scrolled = true;
+ }
+ else
+ if (xAtOffset > clientAreaWidth) {
+ // always make 1/4 of a page visible
+ xAtOffset = Math.min(contentWidth - horizontalScrollOffset, xAtOffset + horizontalIncrement);
+ scrollHorizontalBar(xAtOffset - clientAreaWidth);
+ scrolled = true;
+ }
+ if (line < topIndex) {
+ setVerticalScrollOffset(line * verticalIncrement, true);
+ scrolled = true;
+ }
+ else
+ if (line > getBottomIndex()) {
+ setVerticalScrollOffset((line - getBottomIndex()) * verticalIncrement + verticalScrollOffset, true);
+ scrolled = true;
+ }
+ if (scrolled == false) {
+ caret.setLocation(xAtOffset, line * getLineHeight() - verticalScrollOffset);
+ }
+}
+/**
+ * Scrolls the specified offset into view.
+ * <p>
+ *
+ * @param offset offset that should be scolled into view
+ */
+void showOffset(int offset) {
+ int line = content.getLineAtOffset(offset);
+ int lineOffset = content.getOffsetAtLine(line);
+ int offsetInLine = offset - lineOffset;
+ String lineText = content.getLine(line);
+ int xAtOffset = getXAtOffset(lineText, line, offsetInLine);
+ int clientAreaWidth = getClientArea().width;
+ int verticalIncrement = getVerticalIncrement();
+ int horizontalIncrement = clientAreaWidth / 4;
+
+ if (xAtOffset < 0) {
+ // always make 1/4 of a page visible
+ xAtOffset = Math.max(horizontalScrollOffset * -1, xAtOffset - horizontalIncrement);
+ scrollHorizontalBar(xAtOffset);
+ }
+ else
+ if (xAtOffset > clientAreaWidth) {
+ // always make 1/4 of a page visible
+ xAtOffset = Math.min(contentWidth - horizontalScrollOffset, xAtOffset + horizontalIncrement);
+ scrollHorizontalBar(xAtOffset - clientAreaWidth);
+ }
+ if (line < topIndex) {
+ setVerticalScrollOffset(line * verticalIncrement, true);
+ }
+ else
+ if (line > getBottomIndex()) {
+ setVerticalScrollOffset((line - getBottomIndex()) * verticalIncrement + verticalScrollOffset, true);
+ }
+}
+
+/**
+ * Scrolls the selection into view.
+ * <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();
+ showOffset(selection.x);
+ showOffset(selection.y);
+}
+
+/**
+ * Returns the width of the specified text. Expand tabs to tab stops using
+ * the widget tab width.
+ * <p>
+ *
+ * @param line line to be measured.
+ * @param lineIndex index of the line relative to the first kine of the
+ * document
+ * @param length number of characters to measure. Tabs are counted
+ * as one character in this parameter.
+ * @param gc GC to use for measuring text
+ * @return width of the text with tabs expanded to tab stops or 0 if the
+ * length is beyond the text length.
+ */
+int textWidth(String line, int lineIndex, int length, GC gc) {
+ StyleRange[] styles = null;
+ int lineOffset = content.getOffsetAtLine(lineIndex);
+ StyledTextEvent event = getLineStyleData(lineOffset, line);
+
+ if (event != null) {
+ styles = filterLineStyles(event.styles);
+ }
+ return textWidth(line, lineOffset, 0, length, styles, 0, gc);
+}
+
+/**
+ * Returns the width of the specified text. Expand tabs to tab stops using
+ * the widget tab width.
+ * <p>
+ *
+ * @param text text to be measured.
+ * @param lineOffset offset of the first character in the line.
+ * @param startOffset offset of the character to start measuring and
+ * expand tabs.
+ * @param length number of characters to measure. Tabs are counted
+ * as one character in this parameter.
+ * @param styles line styles
+ * @param startXOffset x position of "startOffset" in "text". Used for
+ * calculating tab stops
+ * @param gc GC to use for measuring text
+ * @return width of the text with tabs expanded to tab stops or 0 if the
+ * startOffset or length is outside the specified text.
+ */
+int textWidth(String text, int lineOffset, int startOffset, int length, StyleRange[] lineStyles, int startXOffset, GC gc) {
+ int paintX = 0;
+ int endOffset = startOffset + length;
+ int textLength = text.length();
+ FontData fontData = gc.getFont().getFontData()[0];
+
+ if (startOffset < 0 || startOffset >= textLength || startOffset + length > textLength) {
+ return paintX;
+ }
+ for (int i = startOffset; i < endOffset; i++) {
+ int tabIndex = text.indexOf(TAB, i);
+ // is tab not present or past the rendering range?
+ if (tabIndex == -1 || tabIndex > endOffset) {
+ tabIndex = endOffset;
+ }
+ if (tabIndex != i) {
+ String tabSegment = text.substring(i, tabIndex);
+ if (lineStyles != null) {
+ paintX = styledTextWidth(tabSegment, lineOffset + i, lineStyles, paintX, 0, gc, false);
+ }
+ else {
+ fontData = setLineFont(gc, fontData, SWT.NORMAL);
+ paintX += gc.stringExtent(tabSegment).x;
+ }
+ if (tabIndex != endOffset && tabWidth > 0) {
+ paintX += tabWidth;
+ paintX -= (startXOffset + paintX) % tabWidth;
+ }
+ i = tabIndex;
+ }
+ else
+ if (tabWidth > 0) {
+ paintX += tabWidth;
+ paintX -= (startXOffset + paintX) % tabWidth;
+ }
+ }
+ return paintX;
+}
+
+/**
+ * Measures the text as rendered at the specified location. Expand tabs to tab stops using
+ * the widget tab width.
+ * <p>
+ *
+ * @param text text to draw
+ * @param textStartOffset offset of the first character in text relative
+ * to the first character in the document
+ * @param lineStyles styles of the line
+ * @param paintX x location to start drawing at
+ * @param paintY y location to draw at
+ * @param gc GC to draw on
+ * @param drawText true=measure and draw text, false=measure text only
+ * @return x location where drawing stopped or 0 if the startOffset or
+ * length is outside the specified text.
+ */
+int styledTextWidth(String text, int textStartOffset, StyleRange[] lineStyles, int paintX, int paintY, GC gc, boolean drawText) {
+ FontData fontData = gc.getFont().getFontData()[0];
+ String textSegment;
+ int textLength = text.length();
+ int textIndex = 0;
+
+ for (int styleIndex = 0; styleIndex < lineStyles.length; styleIndex++) {
+ StyleRange style = lineStyles[styleIndex];
+ int styleSegmentStart = style.start - textStartOffset;
+ int textEnd;
+ if (styleSegmentStart + style.length < 0) {
+ continue;
+ }
+ if (styleSegmentStart >= textLength) {
+ break;
+ }
+ // is there a style for the current string position?
+ if (textIndex < styleSegmentStart) {
+ fontData = setLineFont(gc, fontData, SWT.NORMAL);
+ textSegment = text.substring(textIndex, styleSegmentStart);
+ if (drawText) {
+ gc.drawString(textSegment, paintX - horizontalScrollOffset, paintY, true);
+ }
+ paintX += gc.stringExtent(textSegment).x;
+ textIndex = styleSegmentStart;
+ }
+ textEnd = Math.min(textLength, styleSegmentStart + style.length);
+ fontData = setLineFont(gc, fontData, style.fontStyle);
+ textSegment = text.substring(textIndex, textEnd);
+ if (drawText) {
+ gc.drawString(textSegment, paintX - horizontalScrollOffset, paintY, true);
+ }
+ paintX += gc.stringExtent(textSegment).x;
+ textIndex = textEnd;
+ }
+ // is there unmeasured and unstyled text?
+ if (textIndex < textLength) {
+ fontData = setLineFont(gc, fontData, SWT.NORMAL);
+ textSegment = text.substring(textIndex, textLength);
+ if (drawText) {
+ gc.drawString(textSegment, paintX - horizontalScrollOffset, paintY, true);
+ }
+ paintX += gc.stringExtent(textSegment).x;
+ }
+ return paintX;
+}
+
+/**
+ * Updates the selection and caret position depending on the text change.
+ * 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
+ return;
+ }
+ if (selection.x < startOffset) {
+ // clear selection fragment before text change
+ redrawRange(selection.x, startOffset - selection.x, true);
+ }
+ if (selection.y > startOffset + replacedLength) {
+ // clear selection fragment after text change
+ int netNewLength = newLength - replacedLength;
+ int redrawStart = startOffset + newLength;
+ redrawRange(redrawStart, selection.y + netNewLength - redrawStart, true);
+ }
+ if (selection.y > startOffset && selection.x < startOffset + replacedLength) {
+ // selection intersects replaced text. set caret behind text change
+ caretOffset = selection.x = selection.y = startOffset + newLength;
+ // always update the caret location. fixes 1G8FODP
+ setCaretLocation();
+ }
+ else {
+ // move selection to keep same text selected
+ internalSetSelection(selection.x + newLength - replacedLength, selection.y - selection.x);
+ // always update the caret location. fixes 1G8FODP
+ setCaretLocation();
+ }
+}
+/**
+ * Returns whether the widget can have only one line.
+ * <p>
+ *
+ * @return true if widget can have only one line, false if widget can have
+ * multiple lines
+ */
+boolean isSingleLine() {
+ return (getStyle() & SWT.SINGLE) != 0;
+}
+} \ No newline at end of file
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..839366cbf2
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/StyledTextContent.java
@@ -0,0 +1,177 @@
+package org.eclipse.swt.custom;
+/*
+ * Licensed Materials - Property of IBM,
+ * (c) Copyright IBM Corp 2000, 2001
+ */
+
+/* Imports */
+import org.eclipse.swt.events.*;
+
+public interface StyledTextContent {
+
+/**
+ * Called by StyledText to add itself as an Observer to content changes.
+ * Implementors should send a TextChangedEvent when changes to the content
+ * occur. The widget only updates the screen when it receives a TextChangedEvent.
+ * <p>
+ *
+ * @param listener the listener
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_NULL_ARGUMENT when listener is null</li>
+ * </ul>
+ */
+public void addTextChangedListener(TextChangedListener 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 character offset without delimiters.
+ * <p>
+ *
+ * @param offset offset of the line to return. Does not include delimiters
+ * of preceeding lines. Offset 0 is the first character of the document.
+ * @return the line text without delimiters
+ */
+public String getLine(int offset);
+
+/**
+ * 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
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_NULL_ARGUMENT when listener is null</li>
+ * </ul>
+ */
+public void removeTextChangedListener(TextChangedListener listener);
+
+/**
+ * Replace the text with "newText" starting at position "start"
+ * for a length of "replaceLength".
+ * <p>
+ * Implementors have to notify TextChanged listeners after the content has
+ * been updated. The TextChangedEvent should be set as follows:
+ * <ul>
+ * <li>event.type = SWT.TextReplaced
+ * <li>event.start = start of the replaced text
+ * <li>event.numReplacedLines = number of replaced lines
+ * <li>event.numNewLines = number of new lines
+ * <li>event.replacedLength = length of the replaced text
+ * <li>event.newLength = length of the new text
+ * </ul>
+ * <b>NOTE:</b> numNewLines is the number of inserted lines and numReplacedLines is
+ * the number of deleted lines based on the change that occurs visually. For
+ * example:
+ * <ul>
+ * <li>(replacedText, newText) ==> (numReplacedLines, numNewLines)
+ * <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 preceeding lines, offset 0 is the first character of the document
+ * @param replaceLength start offset of text to replace
+ * @param newText start offset of text to replace
+ */
+public void replaceTextRange(int start, int replaceLength, String text);
+
+/**
+ * Set text to "text".
+ * Implementors have to notify TextChanged listeners after the content has
+ * been updated. The TextChangedEvent being sent must have the event type
+ * set to SWT.TextSet.
+ * <p>
+ *
+ * @param text the new text
+ */
+public void setText (String text);
+} \ No newline at end of file
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..52c6ca7c8d
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/StyledTextEvent.java
@@ -0,0 +1,31 @@
+package org.eclipse.swt.custom;
+/*
+ * Licensed Materials - Property of IBM,
+ * (c) Copyright IBM Corp 2000, 2001
+ */
+
+/* Imports */
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.widgets.*;
+
+/**
+ *
+ */
+class StyledTextEvent extends Event {
+ // used by LineStyleEvent
+ StyleRange[] styles;
+ // used by LineBackgroundEvent
+ Color lineBackground;
+ // used by TextChangedEvent
+ int replacedCharCount;
+ int newCharCount;
+ int replacedLineCount;
+ int newLineCount;
+
+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..7514f0bb44
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/StyledTextListener.java
@@ -0,0 +1,62 @@
+package org.eclipse.swt.custom;
+/*
+ * Licensed Materials - Property of IBM,
+ * (c) Copyright IBM Corp 2000, 2001
+ */
+
+/* Imports */
+import org.eclipse.swt.events.*;
+import org.eclipse.swt.widgets.*;
+import java.util.*;
+
+class StyledTextListener extends TypedListener {
+/**
+ */
+StyledTextListener(EventListener listener) {
+ super(listener);
+}
+/**
+ * Process StyledText events by invoking the event's handler.
+ */
+public void handleEvent(Event e) {
+ TextChangedEvent textChangedEvent;
+
+ switch (e.type) {
+ case StyledText.ExtendedModify:
+ ExtendedModifyEvent extendedModifyEvent = new ExtendedModifyEvent((StyledTextEvent) e);
+ ((ExtendedModifyListener) eventListener).modifyText(extendedModifyEvent);
+ break;
+
+ case StyledText.LineGetStyle:
+ LineStyleEvent lineStyleEvent = new LineStyleEvent((StyledTextEvent) e);
+ ((LineStyleListener) eventListener).lineGetStyle(lineStyleEvent);
+ ((StyledTextEvent) e).styles = lineStyleEvent.styles;
+ break;
+
+ case StyledText.LineGetBackground:
+ LineBackgroundEvent lineBgEvent = new LineBackgroundEvent((StyledTextEvent) e);
+ ((LineBackgroundListener) eventListener).lineGetBackground(lineBgEvent);
+ ((StyledTextEvent) e).lineBackground = lineBgEvent.lineBackground;
+ break;
+
+ case StyledText.VerifyKey:
+ VerifyEvent verifyEvent = new VerifyEvent(e);
+ ((VerifyKeyListener) eventListener).verifyKey(verifyEvent);
+ e.doit = verifyEvent.doit;
+ break;
+
+
+ case StyledText.TextReplaced:
+ textChangedEvent = new TextChangedEvent((StyledTextContent)e.data, (StyledTextEvent) e);
+ ((TextChangedListener) eventListener).textReplaced(textChangedEvent);
+ break;
+
+ case StyledText.TextSet:
+ textChangedEvent = new TextChangedEvent((StyledTextContent)e.data, (StyledTextEvent) e);
+ ((TextChangedListener) eventListener).textSet(textChangedEvent);
+ break;
+ }
+}
+}
+
+
diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/StyledTextPrinter.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/StyledTextPrinter.java
new file mode 100755
index 0000000000..f9e18e66ef
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/StyledTextPrinter.java
@@ -0,0 +1,344 @@
+package org.eclipse.swt.custom;
+
+import java.io.*;
+import java.util.*;
+import org.eclipse.swt.*;
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.widgets.*;
+import org.eclipse.swt.events.*;
+import org.eclipse.swt.layout.*;
+import org.eclipse.swt.custom.*;
+import org.eclipse.swt.printing.*;
+
+class StyledTextPrinter {
+ class RTFState {
+ int fontStyle;
+ int foreground;
+ int background;
+ }
+ Vector savedState = new Vector();
+
+ StyledText styledText;
+ Printer printer;
+ GC gc;
+
+ String rtf;
+ int index, end;
+ StringBuffer wordBuffer;
+
+ /* We can optimize for fonts because we know styledText only has one font.
+ * As soon as we know the font name and point size, we will create and store
+ * Fonts for the following styles: normal, bold, italic, and bold italic.
+ */
+ Font fontTable[][] = new Font[1][4];
+ boolean creatingFontTable = false;
+ String fontName;
+ int fontSize;
+ int currentFontStyle = SWT.NORMAL;
+ int currentFont = -1;
+ int defaultFont = -1;
+
+ Vector colorTable = new Vector();
+ boolean creatingColorTable = false;
+ int red, green, blue;
+ int currentForeground = -1;
+ int currentBackground = -1;
+
+ String tabs = "";
+ int lineHeight = 0;
+ int tabWidth = 0;
+ int leftMargin, rightMargin, topMargin, bottomMargin;
+ int x, y;
+
+ public StyledTextPrinter(StyledText styledText) {
+ this.styledText = styledText;
+ }
+
+ public void print() {
+ printer = new Printer();
+ if (printer.startJob("StyledText")) {
+ Rectangle clientArea = printer.getClientArea();
+ Rectangle trim = printer.computeTrim(0, 0, 0, 0);
+ Point dpi = printer.getDPI();
+ leftMargin = dpi.x + trim.x; // one inch from left side of paper
+ rightMargin = clientArea.width - dpi.x + trim.x + trim.width; // one inch from right side of paper
+ topMargin = dpi.y + trim.y; // one inch from top edge of paper
+ bottomMargin = clientArea.height - dpi.y + trim.y + trim.height; // one inch from bottom edge of paper
+
+ /* Create a buffer for computing tab width. */
+ int tabSize = styledText.getTabs();
+ StringBuffer tabBuffer = new StringBuffer(tabSize);
+ for (int i = 0; i < tabSize; i++) tabBuffer.append(' ');
+ tabs = tabBuffer.toString();
+
+ /* Get RTF from the StyledText, determine what fonts and colors we need, and print. */
+ gc = new GC(printer);
+ x = leftMargin;
+ y = topMargin;
+ printStyledTextRTF();
+ printer.endJob();
+
+ /* Cleanup */
+ gc.dispose();
+ for (int i = 0; i < 4; i++) {
+ fontTable[currentFont][i].dispose();
+ }
+ for (int i = 0; i < colorTable.size(); i++) {
+ ((Color)colorTable.elementAt(i)).dispose();
+ }
+ }
+ printer.dispose();
+ }
+
+ void printStyledTextRTF() {
+ rtf = styledText.getRtf();
+ end = rtf.length();
+ index = 0;
+ wordBuffer = new StringBuffer();
+ while (index < end) {
+ char c = rtf.charAt(index);
+ index++;
+ switch (c) {
+ case '\\':
+ printWordBuffer();
+ parseControlWord();
+ break;
+ case '{':
+ printWordBuffer();
+ saveState();
+ break;
+ case '}':
+ printWordBuffer();
+ restoreState();
+ break;
+ case 0x0a:
+ case 0x0d:
+ printWordBuffer();
+ break;
+ default:
+ parseChar(c);
+ }
+ }
+ }
+
+ void parseControlWord() {
+ if (index >= end) return;
+ char c = rtf.charAt(index);
+ index++;
+ if (!Character.isLetter(c)) {
+ handleControlSymbol(c);
+ return;
+ }
+ StringBuffer controlWord = new StringBuffer();
+ controlWord.append(c);
+ while (index < end) {
+ c = rtf.charAt(index);
+ index++;
+ if (!Character.isLetter(c)) break;
+ controlWord.append(c);
+ }
+ boolean isNegative = false;
+ if (c == '-') {
+ isNegative = true;
+ c = rtf.charAt(index);
+ index++;
+ }
+ boolean hasParameter = false;
+ StringBuffer paramBuffer = new StringBuffer();
+ int parameter = 0;
+ if (Character.isDigit(c)) {
+ hasParameter = true;
+ paramBuffer.append(c);
+ while (index < end) {
+ c = rtf.charAt(index);
+ index++;
+ if (!Character.isDigit(c)) break;
+ paramBuffer.append(c);
+ }
+ try {
+ parameter = Integer.valueOf(paramBuffer.toString()).intValue();
+ } catch (NumberFormatException e) {}
+ if (isNegative) parameter = -parameter;
+ }
+ if (c != ' ') index--;
+ if (hasParameter) {
+ handleControlWord(controlWord.toString(), parameter);
+ } else {
+ handleControlWord(controlWord.toString());
+ }
+ }
+
+ void parseChar(char c) {
+ if (c == 0) return;
+ if (c == ';') {
+ if (creatingFontTable) {
+ fontName = wordBuffer.toString();
+ wordBuffer = new StringBuffer();
+ creatingFontTable = false;
+ return;
+ }
+ if (creatingColorTable) {
+ colorTable.addElement(new Color(printer, red, green, blue));
+ red = green = blue = 0;
+ return;
+ }
+ }
+ if (c != '\t') {
+ wordBuffer.append(c);
+ }
+ if (!Character.isLetterOrDigit(c) && !creatingFontTable) {
+ printWordBuffer();
+ if (c == '\t') {
+ x += tabWidth;
+ }
+ }
+ }
+
+ void printWordBuffer() {
+ if (wordBuffer.length() > 0) {
+ String word = wordBuffer.toString();
+ int wordWidth = gc.stringExtent(word).x;
+ if (x + wordWidth > rightMargin) {
+ /* word doesn't fit on current line, so wrap */
+ newline();
+ }
+ gc.drawString(word, x, y, true);
+ x += wordWidth;
+ wordBuffer = new StringBuffer();
+ }
+ }
+
+ void handleControlSymbol(char c) {
+ switch (c) {
+ case '\\':
+ case '{':
+ case '}':
+ parseChar(c);
+ }
+ }
+
+ void handleControlWord(String controlWord) {
+ if (controlWord.equals("par")) newline();
+ else if (controlWord.equals("b")) setFontStyle(currentFontStyle | SWT.BOLD);
+ else if (controlWord.equals("i")) setFontStyle(currentFontStyle | SWT.ITALIC);
+ else if (controlWord.equals("fnil")) setFont(defaultFont);
+ else if (controlWord.equals("fonttbl")) createFontTable();
+ else if (controlWord.equals("colortbl")) createColorTable();
+ }
+
+ void handleControlWord(String controlWord, int parameter) {
+ if (controlWord.equals("highlight")) setBackground(parameter);
+ else if (controlWord.equals("cf")) setForeground(parameter);
+ else if (controlWord.equals("b")) setFontStyle(currentFontStyle & ~SWT.BOLD);
+ else if (controlWord.equals("i")) setFontStyle(currentFontStyle & ~SWT.ITALIC);
+ else if (controlWord.equals("f")) setFont(parameter);
+ else if (controlWord.equals("fs")) setFontSize(parameter);
+ else if (controlWord.equals("red")) red = parameter;
+ else if (controlWord.equals("green")) green = parameter;
+ else if (controlWord.equals("blue")) blue = parameter;
+ else if (controlWord.equals("deff")) setDefaultFont(parameter);
+ }
+
+ void setDefaultFont(int number) {
+ defaultFont = number;
+ }
+
+ void setFont(int number) {
+ currentFont = number;
+ }
+
+ void createFontTable() {
+ creatingFontTable = true;
+ currentFont = 0;
+ }
+
+ void setFontSize(int size) {
+ fontSize = size / 2;
+ createFonts();
+ }
+
+ void createFonts() {
+ if (fontName != null && fontSize != -1) {
+ // currentFont must already be set
+ fontTable[currentFont][0] = new Font(printer, fontName, fontSize, SWT.NORMAL);
+ fontTable[currentFont][1] = new Font(printer, fontName, fontSize, SWT.BOLD);
+ fontTable[currentFont][2] = new Font(printer, fontName, fontSize, SWT.ITALIC);
+ fontTable[currentFont][3] = new Font(printer, fontName, fontSize, SWT.BOLD | SWT.ITALIC);
+ setFontStyle(SWT.NORMAL);
+ }
+ }
+
+ void setFontStyle(int style) {
+ // currentFont must already be set
+ Font font;
+ if ((style & SWT.BOLD) != 0) {
+ if ((style & SWT.ITALIC) != 0) {
+ font = fontTable[currentFont][3];
+ } else {
+ font = fontTable[currentFont][1];
+ }
+ } else if ((style & SWT.ITALIC) != 0) {
+ font = fontTable[currentFont][2];
+ } else {
+ font = fontTable[currentFont][0];
+ }
+ gc.setFont(font);
+ tabWidth = gc.stringExtent(tabs).x;
+ lineHeight = gc.getFontMetrics().getHeight();
+ currentFontStyle = style;
+ }
+
+ void createColorTable() {
+ creatingColorTable = true;
+ red = green = blue = 0;
+ }
+
+ void setForeground(int color) {
+ if (color != currentForeground) {
+ // colors must already be in table
+ gc.setForeground((Color)colorTable.elementAt(color));
+ currentForeground = color;
+ }
+ }
+
+ void setBackground(int color) {
+ if (color != currentBackground) {
+ // colors must already be in table
+ gc.setBackground((Color)colorTable.elementAt(color));
+ currentBackground = color;
+ }
+ }
+
+ void newline() {
+ x = leftMargin;
+ y += lineHeight;
+ if (y + lineHeight > bottomMargin) {
+ printer.endPage();
+ if (index + 1 < end) {
+ y = topMargin;
+ printer.startPage();
+ }
+ }
+ }
+
+ void saveState() {
+ RTFState state = new RTFState();
+ state.fontStyle = currentFontStyle;
+ state.foreground = currentForeground;
+ state.background = currentBackground;
+ savedState.addElement(state);
+ }
+
+ void restoreState() {
+ if (savedState.isEmpty()) return;
+ if (creatingColorTable) {
+ setForeground(0);
+ setBackground(1);
+ creatingColorTable = false;
+ }
+ RTFState state = (RTFState)savedState.remove(savedState.size() - 1);
+ setFontStyle(state.fontStyle);
+ if (state.foreground != -1) setForeground(state.foreground);
+ if (state.background != -1) setBackground(state.background);
+ }
+}
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..a17caf48d7
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TableEditor.java
@@ -0,0 +1,179 @@
+package org.eclipse.swt.custom;
+
+/*
+ * Licensed Materials - Property of IBM,
+ * (c) Copyright IBM Corp. 1998, 2001 All Rights Reserved
+ */
+
+import org.eclipse.swt.*;
+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>
+* Table table = new Table(parent, SWT.FULL_SELECTION);
+* TableEditor editor = new TableEditor (table);
+* 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
+* int index = table.getSelectionIndex ();
+* if (index == -1) return;
+* TableItem item = table.getItem (index);
+*
+* // The control that will be the editor must be a child of the Table
+* Text text = new Text(table, SWT.NONE);
+*
+* //The text 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;
+*
+* // Open the text editor in the second column of the selected row.
+* editor.setEditor (text, item, 1);
+*
+* // Assign focus to the text control
+* text.setFocus ();
+* }
+* });
+* </pre></code>
+*/
+public class TableEditor extends ControlEditor {
+
+ Table table;
+ TableItem item;
+ int column = -1;
+ Listener columnListener;
+/**
+* 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 Listener() {
+ public void handleEvent(Event e) {
+ resize ();
+ }
+ };
+
+}
+Rectangle computeBounds () {
+ if (item == null || column == -1 || item.isDisposed()) return new Rectangle(0, 0, 0, 0);
+
+ Rectangle cell = item.getBounds(column);
+ Rectangle editorRect = new Rectangle(cell.x, cell.y, minimumWidth, cell.height);
+ Rectangle area = table.getClientArea();
+ if (cell.x < area.x + area.width) {
+ if (cell.x + cell.width > area.x + area.width) {
+ cell.width = area.width - cell.x;
+ }
+ }
+
+ if (grabHorizontal){
+ editorRect.width = Math.max(cell.width, minimumWidth);
+ }
+
+ 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;
+ }
+
+ 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 (this.column > -1 && this.column < table.getColumnCount()){
+ TableColumn tableColumn = table.getColumn(this.column);
+ tableColumn.removeListener(SWT.Resize, columnListener);
+ tableColumn.removeListener(SWT.Move, columnListener);
+ }
+
+ table = 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;
+}
+public void setColumn(int column) {
+
+ if (this.column > -1 && this.column < table.getColumnCount()){
+ TableColumn tableColumn = table.getColumn(this.column);
+ tableColumn.removeListener(SWT.Resize, columnListener);
+ tableColumn.removeListener(SWT.Move, columnListener);
+ this.column = -1;
+ }
+
+ if (column < 0 || column >= table.getColumnCount()) return;
+
+ this.column = column;
+ TableColumn tableColumn = table.getColumn(this.column);
+ tableColumn.addListener(SWT.Resize, columnListener);
+ tableColumn.addListener(SWT.Move, columnListener);
+}
+/**
+* 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;
+}
+public void setItem (TableItem item) {
+ this.item = item;
+}
+public void setEditor (Control editor) {
+ TableItem item = null;
+ if (table.getItemCount() > 0) {
+ item = table.getItem(0);
+ }
+ this.setEditor(editor, item, 0);
+}
+
+/**
+* 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);
+ super.setEditor(editor);
+}
+}
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..8ab256b7e1
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TableTree.java
@@ -0,0 +1,701 @@
+package org.eclipse.swt.custom;
+
+/*
+ * Licensed Materials - Property of IBM,
+ * (c) Copyright IBM Corp. 1998, 2001 All Rights Reserved
+ */
+
+import java.util.Enumeration;
+import java.util.Vector;
+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.
+ *
+ * <dl>
+ * <dt><b>Styles:</b> <dd> SINGLE, MULTI, CHECK, FULL_SELECTION
+ * <dt><b>Events:</b> <dd> Selection, DefaultSelection, Collapse, Expand
+ * </dl>
+ */
+public class TableTree extends Composite {
+ Table table;
+ TableTreeItem[] items = EMPTY_ITEMS;
+ Image plusImage, minusImage, sizeImage;
+
+ /*
+ * 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 overriden (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];
+
+/**
+ * Creates a new instance of the widget.
+ *
+ * @param parent a composite widget
+ * @param style the bitwise OR'ing of widget styles
+ */
+public TableTree(Composite parent, int style) {
+ super(parent, SWT.NONE);
+ table = new Table(this, style);
+ setBackground(table.getBackground());
+ setForeground(table.getForeground());
+ setFont(table.getFont());
+ table.addListener(SWT.MouseDown, new Listener() {
+ public void handleEvent(Event e) {
+ onMouseDown(e);
+ }
+ });
+ table.addListener(SWT.Selection, new Listener() {
+ public void handleEvent(Event e) {
+ onSelection(e);
+ }
+ });
+ table.addListener(SWT.DefaultSelection, new Listener() {
+ public void handleEvent(Event e) {
+ onSelection(e);
+ }
+ });
+
+ addListener(SWT.Dispose, new Listener() {
+ public void handleEvent(Event e) {
+ onDispose();
+ }
+ });
+ addListener(SWT.Resize, new Listener() {
+ public void handleEvent(Event e) {
+ onResize();
+ }
+ });
+ addListener(SWT.FocusIn, new Listener() {
+ public void handleEvent(Event e) {
+ onFocusIn();
+ }
+ });
+}
+
+int addItem(TableTreeItem item, int index) {
+ if (index < 0 || index > items.length) throw new SWTError(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 receive selection events.
+ * <p>
+ *
+ * @param listener the selection listener
+ *
+ * @exception SWTError <ul>
+ * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread
+ * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed
+ * <li>ERROR_NULL_ARGUMENT when listener is null
+ * </ul>
+ */
+public void addSelectionListener(SelectionListener listener) {
+ if (listener == null) throw new SWTError (SWT.ERROR_NULL_ARGUMENT);
+ TypedListener typedListener = new TypedListener (listener);
+ addListener (SWT.Selection,typedListener);
+ addListener (SWT.DefaultSelection,typedListener);
+}
+
+/**
+ * Adds the listener to receive tree events.
+ * <p>
+ *
+ * @param listener the tree listener
+ *
+ * @exception SWTError <ul>
+ * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread
+ * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed
+ * <li>ERROR_NULL_ARGUMENT when listener is null
+ * </ul>
+ */
+public void addTreeListener(TreeListener listener) {
+ if (listener == null) throw new SWTError (SWT.ERROR_NULL_ARGUMENT);
+ TypedListener typedListener = new TypedListener (listener);
+ addListener (SWT.Expand, typedListener);
+ addListener (SWT.Collapse, typedListener);
+}
+
+/**
+ * Computes the preferred size of the widget.
+ * <p>
+ * Calculate the preferred size of the widget based
+ * on the current contents. The hint arguments allow
+ * a specific client area width and/or height to be
+ * requested. The hints may be honored depending on
+ * the platform and the layout.
+ *
+ * @param wHint the width hint (can be SWT.DEFAULT)
+ * @param hHint the height hint (can be SWT.DEFAULT)
+ * @return a point containing the preferred size of the widget including trim
+ *
+ * @exception SWTError <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 Point computeSize (int wHint, int hHint) {
+ return table.computeSize (wHint, hHint, true);
+}
+
+/**
+ * Computes the widget trim.
+ * <p>
+ * Trim is widget specific and may include scroll
+ * bars and menu bar in addition to other trimmings
+ * that are outside of the widget's client area.
+ *
+ * @param x the x location of the client area
+ * @param y the y location of the client area
+ * @param width the width of the client area
+ * @param height the height of the client area
+ * @return a rectangle containing the trim of the widget.
+ *
+ * @exception SWTError <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 Rectangle computeTrim (int x, int y, int width, int height) {
+ 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 SWTError <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 () {
+ table.deselectAll();
+}
+
+/* Expand upward from the specified leaf item. */
+void expandItem (TableTreeItem item) {
+ if (item == null || item.getExpanded()) return;
+ expandItem(item.parentItem);
+ item.setExpanded(true);
+ Event event = new Event();
+ event.item = item;
+ notifyListeners(SWT.Expand, event);
+}
+
+/**
+ * Gets the number of items.
+ * <p>
+ * @return the number of items in the widget
+ */
+public int getItemCount () {
+ 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 SWTError <ul>
+ * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread
+ * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed
+ * <li>ERROR_CANNOT_GET_ITEM_HEIGHT when the operation fails
+ * </ul>
+ */
+public int getItemHeight () {
+ return table.getItemHeight();
+}
+
+/**
+ * Gets the items.
+ * <p>
+ * @return the items in the widget
+ *
+ */
+public TableTreeItem [] getItems () {
+ 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 SWTError <ul>
+ * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
+ * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
+ * <li>ERROR_CANNOT_GET_SELECTION when the operation fails</li>
+ * </ul>
+ */
+public TableTreeItem [] getSelection () {
+ TableItem[] selection = table.getSelection();
+ TableTreeItem [] result = new TableTreeItem[selection.length];
+ for (int i = 0; i < selection.length; i++){
+ result[i] = (TableTreeItem) selection[i].getData();
+ }
+ 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 SWTError <ul>
+ * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
+ * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
+ * <li>ERROR_CANNOT_GET_COUNT when the operation fails</li>
+ * </ul>
+ */
+public int getSelectionCount () {
+ return table.getSelectionCount();
+}
+
+/**
+ * Returns the underlying Table control.
+ *
+ * @return the underlying Table control
+ */
+public Table getTable () {
+ 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) {
+ for (int i = 0; i < items.length; i++) {
+ if (item == items[i]) return i;
+ }
+ return -1;
+}
+
+void onDispose() {
+ 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 () {
+ Rectangle area = getClientArea();
+ table.setBounds(0, 0, area.width, area.height);
+}
+
+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);
+}
+public TableTreeItem getItem(Point point) {
+ 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 () {
+ table.setFocus();
+}
+
+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();
+ 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 SWTError <ul>
+ * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread
+ * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed
+ * <li>ERROR_ITEM_NOT_REMOVED when the operation fails
+ * </ul>
+ */
+public void removeAll () {
+ 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.
+ * <p>
+ *
+ * @param listener the listener
+ *
+ * @exception SWTError <ul>
+ * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread
+ * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed
+ * <li>ERROR_NULL_ARGUMENT when listener is null
+ * </ul>
+ */
+public void removeSelectionListener (SelectionListener listener) {
+ if (listener == null) throw new SWTError (SWT.ERROR_NULL_ARGUMENT);
+ removeListener(SWT.Selection, listener);
+ removeListener(SWT.DefaultSelection, listener);
+}
+
+/**
+ * Removes the listener.
+ *
+ * @param listener the listener
+ *
+ * @exception SWTError <ul>
+ * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread
+ * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed
+ * <li>ERROR_NULL_ARGUMENT when listener is null
+ * </ul>
+ */
+public void removeTreeListener (TreeListener listener) {
+ if (listener == null) throw new SWTError (SWT.ERROR_NULL_ARGUMENT);
+ removeListener(SWT.Expand, listener);
+ removeListener(SWT.Collapse, listener);
+}
+
+/**
+ * Selects all items.
+ * <p>
+ * If an item is not selected, it is selected.
+ * If an item is selected, it remains selected.
+ *
+ * @exception SWTError <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 () {
+ table.selectAll();
+}
+
+/**
+ * Sets the widget background color.
+ * <p>
+ * When new color is null, the background reverts
+ * to the default system color for the widget.
+ *
+ * @param color the new color (or null)
+ *
+ * @exception SWTError <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 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();
+ }
+}
+
+/**
+ * Sets the enabled state.
+ * <p>
+ * A disabled widget is typically not selectable from
+ * the user interface and draws with an inactive or
+ * grayed look.
+ *
+ * @param enabled the new enabled state
+ *
+ * @exception SWTError <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 setEnabled (boolean enabled) {
+ super.setEnabled(enabled);
+ table.setEnabled(enabled);
+}
+
+/**
+ * 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 SWTError <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 setFont (Font font) {
+ super.setFont(font);
+ table.setFont(font);
+}
+
+/**
+ * Gets the widget foreground color.
+ * <p>
+ * @return the widget foreground color
+ *
+ * @exception SWTError <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 setForeground (Color color) {
+ super.setForeground(color);
+ table.setForeground(color);
+}
+
+/**
+ * Sets the pop up menu.
+ * <p>
+ * Every control has an optional pop up menu that is
+ * displayed when the user requests a popup menu for
+ * the control. The sequence of key strokes/button
+ * presses/button releases that is used to request
+ * a pop up menu is platform specific.
+ *
+ * @param menu the new pop up menu
+ *
+ * @exception SWTError <ul>
+ * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
+ * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
+ * <li>ERROR_MENU_NOT_POP_UP when the menu is not a POP_UP</li>
+ * <li>ERROR_NO_COMMON_PARENT when the menu is not in the same widget tree</li>
+ * </ul>
+ */
+public void setMenu (Menu menu) {
+ super.setMenu(menu);
+ table.setMenu(menu);
+}
+
+/**
+ * Sets the selection.
+ * <p>
+ * @param items new selection
+ *
+ * @exception SWTError <ul>
+ * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread
+ * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed
+ * <li>ERROR_NULL_ARGUMENT when items is null
+ * </ul>
+ */
+public void setSelection (TableTreeItem[] items) {
+ TableItem[] tableItems = new TableItem[items.length];
+ for (int i = 0; i < items.length; i++) {
+ if (items[i] == null) throw new SWTError(SWT.ERROR_NULL_ARGUMENT);
+ if (!items[i].getVisible()) expandItem (items[i]);
+ tableItems[i] = items[i].tableItem;
+ }
+ table.setSelection(tableItems);
+}
+
+/**
+ * Sets the tool tip text.
+ * <p>
+ * @param string the new tool tip text (or null)
+ *
+ * @exception SWTError <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 setToolTipText (String string) {
+ super.setToolTipText(string);
+ table.setToolTipText(string);
+}
+
+/**
+ * Shows the item.
+ * <p>
+ * @param item the item to be shown
+ *
+ * @exception SWTError <ul>
+ * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread
+ * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed
+ * <li>ERROR_NULL_ARGUMENT when item is null
+ * </ul>
+ */
+public void showItem (TableTreeItem item) {
+ if (item == null) throw new SWTError (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 SWTError <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 () {
+ 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..75a0814d5f
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TableTreeEditor.java
@@ -0,0 +1,100 @@
+package org.eclipse.swt.custom;
+
+/*
+ * Licensed Materials - Property of IBM,
+ * (c) Copyright IBM Corp. 1998, 2001 All Rights Reserved
+ */
+
+import org.eclipse.swt.*;
+import org.eclipse.swt.widgets.*;
+
+/**
+*
+* 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>The TableTreeEditor performs the same functions as the TableEditor except that the cell is in
+* a TableTree. Refer to TableEditor for additional information. The significant differnce is that
+* the Control provided as the editor <b>must</b> be created with its parent being the <b>Table</b>
+* control that is underneath the TableTree specified in the TableTreeEditor constructor.
+* The Table can be obtained from the TableTree using tableTree.getTable().
+*
+*/
+public class TableTreeEditor {
+
+ public int horizontalAlignment = SWT.CENTER;
+ public boolean grabHorizontal = false;
+ public int minimumWidth = 0;
+
+ TableTreeItem item;
+ TableEditor editor;
+/**
+* Creates a TableTreeEditor for the specified TableTree.
+*
+* @param tableTree the TableTree Control above which this editor will be displayed
+*
+*/
+public TableTreeEditor (TableTree tableTree) {
+ editor = new TableEditor (tableTree.getTable ());
+}
+/**
+ * Removes all associations between the TableTreeEditor and the cell in the TableTree. The
+ * TableTree and the editor Control are <b>not</b> disposed.
+ */
+public void dispose () {
+
+ editor.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 editor.getColumn ();
+}
+/**
+* Returns the Control that is displayed above the cell being edited.
+*
+* @return the Control that is displayed above the cell being edited
+*/
+public Control getEditor () {
+ return editor.getEditor ();
+}
+/**
+* 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;
+}
+/**
+* Specify the Control that is to be displayed and the cell in the TableTree 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 <b>Table</b>
+* control that is underneath the TableTree specified in the TableTreeEditor constructor.
+* The Table can be obtained from the TableTree using tableTree.getTable().
+*
+* @param editor the Control that is displayed above the cell being edited
+* @param item the TableTreeItem 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 control, TableTreeItem item, int column) {
+ if (control == null) {
+ editor.setEditor (null, null, -1);
+ return;
+ }
+
+ this.item = item;
+ TableItem tableItem = item.tableItem;
+ editor.horizontalAlignment = horizontalAlignment;
+ editor.grabHorizontal = grabHorizontal;
+ editor.minimumWidth = minimumWidth;
+
+ editor.setEditor (control, tableItem, column);
+}
+}
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..a73f8f6433
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TableTreeItem.java
@@ -0,0 +1,561 @@
+package org.eclipse.swt.custom;
+
+/*
+ * Licensed Materials - Property of IBM,
+ * (c) Copyright IBM Corp. 1998, 2001 All Rights Reserved
+ */
+
+import org.eclipse.swt.*;
+import org.eclipse.swt.events.*;
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.widgets.*;
+
+/**
+ * A TableTreeItem is a selectable user interface object
+ * that represents an item in a heirarchy of items in a
+ * TableTree.
+ */
+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;
+ boolean expanded;
+ boolean checked;
+
+/**
+ * Create a new instance of a root item.
+ *
+ * @param parent the TableTree that contains this root item
+ * @param style the bitwise OR'ing of widget styles
+ */
+public TableTreeItem(TableTree parent, int style) {
+ this (parent, style, parent.getItemCount());
+}
+
+/**
+ * Create a new instance of a root item in the position
+ * indicated by the specified index.
+ *
+ * @param parent the TableTree that contains this root item
+ * @param style the bitwise OR'ing of widget styles
+ * @param index specifies the position of this item in the TableTree
+ * relative to other root items
+ */
+public TableTreeItem(TableTree parent, int style, int index) {
+ this (parent, null, style, index);
+}
+
+/**
+ * Create a new instance of a sub item.
+ *
+ * @param parent this item's parent in the hierarchy of TableTree items
+ * @param style the bitwise OR'ing of widget styles
+ */
+public TableTreeItem(TableTreeItem parent, int style) {
+ this (parent, style, parent.getItemCount());
+}
+
+/**
+ * Create a new instance of a sub item in the position
+ * indicated by the specified index.
+ *
+ * @param parent this item's parent in the hierarchy of TableTree items
+ * @param style the bitwise OR'ing of widget styles
+ * @param index specifies the position of this item in the TableTree
+ * relative to other children of the same parent
+ */
+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(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(null, 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);
+ }
+ addDisposeListener(new DisposeListener() {
+ public void widgetDisposed(DisposeEvent e) {
+ TableTreeItem.this.widgetDisposed(e);
+ }
+ });
+}
+void addCheck() {
+ Table table = parent.getTable();
+ if ((table.getStyle() & SWT.CHECK) == 0) return;
+ tableItem.setChecked(checked);
+}
+void addItem(TableTreeItem item, int index) {
+ if (item == null) throw new SWTError(SWT.ERROR_NULL_ARGUMENT);
+ if (index < 0 || index > items.length) throw new SWTError(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);
+}
+
+/**
+ * Gets the widget bounds at the specified index.
+ * <p>
+ * @return the widget bounds at the specified index
+ *
+ * @exception SWTError <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 Rectangle getBounds (int index) {
+ if (tableItem != null) {
+ return tableItem.getBounds(index);
+ } else {
+ return new Rectangle(0, 0, 0, 0);
+ }
+}
+/**
+* Gets the checked state.
+* <p>
+* @return the item checked state.
+*
+* @exception SWTError <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 boolean getChecked () {
+ if (tableItem == null) {
+ return checked;
+ }
+ return tableItem.getChecked();
+}
+
+/**
+ * Gets the Display.
+ * <p>
+ * This method gets the Display that is associated
+ * with the widget.
+ *
+ * @return the widget data
+ *
+ * @exception SWTError <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 Display getDisplay () {
+ TableTree parent = this.parent;
+ if (parent == null) throw new SWTError (SWT.ERROR_WIDGET_DISPOSED);
+ return parent.getDisplay ();
+}
+
+/**
+ * Gets the expanded state of the widget.
+ * <p>
+ * @return a boolean that is the expanded state of the widget
+ */
+public boolean getExpanded () {
+ return expanded;
+}
+
+/**
+ * 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
+ */
+public Image getImage () {
+ 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) {
+ if (0 < index && index < images.length) return images[index];
+ return null;
+}
+
+int getIndent() {
+ if (parentItem == null) return 0;
+ return parentItem.getIndent() + 1;
+}
+
+/**
+ * Gets the number of sub items.
+ * <p>
+ * @return the number of sub items
+ */
+public int getItemCount () {
+ return items.length;
+}
+
+/**
+ * Gets the sub items.
+ * <p>
+ * @return the sub items
+ */
+public TableTreeItem[] getItems () {
+ 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;
+}
+
+/**
+ * Gets the parent.
+ * <p>
+ * @return the parent
+ */
+public TableTree getParent () {
+ return parent;
+}
+
+/**
+ * Gets the parent item.
+ * <p>
+ * @return the parent item.
+ */
+public TableTreeItem getParentItem () {
+ return parentItem;
+}
+
+/**
+ * Gets the first item text.
+ * <p>
+ * @return the item text at index 0, which can be null
+ *
+ * @exception SWTError <ul>
+ * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
+ * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
+ * <li>ERROR_CANNOT_GET_TEXT when the operation fails</li>
+ * </ul>
+ */
+public String getText () {
+ 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) {
+ 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) {
+ for (int i = 0; i < items.length; i++) {
+ if (items[i] == item) return i;
+ }
+ return -1;
+}
+
+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;
+}
+
+void widgetDisposed(DisposeEvent e) {
+ for (int i = items.length - 1; i >= 0; i--) {
+ items[i].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;
+}
+
+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 checked state.
+* <p>
+* @param checked the new checked state.
+*
+* @exception SWTError <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 setChecked (boolean checked) {
+ if (tableItem != null) {
+ tableItem.setChecked(checked);
+ }
+ this.checked = checked;
+}
+/**
+ * Sets the expanded state.
+ * <p>
+ * @param expanded the new expanded state.
+ *
+ * @exception SWTError <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) {
+ if (items.length == 0) 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 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 SWTError <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) {
+ 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
+ */
+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 SWTError <ul>
+ * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
+ * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
+ * <li>ERROR_NULL_ARGUMENT when string is null</li>
+ * </ul>
+ */
+public void setText(int index, String text) {
+ 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);
+}
+
+/**
+ * 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 SWTError <ul>
+ * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li>
+ * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li>
+ * <li>ERROR_NULL_ARGUMENT when string is null</li>
+ * </ul>
+ */
+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(this);
+ tableItem.setImageIndent(getIndent());
+ 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/TextChangedEvent.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TextChangedEvent.java
new file mode 100755
index 0000000000..0aea1259ec
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TextChangedEvent.java
@@ -0,0 +1,41 @@
+package org.eclipse.swt.custom;
+/*
+ * Licensed Materials - Property of IBM,
+ * (c) Copyright IBM Corp 2000, 2001
+ */
+
+/* Imports */
+import org.eclipse.swt.events.*;
+
+/**
+ * This event is sent by the StyledTextContent implementor when a change to
+ * the text occurs.
+ */
+public class TextChangedEvent extends TypedEvent {
+ public int start; // replace start offset
+ public String replacedText; // the replaced text or empty String of no text was replaced
+ public int replacedCharCount; // length of text being replaced
+ public int newCharCount; // length of new text
+ public int replacedLineCount; // number of lines replaced
+ public int newLineCount; // number of new lines
+
+/**
+ * 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);
+}
+TextChangedEvent(StyledTextContent source, StyledTextEvent e) {
+ super(source);
+ start = e.start;
+ replacedCharCount = e.replacedCharCount;
+ newCharCount = e.newCharCount;
+ replacedLineCount = e.replacedLineCount;
+ newLineCount = e.newLineCount;
+ replacedText = e.text;
+}
+
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TextChangedListener.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TextChangedListener.java
new file mode 100755
index 0000000000..3a523f1e2c
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TextChangedListener.java
@@ -0,0 +1,41 @@
+package org.eclipse.swt.custom;
+/*
+ * Licensed Materials - Property of IBM,
+ * (c) Copyright IBM Corp 2000, 2001
+ */
+
+/* Imports */
+import java.util.*;
+
+/**
+ * The StyledText widget implements this listener to receive
+ * notification when changes to the model occur.
+ * It is not intended for use by users of the StyledText widget
+ * or implementors of StyledTextContent. Users should listen to
+ * the ModifyEvent or ExtendedModifyEvent that is sent by the StyledText
+ * widget to receive text change notifications.
+ */
+
+public interface TextChangedListener extends EventListener {
+/**
+ * @param event.start replace start offset (input)
+ * @param event.replacedText text being replaced (input)
+ * @param event.replacedCharCount length of text being replaced (input)
+ * @param event.newCharCount length of new text (input)
+ * @param event.replacedLineCount number of lines replaced (input)
+ * @param event.newLineCount number of new lines (input)
+ */
+public void textReplaced(TextChangedEvent event);
+
+/**
+ * @param event.start replace start offset (input)
+ * @param event.replacedText text being replaced (input)
+ * @param event.replacedCharCount length of text being replaced (input)
+ * @param event.newCharCount length of new text (input)
+ * @param event.replacedLineCount number of lines replaced (input)
+ * @param event.newLineCount number of new lines (input)
+ */
+public void textSet(TextChangedEvent event);
+}
+
+
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..b00e3d77ff
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/TreeEditor.java
@@ -0,0 +1,163 @@
+package org.eclipse.swt.custom;
+
+/*
+ * Licensed Materials - Property of IBM,
+ * (c) Copyright IBM Corp. 1998, 2001 All Rights Reserved
+ */
+
+import org.eclipse.swt.*;
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.widgets.*;
+import org.eclipse.swt.events.*;
+
+/**
+*
+* 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>
+* Tree tree = new Tree(parent, SWT.FULL_SELECTION);
+* TreeEditor editor = new TreeEditor (tree);
+* 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
+* int index = tree.getSelectionIndex ();
+* if (index == -1) return;
+* TreeItem item = tree.getItem (index);
+*
+* // The control that will be the editor must be a child of the Tree
+* Text text = new Text(tree, SWT.NONE);
+*
+* //The text 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;
+*
+* // Open the text editor on the selected row.
+* editor.setEditor (text, item);
+*
+* // Assign focus to the text control
+* text.setFocus ();
+* }
+* });
+* </pre></code>
+*/
+public class TreeEditor extends ControlEditor {
+ Tree tree;
+ TreeItem item;
+ TreeListener treeListener;
+/**
+* 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;
+ treeListener = new TreeAdapter () {
+ final Runnable runnable = new Runnable() {
+ public void run() {
+ if (TreeEditor.this.tree.isDisposed() || editor == null) return;
+ resize();
+ editor.setVisible(true);
+ }
+ };
+ public void treeCollapsed(TreeEvent e) {
+ if (editor == null) return;
+ editor.setVisible(false);
+ Display display = TreeEditor.this.tree.getDisplay();
+ display.asyncExec(runnable);
+ }
+ public void treeExpanded(TreeEvent e) {
+ if (editor == null) return;
+ editor.setVisible(false);
+ Display display = TreeEditor.this.tree.getDisplay();
+ display.asyncExec(runnable);
+ }
+ };
+ tree.addTreeListener(treeListener);
+}
+Rectangle computeBounds () {
+ if (item == null || item.isDisposed()) return new Rectangle(0, 0, 0, 0);
+
+ Rectangle cell = item.getBounds();
+ 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, cell.height);
+
+ if (grabHorizontal) {
+ editorRect.width = Math.max(area.x + area.width - cell.x, minimumWidth);
+ }
+
+ if (horizontalAlignment == SWT.RIGHT) {
+ editorRect.x = Math.max(cell.x, cell.x + cell.width - editorRect.width);
+ } else if (horizontalAlignment == SWT.LEFT) {
+ // do nothing - cell.x is the right answer
+ } else { // default is CENTER
+ editorRect.x = Math.max(cell.x, cell.x + (cell.width - editorRect.width)/2);
+ }
+
+ return editorRect;
+}
+/**
+ * Removes all associations between the TreeEditor and the cell in the tree. The
+ * tree and the editor Control are <b>not</b> disposed.
+ */
+public void dispose () {
+ if (treeListener != null)
+ tree.removeTreeListener(treeListener);
+ treeListener = null;
+ tree = null;
+ item = null;
+ super.dispose();
+}
+/**
+* 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;
+}
+public void setItem (TreeItem item) {
+ this.item = item;
+}
+
+/**
+* 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
+*/
+public void setEditor (Control editor, TreeItem item) {
+ setItem (item);
+ super.setEditor (editor);
+}
+public void setEditor (Control editor) {
+ TreeItem item = null;
+ if (tree.getItemCount() != 0) {
+ item = tree.getItems()[0];
+ }
+ this.setEditor(editor, item);
+}
+
+}
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..a5766fc2d8
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/VerifyKeyListener.java
@@ -0,0 +1,19 @@
+package org.eclipse.swt.custom;
+/*
+ * Licensed Materials - Property of IBM,
+ * (c) Copyright IBM Corp 2000, 2001
+ */
+
+/* Imports */
+import org.eclipse.swt.events.*;
+import java.util.*;
+
+public interface VerifyKeyListener extends EventListener {
+/**
+ * @param event.character the character that was typed (input)
+ * @param event.keyCode the key code that was typed (input)
+ * @param event.stateMask the state of the keyboard (input)
+ * @param event.doit processed or not (output)
+ */
+public void verifyKey (VerifyEvent event);
+} \ No newline at end of file
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..aafe3891fa
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/ViewForm.java
@@ -0,0 +1,477 @@
+package org.eclipse.swt.custom;
+
+/*
+ * Licensed Materials - Property of IBM,
+ * (c) Copyright IBM Corp. 1998, 2001 All Rights Reserved
+ */
+
+import org.eclipse.swt.graphics.*;
+import org.eclipse.swt.widgets.*;
+import org.eclipse.swt.*;
+import org.eclipse.swt.events.*;
+
+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;
+
+ /**
+ * Color of innermost line of drop shadow border.
+ */
+ public static RGB borderInsideRGB = new RGB (132, 130, 132);
+ /**
+ * Color of middle line of drop shadow border.
+ */
+ public static RGB borderMiddleRGB = new RGB (143, 141, 138);
+ /**
+ * Color of outermost line of drop shadow border.
+ */
+ public static RGB borderOutsideRGB = new RGB (171, 168, 165);
+
+ // SWT widgets
+ private Control topLeft;
+ private Control topCenter;
+ private Control topRight;
+ private Control content;
+
+ // Configuration and state info
+ private boolean separateTopCenter = false;
+ private int drawLine1 = -1;
+ private int drawLine2 = -1;
+
+ private boolean showBorder = false;
+
+ private int BORDER_TOP = 0;
+ private int BORDER_BOTTOM = 0;
+ private int BORDER_LEFT = 0;
+ private int BORDER_RIGHT = 0;
+
+ private Color borderColor1;
+ private Color borderColor2;
+ private Color borderColor3;
+
+ private Rectangle oldArea;
+/**
+* Creates a ViewForm.
+* <p>
+* This method creates a child widget using style bits
+* to select a particular look or set of properties.
+*
+* @param parent a composite widget (cannot be null)
+* @param style the bitwise OR'ing of widget styles
+*
+* @exception SWTError <ul>
+* <li> ERROR_THREAD_INVALID_ACCESS when called from the wrong thread </li>
+* <li> ERROR_ERROR_NULL_ARGUMENT when the parent is null </li>
+* </ul>
+*/
+public ViewForm(Composite parent, int style) {
+ super(parent, checkStyle(style));
+
+ borderColor1 = new Color(getDisplay(), borderInsideRGB);
+ borderColor2 = new Color(getDisplay(), borderMiddleRGB);
+ borderColor3 = new Color(getDisplay(), borderOutsideRGB);
+ setBorderVisible((style & SWT.BORDER) != 0);
+
+ addPaintListener(new PaintListener() {
+ public void paintControl(PaintEvent event) {
+ onPaint(event.gc);
+ }
+ });
+ addControlListener(new ControlAdapter(){
+ public void controlResized(ControlEvent e) {
+ onResize();
+ }
+ });
+
+ addListener(SWT.Dispose, new Listener() {
+ public void handleEvent(Event e) {
+ onDispose();
+ }
+ });
+}
+/**
+ * Check the style bits to ensure that no invalid styles are applied.
+ * @private
+ */
+private static int checkStyle (int style) {
+ int mask = SWT.FLAT;
+ return style & mask | SWT.NO_REDRAW_RESIZE;
+}
+public Point computeSize(int wHint, int hHint, boolean changed) {
+ // size of title bar area
+ Point leftSize = new Point(0, 0);
+ if (topLeft != null) {
+ leftSize = topLeft.computeSize(SWT.DEFAULT, SWT.DEFAULT);
+ }
+ Point centerSize = new Point(0, 0);
+ if (topCenter != null) {
+ centerSize = topCenter.computeSize(SWT.DEFAULT, SWT.DEFAULT);
+ }
+ Point rightSize = new Point(0, 0);
+ if (topRight != null) {
+ rightSize = topRight.computeSize(SWT.DEFAULT, SWT.DEFAULT);
+ }
+ Point size = new Point(0, 0);
+ // calculate width of title bar
+ if (separateTopCenter) {
+ size.x = leftSize.x + rightSize.x;
+ size.x = Math.max(centerSize.x, size.x);
+ size.y = Math.max(leftSize.y, rightSize.y);
+ if (topCenter != null){
+ size.y += centerSize.y;
+ }
+ } else {
+ size.x = leftSize.x + centerSize.x + rightSize.x;
+ size.y = Math.max(leftSize.y, Math.max(centerSize.y, rightSize.y));
+ }
+
+ if (content != null) {
+ Point contentSize = new Point(0, 0);
+ contentSize = content.computeSize(SWT.DEFAULT, SWT.DEFAULT);
+ size.x = Math.max (size.x, contentSize.x);
+ size.y += contentSize.y;
+ }
+
+ size.x += 2 * marginWidth + BORDER_LEFT + BORDER_RIGHT;
+ size.y += 2 * marginHeight + BORDER_TOP + BORDER_BOTTOM;
+
+ return size;
+}
+public Rectangle getClientArea() {
+ Rectangle clientArea = super.getClientArea();
+ clientArea.x += BORDER_LEFT;
+ clientArea.y += BORDER_TOP;
+ clientArea.width -= BORDER_LEFT + BORDER_RIGHT;
+ clientArea.height -= BORDER_TOP + BORDER_BOTTOM;
+ return clientArea;
+}
+/**
+* Returns the content area.
+*/
+public Control getContent() {
+ return content;
+}
+/**
+* Returns Control that appears in the top center of the pane.
+* Typically this is a toolbar.
+*/
+public Control getTopCenter() {
+ return topCenter;
+}
+/**
+* Returns the Control that appears in the top left corner of the pane.
+* Typically this is a label such as CLabel.
+*/
+public Control getTopLeft() {
+ 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.
+*/
+public Control getTopRight() {
+ return topRight;
+}
+public void layout (boolean changed) {
+ Rectangle rect = getClientArea();
+
+ drawLine1 = -1;
+ drawLine2 = -1;
+
+ Point leftSize = new Point(0, 0);
+ if (topLeft != null && !topLeft.isDisposed()) {
+ leftSize = topLeft.computeSize(SWT.DEFAULT, SWT.DEFAULT);
+ }
+ Point centerSize = new Point(0, 0);
+ if (topCenter != null && !topCenter.isDisposed()) {
+ centerSize = topCenter.computeSize(SWT.DEFAULT, SWT.DEFAULT);
+ }
+ Point rightSize = new Point(0, 0);
+ if (topRight != null && !topRight.isDisposed()) {
+ rightSize = topRight.computeSize(SWT.DEFAULT, SWT.DEFAULT);
+ }
+
+ int minTopWidth = leftSize.x + centerSize.x + rightSize.x + 2*marginWidth + 1; // +1 for highlight line
+ int height = rect.y + marginHeight;
+
+ boolean top = false;
+ if (separateTopCenter || minTopWidth > rect.width) {;
+ int topHeight = Math.max(rightSize.y, leftSize.y);
+ if (topRight != null && !topRight.isDisposed()) {
+ top = true;
+ topRight.setBounds(rect.x + rect.width - marginWidth - rightSize.x,
+ rect.y + 1 + marginHeight,
+ rightSize.x, topHeight);
+ height += 1 + topHeight; // +1 for highlight line
+ }
+ if (topLeft != null && !topLeft.isDisposed()) {
+ top = true;
+ leftSize = topLeft.computeSize(rect.width - 2* marginWidth - rightSize.x - 1, SWT.DEFAULT);
+ topLeft.setBounds(rect.x + 1 + marginWidth,
+ rect.y + 1 + marginHeight,
+ leftSize.x, topHeight);
+ height = Math.max(height, rect.y + marginHeight + 1 + topHeight); // +1 for highlight line
+ }
+ if (topCenter != null && !topCenter.isDisposed()) {
+ top = true;
+ if (height > rect.y + marginHeight) {
+ drawLine1 = height;
+ height += 1; // +1 for divider line
+ }
+ centerSize = topCenter.computeSize(rect.width - 2 * marginWidth, SWT.DEFAULT);
+ topCenter.setBounds(rect.x + rect.width - marginWidth - centerSize.x,
+ height,
+ centerSize.x, centerSize.y);
+ height += centerSize.y;
+
+ }
+ } else {
+ int topHeight = Math.max(rightSize.y, Math.max(centerSize.y, leftSize.y));
+ if (topRight != null && !topRight.isDisposed()) {
+ top = true;
+ topRight.setBounds(rect.x + rect.width - marginWidth - rightSize.x,
+ rect.y + marginHeight + 1, // +1 for highlight line
+ rightSize.x, topHeight);
+ height += 1 + topHeight; // +1 for highlight line
+ }
+ if (topCenter != null && !topCenter.isDisposed()) {
+ top = true;
+ topCenter.setBounds(rect.x + rect.width - marginWidth - rightSize.x - centerSize.x,
+ rect.y + marginHeight + 1, // +1 for highlight line
+ centerSize.x, topHeight);
+ height = Math.max(height, rect.y + marginHeight + 1 + topHeight); // +1 for highlight line
+ }
+ if (topLeft != null && !topLeft.isDisposed()) {
+ top = true;
+ leftSize = topLeft.computeSize(rect.width - 2 * marginWidth - rightSize.x - centerSize.x - 1, topHeight);
+ topLeft.setBounds(rect.x + marginWidth + 1, // +1 for highlight line
+ rect.y + marginHeight + 1, // +1 for highlight line
+ leftSize.x, topHeight);
+ height = Math.max(height, rect.y + marginHeight + 1 + topHeight); // +1 for highlight line
+ }
+ }
+
+ if (content != null && !content.isDisposed()) {
+ if (top) {
+ drawLine2 = height;
+ height += 1; // +1 for divider line
+ }
+ content.setBounds(rect.x + marginWidth,
+ height,
+ rect.width - 2 * marginWidth,
+ rect.y + rect.height - height - marginHeight);
+ }
+}
+private void onDispose() {
+ if (borderColor1 != null) {
+ borderColor1.dispose();
+ }
+ borderColor1 = null;
+
+ if (borderColor2 != null) {
+ borderColor2.dispose();
+ }
+ borderColor2 = null;
+
+ if (borderColor3 != null) {
+ borderColor3.dispose();
+ }
+ borderColor3 = null;
+}
+/**
+* Draws the focus border.
+*/
+private void onPaint(GC gc) {
+ Rectangle d = super.getClientArea();
+
+ if (showBorder) {
+ if ((getStyle() & SWT.FLAT) !=0) {
+ gc.setForeground(borderColor1);
+ gc.drawRectangle(d.x, d.y, d.x + d.width - 1, d.y + d.height - 1);
+ } else {
+ gc.setForeground(borderColor1);
+ gc.drawRectangle(d.x, d.y, d.x + d.width - 3, d.y + d.height - 3);
+
+ gc.setForeground(borderColor2);
+ gc.drawLine(d.x + 1, d.y + d.height - 2, d.x + d.width - 1, d.y + d.height - 2);
+ gc.drawLine(d.x + d.width - 2, d.y + 1, d.x + d.width - 2, d.y + d.height - 1);
+
+ gc.setForeground(borderColor3);
+ gc.drawLine(d.x + 2, d.y + d.height - 1, d.x + d.width - 2, d.y + d.height - 1);
+ gc.drawLine(d.x + d.width - 1, d.y + 2, d.x + d.width - 1, d.y + d.height - 2);
+ }
+ }
+
+ if (drawLine1 != -1) {
+ // top seperator line
+ gc.setForeground(borderColor1);
+ gc.drawLine(d.x + BORDER_LEFT, drawLine1, d.x + d.width - BORDER_RIGHT, drawLine1);
+ }
+ if (drawLine2 != -1) {
+ // content separator line
+ gc.setForeground(borderColor1);
+ gc.drawLine(d.x + BORDER_LEFT, drawLine2, d.x + d.width - BORDER_RIGHT, drawLine2);
+ }
+ // highlight on top
+ int y = drawLine1;
+ if (y == -1){
+ y = drawLine2;
+ }
+ if (y != -1) {
+ gc.setForeground(getDisplay().getSystemColor(SWT.COLOR_WIDGET_HIGHLIGHT_SHADOW));
+ gc.drawLine(d.x + BORDER_LEFT + marginWidth, d.y + BORDER_TOP + marginHeight,
+ d.x + BORDER_LEFT + marginWidth, y - 1);
+ gc.drawLine(d.x + BORDER_LEFT + marginWidth, d.y + BORDER_TOP + marginHeight,
+ d.x + d.width - BORDER_RIGHT - marginWidth - 1, d.y + BORDER_TOP + marginHeight);
+ }
+
+ gc.setForeground(getForeground());
+}
+private void onResize() {
+ layout();
+
+ Rectangle area = super.getClientArea();
+ if (oldArea == null || oldArea.width == 0 || oldArea.height == 0) {
+ redraw();
+ } else {
+ int width = 0;
+ if (oldArea.width < area.width) {
+ width = area.width - oldArea.width + BORDER_RIGHT;
+ } else if (oldArea.width > area.width) {
+ width = BORDER_RIGHT;
+ }
+ redraw(area.x + area.width - width, area.y, width, area.height, false);
+
+ int height = 0;
+ if (oldArea.height < area.height) {
+ height = area.height - oldArea.height + BORDER_BOTTOM;
+ }
+ if (oldArea.height > area.height) {
+ height = BORDER_BOTTOM;
+ }
+ redraw(area.x, area.y + area.height - height, area.width, height, false);
+ }
+ oldArea = area;
+}
+/**
+* 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.
+*/
+public void setContent(Control content) {
+ if (content != null && content.getParent() != this) {
+ SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ }
+ if (this.content != null && !this.content.isDisposed()) {
+ this.content.setVisible(false);
+ }
+ this.content = content;
+ layout();
+}
+
+/**
+* Set the widget font.
+* This will apply the font to the topLeft, topRight and topCenter widgets.
+*/
+public void setFont(Font f) {
+ super.setFont(f);
+ if (topLeft != null && !topLeft.isDisposed()) topLeft.setFont(f);
+ if (topCenter != null && !topCenter.isDisposed()) topCenter.setFont(f);
+ if (topRight != null && !topRight.isDisposed()) topRight.setFont(f);
+
+ layout();
+}
+public void setLayout (Layout layout) {
+ // no layout may be set on the ViewForm
+ return;
+}
+/**
+* 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.
+*/
+public void setTopCenter(Control topCenter) {
+ if (topCenter != null && topCenter.getParent() != this) {
+ SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ }
+ if (this.topCenter != null && !this.topCenter.isDisposed()) {
+ this.topCenter.setVisible(false);
+ }
+ this.topCenter = topCenter;
+ layout();
+}
+/**
+* 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.
+*/
+public void setTopLeft(Control c) {
+ if (c != null && c.getParent() != this) {
+ SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ }
+ if (this.topLeft != null && !this.topLeft.isDisposed()) {
+ this.topLeft.setVisible(false);
+ }
+ this.topLeft = c;
+ layout();
+}
+/**
+* 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.
+*/
+public void setTopRight(Control c) {
+ if (c != null && c.getParent() != this) {
+ SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ }
+ if (this.topRight != null && !this.topRight.isDisposed()) {
+ this.topRight.setVisible(false);
+ }
+ this.topRight = c;
+ layout();
+}
+public void setBorderVisible(boolean show) {
+ if (showBorder == show) return;
+
+ showBorder = show;
+ if (showBorder) {
+ if ((getStyle() & SWT.FLAT)!= 0) {
+ BORDER_LEFT = BORDER_TOP = BORDER_RIGHT = BORDER_BOTTOM = 1;
+ } else {
+ BORDER_LEFT = BORDER_TOP = 1;
+ BORDER_RIGHT = BORDER_BOTTOM = 3;
+ }
+ } else {
+ BORDER_BOTTOM = BORDER_TOP = BORDER_LEFT = BORDER_RIGHT = 0;
+ }
+
+ layout();
+ 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.
+*/
+public void setTopCenterSeparate(boolean show) {
+ separateTopCenter = show;
+ layout();
+}
+
+}
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..2f36647b51
--- /dev/null
+++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/package.html
@@ -0,0 +1,13 @@
+ <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>
+Brief overview will be provided here.
+<h2>
+Package Specification</h2>
+Detailed overview will be provided here.
+</body>
+</html>