Java tutorial
/******************************************************************************* * Copyright (c) 2002, 2017 Innoopract Informationssysteme GmbH 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: * Innoopract Informationssysteme GmbH - initial API and implementation * EclipseSource - ongoing development ******************************************************************************/ package org.eclipse.swt.custom; import org.eclipse.rap.rwt.internal.lifecycle.WidgetLCA; import org.eclipse.rap.rwt.internal.textsize.TextSizeUtil; import org.eclipse.rap.rwt.internal.theme.ThemeAdapter; import org.eclipse.rap.rwt.theme.BoxDimensions; import org.eclipse.swt.SWT; import org.eclipse.swt.SWTException; import org.eclipse.swt.events.ControlAdapter; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.ControlListener; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.FocusEvent; import org.eclipse.swt.events.FocusListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.internal.custom.ICTabFolderAdapter; import org.eclipse.swt.internal.custom.ctabfolderkit.CTabFolderLCA; import org.eclipse.swt.internal.custom.ctabfolderkit.CTabFolderThemeAdapter; import org.eclipse.swt.internal.events.EventTypes; import org.eclipse.swt.internal.widgets.IItemHolderAdapter; import org.eclipse.swt.internal.widgets.IWidgetGraphicsAdapter; import org.eclipse.swt.internal.widgets.ItemHolder; import org.eclipse.swt.internal.widgets.WidgetGraphicsAdapter; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Layout; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.MenuItem; import org.eclipse.swt.widgets.TypedListener; /** * Instances of this class implement the notebook user interface * metaphor. It allows the user to select a notebook page from * set of pages. * <p> * The item children that may be added to instances of this class * must be of type <code>CTabItem</code>. * <code>Control</code> children are created and then set into a * tab item using <code>CTabItem#setControl</code>. * </p><p> * Note that although this class is a subclass of <code>Composite</code>, * it does not make sense to set a layout on it. * </p><p> * IMPORTANT: This class is <em>not</em> intended to be subclassed. * </p><p> * <dl> * <dt><b>Styles:</b></dt> * <dd>CLOSE, TOP, BOTTOM, FLAT, BORDER, SINGLE, MULTI</dd> * <dt><b>Events:</b></dt> * <dd>Selection</dd> * <dd>"CTabFolder2"</dd> * </dl> * </p> * <p> * Note: Only one of the styles TOP and BOTTOM * may be specified. * </p> * <hr/> * <p>Implementation Status: </p> * <p>Attributes, found in SWT, that are not supported</p> * <ul> * <li>simple (treated as <code>true</code>)</li> * </ul> * * @since 1.0 */ public class CTabFolder extends Composite { // internal constants static final int DEFAULT_WIDTH = 64; static final int DEFAULT_HEIGHT = 64; static final int BUTTON_SIZE = 18; static final int SELECTION_FOREGROUND = SWT.COLOR_LIST_FOREGROUND; static final int SELECTION_BACKGROUND = SWT.COLOR_LIST_BACKGROUND; static final int BORDER1_COLOR = SWT.COLOR_WIDGET_NORMAL_SHADOW; static final int FOREGROUND = SWT.COLOR_WIDGET_FOREGROUND; static final int BACKGROUND = SWT.COLOR_WIDGET_BACKGROUND; static final int BUTTON_BORDER = SWT.COLOR_WIDGET_DARK_SHADOW; static final int BUTTON_FILL = SWT.COLOR_LIST_BACKGROUND; /** * 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; private transient ICTabFolderAdapter tabFolderAdapter; private final IWidgetGraphicsAdapter selectionGraphicsAdapter; private final ItemHolder<CTabItem> itemHolder = new ItemHolder<>(CTabItem.class); private final ControlListener resizeListener; private FocusListener focusListener; private Menu showMenu; int selectedIndex = -1; private int firstIndex = -1; // index of the left most visible tab private boolean mru; private int[] priority = new int[0]; final boolean showClose; boolean showUnselectedClose = true; boolean showUnselectedImage = true; boolean showMax; boolean showMin; private boolean inDispose; boolean minimized; boolean maximized; boolean onBottom; final boolean simple = true; // no curvy tab items supported (yet) boolean single; private final Rectangle maxRect = new Rectangle(0, 0, 0, 0); private final Rectangle minRect = new Rectangle(0, 0, 0, 0); // Chevron private final Rectangle chevronRect = new Rectangle(0, 0, 0, 0); private boolean showChevron; // Tab bar private int fixedTabHeight = SWT.DEFAULT; int tabHeight = 0; int minChars = 20; // TopRight control Control topRight; private int topRightAlignment = SWT.RIGHT; private final Rectangle topRightRect = new Rectangle(0, 0, 0, 0); // Client origin and border dimensions private int xClient; private int yClient; private final int highlight_margin; private int highlight_header; private int borderRight; private int borderLeft; private int borderBottom; private int borderTop; // keep track of size changes in order to redraw only affected area // on Resize // Point oldSize; // Colors private Color selectionBackground = null; private Color selectionForeground = null; private Image selectionBgImage = null; /** * Constructs a new instance of this class given its parent * and a style value describing its behavior and appearance. * <p> * The style value is either one of the style constants defined in * class <code>SWT</code> which is applicable to instances of this * class, or must be built by <em>bitwise OR</em>'ing together * (that is, using the <code>int</code> "|" operator) two or more * of those <code>SWT</code> style constants. The class description * lists the style constants that are applicable to the class. * Style bits are also inherited from superclasses. * </p> * * @param parent a widget which will be the parent of the new instance (cannot be null) * @param style the style of widget to construct * * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT - if the parent is null</li> * </ul> * @exception SWTException <ul> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li> * </ul> * * @see SWT#TOP * @see SWT#BOTTOM * @see SWT#FLAT * @see SWT#BORDER * @see SWT#SINGLE * @see SWT#MULTI * @see #getStyle() */ public CTabFolder(Composite parent, int style) { super(parent, checkStyle(style)); super.setLayout(new CTabFolderLayout()); onBottom = (super.getStyle() & SWT.BOTTOM) != 0; single = (super.getStyle() & SWT.SINGLE) != 0; showClose = (super.getStyle() & SWT.CLOSE) != 0; borderRight = (style & SWT.BORDER) != 0 ? 1 : 0; borderLeft = borderRight; borderTop = onBottom ? borderLeft : 0; borderBottom = onBottom ? 0 : borderLeft; highlight_header = (style & SWT.FLAT) != 0 ? 1 : 3; highlight_margin = (style & SWT.FLAT) != 0 ? 0 : 2; updateTabHeight(false); resizeListener = new ControlAdapter() { @Override public void controlResized(ControlEvent event) { onResize(); } }; addControlListener(resizeListener); registerDisposeListener(); selectionGraphicsAdapter = new WidgetGraphicsAdapter(); } ////////////////// // Item management /** * Return the tab items. * * @return the tab items * * @exception SWTException <ul> * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li> * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li> * </ul> */ public CTabItem[] getItems() { checkWidget(); return itemHolder.getItems(); } /** * Return the tab that is located at the specified index. * * @param index the index of the tab item * @return the item at the specified index * * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_RANGE - if the index is out of range</li> * </ul> * @exception SWTException <ul> * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li> * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li> * </ul> */ public CTabItem getItem(int index) { checkWidget(); return itemHolder.getItem(index); } /** * Gets the item at a point in the widget. * * @param pt the point in coordinates relative to the CTabFolder * @return the item at a point or null * * @exception SWTException <ul> * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li> * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li> * </ul> * @since 1.2 */ public CTabItem getItem(Point pt) { // checkWidget(); CTabItem result = null; Point size = getSize(); boolean onChevron = showChevron && chevronRect.contains(pt); int itemCount = itemHolder.size(); if (itemCount > 0 && size.x > borderLeft + borderRight && !onChevron) { CTabItem[] items = itemHolder.getItems(); for (int i = 0; result == null && i < priority.length; i++) { CTabItem item = items[priority[i]]; Rectangle rect = item.getBounds(); if (rect.contains(pt)) { result = item; } } } return result; } /** * Return the number of tabs in the folder. * * @return the number of tabs in the folder * * @exception SWTException <ul> * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li> * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li> * </ul> */ public int getItemCount() { checkWidget(); return itemHolder.size(); } /** * Return the index of the specified tab or -1 if the tab is not * in the receiver. * * @param item the tab item for which the index is required * * @return the index of the specified tab item or -1 * * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT - if the listener is null</li> * </ul> * * @exception SWTException <ul> * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li> * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li> * </ul> */ public int indexOf(CTabItem item) { checkWidget(); return itemHolder.indexOf(item); } /////////////////////// // Selection management /** * Set the selection to the tab at the specified index. * * @param index the index of the tab item to be selected * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public void setSelection(int index) { checkWidget(); if (index >= 0 && index <= itemHolder.size() - 1) { if (selectedIndex != index) { int oldSelectionIndex = selectedIndex; selectedIndex = index; getItem(selectedIndex).showing = false; Control control = getItem(selectedIndex).getControl(); // Adjust bounds of selected control and make it visible (if any) if (control != null && !control.isDisposed()) { control.setBounds(getClientArea()); control.setVisible(true); } // Hide control of previous selection (if any) if (oldSelectionIndex >= 0 && oldSelectionIndex < getItemCount()) { Control oldControl = getItem(oldSelectionIndex).getControl(); if (oldControl != null && !oldControl.isDisposed()) { oldControl.setVisible(false); } } } showItem(getSelection()); } } /** * Return the index of the selected tab item, or -1 if there * is no selection. * * @return the index of the selected tab item or -1 * * @exception SWTException <ul> * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li> * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li> * </ul> */ public int getSelectionIndex() { checkWidget(); return selectedIndex; } /** * Set the selection to the tab at the specified item. * * @param item the tab item to be selected * * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT - if the listener is null</li> * </ul> * * @exception SWTException <ul> * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li> * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li> * </ul> */ public void setSelection(CTabItem item) { checkWidget(); if (item == null) { SWT.error(SWT.ERROR_NULL_ARGUMENT); } int index = itemHolder.indexOf(item); setSelection(index); } /** * Return the selected tab item, or null if there is no selection. * * @return the selected tab item, or null if none has been selected * * @exception SWTException <ul> * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li> * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li> * </ul> */ public CTabItem getSelection() { checkWidget(); CTabItem result = null; if (selectedIndex != -1) { result = itemHolder.getItem(selectedIndex); } return result; } /** * Shows the selection. If the selection is already showing in the receiver, * this method simply returns. Otherwise, the items are scrolled until * the selection is visible. * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @see CTabFolder#showItem(CTabItem) */ public void showSelection() { checkWidget(); if (selectedIndex != -1) { showItem(getSelection()); } } /** * Shows the item. If the item is already showing in the receiver, * this method simply returns. Otherwise, the items are scrolled until * the item is visible. * * @param item the item to be shown * * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT - if the item is null</li> * <li>ERROR_INVALID_ARGUMENT - if the item has been disposed</li> * </ul> * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @see CTabFolder#showSelection() */ public void showItem(CTabItem item) { checkWidget(); if (item == null) { SWT.error(SWT.ERROR_NULL_ARGUMENT); } if (item.isDisposed()) { SWT.error(SWT.ERROR_INVALID_ARGUMENT); } int index = indexOf(item); if (index == -1) { SWT.error(SWT.ERROR_INVALID_ARGUMENT); } int idx = -1; for (int i = 0; idx == -1 && i < priority.length; i++) { if (priority[i] == index) { idx = i; } } if (mru) { // move to front of mru order int[] newPriority = new int[priority.length]; System.arraycopy(priority, 0, newPriority, 1, idx); System.arraycopy(priority, idx + 1, newPriority, idx + 1, priority.length - idx - 1); newPriority[0] = index; priority = newPriority; } if (!item.isShowing()) { updateItems(index); } } ////////////////////////////// // Most recently used settings /** * When there is not enough horizontal space to show all the tabs, * by default, tabs are shown sequentially from left to right in * order of their index. When the MRU visibility is turned on, * the tabs that are visible will be the tabs most recently selected. * Tabs will still maintain their left to right order based on index * but only the most recently selected tabs are visible. * <p> * For example, consider a CTabFolder that contains "Tab 1", "Tab 2", * "Tab 3" and "Tab 4" (in order by index). The user selects * "Tab 1" and then "Tab 3". If the CTabFolder is now * compressed so that only two tabs are visible, by default, * "Tab 2" and "Tab 3" will be shown ("Tab 3" since it is currently * selected and "Tab 2" because it is the previous item in index order). * If MRU visibility is enabled, the two visible tabs will be "Tab 1" * and "Tab 3" (in that order from left to right).</p> * * @param show the new visibility state * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public void setMRUVisible(boolean show) { checkWidget(); if (mru != show) { mru = show; if (!mru) { int idx = firstIndex; int next = 0; for (int i = firstIndex; i < priority.length; i++) { priority[next++] = i; } for (int i = 0; i < idx; i++) { priority[next++] = i; } updateItems(); } } } /** * Returns <code>true</code> if the receiver displays most * recently used tabs and <code>false</code> otherwise. * <p> * When there is not enough horizontal space to show all the tabs, * by default, tabs are shown sequentially from left to right in * order of their index. When the MRU visibility is turned on, * the tabs that are visible will be the tabs most recently selected. * Tabs will still maintain their left to right order based on index * but only the most recently selected tabs are visible. * <p> * For example, consider a CTabFolder that contains "Tab 1", "Tab 2", * "Tab 3" and "Tab 4" (in order by index). The user selects * "Tab 1" and then "Tab 3". If the CTabFolder is now * compressed so that only two tabs are visible, by default, * "Tab 2" and "Tab 3" will be shown ("Tab 3" since it is currently * selected and "Tab 2" because it is the previous item in index order). * If MRU visibility is enabled, the two visible tabs will be "Tab 1" * and "Tab 3" (in that order from left to right).</p> * * @return the receiver's header's visibility state * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public boolean getMRUVisible() { checkWidget(); return mru; } //////////////////////////////// // Minimize / Maximize / Restore /** * Marks the receiver's maximize button as visible if the argument is <code>true</code>, * and marks it invisible otherwise. * * @param maximizeVisible the new visibility state * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public void setMaximizeVisible(boolean maximizeVisible) { checkWidget(); if (showMax != maximizeVisible) { showMax = maximizeVisible; updateItems(); } } /** * Returns <code>true</code> if the maximize button * is visible. * * @return the visibility of the maximized button * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public boolean getMaximizeVisible() { checkWidget(); return showMax; } /** * Marks the receiver's minimize button as visible if the argument is <code>true</code>, * and marks it invisible otherwise. * * @param minimizeVisible the new visibility state * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public void setMinimizeVisible(boolean minimizeVisible) { checkWidget(); if (showMin != minimizeVisible) { showMin = minimizeVisible; updateItems(); } } /** * Returns <code>true</code> if the minimize button * is visible. * * @return the visibility of the minimized button * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public boolean getMinimizeVisible() { checkWidget(); return showMin; } /** * Marks the receiver's minimize button as visible if the argument is <code>true</code>, * and marks it invisible otherwise. * * @param minimized the new visibility state * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public void setMinimized(boolean minimized) { checkWidget(); if (this.minimized != minimized) { if (minimized && maximized) { setMaximized(false); } this.minimized = minimized; } } /** * Returns <code>true</code> if the receiver is minimized. * * @return the receiver's minimized state * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public boolean getMinimized() { checkWidget(); return minimized; } /** * Sets the maximized state of the receiver. * * @param maximized the new maximized state * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public void setMaximized(boolean maximized) { checkWidget(); if (this.maximized != maximized) { if (maximized && minimized) { setMinimized(false); } this.maximized = maximized; } } /** * Returns <code>true</code> if the receiver is maximized. * <p> * * @return the receiver's maximized state * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public boolean getMaximized() { checkWidget(); return maximized; } ////////////////////////////////////// // Appearance and dimension properties /** * Sets the layout which is associated with the receiver to be * the argument which may be null. * <p> * Note: No Layout can be set on this Control because it already * manages the size and position of its children. * </p> * * @param layout the receiver's new layout or null * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ @Override public void setLayout(Layout layout) { checkWidget(); // ignore - CTabFolder manages its own layout } /** * Specify a fixed height for the tab items. If no height is specified, * the default height is the height of the text or the image, whichever * is greater. Specifying a height of -1 will revert to the default height. * * @param height the pixel value of the height or -1 * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * <li>ERROR_INVALID_ARGUMENT - if called with a height of less than 0</li> * </ul> */ public void setTabHeight(int height) { checkWidget(); if (height < -1) { SWT.error(SWT.ERROR_INVALID_ARGUMENT); } fixedTabHeight = height; updateTabHeight(false); } /** * Returns the height of the tab * * @return the height of the tab * * @exception SWTException <ul> * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li> * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li> * </ul> */ public int getTabHeight() { checkWidget(); int result; if (fixedTabHeight != SWT.DEFAULT) { result = fixedTabHeight; } else { result = tabHeight - 1; // -1 for line drawn across top of tab } return result; } /** * Returns the number of characters that will * appear in a fully compressed tab. * * @return number of characters that will appear in a fully compressed tab */ public int getMinimumCharacters() { checkWidget(); return minChars; } /** * Sets the minimum number of characters that will * be displayed in a fully compressed tab. * * @param minimumCharacters the minimum number of characters that will be displayed in a fully compressed tab * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * <li>ERROR_INVALID_RANGE - if the count is less than zero</li> * </ul> */ public void setMinimumCharacters(int minimumCharacters) { checkWidget(); if (minimumCharacters < 0) { SWT.error(SWT.ERROR_INVALID_RANGE); } if (minChars != minimumCharacters) { minChars = minimumCharacters; updateItems(); } } @Override public int getStyle() { checkWidget(); int result = super.getStyle(); result &= ~(SWT.TOP | SWT.BOTTOM); result |= onBottom ? SWT.BOTTOM : SWT.TOP; result &= ~(SWT.SINGLE | SWT.MULTI); result |= single ? SWT.SINGLE : SWT.MULTI; if (borderLeft != 0) { result |= SWT.BORDER; } return result; } /** * Returns <code>true</code> if the CTabFolder only displays the selected tab * and <code>false</code> if the CTabFolder displays multiple tabs. * * @return <code>true</code> if the CTabFolder only displays the selected tab and <code>false</code> if the CTabFolder displays multiple tabs */ public boolean getSingle() { checkWidget(); return single; } /** * Sets the number of tabs that the CTabFolder should display * * @param single <code>true</code> if only the selected tab should be displayed otherwise, multiple tabs will be shown. * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public void setSingle(boolean single) { checkWidget(); if (this.single != single) { this.single = single; updateItemsWithResizeEvent(); } } /** * Returns the position of the tab. Possible values are SWT.TOP or SWT.BOTTOM. * * @return the position of the tab * * @exception SWTException <ul> * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li> * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li> * </ul> */ public int getTabPosition() { checkWidget(); return onBottom ? SWT.BOTTOM : SWT.TOP; } /** * Specify whether the tabs should appear along the top of the folder * or along the bottom of the folder. * * @param position <code>SWT.TOP</code> for tabs along the top or <code>SWT.BOTTOM</code> for tabs along the bottom * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * <li>ERROR_INVALID_ARGUMENT - if the position value is not either SWT.TOP or SWT.BOTTOM</li> * </ul> */ public void setTabPosition(int position) { checkWidget(); if (position != SWT.TOP && position != SWT.BOTTOM) { SWT.error(SWT.ERROR_INVALID_ARGUMENT); } if (onBottom != (position == SWT.BOTTOM)) { onBottom = position == SWT.BOTTOM; borderTop = onBottom ? borderLeft : 0; borderBottom = onBottom ? 0 : borderRight; updateTabHeight(true); updateItemsWithResizeEvent(); } } /** * Returns <code>true</code> if the receiver's border is visible. * * @return the receiver's border visibility state * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public boolean getBorderVisible() { checkWidget(); return borderLeft == 1; } /** * Toggle the visibility of the border * * @param show true if the border should be displayed * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public void setBorderVisible(boolean show) { checkWidget(); if ((borderLeft != 1) != !show) { borderLeft = borderRight = show ? 1 : 0; borderTop = onBottom ? borderLeft : 0; borderBottom = onBottom ? 0 : borderLeft; updateItemsWithResizeEvent(); } } /** * Returns <code>true</code> if an image appears * in unselected tabs. * * @return <code>true</code> if an image appears in unselected tabs * * @since 1.3 */ public boolean getUnselectedImageVisible() { checkWidget(); return showUnselectedImage; } /** * Specify whether the image appears on unselected tabs. * * @param visible <code>true</code> makes the image appear * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * * @since 1.3 */ public void setUnselectedImageVisible(boolean visible) { checkWidget(); if (showUnselectedImage != visible) { showUnselectedImage = visible; updateItems(); } } /** * Returns <code>true</code> if the close button appears * when the user hovers over an unselected tabs. * * @return <code>true</code> if the close button appears on unselected tabs */ public boolean getUnselectedCloseVisible() { checkWidget(); return showUnselectedClose; } /** * Specify whether the close button appears * when the user hovers over an unselected tabs. * * @param visible <code>true</code> makes the close button appear * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public void setUnselectedCloseVisible(boolean visible) { checkWidget(); if (showUnselectedClose != visible) { showUnselectedClose = visible; updateItems(); } } @Override public Rectangle computeTrim(int x, int y, int width, int height) { checkWidget(); int trimX = x - marginWidth - highlight_margin - borderLeft; int trimWidth = width + borderLeft + borderRight + 2 * marginWidth + 2 * highlight_margin; int trimY; int trimHeight; if (minimized) { trimY = onBottom ? y - borderTop : y - highlight_header - tabHeight - borderTop; trimHeight = borderTop + borderBottom + tabHeight + highlight_header; } else { trimY = onBottom ? y - marginHeight - highlight_margin - borderTop : y - marginHeight - highlight_header - tabHeight - borderTop; trimHeight = height + borderTop + borderBottom + 2 * marginHeight + tabHeight + highlight_header + highlight_margin; } return new Rectangle(trimX, trimY, trimWidth, trimHeight); } /////////////////// // Selection colors /** * Sets the receiver's selection background color to the color specified * by the argument, or to the default system color for the control * if the argument is null. * * @param color the new color (or null) * * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li> * </ul> * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public void setSelectionBackground(Color color) { checkWidget(); if (null != color && color.isDisposed()) { SWT.error(SWT.ERROR_INVALID_ARGUMENT); } selectionBackground = color; } /** * Returns the receiver's selection background color. * * @return the selection background color of the receiver * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public Color getSelectionBackground() { checkWidget(); Color result = selectionBackground; if (result == null) { result = getThemeAdapter().getSelectedBackground(); } if (result == null) { // Should never happen as the theming must prevent transparency for // this color throw new IllegalStateException("Transparent selection background color"); } return result; } /** * Specify a gradient of colours to be draw in the background of the selected tab. * For example to draw a gradient that varies from dark blue to blue and then to * white, use the following call to setBackground: * <pre> * cfolder.setBackground(new Color[]{display.getSystemColor(SWT.COLOR_DARK_BLUE), * display.getSystemColor(SWT.COLOR_BLUE), * display.getSystemColor(SWT.COLOR_WHITE), * display.getSystemColor(SWT.COLOR_WHITE)}, * new int[] {25, 50, 100}); * </pre> * * @param colors an array of Color that specifies the colors to appear in the gradient * in order of appearance left to right. The value <code>null</code> clears the * background gradient. * @param percents an array of integers between 0 and 100 specifying the percent of the width * of the widget at which the color should change. The size of the percents array must be one * less than the size of the colors array. * * @exception SWTException <ul> * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li> * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li> * </ul> * @since 1.3 */ public void setSelectionBackground(Color[] colors, int[] percents) { setSelectionBackground(colors, percents, false); } /** * Specify a gradient of colours to be draw in the background of the selected tab. * For example to draw a vertical gradient that varies from dark blue to blue and then to * white, use the following call to setBackground: * <pre> * cfolder.setBackground(new Color[]{display.getSystemColor(SWT.COLOR_DARK_BLUE), * display.getSystemColor(SWT.COLOR_BLUE), * display.getSystemColor(SWT.COLOR_WHITE), * display.getSystemColor(SWT.COLOR_WHITE)}, * new int[] {25, 50, 100}, true); * </pre> * * @param colors an array of Color that specifies the colors to appear in the gradient * in order of appearance top to bottom. The value <code>null</code> clears the * background gradient. * @param percents an array of integers between 0 and 100 specifying the percent of the width * of the widget at which the color should change. The size of the percents array must be one * less than the size of the colors array. * * @param vertical indicate the direction of the gradient. True is vertical and false is horizontal. * * @exception IllegalArgumentException <ul> * <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li> * * @exception SWTException <ul> * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li> * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li> * </ul> * * @since 1.3 */ public void setSelectionBackground(Color[] colors, int[] percents, boolean vertical) { checkWidget(); if (colors != null) { for (int i = 0; i < colors.length; i++) { if (colors[i] != null && colors[i].isDisposed()) { SWT.error(SWT.ERROR_INVALID_ARGUMENT); } } // The colors array can optionally have an extra entry which describes the // highlight top color. // Thus its either one or two larger than the percents array if (percents == null || !((percents.length == colors.length - 1) || (percents.length == colors.length - 2))) { SWT.error(SWT.ERROR_INVALID_ARGUMENT); } for (int i = 0; i < percents.length; i++) { if (percents[i] < 0 || percents[i] > 100) { SWT.error(SWT.ERROR_INVALID_ARGUMENT); } if (i > 0 && percents[i] < percents[i - 1]) { SWT.error(SWT.ERROR_INVALID_ARGUMENT); } } } if (colors == null) { selectionGraphicsAdapter.setBackgroundGradient(null, null, vertical); setSelectionBackground((Color) null); } else { int colorsLength = colors.length; if (percents.length == colors.length - 2) { colorsLength = colors.length - 1; } Color[] gradientColors = new Color[colorsLength]; System.arraycopy(colors, 0, gradientColors, 0, colorsLength); int[] gradientPercents = new int[gradientColors.length]; if (gradientColors.length > 0) { gradientPercents[0] = 0; for (int i = 1; i < gradientPercents.length; i++) { gradientPercents[i] = percents[i - 1]; } selectionGraphicsAdapter.setBackgroundGradient(gradientColors, gradientPercents, vertical); setSelectionBackground(gradientColors[gradientColors.length - 1]); } } } /** * Set the image to be drawn in the background of the selected tab. Image * is stretched or compressed to cover entire selection tab area. * * @param image the image to be drawn in the background * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> * @since 1.3 */ public void setSelectionBackground(Image image) { checkWidget(); selectionBgImage = image; } /** * Set the foreground color of the selected tab. * * @param color the color of the text displayed in the selected tab * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public void setSelectionForeground(Color color) { checkWidget(); selectionForeground = color; } /** * Returns the receiver's selection foreground color. * * @return the selection foreground color of the receiver * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * </ul> */ public Color getSelectionForeground() { checkWidget(); Color result = selectionForeground; if (result == null) { result = getThemeAdapter().getSelectedForeground(); } if (result == null) { // Should never happen as the theming must prevent transparency for // this color throw new IllegalStateException("Transparent selection foreground color"); } return result; } ////////////////////////////////// // Manipulation of topRight control /** * Set the control that appears in the top right corner of the tab folder. * Typically this is a close button or a composite with a Menu and close button. * The topRight control is optional. Setting the top right control to null will * remove it from the tab folder. * * @param control the control to be displayed in the top right corner or null * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * <li>ERROR_INVALID_ARGUMENT - if the control is not a child of this CTabFolder</li> * </ul> */ public void setTopRight(Control control) { checkWidget(); setTopRight(control, SWT.RIGHT); } /** * Set the control that appears in the top right corner of the tab folder. * Typically this is a close button or a composite with a Menu and close button. * The topRight control is optional. Setting the top right control to null * will remove it from the tab folder. * <p> * The alignment parameter sets the layout of the control in the tab area. * <code>SWT.RIGHT</code> will cause the control to be positioned on the far * right of the folder and it will have its default size. <code>SWT.FILL</code> * will size the control to fill all the available space to the right of the * last tab. If there is no available space, the control will not be visible. * </p> * * @param control the control to be displayed in the top right corner or null * @param alignment <code>SWT.RIGHT</code> or <code>SWT.FILL</code> * * @exception SWTException <ul> * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li> * <li>ERROR_INVALID_ARGUMENT - if the control is not a child of this CTabFolder</li> * </ul> */ public void setTopRight(Control control, int alignment) { checkWidget(); if (alignment != SWT.RIGHT && alignment != SWT.FILL) { SWT.error(SWT.ERROR_INVALID_ARGUMENT); } if (control != null && control.getParent() != this) { SWT.error(SWT.ERROR_INVALID_PARENT); } if (topRight != control || topRightAlignment != alignment) { topRight = control; topRightAlignment = alignment; if (updateItems()) { redraw(); } } } /** * Returns the control in the top right corner of the tab folder. * Typically this is a close button or a composite with a menu and close button. * * @return the control in the top right corner of the tab folder or null * * @exception SWTException <ul> * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li> * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li> * </ul> */ public Control getTopRight() { checkWidget(); return topRight; } /** * Returns the alignment of the top right control. * * @return the alignment of the top right control which is either * <code>SWT.RIGHT</code> or <code>SWT.FILL</code> * * @exception SWTException <ul> * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li> * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li> * </ul> * * @since 1.3 */ public int getTopRightAlignment() { checkWidget(); return topRightAlignment; } /////////////////////////// // Adaptable implementation @Override @SuppressWarnings("unchecked") public <T> T getAdapter(Class<T> adapter) { if (adapter == IItemHolderAdapter.class) { return (T) itemHolder; } if (adapter == ICTabFolderAdapter.class) { if (tabFolderAdapter == null) { tabFolderAdapter = new CTabFolderAdapter(); } return (T) tabFolderAdapter; } if (adapter == WidgetLCA.class) { return (T) CTabFolderLCA.INSTANCE; } return super.getAdapter(adapter); } //////////////////// // Control overrides @Override public void setFont(Font font) { checkWidget(); if (font != getFont()) { super.setFont(font); if (!updateTabHeight(false)) { updateItems(); } } } @Override public int getBorderWidth() { checkWidget(); return 0; } ////////////////////// // Composite overrides @Override public Rectangle getClientArea() { checkWidget(); Rectangle result; if (minimized) { result = new Rectangle(xClient, yClient, 0, 0); } else { Point size = getSize(); int width = size.x - borderLeft - borderRight - 2 * marginWidth - 2 * highlight_margin; int height = size.y - borderTop - borderBottom - 2 * marginHeight - highlight_margin - highlight_header; height -= tabHeight; result = new Rectangle(xClient, yClient, width, height); } return result; } /////////////////////////////////////// // Listener registration/deregistration /** * Adds the listener to receive events. * <p> * * @param listener the listener * * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT - if the listener is null</li> * </ul> * @exception SWTException <ul> * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li> * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li> * </ul> */ public void addSelectionListener(SelectionListener listener) { checkWidget(); if (listener == null) { SWT.error(SWT.ERROR_NULL_ARGUMENT); } TypedListener typedListener = new TypedListener(listener); addListener(SWT.Selection, typedListener); addListener(SWT.DefaultSelection, typedListener); } /** * Removes the listener. * * @param listener the listener * * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT - if the listener is null</li> * </ul> * * @exception SWTException <ul> * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li> * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li> * </ul> */ public void removeSelectionListener(SelectionListener listener) { checkWidget(); if (listener == null) { SWT.error(SWT.ERROR_NULL_ARGUMENT); } removeListener(SWT.Selection, listener); removeListener(SWT.DefaultSelection, listener); } /** * * Adds the listener to the collection of listeners who will * be notified when a tab item is closed, minimized, maximized, * restored, or to show the list of items that are not * currently visible. * * @param listener the listener which should be notified * * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT - if the listener is null</li> * </ul> * * @exception SWTException <ul> * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li> * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li> * </ul> * * @see CTabFolder2Listener * @see #removeCTabFolder2Listener(CTabFolder2Listener) */ public void addCTabFolder2Listener(CTabFolder2Listener listener) { checkWidget(); if (listener == null) { SWT.error(SWT.ERROR_NULL_ARGUMENT); } TypedCTabFolderListener typedListener = new TypedCTabFolderListener(listener); addListener(EventTypes.CTAB_FOLDER_MINIMIZE, typedListener); addListener(EventTypes.CTAB_FOLDER_MAXIMIZE, typedListener); addListener(EventTypes.CTAB_FOLDER_RESTORE, typedListener); addListener(EventTypes.CTAB_FOLDER_CLOSE, typedListener); addListener(EventTypes.CTAB_FOLDER_SHOW_LIST, typedListener); } /** * Removes the listener. * * @param listener the listener * * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT - if the listener is null</li> * </ul> * * @exception SWTException <ul> * <li>ERROR_THREAD_INVALID_ACCESS when called from the wrong thread</li> * <li>ERROR_WIDGET_DISPOSED when the widget has been disposed</li> * </ul> * * @see #addCTabFolder2Listener(CTabFolder2Listener) */ public void removeCTabFolder2Listener(CTabFolder2Listener listener) { checkWidget(); if (listener == null) { SWT.error(SWT.ERROR_NULL_ARGUMENT); } removeListener(EventTypes.CTAB_FOLDER_MINIMIZE, listener); removeListener(EventTypes.CTAB_FOLDER_MAXIMIZE, listener); removeListener(EventTypes.CTAB_FOLDER_RESTORE, listener); removeListener(EventTypes.CTAB_FOLDER_CLOSE, listener); removeListener(EventTypes.CTAB_FOLDER_SHOW_LIST, listener); } /////////////////////////////////// // Helping methods to arrange items boolean updateItems() { return updateItems(selectedIndex); } boolean updateItems(int showIndex) { CTabItem[] items = itemHolder.getItems(); if (!single && !mru && showIndex != -1) { // make sure selected item will be showing int firstIndex = showIndex; if (priority[0] < showIndex) { int maxWidth = getRightItemEdge() - borderLeft; // if (!simple) maxWidth -= curveWidth - 2*curveIndent; int width = 0; int[] widths = new int[items.length]; // GC gc = new GC(this); for (int i = priority[0]; i <= showIndex; i++) { // widths[i] = items[i].preferredWidth(gc, i == selectedIndex, true); widths[i] = items[i].preferredWidth(i == selectedIndex, true); width += widths[i]; if (width > maxWidth) { break; } } if (width > maxWidth) { width = 0; for (int i = showIndex; i >= 0; i--) { // if (widths[i] == 0) widths[i] = items[i].preferredWidth(gc, i == selectedIndex, true); if (widths[i] == 0) { widths[i] = items[i].preferredWidth(i == selectedIndex, true); } width += widths[i]; if (width > maxWidth) { break; } firstIndex = i; } } else { firstIndex = priority[0]; for (int i = showIndex + 1; i < items.length; i++) { // widths[i] = items[i].preferredWidth(gc, i == selectedIndex, true); widths[i] = items[i].preferredWidth(i == selectedIndex, true); width += widths[i]; if (width >= maxWidth) { break; } } if (width < maxWidth) { for (int i = priority[0] - 1; i >= 0; i--) { // if (widths[i] == 0) widths[i] = items[i].preferredWidth(gc, i == selectedIndex, true); if (widths[i] == 0) { widths[i] = items[i].preferredWidth(i == selectedIndex, true); } width += widths[i]; if (width > maxWidth) { break; } firstIndex = i; } } } // gc.dispose(); } if (firstIndex != priority[0]) { int index = 0; for (int i = firstIndex; i < items.length; i++) { priority[index++] = i; } for (int i = 0; i < firstIndex; i++) { priority[index++] = i; } } } boolean oldShowChevron = showChevron; boolean changed = setItemSize(); changed |= setItemLocation(); setButtonBounds(); changed |= showChevron != oldShowChevron; // if (changed && getToolTipText() != null) { // Point pt = getDisplay().getCursorLocation(); // pt = toControl(pt); // _setToolTipText(pt.x, pt.y); // } redraw(); return changed; } boolean setItemLocation() { CTabItem[] items = itemHolder.getItems(); boolean changed = false; if (items.length == 0) { return false; } Point size = getSize(); int y = onBottom ? Math.max(borderBottom, size.y - borderBottom - tabHeight) : borderTop; if (single) { int defaultX = getDisplay().getBounds().width + 10; // off screen for (int i = 0; i < items.length; i++) { CTabItem item = items[i]; if (i == selectedIndex) { firstIndex = selectedIndex; int oldX = item.x, oldY = item.y; item.x = borderLeft; item.y = y; item.showing = true; if (showClose || item.showClose) { item.closeRect.x = borderLeft + getItemPaddingLeft(true); item.closeRect.y = onBottom ? size.y - borderBottom - tabHeight + (tabHeight - BUTTON_SIZE) / 2 : borderTop + (tabHeight - BUTTON_SIZE) / 2; } if (item.x != oldX || item.y != oldY) { changed = true; } } else { item.x = defaultX; item.showing = false; } } } else { int rightItemEdge = getRightItemEdge(); int maxWidth = rightItemEdge - borderLeft; int width = 0; for (int i = 0; i < priority.length; i++) { CTabItem item = items[priority[i]]; width += item.width; item.showing = i == 0 ? true : item.width > 0 && width <= maxWidth; // if (!simple && priority[i] == selectedIndex) width += curveWidth - 2*curveIndent; } int x = 0; int defaultX = getDisplay().getBounds().width + 10; // off screen firstIndex = items.length - 1; for (int i = 0; i < items.length; i++) { CTabItem item = items[i]; if (!item.showing) { if (item.x != defaultX) { changed = true; } item.x = defaultX; } else { firstIndex = Math.min(firstIndex, i); if (item.x != x || item.y != y) { changed = true; } item.x = x; item.y = y; if (i == selectedIndex) { int edge = Math.min(item.x + item.width, rightItemEdge); item.closeRect.x = edge - getItemPaddingRight(true) - BUTTON_SIZE; } else { int rightPadding = getItemPaddingRight(false); item.closeRect.x = item.x + item.width - rightPadding - BUTTON_SIZE; } item.closeRect.y = onBottom ? size.y - borderBottom - tabHeight + (tabHeight - BUTTON_SIZE) / 2 : borderTop + (tabHeight - BUTTON_SIZE) / 2; x = x + item.width; // if (!simple && i == selectedIndex) x += curveWidth - 2*curveIndent; } } } return changed; } boolean setItemSize() { CTabItem[] items = itemHolder.getItems(); boolean changed = false; if (isDisposed()) { return changed; } Point size = getSize(); if (size.x <= 0 || size.y <= 0) { return changed; } // Note [rst] We have to substract the border here because on the client, // the position 0,0 is not the origin of the border but the origin of the // area inside the border. xClient = borderLeft + marginWidth + highlight_margin; if (onBottom) { yClient = borderTop + highlight_margin + marginHeight; } else { yClient = borderTop + tabHeight + highlight_header + marginHeight; } showChevron = false; if (single) { showChevron = true; if (selectedIndex != -1) { CTabItem tab = items[selectedIndex]; // GC gc = new GC(this); // int width = tab.preferredWidth(gc, true, false); // gc.dispose(); int width = tab.preferredWidth(true, false); width = Math.min(width, getRightItemEdge() - borderLeft); if (tab.height != tabHeight || tab.width != width) { changed = true; tab.shortenedText = null; tab.shortenedTextWidth = 0; tab.height = tabHeight; tab.width = width; tab.closeRect.width = tab.closeRect.height = 0; if (showClose || tab.showClose) { tab.closeRect.width = BUTTON_SIZE; tab.closeRect.height = BUTTON_SIZE; } } } return changed; } if (items.length == 0) { return changed; } int[] widths; // GC gc = new GC(this); int tabAreaWidth = size.x - borderLeft - borderRight - 3; if (showMin) { tabAreaWidth -= BUTTON_SIZE; } if (showMax) { tabAreaWidth -= BUTTON_SIZE; } if (topRightAlignment == SWT.RIGHT && topRight != null) { Point rightSize = topRight.computeSize(SWT.DEFAULT, SWT.DEFAULT, false); tabAreaWidth -= rightSize.x + 3; } // if (!simple) tabAreaWidth -= curveWidth - 2*curveIndent; tabAreaWidth = Math.max(0, tabAreaWidth); // First, try the minimum tab size at full compression. int minWidth = 0; int[] minWidths = new int[items.length]; for (int i = 0; i < priority.length; i++) { int index = priority[i]; // minWidths[index] = items[index].preferredWidth(gc, index == selectedIndex, true); minWidths[index] = items[index].preferredWidth(index == selectedIndex, true); minWidth += minWidths[index]; if (minWidth > tabAreaWidth) { break; } } if (minWidth > tabAreaWidth) { // full compression required and a chevron showChevron = items.length > 1; if (showChevron) { tabAreaWidth -= 3 * BUTTON_SIZE / 2; } widths = minWidths; int index = selectedIndex != -1 ? selectedIndex : 0; if (tabAreaWidth < widths[index]) { widths[index] = Math.max(0, tabAreaWidth); } } else { int maxWidth = 0; int[] maxWidths = new int[items.length]; for (int i = 0; i < items.length; i++) { // maxWidths[i] = items[i].preferredWidth(gc, i == selectedIndex, false); maxWidths[i] = items[i].preferredWidth(i == selectedIndex, false); maxWidth += maxWidths[i]; } if (maxWidth <= tabAreaWidth) { // no compression required widths = maxWidths; } else { // determine compression for each item int extra = (tabAreaWidth - minWidth) / items.length; while (true) { int large = 0, totalWidth = 0; for (int i = 0; i < items.length; i++) { if (maxWidths[i] > minWidths[i] + extra) { totalWidth += minWidths[i] + extra; large++; } else { totalWidth += maxWidths[i]; } } if (totalWidth >= tabAreaWidth) { extra--; break; } if (large == 0 || tabAreaWidth - totalWidth < large) { break; } extra++; } widths = new int[items.length]; for (int i = 0; i < items.length; i++) { widths[i] = Math.min(maxWidths[i], minWidths[i] + extra); } } } // gc.dispose(); for (int i = 0; i < items.length; i++) { CTabItem tab = items[i]; int width = widths[i]; if (tab.height != tabHeight || tab.width != width) { changed = true; tab.shortenedText = null; tab.shortenedTextWidth = 0; tab.height = tabHeight; tab.width = width; tab.closeRect.width = tab.closeRect.height = 0; if (showClose || tab.showClose) { if (i == selectedIndex || showUnselectedClose) { tab.closeRect.width = BUTTON_SIZE; tab.closeRect.height = BUTTON_SIZE; } } } } return changed; } @SuppressWarnings("unused") void setButtonBounds() { CTabItem[] items = itemHolder.getItems(); Point size = getSize(); // int oldX, oldY, oldWidth, oldHeight; // max button // oldX = maxRect.x; // oldY = maxRect.y; // oldWidth = maxRect.width; // oldHeight = maxRect.height; maxRect.x = maxRect.y = maxRect.width = maxRect.height = 0; if (showMax) { maxRect.x = size.x - borderRight - BUTTON_SIZE - 3; if (borderRight > 0) { maxRect.x += 1; } maxRect.y = onBottom ? size.y - borderBottom - tabHeight + (tabHeight - BUTTON_SIZE) / 2 : borderTop + (tabHeight - BUTTON_SIZE) / 2; maxRect.width = BUTTON_SIZE; maxRect.height = BUTTON_SIZE; } // if (oldX != maxRect.x || oldWidth != maxRect.width || // oldY != maxRect.y || oldHeight != maxRect.height) { // int left = Math.min(oldX, maxRect.x); // int right = Math.max(oldX + oldWidth, maxRect.x + maxRect.width); // int top = onBottom ? size.y - borderBottom - tabHeight: borderTop + 1; // redraw(left, top, right - left, tabHeight, false); // } // min button // oldX = minRect.x; // oldY = minRect.y; // oldWidth = minRect.width; // oldHeight = minRect.height; minRect.x = minRect.y = minRect.width = minRect.height = 0; if (showMin) { minRect.x = size.x - borderRight - maxRect.width - BUTTON_SIZE - 3; if (borderRight > 0) { minRect.x += 1; } minRect.y = onBottom ? size.y - borderBottom - tabHeight + (tabHeight - BUTTON_SIZE) / 2 : borderTop + (tabHeight - BUTTON_SIZE) / 2; minRect.width = BUTTON_SIZE; minRect.height = BUTTON_SIZE; } // if (oldX != minRect.x || oldWidth != minRect.width || // oldY != minRect.y || oldHeight != minRect.height) { // int left = Math.min(oldX, minRect.x); // int right = Math.max(oldX + oldWidth, minRect.x + minRect.width); // int top = onBottom ? size.y - borderBottom - tabHeight: borderTop + 1; // redraw(left, top, right - left, tabHeight, false); // } // top right control // oldX = topRightRect.x; // oldY = topRightRect.y; // oldWidth = topRightRect.width; // oldHeight = topRightRect.height; topRightRect.x = topRightRect.y = topRightRect.width = topRightRect.height = 0; if (topRight != null) { switch (topRightAlignment) { case SWT.FILL: { int rightEdge = size.x - borderRight - 3 - maxRect.width - minRect.width; if (!simple && borderRight > 0 && !showMax && !showMin) { rightEdge -= 2; } if (single) { if (items.length == 0 || selectedIndex == -1) { topRightRect.x = borderLeft + 3; topRightRect.width = rightEdge - topRightRect.x; } else { // fill size is 0 if item compressed CTabItem item = items[selectedIndex]; if (item.x + item.width + 7 + 3 * BUTTON_SIZE / 2 >= rightEdge) { break; } topRightRect.x = item.x + item.width + 7 + 3 * BUTTON_SIZE / 2; topRightRect.width = rightEdge - topRightRect.x; } } else { // fill size is 0 if chevron showing if (showChevron) { break; } if (items.length == 0) { topRightRect.x = borderLeft + 3; } else { CTabItem item = items[items.length - 1]; topRightRect.x = item.x + item.width; // if (!simple && items.length - 1 == selectedIndex) topRightRect.x += curveWidth - curveIndent; } topRightRect.width = Math.max(0, rightEdge - topRightRect.x); } topRightRect.y = onBottom ? size.y - borderBottom - tabHeight : borderTop + 1; topRightRect.height = tabHeight - 1; break; } case SWT.RIGHT: { Point topRightSize = topRight.computeSize(SWT.DEFAULT, tabHeight, false); int rightEdge = size.x - borderRight - 3 - maxRect.width - minRect.width; if (!simple && borderRight > 0 && !showMax && !showMin) { rightEdge -= 2; } topRightRect.x = rightEdge - topRightSize.x; topRightRect.width = topRightSize.x; topRightRect.y = onBottom ? size.y - borderBottom - tabHeight : borderTop + 1; topRightRect.height = tabHeight - 1; } } topRight.setBounds(topRightRect); } // if (oldX != topRightRect.x || oldWidth != topRightRect.width || // oldY != topRightRect.y || oldHeight != topRightRect.height) { // int left = Math.min(oldX, topRightRect.x); // int right = Math.max(oldX + oldWidth, topRightRect.x + topRightRect.width); // int top = onBottom ? size.y - borderBottom - tabHeight : borderTop + 1; // redraw(left, top, right - left, tabHeight, false); // } // chevron button // oldX = chevronRect.x; // oldY = chevronRect.y; // oldWidth = chevronRect.width; // oldHeight = chevronRect.height; chevronRect.x = chevronRect.y = chevronRect.height = chevronRect.width = 0; if (single) { if (selectedIndex == -1 || items.length > 1) { chevronRect.width = 3 * BUTTON_SIZE / 2; chevronRect.height = BUTTON_SIZE; chevronRect.y = onBottom ? size.y - borderBottom - tabHeight + (tabHeight - chevronRect.height) / 2 : borderTop + (tabHeight - chevronRect.height) / 2; if (selectedIndex == -1) { chevronRect.x = size.x - borderRight - 3 - minRect.width - maxRect.width - topRightRect.width - chevronRect.width; } else { CTabItem item = items[selectedIndex]; int w = size.x - borderRight - 3 - minRect.width - maxRect.width - chevronRect.width; if (topRightRect.width > 0) { w -= topRightRect.width + 3; } chevronRect.x = Math.min(item.x + item.width + 3, w); } if (borderRight > 0) { chevronRect.x += 1; } } } else { if (showChevron) { chevronRect.width = 3 * BUTTON_SIZE / 2; chevronRect.height = BUTTON_SIZE; int i = 0, lastIndex = -1; while (i < priority.length && items[priority[i]].showing) { lastIndex = Math.max(lastIndex, priority[i++]); } if (lastIndex == -1) { lastIndex = firstIndex; } CTabItem lastItem = items[lastIndex]; int w = lastItem.x + lastItem.width + 3; // if (!simple && lastIndex == selectedIndex) w += curveWidth - 2*curveIndent; chevronRect.x = Math.min(w, getRightItemEdge()); chevronRect.y = onBottom ? size.y - borderBottom - tabHeight + (tabHeight - chevronRect.height) / 2 : borderTop + (tabHeight - chevronRect.height) / 2; } } // if (oldX != chevronRect.x || oldWidth != chevronRect.width || // oldY != chevronRect.y || oldHeight != chevronRect.height) { // int left = Math.min(oldX, chevronRect.x); // int right = Math.max(oldX + oldWidth, chevronRect.x + chevronRect.width); // int top = onBottom ? size.y - borderBottom - tabHeight: borderTop + 1; // redraw(left, top, right - left, tabHeight, false); // } } boolean updateTabHeight(boolean force) { CTabItem[] items = itemHolder.getItems(); int style = getStyle(); if (fixedTabHeight == 0 && (style & SWT.FLAT) != 0 && (style & SWT.BORDER) == 0) { highlight_header = 0; } int oldHeight = tabHeight; if (fixedTabHeight != SWT.DEFAULT) { tabHeight = fixedTabHeight == 0 ? 0 : fixedTabHeight + 1; // +1 for line drawn across top of tab } else { int tempHeight = 0; // GC gc = new GC(this); if (items.length == 0) { // tempHeight = gc.textExtent("Default", CTabItem.FLAGS).y + CTabItem.TOP_MARGIN + CTabItem.BOTTOM_MARGIN; //$NON-NLS-1$ BoxDimensions padding = getItemPadding(false); tempHeight = TextSizeUtil.getCharHeight(getFont()) + padding.top + padding.bottom; } else { for (int i = 0; i < items.length; i++) { // tempHeight = Math.max(tempHeight, items[i].preferredHeight(gc)); tempHeight = Math.max(tempHeight, items[i].preferredHeight(i == selectedIndex)); } } // gc.dispose(); tabHeight = tempHeight; } if (!force && tabHeight == oldHeight) { return false; } // oldSize = null; // if (onBottom) { // int d = tabHeight - 12; // curve = new int[]{0,13+d, 0,12+d, 2,12+d, 3,11+d, 5,11+d, 6,10+d, 7,10+d, 9,8+d, 10,8+d, // 11,7+d, 11+d,7, // 12+d,6, 13+d,6, 15+d,4, 16+d,4, 17+d,3, 19+d,3, 20+d,2, 22+d,2, 23+d,1}; // curveWidth = 26+d; // curveIndent = curveWidth/3; // } else { // int d = tabHeight - 12; // curve = new int[]{0,0, 0,1, 2,1, 3,2, 5,2, 6,3, 7,3, 9,5, 10,5, // 11,6, 11+d,6+d, // 12+d,7+d, 13+d,7+d, 15+d,9+d, 16+d,9+d, 17+d,10+d, 19+d,10+d, 20+d,11+d, 22+d,11+d, 23+d,12+d}; // curveWidth = 26+d; // curveIndent = curveWidth/3; // // //this could be static but since values depend on curve, better to keep in one place // topCurveHighlightStart = new int[] { // 0, 2, 1, 2, 2, 2, // 3, 3, 4, 3, 5, 3, // 6, 4, 7, 4, // 8, 5, // 9, 6, 10, 6}; // // //also, by adding in 'd' here we save some math cost when drawing the curve // topCurveHighlightEnd = new int[] { // 10+d, 6+d, // 11+d, 7+d, // 12+d, 8+d, 13+d, 8+d, // 14+d, 9+d, // 15+d, 10+d, 16+d, 10+d, // 17+d, 11+d, 18+d, 11+d, 19+d, 11+d, // 20+d, 12+d, 21+d, 12+d, 22+d, 12+d }; // } notifyListeners(SWT.Resize, new Event()); return true; } int getRightItemEdge() { int x = getSize().x - borderRight - 3; if (showMin) { x -= BUTTON_SIZE; } if (showMax) { x -= BUTTON_SIZE; } if (showChevron) { x -= 3 * BUTTON_SIZE / 2; } if (topRight != null && topRightAlignment != SWT.FILL) { Point rightSize = topRight.computeSize(SWT.DEFAULT, SWT.DEFAULT); x -= rightSize.x + 3; } return Math.max(0, x); } private void updateItemsWithResizeEvent() { Rectangle rectBefore = getClientArea(); updateItems(); Rectangle rectAfter = getClientArea(); if (!rectBefore.equals(rectAfter)) { notifyListeners(SWT.Resize, new Event()); } } void onResize() { // if (updateItems()) redrawTabs(); updateItems(); // Point size = getSize(); // if (oldSize == null) { // redraw(); // } else { // if (onBottom && size.y != oldSize.y) { // redraw(); // } else { // int x1 = Math.min(size.x, oldSize.x); // if (size.x != oldSize.x) x1 -= borderRight + highlight_margin + 2; // if (!simple) x1 -= 5; // rounded top right corner // int y1 = Math.min(size.y, oldSize.y); // if (size.y != oldSize.y) y1 -= borderBottom + highlight_margin; // int x2 = Math.max(size.x, oldSize.x); // int y2 = Math.max(size.y, oldSize.y); // redraw(0, y1, x2, y2 - y1, false); // redraw(x1, 0, x2 - x1, y2, false); // } // } // oldSize = size; // Differs from SWT code: adjust bounds of the selected items' control if (selectedIndex != -1) { CTabItem item = itemHolder.getItem(selectedIndex); Control control = item.getControl(); if (control != null && !control.isDisposed()) { control.setBounds(getClientArea()); } } } /////////// // Disposal void onDispose() { /* * Usually when an item is disposed of, destroyItem will change the size of * the items array, reset the bounds of all the tabs and manage the widget * associated with the tab. Since the whole folder is being disposed, this * is not necessary. For speed the inDispose flag is used to skip over this * part of the item dispose. */ inDispose = true; removeControlListener(resizeListener); unregisterFocusListener(); if (showMenu != null && !showMenu.isDisposed()) { showMenu.dispose(); showMenu = null; } while (itemHolder.size() > 0) { CTabItem item = itemHolder.getItem(0); item.dispose(); itemHolder.remove(item); } } ////////////////// // Helping methods private static int checkStyle(int style) { int mask = SWT.CLOSE | SWT.TOP | SWT.BOTTOM | SWT.FLAT | SWT.LEFT_TO_RIGHT // | SWT.RIGHT_TO_LEFT | SWT.SINGLE | SWT.MULTI; int result = style & mask; // TOP and BOTTOM are mutually exclusive, TOP is the default if ((result & SWT.TOP) != 0) { result = result & ~SWT.BOTTOM; } // SINGLE and MULTI are mutually exclusive, MULTI is the default if ((result & SWT.MULTI) != 0) { result = result & ~SWT.SINGLE; } return result; } private void registerDisposeListener() { addDisposeListener(new DisposeListener() { @Override public void widgetDisposed(DisposeEvent event) { onDispose(); CTabFolder.this.removeDisposeListener(this); } }); } private void registerFocusListener() { if (focusListener == null) { focusListener = new FocusListener() { @Override public void focusGained(FocusEvent event) { onFocus(); } @Override public void focusLost(FocusEvent event) { onFocus(); } }; addFocusListener(focusListener); } } private void onFocus() { if (selectedIndex < 0) { setSelection(0, true); } unregisterFocusListener(); } private void unregisterFocusListener() { if (focusListener != null) { addFocusListener(focusListener); focusListener = null; } } private void showListMenu() { CTabItem[] items = getItems(); if (items.length == 0 || !showChevron) { return; } if (showMenu == null || showMenu.isDisposed()) { showMenu = new Menu(this); } else { // TODO [rh] optimize: reuse existing menuItems if possible MenuItem[] menuItems = showMenu.getItems(); for (int i = 0; i < menuItems.length; i++) { menuItems[i].dispose(); } } showMenu.setOrientation(getOrientation()); final String id = "CTabFolder_showList_Index"; //$NON-NLS-1$ for (int i = 0; i < items.length; i++) { CTabItem tab = items[i]; if (!tab.showing) { MenuItem item = new MenuItem(showMenu, SWT.NONE); item.setText(tab.getText()); item.setImage(tab.getImage()); item.setData(id, tab); item.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent event) { MenuItem menuItem = (MenuItem) event.getSource(); int index = indexOf((CTabItem) menuItem.getData(id)); setSelection(index, true); } }); } } // show menu if it contains any item if (showMenu.getItemCount() > 0) { int x = chevronRect.x; int y = chevronRect.y + chevronRect.height + 1; Point location = getDisplay().map(this, null, x, y); showMenu.setLocation(location.x, location.y); showMenu.setVisible(true); } } private void setSelection(int index, boolean notify) { int oldSelectedIndex = selectedIndex; setSelection(index); if (notify && selectedIndex != oldSelectedIndex && selectedIndex != -1) { Event event = new Event(); event.item = getSelection(); notifyListeners(SWT.Selection, event); } } void createItem(CTabItem item, int index) { itemHolder.insert(item, index); if (selectedIndex >= index) { selectedIndex++; } int[] newPriority = new int[priority.length + 1]; int next = 0, priorityIndex = priority.length; for (int i = 0; i < priority.length; i++) { if (!mru && priority[i] == index) { priorityIndex = next++; } newPriority[next++] = priority[i] >= index ? priority[i] + 1 : priority[i]; } newPriority[priorityIndex] = index; priority = newPriority; updateItems(); if (getItemCount() == 1) { registerFocusListener(); // RWT specific if (!updateTabHeight(false)) { updateItems(); } redraw(); } else { updateItems(); // redrawTabs(); redraw(); } } void destroyItem(CTabItem item) { int index = indexOf(item); if (!inDispose && index != -1) { CTabItem[] items = getItems(); if (items.length == 1) { itemHolder.remove(item); priority = new int[0]; firstIndex = -1; selectedIndex = -1; Control control = item.getControl(); if (control != null && !control.isDisposed()) { control.setVisible(false); } setToolTipText(null); setButtonBounds(); } else { itemHolder.remove(item); int[] newPriority = new int[priority.length - 1]; int next = 0; for (int i = 0; i < priority.length; i++) { if (priority[i] == index) { continue; } newPriority[next++] = priority[i] > index ? priority[i] - 1 : priority[i]; } priority = newPriority; // move the selection if this item is selected if (selectedIndex == index) { Control control = item.getControl(); selectedIndex = -1; int nextSelection = mru ? priority[0] : Math.max(0, index - 1); setSelection(nextSelection, true); if (control != null && !control.isDisposed()) { control.setVisible(false); } } else if (selectedIndex > index) { selectedIndex--; } } updateItems(); if (getItemCount() == 0) { unregisterFocusListener(); } } } ////////////////// // Theming related int getItemPaddingLeft(boolean selected) { return getItemPadding(selected).left; } int getItemPaddingRight(boolean selected) { return getItemPadding(selected).right; } BoxDimensions getItemPadding(boolean selected) { return getThemeAdapter().getItemPadding(selected); } int getItemSpacing(boolean selected) { return getThemeAdapter().getItemSpacing(selected); } Font getItemFont(boolean selected) { return getThemeAdapter().getItemFont(selected); } private CTabFolderThemeAdapter getThemeAdapter() { return (CTabFolderThemeAdapter) getAdapter(ThemeAdapter.class); } /////////////////// // Skinning support @Override public void reskin(int flags) { super.reskin(flags); CTabItem[] items = getItems(); for (int i = 0; i < items.length; i++) { items[i].reskin(flags); } } //////////////// // Inner classes private final class CTabFolderAdapter implements ICTabFolderAdapter { @Override public Rectangle getChevronRect() { Rectangle rect = chevronRect; return new Rectangle(rect.x, rect.y, rect.width, rect.height); } @Override public boolean getChevronVisible() { return showChevron; } @Override public Rectangle getMinimizeRect() { Rectangle rect = minRect; return new Rectangle(rect.x, rect.y, rect.width, rect.height); } @Override public Rectangle getMaximizeRect() { Rectangle rect = maxRect; return new Rectangle(rect.x, rect.y, rect.width, rect.height); } @Override public void showListMenu() { CTabFolder.this.showListMenu(); } @Override public boolean showItemImage(CTabItem item) { return item.showImage(); } @Override public boolean showItemClose(CTabItem item) { return item.parent.showClose || item.showClose; } @Override public String getShortenedItemText(CTabItem item) { return item.getShortenedText(item.getParent().getSelection() == item); } @Override public Color getUserSelectionForeground() { return selectionForeground; } @Override public Color getUserSelectionBackground() { return selectionBackground; } @Override public Image getUserSelectionBackgroundImage() { return selectionBgImage; } @Override public IWidgetGraphicsAdapter getUserSelectionBackgroundGradient() { return selectionGraphicsAdapter; } @Override public void doRedraw() { setButtonBounds(); } } }