blob: 20892685a1fbfb32571d4fe5e3db3d87f6bb18ef [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2012 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.widgets;
import org.eclipse.rap.rwt.graphics.Graphics;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.internal.widgets.*;
/**
* Instances of this class support the layout of selectable expand bar items.
* <p>
* The item children that may be added to instances of this class must be of
* type <code>ExpandItem</code>.
* </p>
* <p>
* <dl>
* <dt><b>Styles:</b></dt>
* <dd>V_SCROLL</dd>
* <dt><b>Events:</b></dt>
* <dd>Expand, Collapse</dd>
* </dl>
* </p>
* <p>
* IMPORTANT: This class is <em>not</em> intended to be subclassed.
* </p>
*
* @see ExpandItem
* @see ExpandEvent
* @see ExpandListener
* @see ExpandAdapter
* @since 1.2
*/
public class ExpandBar extends Composite {
private final class ExpandBarAdapter implements IExpandBarAdapter {
public Rectangle getBounds( ExpandItem item ) {
int index = ExpandBar.this.indexOf( item );
return ExpandBar.this.getItem( index ).getBounds();
}
public Rectangle getBottomSpacingBounds() {
return ExpandBar.this.getBottomSpacingBounds();
}
}
private final class ResizeListener extends ControlAdapter {
@Override
public void controlResized( ControlEvent event ) {
layoutItems( 0, true );
}
}
ExpandItem focusItem;
int spacing;
int allItemsHeight;
int charHeight;
private transient IExpandBarAdapter expandBarAdapter;
private final ItemHolder<ExpandItem> itemHolder;
private final ResizeListener resizeListener;
/**
* Constructs a new instance of this class given its parent and a style value
* describing its behavior and appearance.
* <p>
* The style value is either one of the style constants defined in class
* <code>SWT</code> which is applicable to instances of this class, or must
* be built by <em>bitwise OR</em>'ing together (that is, using the
* <code>int</code> "|" operator) two or more of those <code>SWT</code>
* style constants. The class description lists the style constants that are
* applicable to the class. Style bits are also inherited from superclasses.
* </p>
*
* @param parent a composite control which will be the parent of the new
* instance (cannot be null)
* @param style the style of control to construct
* @exception IllegalArgumentException
* <ul>
* <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
* </ul>
* @exception SWTException
* <ul>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
* thread that created the parent</li>
* <li>ERROR_INVALID_SUBCLASS - if this class is not an allowed
* subclass</li>
* </ul>
* @see Widget#checkSubclass
* @see Widget#getStyle
*/
public ExpandBar( Composite parent, int style ) {
super( parent, checkStyle( style ) );
spacing = 4;
resizeListener = new ResizeListener();
addControlListener( resizeListener );
itemHolder = new ItemHolder<ExpandItem>( ExpandItem.class );
}
/**
* Adds the listener to the collection of listeners who will be notified when
* an item in the receiver is expanded or collapsed by sending it one of the
* messages defined in the <code>ExpandListener</code> interface.
*
* @param listener the listener which should be notified
* @exception IllegalArgumentException
* <ul>
* <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
* </ul>
* @exception SWTException
* <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
* thread that created the receiver</li>
* </ul>
* @see ExpandListener
* @see #removeExpandListener
*/
public void addExpandListener( ExpandListener listener ) {
checkWidget();
if( listener == null ) {
error( SWT.ERROR_NULL_ARGUMENT );
}
TypedListener typedListener = new TypedListener( listener );
addListener( SWT.Expand, typedListener );
addListener( SWT.Collapse, typedListener );
}
/**
* Removes the listener from the collection of listeners who will be notified
* when items in the receiver are expanded or collapsed.
*
* @param listener the listener which should no longer be notified
* @exception IllegalArgumentException
* <ul>
* <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
* </ul>
* @exception SWTException
* <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
* thread that created the receiver</li>
* </ul>
* @see ExpandListener
* @see #addExpandListener
*/
public void removeExpandListener( ExpandListener listener ) {
checkWidget();
if( listener == null ) {
error( SWT.ERROR_NULL_ARGUMENT );
}
removeListener( SWT.Expand, listener );
removeListener( SWT.Collapse, listener );
}
@Override
public Point computeSize( int wHint, int hHint, boolean changed ) {
checkWidget();
int height = 0, width = 0;
int itemCount = getItemCount();
if( wHint == SWT.DEFAULT || hHint == SWT.DEFAULT ) {
if( itemCount > 0 ) {
height += spacing;
for( int i = 0; i < itemCount; i++ ) {
ExpandItem item = getItem( i );
height += item.getHeaderHeight();
if( item.expanded ) {
height += item.height;
}
height += spacing;
int barPreferredWidth = item.getPreferredWidth() + 2 * spacing;
width = Math.max( width, barPreferredWidth );
}
}
}
if( width == 0 ) {
width = DEFAULT_WIDTH;
}
if( height == 0 ) {
height = DEFAULT_HEIGHT;
}
if( wHint != SWT.DEFAULT ) {
width = wHint;
}
if( hHint != SWT.DEFAULT ) {
height = hHint;
}
int border = getBorderWidth();
width += border * 2;
height += border * 2;
if( ( style & SWT.V_SCROLL ) != 0 ) {
width += getVerticalBar().getSize().x;
}
return new Point( width, height );
}
void createItem( ExpandItem item, int style, int index ) {
itemHolder.insert( item, index );
if( focusItem == null ) {
focusItem = item;
}
layoutItems( index, true );
}
void destroyItem( ExpandItem item ) {
int index = 0;
int itemCount = getItemCount();
for( int i = 0; i < itemCount; i++ ) {
if( getItem( i ) == item ) {
index = i;
}
}
if( index != itemCount ) {
if( item == focusItem ) {
int focusIndex = index > 0
? index - 1
: 1;
if( focusIndex < itemCount ) {
focusItem = getItem( focusIndex );
} else {
focusItem = null;
}
}
itemHolder.remove( item );
layoutItems( index, true );
}
}
@Override
Control findBackgroundControl() {
Control control = super.findBackgroundControl();
if( !isAppThemed() ) {
if( control == null ) {
control = this;
}
}
return control;
}
int getBandHeight() {
return Math.max( ExpandItem.CHEVRON_SIZE, charHeight );
}
/**
* Returns the item at the given, zero-relative index in the receiver. Throws
* an exception if the index is out of range.
*
* @param index the index of the item to return
* @return the item at the given index
* @exception IllegalArgumentException
* <ul>
* <li>ERROR_INVALID_RANGE - if the index is not between 0 and
* the number of elements in the list minus 1 (inclusive)</li>
* </ul>
* @exception SWTException
* <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
* thread that created the receiver</li>
* </ul>
*/
public ExpandItem getItem( int index ) {
checkWidget();
return itemHolder.getItem( index );
}
/**
* Returns the number of items contained in the receiver.
*
* @return the number of items
* @exception SWTException
* <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
* thread that created the receiver</li>
* </ul>
*/
public int getItemCount() {
checkWidget();
return itemHolder.size();
}
/**
* Returns an array of <code>ExpandItem</code>s which are the items in the
* receiver.
* <p>
* Note: This is not the actual structure used by the receiver to maintain its
* list of items, so modifying the array will not affect the receiver.
* </p>
*
* @return the items in the receiver
* @exception SWTException
* <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
* thread that created the receiver</li>
* </ul>
*/
public ExpandItem[] getItems() {
checkWidget();
return itemHolder.getItems();
}
/**
* Returns the receiver's spacing.
*
* @return the spacing
* @exception SWTException
* <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
* thread that created the receiver</li>
* </ul>
*/
public int getSpacing() {
checkWidget();
return spacing;
}
/**
* Searches the receiver's list starting at the first item (index 0) until an
* item is found that is equal to the argument, and returns the index of that
* item. If no item is found, returns -1.
*
* @param item the search item
* @return the index of the item
* @exception IllegalArgumentException
* <ul>
* <li>ERROR_NULL_ARGUMENT - if the item is null</li>
* <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>
*/
public int indexOf( ExpandItem item ) {
checkWidget();
if( item == null ) {
error( SWT.ERROR_NULL_ARGUMENT );
}
if( item.isDisposed() ) {
error( SWT.ERROR_INVALID_ARGUMENT );
}
return itemHolder.indexOf( item );
}
void layoutItems( int index, boolean setScrollbar ) {
int itemCount = getItemCount();
if( index < itemCount ) {
int y = spacing;
for( int i = 0; i < index; i++ ) {
ExpandItem item = getItem( i );
if( item.expanded ) {
y += item.height;
}
y += item.getHeaderHeight() + spacing;
}
for( int i = index; i < itemCount; i++ ) {
ExpandItem item = getItem( i );
item.setBounds( spacing, y, 0, 0, true, false );
if( item.expanded ) {
y += item.height;
}
y += item.getHeaderHeight() + spacing;
}
}
// Calculate all items size
if( itemCount > 0 ) {
ExpandItem lastItem = getItem( itemCount - 1 );
allItemsHeight = lastItem.y + lastItem.getBounds().height;
}
updateScrollBars();
// Set items width based on scrollbar visibility
Rectangle bounds = getBounds();
int border = getBorderWidth();
int scrollBarWidth = getVScrollBarWidth();
for( int i = 0; i < itemCount; i++ ) {
ExpandItem item = getItem( i );
if( isVScrollBarVisible() ) {
int width = bounds.width - scrollBarWidth - 2 * border - 2 * spacing;
item.setBounds( 0, 0, width, item.height, false, true );
} else {
int width = bounds.width - 2 * border - 2 * spacing;
item.setBounds( 0, 0, width, item.height, false, true );
}
}
}
/**
* Sets the font that the receiver will use to paint textual information
* to the font specified by the argument, or to the default font for that
* kind of control if the argument is null.
*
* @param font the new font (or null)
*
* @exception IllegalArgumentException <ul>
* <li>ERROR_INVALID_ARGUMENT - if the argument has been disposed</li>
* </ul>
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*/
@Override
public void setFont( Font font ) {
if( font != getFont() ) {
super.setFont( font );
charHeight = Graphics.getCharHeight( getFont() ) + 4;
}
}
/**
* Sets the receiver's spacing. Spacing specifies the number of pixels
* allocated around each item.
*
* @exception SWTException
* <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
* thread that created the receiver</li>
* </ul>
*/
public void setSpacing( int spacing ) {
checkWidget();
if( spacing >= 0 ) {
if( spacing != this.spacing ) {
this.spacing = spacing;
layoutItems( 0, true );
}
}
}
void showItem( ExpandItem item ) {
Control control = item.control;
if( control != null && !control.isDisposed() ) {
control.setVisible( item.expanded );
}
int index = indexOf( item );
layoutItems( index + 1, true );
}
@Override
protected void checkSubclass() {
if( !isValidSubclass() ) {
error( SWT.ERROR_INVALID_SUBCLASS );
}
}
static int checkStyle( int style ) {
int aStyle = style & ~SWT.H_SCROLL;
return aStyle;
}
boolean isAppThemed() {
return false;
}
private void updateScrollBars() {
ScrollBar vScroll = getVerticalBar();
if( vScroll != null ) {
vScroll.setVisible( allItemsHeight > getBounds().height - 2 * getBorderWidth() - spacing );
}
}
private boolean isVScrollBarVisible() {
ScrollBar vScroll = getVerticalBar();
return vScroll != null && vScroll.getVisible();
}
Rectangle getBottomSpacingBounds() {
return new Rectangle( spacing, allItemsHeight, 10, spacing );
}
@Override
@SuppressWarnings("unchecked")
public <T> T getAdapter( Class<T> adapter ) {
T result;
if( adapter == IItemHolderAdapter.class ) {
result = ( T )itemHolder;
} else if( adapter == IExpandBarAdapter.class ) {
if( expandBarAdapter == null ) {
expandBarAdapter = new ExpandBarAdapter();
}
result = ( T )expandBarAdapter;
} else {
result = super.getAdapter( adapter );
}
return result;
}
/////////////////////
// Destroy expand bar
@Override
void releaseWidget() {
if( resizeListener != null ) {
removeControlListener( resizeListener );
}
super.releaseWidget();
}
@Override
void releaseChildren() {
Item[] expandItems = new ExpandItem[ getItemCount() ];
System.arraycopy( getItems(), 0, expandItems, 0, getItems().length );
for( int i = 0; i < expandItems.length; i++ ) {
if( expandItems[ i ] != null ) {
expandItems[ i ].dispose();
}
}
}
////////////////////////////
// Helping methods - various
@Override
int getVScrollBarWidth() {
int result = 0;
if( ( style & SWT.V_SCROLL ) != 0 ) {
result = getVerticalBar().getSize().x;
}
return result;
}
///////////////////
// Skinning support
@Override
void reskinChildren( int flags ) {
ExpandItem[] items = getItems();
if( items != null ) {
for( int i = 0; i < items.length; i++ ) {
ExpandItem item = items[ i ];
if( item != null ) {
item.reskin( flags );
}
}
}
super.reskinChildren( flags );
}
}