| /******************************************************************************* |
| * Copyright (c) 2000, 2008 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.swt.widgets; |
| |
| |
| import org.eclipse.swt.*; |
| import org.eclipse.swt.events.*; |
| import org.eclipse.swt.graphics.*; |
| |
| /** |
| * Instances of this class implement the notebook user interface |
| * metaphor. It allows the user to select a notebook page from |
| * set of pages. |
| * <p> |
| * The item children that may be added to instances of this class |
| * must be of type <code>TabItem</code>. |
| * <code>Control</code> children are created and then set into a |
| * tab item using <code>TabItem#setControl</code>. |
| * </p><p> |
| * Note that although this class is a subclass of <code>Composite</code>, |
| * it does not make sense to set a layout on it. |
| * </p><p> |
| * <dl> |
| * <dt><b>Styles:</b></dt> |
| * <dd>TOP, BOTTOM</dd> |
| * <dt><b>Events:</b></dt> |
| * <dd>Selection</dd> |
| * </dl> |
| * <p> |
| * Note: Only one of the styles TOP and BOTTOM may be specified. |
| * </p><p> |
| * IMPORTANT: This class is <em>not</em> intended to be subclassed. |
| * </p> |
| * |
| * @see <a href="http://www.eclipse.org/swt/snippets/#tabfolder">TabFolder, TabItem snippets</a> |
| * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: ControlExample</a> |
| * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a> |
| * @noextend This class is not intended to be subclassed by clients. |
| */ |
| public class TabFolder extends Composite { |
| TabItem items[] = new TabItem [0]; |
| int selectedIndex = -1; |
| int xClient, yClient; |
| int imageHeight = -1; // all images have the height of the first image ever set |
| int topTabIndex = 0; // index of the first visible tab. Used for tab scrolling |
| boolean scrollButtonDown = false; // true=one of the scroll buttons is being pushed |
| boolean inDispose = false; |
| String toolTipText; |
| |
| // internal constants |
| static final int SCROLL_BUTTON_SIZE = 20; // width/height of the scroll button used for scrolling tab items |
| static final int CLIENT_MARGIN_WIDTH = 2; // distance between widget border and client rect |
| static final int SELECTED_TAB_TOP_EXPANSION = 2; // amount we expand the selected tab on top |
| static final int SELECTED_TAB_HORIZONTAL_EXPANSION = 2; // amount we expand so it overlays to left and right |
| |
| /** |
| * Constructs a new instance of this class given its parent |
| * and a style value describing its behavior and appearance. |
| * <p> |
| * The style value is either one of the style constants defined in |
| * class <code>SWT</code> which is applicable to instances of this |
| * class, or must be built by <em>bitwise OR</em>'ing together |
| * (that is, using the <code>int</code> "|" operator) two or more |
| * of those <code>SWT</code> style constants. The class description |
| * lists the style constants that are applicable to the class. |
| * Style bits are also inherited from superclasses. |
| * </p> |
| * |
| * @param parent a composite control which will be the parent of the new instance (cannot be null) |
| * @param style the style of control to construct |
| * |
| * @exception IllegalArgumentException <ul> |
| * <li>ERROR_NULL_ARGUMENT - if the parent is null</li> |
| * </ul> |
| * @exception SWTException <ul> |
| * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li> |
| * <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li> |
| * </ul> |
| * |
| * @see SWT |
| * @see SWT#TOP |
| * @see SWT#BOTTOM |
| * @see Widget#checkSubclass |
| * @see Widget#getStyle |
| */ |
| public TabFolder(Composite parent, int style) { |
| super(parent, checkStyle (style)); |
| Listener listener = new Listener() { |
| public void handleEvent(Event event) {handleEvents(event);} |
| }; |
| addListener (SWT.Dispose, listener); |
| addListener (SWT.MouseDown, listener); |
| addListener (SWT.MouseUp, listener); |
| addListener (SWT.MouseHover, listener); |
| addListener (SWT.Paint, listener); |
| // addListener (SWT.Resize, listener); |
| addListener (SWT.Traverse, listener); |
| addListener (SWT.KeyDown, listener); |
| addListener (SWT.FocusIn, listener); |
| addListener (SWT.FocusOut, listener); |
| } |
| /** |
| * Adds the listener to the collection of listeners who will |
| * be notified when the user changes the receiver's selection, by sending |
| * it one of the messages defined in the <code>SelectionListener</code> |
| * interface. |
| * <p> |
| * When <code>widgetSelected</code> is called, the item field of the event object is valid. |
| * <code>widgetDefaultSelected</code> is not called. |
| * </p> |
| * |
| * @param listener the listener which should be notified when the user changes the receiver's selection |
| * |
| * @exception IllegalArgumentException <ul> |
| * <li>ERROR_NULL_ARGUMENT - if the listener is null</li> |
| * </ul> |
| * @exception SWTException <ul> |
| * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> |
| * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> |
| * </ul> |
| * |
| * @see SelectionListener |
| * @see #removeSelectionListener |
| * @see SelectionEvent |
| */ |
| public void addSelectionListener(SelectionListener listener) { |
| checkWidget(); |
| if (listener == null) error (SWT.ERROR_NULL_ARGUMENT); |
| TypedListener typedListener = new TypedListener(listener); |
| addListener(SWT.Selection,typedListener); |
| addListener(SWT.DefaultSelection,typedListener); |
| } |
| static int checkStyle (int style) { |
| style = checkBits (style, SWT.TOP, SWT.BOTTOM, 0, 0, 0, 0); |
| /* |
| * Even though it is legal to create this widget |
| * with scroll bars, they serve no useful purpose |
| * because they do not automatically scroll the |
| * widget's client area. The fix is to clear |
| * the SWT style. |
| */ |
| return style & ~(SWT.H_SCROLL | SWT.V_SCROLL); |
| } |
| protected void checkSubclass () { |
| if (!isValidSubclass ()) error (SWT.ERROR_INVALID_SUBCLASS); |
| } |
| public Point computeSize (int wHint, int hHint, boolean changed) { |
| Point size = super.computeSize (wHint, hHint, changed); |
| if (items.length > 0) { |
| TabItem lastItem = items[items.length-1]; |
| int border = getBorderWidth (); |
| int width = lastItem.x + lastItem.width + border * 2 + CLIENT_MARGIN_WIDTH * 2 + TabItem.SHADOW_WIDTH * 2; |
| size.x = Math.max (width, size.x); |
| } |
| return size; |
| } |
| public Rectangle computeTrim (int x, int y, int width, int height) { |
| checkWidget(); |
| int border = getBorderWidth (); |
| int trimX = x - border - CLIENT_MARGIN_WIDTH - TabItem.SHADOW_WIDTH; |
| int trimY = y - border - CLIENT_MARGIN_WIDTH - TabItem.SHADOW_WIDTH; |
| int tabHeight = 0; |
| if (items.length > 0) { |
| TabItem item = items [0]; |
| tabHeight = item.y + item.height; // only use height of the first item because all items should be the same height |
| } |
| int trimWidth = width + border * 2 + CLIENT_MARGIN_WIDTH * 2 + TabItem.SHADOW_WIDTH * 2; |
| int trimHeight = height + tabHeight + border * 2 + CLIENT_MARGIN_WIDTH * 2 + TabItem.SHADOW_WIDTH * 2; |
| return new Rectangle (trimX, trimY - tabHeight, trimWidth, trimHeight); |
| } |
| /** |
| * Create the specified item at 'index'. |
| */ |
| void createChild (TabItem item, int index) { |
| boolean isTabScrolling = isTabScrolling(); |
| |
| if (!(0 <= index && index <= getItemCount ())) error (SWT.ERROR_INVALID_RANGE); |
| item.parent = this; |
| |
| // grow by one and rearrange the array. |
| TabItem[] newItems = new TabItem [items.length + 1]; |
| System.arraycopy(items, 0, newItems, 0, index); |
| newItems[index] = item; |
| System.arraycopy(items, index, newItems, index + 1, items.length - index); |
| items = newItems; |
| if (selectedIndex >= index) selectedIndex ++; |
| |
| layoutItems(); |
| redrawTabs(); |
| // redraw scroll buttons if they just became visible |
| // fixes 1G5X1QL |
| if (isTabScrolling() != isTabScrolling && isTabScrolling == false) { |
| redrawScrollButtons(); |
| } |
| if (getItemCount() == 1) { |
| // select the first added item and send a selection event. |
| // fixes 1GAP79N |
| setSelection(0, true); |
| } |
| } |
| /** |
| * Destroy the specified item. |
| */ |
| void destroyChild (TabItem item) { |
| int index = indexOf(item); |
| if (index == -1) return; // should trigger an error? |
| if (items.length == 1) { |
| items = new TabItem [0]; |
| selectedIndex = -1; |
| topTabIndex = 0; |
| if (!inDispose){ |
| Control control = item.control; |
| if (control != null && !control.isDisposed()) { |
| control.setVisible(false); |
| } |
| redraw(); |
| } |
| } else { |
| // shrink by one and rearrange the array. |
| TabItem[] newItems = new TabItem [items.length - 1]; |
| System.arraycopy(items, 0, newItems, 0, index); |
| System.arraycopy(items, index + 1, newItems, index, items.length - index - 1); |
| items = newItems; |
| |
| // move the selection if this item is selected |
| if (selectedIndex == index) { |
| if (!inDispose) { |
| Control control = item.control; |
| if (control != null && !control.isDisposed()) { |
| control.setVisible(false); |
| } |
| selectedIndex = -1; |
| setSelection(Math.max(0, index - 1), true); |
| } |
| } else if (selectedIndex > index) { |
| selectedIndex--; |
| } |
| if (topTabIndex == items.length) { |
| --topTabIndex; |
| } |
| } |
| // Make sure that the first tab is visible if scroll buttons are no longer drawn. |
| // Fixes 1FXW5DV |
| if (topTabIndex > 0 && !isTabScrolling()) { |
| topTabIndex = 0; |
| } |
| if (!inDispose) { |
| layoutItems(); |
| redrawTabs(); |
| } |
| } |
| /** |
| * Dispose the items of the receiver |
| */ |
| void doDispose(Event event) { |
| if (inDispose) return; |
| inDispose = true; |
| notifyListeners(SWT.Dispose, event); |
| event.type = SWT.None; |
| // items array is resized during TabItem.dispose |
| // it is length 0 if the last item is removed |
| while (items.length > 0) { |
| if (items[items.length-1] != null) { |
| items[items.length-1].dispose(); |
| } |
| } |
| } |
| /** |
| * Draw an arrow like that used in Button with SWT.ARROW style. |
| * @param gc - GC to draw on |
| * @param xPos - x position the underlying button is drawn at |
| * @param yPos - y position the underlying button is drawn at |
| * @param size - size of the underlying button |
| * @param left - true=arrow is facing left. false=arrow is facing right |
| */ |
| void drawArrow(GC gc, int xPos, int yPos, int size, boolean left) { |
| int arrowWidth = size / 4; |
| int arrow[] = new int[6]; |
| |
| if (!left) arrowWidth *= -1; |
| // start polygon lines with vertical line which is always the same |
| arrow[0] = xPos + (size + arrowWidth) / 2; |
| arrow[1] = yPos + size / 4; |
| arrow[2] = arrow[0]; |
| arrow[3] = arrow[1] + size / 2; |
| |
| arrow[4] = arrow[0] - arrowWidth; |
| arrow[5] = yPos + size / 2; |
| |
| gc.setBackground(getForeground()); |
| gc.fillPolygon(arrow); |
| gc.setBackground(getBackground()); |
| } |
| /** |
| * Draw a border around the receiver. |
| */ |
| void drawBorder(Event event) { |
| GC gc = event.gc; |
| Rectangle clientArea = getClientArea(); |
| int wClient = clientArea.width; |
| int hClient = clientArea.height; |
| int x, y, x1, y1; |
| final Color HighlightShadow = display.getSystemColor(SWT.COLOR_WIDGET_HIGHLIGHT_SHADOW); |
| final Color LightShadow = display.getSystemColor(SWT.COLOR_WIDGET_LIGHT_SHADOW); |
| |
| // Draw the left line |
| gc.setForeground(HighlightShadow); |
| gc.drawLine((x = xClient - CLIENT_MARGIN_WIDTH), |
| yClient + hClient + CLIENT_MARGIN_WIDTH, |
| x, |
| (y = yClient - CLIENT_MARGIN_WIDTH) + 1); |
| // Second, darker, line right of the previous line. |
| // Necessary to workaround color constant differences on Windows/Motif |
| gc.setForeground(LightShadow); |
| gc.drawLine(x + 1, yClient + hClient + CLIENT_MARGIN_WIDTH, x + 1, y + 1); |
| gc.setForeground(HighlightShadow); |
| |
| // Draw the upper line in two chunks so we don't overwrite the selected tab |
| if (selectedIndex == -1) { |
| gc.setForeground(LightShadow); |
| gc.drawLine(x + 1, y + 1, xClient + wClient + CLIENT_MARGIN_WIDTH, y + 1); |
| } else { |
| TabItem item = items[selectedIndex]; |
| gc.setForeground(LightShadow); |
| if (selectedIndex > 0) { |
| gc.drawLine(x + 1, y + 1, item.x - 1 + CLIENT_MARGIN_WIDTH, y + 1); |
| } |
| gc.drawLine(item.x + item.width, y + 1, xClient + wClient + CLIENT_MARGIN_WIDTH, y + 1); |
| } |
| |
| // Draw the right and bottom black lines |
| gc.setForeground(display.getSystemColor(SWT.COLOR_WIDGET_FOREGROUND)); |
| gc.drawLine((x = xClient - CLIENT_MARGIN_WIDTH), |
| (y = yClient + hClient + CLIENT_MARGIN_WIDTH), |
| (x1 = xClient + wClient + CLIENT_MARGIN_WIDTH), |
| y); |
| gc.drawLine(x1, y, x1, (y1 = yClient - CLIENT_MARGIN_WIDTH + 1)); |
| x1--; |
| x++; |
| y--; |
| y1++; |
| |
| |
| // There is a dark gray line above the bottom back line |
| gc.setForeground(display.getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW)); |
| gc.drawLine(x, y, x1, y); |
| // On the right there is a dark gray line, left of the black one |
| gc.drawLine(x1, y-1, x1, y1); |
| |
| // restore the foreground color. |
| gc.setForeground(getForeground()); |
| } |
| /** |
| * Draw a plain push button |
| * @param gc - GC to draw on |
| * @param xPos - x position the button is drawn at |
| * @param yPos - y position the button is drawn at |
| * @param size - size of the button |
| */ |
| void drawPlainButton(GC gc, int xPos, int yPos, int size) { |
| Color rightBottomColor = getForeground(); |
| Color leftTopColor = display.getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW); |
| Color rightBottomInnerColor = display.getSystemColor(SWT.COLOR_WIDGET_DARK_SHADOW); |
| Color leftTopInnerColor = display.getSystemColor(SWT.COLOR_WIDGET_LIGHT_SHADOW); |
| int upper = yPos; |
| int left = xPos; |
| int lower = yPos + size - 1; |
| int right = xPos + size - 1; |
| |
| if (scrollButtonDown) { // draw the button in the pressed down state? |
| rightBottomColor = display.getSystemColor(SWT.COLOR_WIDGET_LIGHT_SHADOW); |
| leftTopColor = display.getSystemColor(SWT.COLOR_WIDGET_DARK_SHADOW); |
| rightBottomInnerColor = display.getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW); |
| leftTopInnerColor = getForeground(); |
| } |
| gc.fillRectangle(left, upper, right - left, lower - upper); |
| // draw right, bottom line in foreground color |
| gc.setForeground(rightBottomColor); |
| gc.drawLine(right, upper, right, lower); |
| gc.drawLine(left, lower, right, lower); |
| |
| // draw left, top line in normal shadow (default light gray) |
| gc.setForeground(leftTopColor); |
| gc.drawLine(left, upper, left, lower - 1); |
| gc.drawLine(left, upper, right - 1, upper); |
| |
| upper++; |
| left++; |
| lower--; |
| right--; |
| // draw right, bottom line in dark shadow (default dark gray) |
| gc.setForeground(rightBottomInnerColor); |
| gc.drawLine(right, upper, right, lower); |
| gc.drawLine(left, lower, right, lower); |
| |
| // draw left, top line in high light shadow (default off white) |
| gc.setForeground(leftTopInnerColor); |
| gc.drawLine(left, upper, left, lower - 1); |
| gc.drawLine(left, upper, right - 1, upper); |
| gc.setForeground(getForeground()); |
| } |
| /** |
| * Draw the buttons used to scroll tab items |
| */ |
| void drawScrollButtons(Event event) { |
| Rectangle buttonArea = getScrollButtonArea(); |
| int buttonSize = buttonArea.width / 2; |
| |
| drawPlainButton(event.gc, buttonArea.x, buttonArea.y, buttonSize); |
| drawPlainButton(event.gc, buttonArea.x + buttonSize, buttonArea.y, buttonSize); |
| if (scrollButtonDown) { |
| drawArrow(event.gc, buttonArea.x, buttonArea.y, buttonSize, true); |
| drawArrow(event.gc, buttonArea.x + buttonSize + 1, buttonArea.y, buttonSize + 1, false); |
| } |
| else { |
| drawArrow(event.gc, buttonArea.x - 1, buttonArea.y - 1, buttonSize, true); |
| drawArrow(event.gc, buttonArea.x + buttonSize, buttonArea.y - 1, buttonSize, false); |
| } |
| } |
| |
| /** |
| * Make sure that the first tab is visible if scroll buttons are no |
| * longer drawn. |
| */ |
| void ensureRightFreeSpaceUsed() { |
| if (topTabIndex > 0 && !isTabScrolling()) { |
| topTabIndex = 0; |
| layoutItems(); |
| redrawTabs(); |
| } |
| } |
| |
| /** |
| * If the tab at 'tabIndex' is not visible or partially covered by the tab |
| * scroll buttons and there is enough space to completely show the tab, |
| * the tab is scrolled to the left to make it fully visible. |
| */ |
| void ensureVisible(int tabIndex) { |
| if (tabIndex < 0 || tabIndex >= items.length) return; |
| if (!isTabScrolling()) return; |
| if (tabIndex < topTabIndex) { |
| topTabIndex = tabIndex; |
| layoutItems(); |
| redrawTabs(); |
| return; |
| } |
| int rightEdge = getScrollButtonArea().x; |
| TabItem tabItem = items[tabIndex]; |
| while (tabItem.x + tabItem.width > rightEdge && tabIndex != topTabIndex) { |
| topTabIndex++; |
| layoutItems(); |
| redrawTabs(); |
| } |
| } |
| void focus (Event e) { |
| if (selectedIndex == -1) return; |
| TabItem tab = items[selectedIndex]; |
| redraw(tab.x, tab.y, tab.width, tab.height); |
| } |
| |
| public Rectangle getClientArea() { |
| checkWidget(); |
| Rectangle clientArea = super.getClientArea(); |
| |
| if (yClient == 0) { // position not calculated yet |
| layoutItems(); // calculate tab folder bounds as soon as there is tab data to use. |
| } |
| clientArea.x = xClient; |
| clientArea.y = yClient; |
| clientArea.width = Math.max (0, clientArea.width - (xClient + CLIENT_MARGIN_WIDTH + 1)); |
| clientArea.height = Math.max (0, clientArea.height - (yClient + CLIENT_MARGIN_WIDTH + 1)); |
| return clientArea; |
| } |
| /** |
| * Return the height of item images. All images are scaled to |
| * the height of the first image. |
| */ |
| int getImageHeight() { |
| return imageHeight; |
| } |
| /** |
| * Returns the item at the given, zero-relative index in the |
| * receiver. Throws an exception if the index is out of range. |
| * |
| * @param index the index of the item to return |
| * @return the item at the given index |
| * |
| * @exception IllegalArgumentException <ul> |
| * <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the list minus 1 (inclusive)</li> |
| * </ul> |
| * @exception SWTException <ul> |
| * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> |
| * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> |
| * </ul> |
| */ |
| public TabItem getItem (int index) { |
| checkWidget(); |
| if (!(0 <= index && index < getItemCount())) error(SWT.ERROR_INVALID_RANGE); |
| return items [index]; |
| } |
| /** |
| * Returns the tab item at the given point in the receiver |
| * or null if no such item exists. The point is in the |
| * coordinate system of the receiver. |
| * |
| * @param point the point used to locate the item |
| * @return the tab item at the given point, or null if the point is not in a tab item |
| * |
| * @exception IllegalArgumentException <ul> |
| * <li>ERROR_NULL_ARGUMENT - if the point is null</li> |
| * </ul> |
| * @exception SWTException <ul> |
| * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> |
| * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> |
| * </ul> |
| * |
| * @since 3.4 |
| */ |
| public TabItem getItem (Point point) { |
| checkWidget(); |
| if (point == null) error (SWT.ERROR_NULL_ARGUMENT); |
| int count = items.length; |
| for (int index = 0; index < count; index++) { |
| TabItem item = items[index]; |
| Rectangle bounds = item.getBounds(); |
| if (bounds.contains(point)) return item; |
| } |
| return null; |
| } |
| /** |
| * Returns the number of items contained in the receiver. |
| * |
| * @return the number of items |
| * |
| * @exception SWTException <ul> |
| * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> |
| * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> |
| * </ul> |
| */ |
| public int getItemCount(){ |
| checkWidget(); |
| return items.length; |
| } |
| /** |
| * Returns an array of <code>TabItem</code>s which are the items |
| * in the receiver. |
| * <p> |
| * Note: This is not the actual structure used by the receiver |
| * to maintain its list of items, so modifying the array will |
| * not affect the receiver. |
| * </p> |
| * |
| * @return the items in the receiver |
| * |
| * @exception SWTException <ul> |
| * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> |
| * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> |
| * </ul> |
| */ |
| public TabItem [] getItems() { |
| checkWidget(); |
| TabItem[] tabItems = new TabItem [items.length]; |
| System.arraycopy(items, 0, tabItems, 0, items.length); |
| return tabItems; |
| } |
| char getMnemonic (String string) { |
| int index = 0; |
| int length = string.length (); |
| do { |
| while ((index < length) && (string.charAt (index) != '&')) index++; |
| if (++index >= length) return '\0'; |
| if (string.charAt (index) != '&') return string.charAt (index); |
| index++; |
| } while (index < length); |
| return '\0'; |
| } |
| /** |
| * Returns the area where the two scroll buttons are drawn. |
| */ |
| Rectangle getScrollButtonArea() { |
| return new Rectangle( |
| super.getClientArea().width - SCROLL_BUTTON_SIZE * 2, SELECTED_TAB_TOP_EXPANSION, |
| SCROLL_BUTTON_SIZE * 2, SCROLL_BUTTON_SIZE); |
| } |
| /** |
| * Returns an array of <code>TabItem</code>s that are currently |
| * selected in the receiver. An empty array indicates that no |
| * items are selected. |
| * <p> |
| * Note: This is not the actual structure used by the receiver |
| * to maintain its selection, so modifying the array will |
| * not affect the receiver. |
| * </p> |
| * @return an array representing 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 TabItem [] getSelection() { |
| checkWidget(); |
| if (selectedIndex == -1) return new TabItem [0]; |
| return new TabItem [] {items[selectedIndex]}; |
| } |
| /** |
| * Returns the zero-relative index of the item which is currently |
| * selected in the receiver, or -1 if no item is selected. |
| * |
| * @return the index of the selected item |
| * |
| * @exception SWTException <ul> |
| * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> |
| * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> |
| * </ul> |
| */ |
| public int getSelectionIndex() { |
| checkWidget(); |
| return selectedIndex; |
| } |
| public String getToolTipText () { |
| checkWidget(); |
| return toolTipText; |
| } |
| /** |
| * Handle the events that I have hooked on the canvas. |
| */ |
| void handleEvents (Event event){ |
| switch (event.type) { |
| case SWT.Dispose: |
| doDispose(event); |
| break; |
| case SWT.Paint: |
| paint(event); |
| break; |
| // case SWT.Resize: |
| // resize(); |
| // break; |
| case SWT.MouseDown: |
| mouseDown(event); |
| break; |
| case SWT.MouseUp: |
| mouseUp(event); |
| break; |
| case SWT.MouseHover: |
| mouseHover(event); |
| break; |
| case SWT.Traverse: |
| traversal(event); |
| break; |
| case SWT.FocusIn: |
| case SWT.FocusOut: |
| focus(event); |
| break; |
| case SWT.KeyDown: |
| // this callback is always needed so that widget is included in tab order |
| keyDown(event); |
| break; |
| default: |
| break; |
| } |
| } |
| /** |
| * Searches the receiver's list starting at the first item |
| * (index 0) until an item is found that is equal to the |
| * argument, and returns the index of that item. If no item |
| * is found, returns -1. |
| * |
| * @param item the search item |
| * @return the index of the item |
| * |
| * @exception IllegalArgumentException <ul> |
| * <li>ERROR_NULL_ARGUMENT - if the item is null</li> |
| * </ul> |
| * @exception SWTException <ul> |
| * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> |
| * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> |
| * </ul> |
| */ |
| public int indexOf(TabItem item) { |
| checkWidget(); |
| if (item == null) { |
| error(SWT.ERROR_NULL_ARGUMENT); |
| } |
| for (int i = 0; i < items.length; i++) { |
| if (items[i] == item) return i; |
| } |
| return -1; |
| } |
| /** |
| * Answer true when the left scroll button was clicked with mouse button 1. |
| */ |
| boolean isLeftButtonHit(Event event) { |
| Rectangle buttonArea = getScrollButtonArea(); |
| |
| buttonArea.width /= 2; |
| return isTabScrolling() && event.button == 1 && buttonArea.contains(event.x, event.y); |
| } |
| /** |
| * Answer true when the right scroll button was clicked with mouse button 1. |
| */ |
| boolean isRightButtonHit(Event event) { |
| Rectangle buttonArea = getScrollButtonArea(); |
| int buttonSize = buttonArea.width / 2; |
| |
| buttonArea.x += buttonSize; |
| buttonArea.width = buttonSize; |
| return isTabScrolling() && event.button == 1 && buttonArea.contains(event.x, event.y); |
| } |
| /** |
| * Answer true if not all tabs can be visible in the receive |
| * thus requiring the scroll buttons to be visible. |
| */ |
| boolean isTabScrolling() { |
| boolean isVisible = false; |
| |
| if (items.length > 0) { |
| TabItem tabItem = items[items.length-1]; |
| int tabStopX = tabItem.x + tabItem.width; |
| tabItem = items[0]; |
| if (tabStopX - tabItem.x > super.getClientArea().width) { |
| isVisible = true; // not all tabs fit in the client area |
| } |
| } |
| return isVisible; |
| } |
| /** |
| * 'item' has changed. Store the image size if this is the |
| * first item with an image. |
| */ |
| void itemChanged(TabItem item) { |
| Image itemImage = item.getImage(); |
| boolean isTabScrolling = isTabScrolling(); |
| |
| if (imageHeight == -1 && itemImage != null) { |
| imageHeight = itemImage.getBounds().height; |
| } |
| layoutItems(); |
| redrawTabs(); |
| // redraw scroll buttons if they just became visible |
| // fixes 1G5X1QL |
| if (isTabScrolling() != isTabScrolling && isTabScrolling == false) { |
| redrawScrollButtons(); |
| } |
| } |
| /** |
| * A key was pressed. If one of the tab-selection keys that is not a traversal |
| * was pressed then change tabs accordingly. |
| */ |
| void keyDown(Event event) { |
| int count = items.length; |
| if (count <= 1) return; |
| switch (event.keyCode) { |
| case SWT.ARROW_RIGHT: |
| if (selectedIndex < items.length - 1) { |
| setSelection(selectedIndex + 1, true); |
| } |
| break; |
| case SWT.ARROW_LEFT: |
| if (selectedIndex > 0) { |
| setSelection(selectedIndex - 1, true); |
| } |
| break; |
| case SWT.HOME: |
| if (selectedIndex > 0) { |
| setSelection(0, true); |
| } |
| break; |
| case SWT.END: |
| if (selectedIndex < count - 1) { |
| setSelection(count - 1, true); |
| } |
| break; |
| } |
| } |
| /** |
| * Layout the items and store the client area size. |
| */ |
| void layoutItems() { |
| int x = SELECTED_TAB_HORIZONTAL_EXPANSION; |
| int y = SELECTED_TAB_TOP_EXPANSION; |
| int tabHeight = 0; |
| |
| GC gc = new GC(this); |
| for (int i=topTabIndex - 1; i>=0; i--) { // if the first visible tab is not the first tab |
| TabItem tab = items[i]; |
| tab.width = tab.preferredWidth(gc); |
| tab.height = tab.preferredHeight(gc); |
| x -= tab.width; // layout tab items from right to left thus making them invisible |
| tab.x = x; |
| tab.y = y; |
| if (tab.height > tabHeight) tabHeight = tab.height; |
| } |
| x = SELECTED_TAB_HORIZONTAL_EXPANSION; |
| for (int i=topTabIndex; i<items.length; i++) { // continue laying out remaining, visible items left to right |
| TabItem tab = items[i]; |
| tab.x = x; |
| tab.y = y; |
| tab.width = tab.preferredWidth(gc); |
| tab.height = tab.preferredHeight(gc); |
| x = x + tab.width; |
| if (tab.height > tabHeight) tabHeight = tab.height; |
| } |
| gc.dispose(); |
| xClient = CLIENT_MARGIN_WIDTH; |
| yClient = CLIENT_MARGIN_WIDTH + tabHeight; |
| TabItem selection[] = getSelection(); |
| if (selection.length > 0) |
| selection[0].expand(SELECTED_TAB_HORIZONTAL_EXPANSION, SELECTED_TAB_TOP_EXPANSION, SELECTED_TAB_HORIZONTAL_EXPANSION, 0); |
| } |
| Point minimumSize (int wHint, int hHint, boolean flushCache) { |
| Control [] children = _getChildren (); |
| int width = 0, height = 0; |
| for (int i=0; i<children.length; i++) { |
| Control child = children [i]; |
| int index = 0; |
| while (index < items.length) { |
| if (items [index].control == child) break; |
| index++; |
| } |
| if (index == items.length) { |
| Rectangle rect = child.getBounds (); |
| width = Math.max (width, rect.x + rect.width); |
| height = Math.max (height, rect.y + rect.height); |
| } else { |
| Point size = child.computeSize (wHint, hHint, flushCache); |
| width = Math.max (width, size.x); |
| height = Math.max (height, size.y); |
| } |
| } |
| return new Point (width, height); |
| } |
| boolean mnemonicHit (char key) { |
| for (int i = 0; i < items.length; i++) { |
| char mnemonic = getMnemonic (items[i].getText ()); |
| if (mnemonic != '\0') { |
| if (Character.toUpperCase (key) == Character.toUpperCase (mnemonic)) { |
| if (forceFocus ()) { |
| if (i != selectedIndex) setSelection(i, true); |
| return true; |
| } |
| } |
| } |
| } |
| return false; |
| } |
| /** |
| * 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. |
| */ |
| void mouseDown(Event event) { |
| if (isLeftButtonHit(event)) { |
| scrollButtonDown = true; |
| redrawHitButton(event); |
| scrollLeft(); |
| } |
| else |
| if (isRightButtonHit(event)) { |
| scrollButtonDown = true; |
| redrawHitButton(event); |
| scrollRight(); |
| } |
| else { |
| for (int i=0; i<items.length; i++) { |
| if (items[i].getBounds().contains(new Point(event.x, event.y))) { |
| forceFocus(); |
| setSelection(i, true); |
| return; |
| } |
| } |
| } |
| } |
| void mouseHover(Event event) { |
| String current = super.getToolTipText(); |
| if (toolTipText == null) { |
| Point point = new Point(event.x, event.y); |
| for (int i=0; i<items.length; i++) { |
| if (items[i].getBounds().contains(point)) { |
| String string = items[i].getToolTipText(); |
| if (string != null && !string.equals(current)) { |
| super.setToolTipText(string); |
| } |
| return; |
| } |
| } |
| if (current != null) super.setToolTipText(null); |
| return; |
| } |
| if (!toolTipText.equals(current)) { |
| super.setToolTipText(toolTipText); |
| } |
| } |
| /** |
| * A mouse button was released. |
| */ |
| void mouseUp(Event event) { |
| if (scrollButtonDown && event.button == 1) { |
| scrollButtonDown = false; |
| redrawHitButton(event); |
| } |
| } |
| /** |
| * Paint the receiver. |
| */ |
| void paint(Event event) { |
| // Draw the unselected tabs first. |
| for (int i=0; i<getItemCount(); i++) { |
| if (i != selectedIndex && event.getBounds().intersects(items[i].getBounds())) { |
| items[i].paint(event.gc, false); |
| } |
| } |
| drawBorder(event); |
| // Selected tab comes last since selected tabs overlay adjacent tabs |
| // and the border |
| if (selectedIndex != -1) { |
| items[selectedIndex].paint(event.gc, true); |
| } |
| if (isTabScrolling()) drawScrollButtons(event); |
| } |
| /** |
| * Redraw the area of the receiver specified by x, y, width, height. |
| * Don't redraw the scroll buttons to avoid flashing. |
| */ |
| void redraw (int x, int y, int width, int height) { |
| Rectangle buttonArea = getScrollButtonArea(); |
| boolean fixScrollButtons = false; |
| |
| if (isTabScrolling()) { |
| if (x > buttonArea.x) { |
| x = buttonArea.x; |
| fixScrollButtons = true; |
| } |
| if (x + width > buttonArea.x) { |
| width = buttonArea.x - x; |
| fixScrollButtons = true; |
| } |
| } |
| redraw(x, y, width, height, false); |
| if (fixScrollButtons) { |
| redraw(buttonArea.x, 0, buttonArea.width, buttonArea.y, false); // redraw space above scroll buttons |
| if (buttonArea.height < getClientArea().y) { |
| int redrawY = buttonArea.y + buttonArea.height; |
| redraw( |
| buttonArea.x, redrawY, |
| buttonArea.width, getClientArea().y - redrawY, false); // redraw space below scroll buttons |
| } |
| } |
| } |
| /** |
| * Redraw the scroll button that was pressed down |
| */ |
| void redrawHitButton(Event event) { |
| Rectangle scrollButtonArea = getScrollButtonArea(); |
| int scrollButtonWidth = scrollButtonArea.width / 2; |
| |
| if (isLeftButtonHit(event)) { |
| redraw( |
| scrollButtonArea.x, scrollButtonArea.y, |
| scrollButtonWidth, scrollButtonArea.height, false); |
| } |
| else |
| if (isRightButtonHit(event)) { |
| redraw( |
| scrollButtonArea.x + scrollButtonWidth, scrollButtonArea.y, |
| scrollButtonWidth, scrollButtonArea.height, false); |
| } |
| } |
| /** |
| * Redraw both scroll buttons |
| */ |
| void redrawScrollButtons() { |
| Rectangle scrollButtonArea = getScrollButtonArea(); |
| |
| redraw( |
| scrollButtonArea.x, scrollButtonArea.y, |
| scrollButtonArea.width, scrollButtonArea.height, false); |
| } |
| /** |
| * Redraw the tabs at the specified indexes. |
| */ |
| void redrawSelectionChange(int oldSelection, int newSelection) { |
| if (oldSelection != -1) { |
| TabItem tab = items[oldSelection]; |
| // since the tab used to be selected, we need to clear its old expanded size |
| redraw(tab.x - SELECTED_TAB_HORIZONTAL_EXPANSION, |
| tab.y - SELECTED_TAB_TOP_EXPANSION, |
| tab.width + 2 * SELECTED_TAB_HORIZONTAL_EXPANSION, |
| tab.height + SELECTED_TAB_TOP_EXPANSION); |
| } |
| if (newSelection != -1) { |
| TabItem tab = items[newSelection]; |
| // this tab is already at the expanded size |
| redraw(tab.x, tab.y, tab.width, tab.height); |
| } |
| // make sure the tab is repainted before the new page is made visible. |
| // The latter could take a long time and delay the screen update. |
| update(); |
| } |
| /** |
| * Redraw the whole tab area |
| */ |
| void redrawTabs() { |
| redraw(0, 0, super.getClientArea().width, getClientArea().y); |
| } |
| void removeControl (Control control) { |
| super.removeControl (control); |
| for (int i=0; i<items.length; i++) { |
| TabItem item = items [i]; |
| if (item.control == control) item.setControl (null); |
| } |
| } |
| /** |
| * Removes the listener from the collection of listeners who will |
| * be notified when the user changes the receiver's selection. |
| * |
| * @param listener the listener which should no longer be notified |
| * |
| * @exception IllegalArgumentException <ul> |
| * <li>ERROR_NULL_ARGUMENT - if the listener is null</li> |
| * </ul> |
| * @exception SWTException <ul> |
| * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> |
| * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> |
| * </ul> |
| * |
| * @see SelectionListener |
| * @see #addSelectionListener |
| */ |
| public void removeSelectionListener(SelectionListener listener) { |
| checkWidget(); |
| if (listener == null) { |
| error(SWT.ERROR_NULL_ARGUMENT); |
| } |
| removeListener(SWT.Selection, listener); |
| removeListener(SWT.DefaultSelection, listener); |
| } |
| /** |
| * The widget was resized. Adjust the size of the currently selected page. |
| */ |
| void resize() { |
| if (selectedIndex != -1) { |
| Control control = items[selectedIndex].getControl(); |
| if (control != null && !control.isDisposed()) { |
| control.setBounds(getClientArea()); |
| } |
| } |
| ensureRightFreeSpaceUsed(); |
| } |
| |
| /** |
| * Scroll the tab items to the left. |
| */ |
| void scrollLeft() { |
| if (topTabIndex > 0) { |
| --topTabIndex; |
| layoutItems(); |
| redrawTabs(); |
| } |
| } |
| /** |
| * Scroll the tab items to the right. |
| */ |
| void scrollRight() { |
| if (items.length > 0 && topTabIndex < items.length - 1) { |
| TabItem lastTabItem = items[items.length-1]; |
| int tabStopX = lastTabItem.x + lastTabItem.width; |
| if (tabStopX > super.getClientArea().width - SCROLL_BUTTON_SIZE * 2) { |
| topTabIndex++; |
| layoutItems(); |
| redrawTabs(); |
| } |
| } |
| } |
| boolean setBounds (int x, int y, int width, int height, boolean move, boolean resize) { |
| boolean changed = super.setBounds (x, y, width, height, move, resize); |
| if (changed && resize) resize (); |
| return changed; |
| } |
| public void setFont(Font font) { |
| checkWidget(); |
| if (font != null && font.equals(getFont())) return; |
| super.setFont(font); |
| layoutItems(); |
| redrawTabs(); |
| } |
| /** |
| * Selects the item at the given zero-relative index in the receiver. |
| * If the item at the index was already selected, it remains selected. |
| * The current selection is first cleared, then the new items are |
| * selected. Indices that are out of range are ignored. |
| * |
| * @param index the index of the item to select |
| * |
| * @exception SWTException <ul> |
| * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> |
| * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> |
| * </ul> |
| */ |
| public void setSelection(int index) { |
| checkWidget(); |
| if (!(0 <= index && index < items.length)) return; |
| setSelection(index, false); |
| } |
| /** |
| * Sets the receiver's selection to the given item. |
| * The current selected is first cleared, then the new item is |
| * selected. |
| * |
| * @param item the item to select |
| * |
| * @exception IllegalArgumentException <ul> |
| * <li>ERROR_NULL_ARGUMENT - if the item is null</li> |
| * </ul> |
| * @exception SWTException <ul> |
| * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> |
| * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> |
| * </ul> |
| * |
| * @since 3.2 |
| */ |
| public void setSelection(TabItem item) { |
| checkWidget(); |
| if (item == null) error(SWT.ERROR_NULL_ARGUMENT); |
| setSelection(new TabItem[]{item}); |
| } |
| /** |
| * Sets the receiver's selection to be the given array of items. |
| * The current selected is first cleared, then the new items are |
| * selected. |
| * |
| * @param items the array of items |
| * |
| * @exception IllegalArgumentException <ul> |
| * <li>ERROR_NULL_ARGUMENT - if the items array is null</li> |
| * </ul> |
| * @exception SWTException <ul> |
| * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> |
| * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> |
| * </ul> |
| */ |
| public void setSelection(TabItem selectedItems[]) { |
| checkWidget(); |
| if (selectedItems == null) error(SWT.ERROR_NULL_ARGUMENT); |
| int index = -1; |
| if (selectedItems.length > 0) { |
| index = indexOf(selectedItems[0]); |
| } |
| setSelection(index, false); |
| } |
| /** |
| * Set the selection to the tab at the specified index. |
| */ |
| void setSelection(int index, boolean notify) { |
| if (selectedIndex == index) return; |
| int oldIndex = selectedIndex; |
| |
| if (selectedIndex == index || index >= getItemCount()) return; |
| if (selectedIndex != -1) { |
| Control control = items[selectedIndex].control; |
| if (control != null && !control.isDisposed()) { |
| control.setVisible(false); |
| } |
| } |
| if (index < 0) { |
| index = -1; // make sure the index is always -1 if it's negative |
| } |
| selectedIndex = index; |
| layoutItems(); |
| ensureVisible(index); |
| redrawSelectionChange(oldIndex, index); |
| if (index >= 0) { |
| Control control = items[index].control; |
| if (control != null && !control.isDisposed()) { |
| control.setBounds(getClientArea()); |
| control.setVisible(true); |
| } |
| } |
| |
| if (notify) { |
| if (selectedIndex != oldIndex && selectedIndex != -1) { |
| Event event = new Event(); |
| event.item = getSelection()[0]; |
| notifyListeners(SWT.Selection, event); |
| } |
| } |
| } |
| public void setToolTipText (String string) { |
| checkWidget(); |
| super.setToolTipText (string); |
| toolTipText = string; |
| } |
| void traversal(Event event) { |
| switch (event.detail) { |
| case SWT.TRAVERSE_ESCAPE: |
| case SWT.TRAVERSE_RETURN: |
| case SWT.TRAVERSE_TAB_NEXT: |
| case SWT.TRAVERSE_TAB_PREVIOUS: |
| case SWT.TRAVERSE_MNEMONIC: |
| case SWT.TRAVERSE_PAGE_NEXT: |
| case SWT.TRAVERSE_PAGE_PREVIOUS: |
| event.doit = true; |
| } |
| } |
| boolean traverseItem (boolean next) { |
| return false; |
| } |
| boolean traversePage (boolean next) { |
| int count = items.length; |
| if (count == 0) return false; |
| int index = selectedIndex; |
| if (index == -1) { |
| index = 0; |
| } else { |
| int offset = next ? 1 : -1; |
| index = (index + offset + count) % count; |
| } |
| setSelection (index, true); |
| return true; |
| } |
| } |