/*******************************************************************************
* Copyright (c) 2002, 2009 Innoopract Informationssysteme GmbH.
* 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.widgets;
import org.eclipse.rwt.graphics.Graphics;
import org.eclipse.rwt.internal.theme.IThemeAdapter;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.internal.widgets.IToolItemAdapter;
import org.eclipse.swt.internal.widgets.ItemHolder;
import org.eclipse.swt.internal.widgets.toolbarkit.ToolBarThemeAdapter;
/**
* Instances of this class represent a selectable user interface object
* that represents a button in a tool bar.
* <dl>
* <dt><b>Styles:</b></dt>
* <dd>PUSH, CHECK, RADIO, SEPARATOR, DROP_DOWN</dd>
* <dt><b>Events:</b></dt>
* <dd>Selection</dd>
* </dl>
* <p>
* Note: Only one of the styles CHECK, PUSH, RADIO, SEPARATOR and DROP_DOWN
* may be specified.
* </p><p>
* IMPORTANT: This class is <em>not</em> intended to be subclassed.
* </p>
*/
public class ToolItem extends Item {
private static final int DEFAULT_WIDTH = 24;
private static final int DEFAULT_HEIGHT = 22;
private final ToolBar parent;
private boolean selected;
private Control control;
private int width;
private boolean computedWidth = true;
private String toolTipText;
private boolean visible;
private Image disabledImage;
private Image hotImage;
private final IToolItemAdapter toolItemAdapter;
/**
* Constructs a new instance of this class given its parent
* (which must be a <code>ToolBar</code>) and a style value
* describing its behavior and appearance. The item is added
* to the end of the items maintained by its parent.
* <p>
* The style value is either one of the style constants defined in
* class <code>SWT</code> which is applicable to instances of this
* class, or must be built by <em>bitwise OR</em>'ing together
* (that is, using the <code>int</code> "|" operator) two or more
* of those <code>SWT</code> style constants. The class description
* lists the style constants that are applicable to the class.
* Style bits are also inherited from superclasses.
* </p>
*
* @param parent a composite control which will be the parent of the new instance (cannot be null)
* @param style the style of control to construct
*
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
* <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
* </ul>
*
* @see SWT#PUSH
* @see SWT#CHECK
* @see SWT#RADIO
* @see SWT#SEPARATOR
* @see SWT#DROP_DOWN
* @see Widget#checkSubclass
* @see Widget#getStyle
*/
public ToolItem( final ToolBar parent, final int style ) {
this( checkNull( parent ), checkStyle( style ), parent.getItemCount() );
}
/**
* Constructs a new instance of this class given its parent
* (which must be a <code>ToolBar</code>), a style value
* describing its behavior and appearance, and the index
* at which to place it in the items maintained by its parent.
* <p>
* The style value is either one of the style constants defined in
* class <code>SWT</code> which is applicable to instances of this
* class, or must be built by <em>bitwise OR</em>'ing together
* (that is, using the <code>int</code> "|" operator) two or more
* of those <code>SWT</code> style constants. The class description
* lists the style constants that are applicable to the class.
* Style bits are also inherited from superclasses.
* </p>
*
* @param parent a composite control which will be the parent of the new instance (cannot be null)
* @param style the style of control to construct
* @param index the zero-relative index to store the receiver in its parent
*
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
* <li>ERROR_INVALID_RANGE - if the index is not between 0 and the number of elements in the parent (inclusive)</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
* <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed subclass</li>
* </ul>
*
* @see SWT#PUSH
* @see SWT#CHECK
* @see SWT#RADIO
* @see SWT#SEPARATOR
* @see SWT#DROP_DOWN
* @see Widget#checkSubclass
* @see Widget#getStyle
*/
public ToolItem( final ToolBar parent, final int style, final int index ) {
super( parent, checkStyle( style ) );
this.parent = parent;
visible = true;
ItemHolder.insertItem( parent, this, index );
computeInitialWidth();
toolItemAdapter = new IToolItemAdapter() {
public boolean getVisible() {
return ToolItem.this.visible;
}
};
}
/**
* Returns the receiver's parent, which must be a <code>ToolBar</code>.
*
* @return the receiver's parent
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public ToolBar getParent() {
checkWidget();
return parent;
}
////////////////////////////////////////
// Displayed content (text, image, etc.)
/**
* Sets the receiver's text. The string may include
* the mnemonic character.
* </p>
* <p>
* Mnemonics are indicated by an '&' that causes the next
* character to be the mnemonic. When the user presses a
* key sequence that matches the mnemonic, a selection
* event occurs. On most platforms, the mnemonic appears
* underlined but may be emphasised in a platform specific
* manner. The mnemonic indicator character '&' can be
* escaped by doubling it in the string, causing a single
* '&' to be displayed.
* </p>
*
* @param text the new text
*
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the text is null</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public void setText( final String text ) {
checkWidget();
if( text == null ) {
SWT.error( SWT.ERROR_NULL_ARGUMENT );
}
if( ( style & SWT.SEPARATOR ) == 0 ) {
super.setText( text );
}
}
public void setImage( final Image image ) {
checkWidget();
if( ( style & SWT.SEPARATOR ) == 0 ) {
super.setImage( image );
}
}
/**
* Sets the receiver's disabled image to the argument, which may be
* null indicating that no disabled image should be displayed.
* <p>
* The disabled image is displayed when the receiver is disabled.
* </p>
*
* @param image the disabled image to display on the receiver (may be null)
*
* @exception IllegalArgumentException <ul>
* <li>ERROR_INVALID_ARGUMENT - if the image has been disposed</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*
* @since 1.2
*/
public void setDisabledImage( final Image image ) {
checkWidget();
if( ( style & SWT.SEPARATOR ) == 0 ) {
disabledImage = image;
}
}
/**
* Returns the receiver's disabled image if it has one, or null
* if it does not.
* <p>
* The disabled image is displayed when the receiver is disabled.
* </p>
*
* @return the receiver's disabled image
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - 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.2
*/
public Image getDisabledImage() {
checkWidget();
return disabledImage;
}
/**
* Sets the receiver's hot image to the argument, which may be
* null indicating that no hot image should be displayed.
* <p>
* The hot image is displayed when the mouse enters the receiver.
* </p>
*
* @param image the hot image to display on the receiver (may be null)
*
* @exception IllegalArgumentException <ul>
* <li>ERROR_INVALID_ARGUMENT - if the image has been disposed</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
* @since 1.2
*/
public void setHotImage( final Image image ) {
checkWidget();
if( ( style & SWT.SEPARATOR ) == 0 ) {
hotImage = image;
}
}
/**
* Returns the receiver's hot image if it has one, or null
* if it does not.
* <p>
* The hot image is displayed when the mouse enters the receiver.
* </p>
*
* @return the receiver's hot image
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - 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.2
*/
public Image getHotImage() {
checkWidget();
return hotImage;
}
/**
* Sets the control that is used to fill the bounds of
* the item when the item is a <code>SEPARATOR</code>.
*
* @param control the new control
*
* @exception IllegalArgumentException <ul>
* <li>ERROR_INVALID_ARGUMENT - if the control has been disposed</li>
* <li>ERROR_INVALID_PARENT - if the control is not in the same widget tree</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public void setControl( final Control control ) {
checkWidget();
if( control != null ) {
if( control.isDisposed() ) {
SWT.error( SWT.ERROR_INVALID_ARGUMENT );
}
if( control.getParent() != parent ) {
SWT.error( SWT.ERROR_INVALID_PARENT );
}
}
if( ( style & SWT.SEPARATOR ) != 0 ) {
if( this.control != null && !this.control.isDisposed() ) {
this.control.setVisible( false );
}
this.control = control;
if( this.control != null ) {
this.control.setVisible( true );
}
resizeControl();
}
}
/**
* Returns the control that is used to fill the bounds of
* the item when the item is a <code>SEPARATOR</code>.
*
* @return the control
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public Control getControl() {
checkWidget();
return control;
}
/**
* Sets the receiver's tool tip text to the argument, which
* may be null indicating that no tool tip text should be shown.
*
* @param string the new tool tip text (or null)
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public void setToolTipText( final String string ) {
checkWidget();
toolTipText = string;
}
/**
* Returns the receiver's tool tip text, or null if it has not been set.
*
* @return the receiver's tool tip text
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public String getToolTipText() {
checkWidget();
return toolTipText;
}
///////////
// Enabled
/**
* Enables the receiver if the argument is <code>true</code>,
* and disables it otherwise.
* <p>
* A disabled control is typically
* not selectable from the user interface and draws with an
* inactive or "grayed" look.
* </p>
*
* @param enabled the new enabled 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 setEnabled( final boolean enabled ) {
checkWidget();
if( enabled ) {
state &= ~DISABLED;
} else {
state |= DISABLED;
}
}
/**
* Returns <code>true</code> if the receiver is enabled, and
* <code>false</code> otherwise. A disabled control is typically
* not selectable from the user interface and draws with an
* inactive or "grayed" look.
*
* @return the receiver's enabled 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>
*
* @see #isEnabled
*/
public boolean getEnabled() {
checkWidget();
return ( state & DISABLED ) == 0;
}
/**
* Returns <code>true</code> if the receiver is enabled and all
* of the receiver's ancestors are enabled, and <code>false</code>
* otherwise. A disabled control is typically not selectable from the
* user interface and draws with an inactive or "grayed" look.
*
* @return the receiver's enabled 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>
*
* @see #getEnabled
*/
public boolean isEnabled() {
checkWidget();
return getEnabled() && parent.isEnabled();
}
/////////////
// Dimensions
/**
* Returns a rectangle describing the receiver's size and location
* relative to its parent.
*
* @return the receiver's bounding rectangle
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that
* created the receiver</li>
* </ul>
*/
public Rectangle getBounds() {
checkWidget();
Rectangle clientArea = parent.getClientArea();
int left = clientArea.x;
int top = clientArea.y;
int width = getWidth();
int height = getHeight();
int index = parent.indexOf( this );
if( ( parent.style & SWT.VERTICAL ) != 0 ) {
if( index > 0 ) {
Rectangle upperSiblingBounds = parent.getItem( index - 1 ).getBounds();
top += upperSiblingBounds.y + upperSiblingBounds.height;
top += getToolBarSpacing();
} else {
top += parent.getToolBarPadding().y;
}
int innerParentWidth = parent.getSize().x
- parent.getToolBarPadding().width;
left += parent.getToolBarPadding().x
+ innerParentWidth / 2
- width / 2;
left = Math.max( left, 0 );
} else {
if( index > 0 ) {
Rectangle leftSiblingBounds = parent.getItem( index - 1 ).getBounds();
left += leftSiblingBounds.x + leftSiblingBounds.width;
left += getToolBarSpacing();
} else {
left += parent.getToolBarPadding().x;
}
int innerParentHeight = parent.getSize().y
- parent.getToolBarPadding().height
- parent.getBorderWidth() * 2;
top += parent.getToolBarPadding().y
+ innerParentHeight / 2
- height / 2;
top = Math.max( top, 0 );
}
return new Rectangle( left, top, width, height );
}
// TODO [tb] : if needed, cache dimensions to optimize performance
private int getHeight() {
int height;
if( ( parent.style & SWT.VERTICAL ) != 0
&& ( style & SWT.SEPARATOR ) != 0
&& getControl() == null )
{
height = getSeparatorWidth();
} else {
height = 0;
ToolItem[] siblings = getParent().getItems();
for( int i = 0; i < siblings.length; i++ ) {
height = Math.max( height, siblings[ i ].getPreferredHeight() );
}
}
return height;
}
/**
* Gets the width of the receiver.
*
* @return the width
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that
* created the receiver</li>
* </ul>
*/
public int getWidth() {
checkWidget();
int result;
boolean isVertical = ( parent.style & SWT.VERTICAL ) != 0;
if( ( style & SWT.SEPARATOR ) != 0 && ( !isVertical || !computedWidth ) ) {
result = width;
} else {
if( isVertical ) {
result = 0;
ToolItem[] siblings = getParent().getItems();
for( int i = 0; i < siblings.length; i++ ) {
if( ( siblings[ i ].style & SWT.SEPARATOR ) == 0 ) {
result = Math.max( result, siblings[ i ].getPreferredWidth() );
}
}
} else {
result = getPreferredWidth();
}
}
return result;
}
int getPreferredHeight() {
int height = DEFAULT_HEIGHT;
if( ( style & SWT.SEPARATOR ) == 0 ) {
int frameHeight = getPadding().height + ( getBorderWidth() * 2 );
if( !"".equals( getText() ) ) {
int charHeight = Graphics.getCharHeight( parent.getFont() );
height = Math.max( DEFAULT_HEIGHT, charHeight + frameHeight );
}
if( getImage() != null ) {
int imageHeight = getImage().getBounds().height;
height = Math.max( height, imageHeight + frameHeight );
}
}
return height;
}
int getPreferredWidth() {
int result = 0;
boolean hasImage = image != null;
boolean hasText = !"".equals( text );
if( hasImage ) {
result += image.getBounds().width;
}
if( hasText ) {
Font font = parent.getFont();
result += Graphics.stringExtent( font, text ).x;
}
if( hasText && hasImage ) {
result += getSpacing();
}
int paddingWidth = getPadding().width;
int borderWidth = getBorderWidth() * 2;
if( ( style & SWT.DROP_DOWN ) != 0 ) {
result += getSpacing() * 2;
result += 1; // the separator-line
result += getDropDownImageDimension().x;
}
result += paddingWidth + borderWidth;
return result;
}
private int getBorderWidth() {
ToolBarThemeAdapter adapter = getToolBarThemeAdapter();
return adapter.getItemBorderWidth( parent );
}
private Point getDropDownImageDimension() {
ToolBarThemeAdapter adapter = getToolBarThemeAdapter();
return adapter.getDropDownImageDimension( parent );
}
private Rectangle getPadding() {
ToolBarThemeAdapter adapter = getToolBarThemeAdapter();
return adapter.getItemPadding( parent );
}
private int getToolBarSpacing() {
ToolBarThemeAdapter adapter = getToolBarThemeAdapter();
return adapter.getToolBarSpacing( parent );
}
private int getSpacing() {
ToolBarThemeAdapter adapter = getToolBarThemeAdapter();
return adapter.getItemSpacing( parent );
}
int getSeparatorWidth() {
ToolBarThemeAdapter adapter = getToolBarThemeAdapter();
return adapter.getSeparatorWidth( parent );
}
private ToolBarThemeAdapter getToolBarThemeAdapter() {
return ( ToolBarThemeAdapter )parent.getAdapter( IThemeAdapter.class );
}
/**
* Sets the width of the receiver, for <code>SEPARATOR</code> ToolItems.
*
* @param width the new width
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
public void setWidth( final int width ) {
checkWidget();
if( ( style & SWT.SEPARATOR ) != 0 && width >= 0 ) {
computedWidth = false;
this.width = width;
resizeControl();
}
}
////////////
// Selection
/**
* Returns <code>true</code> if the receiver is selected,
* and false otherwise.
* <p>
* When the receiver is of type <code>CHECK</code> or <code>RADIO</code>,
* it is selected when it is checked (which some platforms draw as a
* pushed in button). If the receiver is of any other type, this method
* returns false.
* </p>
*
* @return the selection 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 getSelection() {
checkWidget();
boolean result = selected;
if( ( style & ( SWT.CHECK | SWT.RADIO ) ) == 0 ) {
result = false;
}
return result;
}
/**
* Sets the selection state of the receiver.
* <p>
* When the receiver is of type <code>CHECK</code> or <code>RADIO</code>,
* it is selected when it is checked (which some platforms draw as a
* pushed in button).
* </p>
*
* @param selected the new selection 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 setSelection( final boolean selected ) {
checkWidget();
if( ( style & ( SWT.CHECK | SWT.RADIO ) ) != 0 ) {
this.selected = selected;
}
}
///////////////////////////////////////////
// Listener registration and deregistration
/**
* Adds the listener to the collection of listeners who will
* be notified when the control is selected, by sending
* it one of the messages defined in the <code>SelectionListener</code>
* interface.
* <p>
* When <code>widgetSelected</code> is called when the mouse is over the arrow portion of a drop-down tool,
* the event object detail field contains the value <code>SWT.ARROW</code>.
* <code>widgetDefaultSelected</code> is not called.
* </p>
*
* @param listener the listener which should be notified
*
* @exception IllegalArgumentException <ul>
* <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*
* @see SelectionListener
* @see #removeSelectionListener
* @see SelectionEvent
*/
public void addSelectionListener( final SelectionListener listener ) {
checkWidget();
SelectionEvent.addListener( this, listener );
}
/**
* Removes the listener from the collection of listeners who will
* be notified when the control is selected.
*
* @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( final SelectionListener listener ) {
checkWidget();
SelectionEvent.removeListener( this, listener );
}
///////////////////////////////////
// Methods to dispose of the widget
void releaseParent() {
super.releaseParent();
ItemHolder.removeItem( parent, this );
}
//////////////////
// Helping methods
public Object getAdapter( final Class adapter ) {
Object result;
if ( adapter == IToolItemAdapter.class ) {
result = toolItemAdapter;
} else {
result = super.getAdapter( adapter );
}
return result;
}
void resizeControl() {
if( control != null && !control.isDisposed() ) {
control.setBounds( getBounds() );
}
}
private void computeInitialWidth() {
if( ( style & SWT.SEPARATOR ) != 0 ) {
width = getSeparatorWidth();
} else {
width = DEFAULT_WIDTH;
if( ( style & SWT.DROP_DOWN ) != 0 ) {
width += getDropDownImageDimension().x;
}
}
}
private static ToolBar checkNull( final ToolBar parent ) {
if( parent == null ) {
SWT.error( SWT.ERROR_NULL_ARGUMENT );
}
return parent;
}
private static int checkStyle( final int style ) {
return checkBits( style,
SWT.PUSH,
SWT.CHECK,
SWT.RADIO,
SWT.SEPARATOR,
SWT.DROP_DOWN,
0 );
}
void setVisible( final boolean visible ) {
this.visible = visible;
}
}