/*
* Bibliothek - DockingFrames
* Library built on Java/Swing, allows the user to "drag and drop"
* panels containing any Swing-Component the developer likes to add.
*
* Copyright (C) 2012 Herve Guillaume, Benjamin Sigg
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Herve Guillaume
* rvguillaume@hotmail.com
* FR - France
*
* Benjamin Sigg
* benjamin_sigg@gmx.ch
* CH - Switzerland
*/
package bibliothek.gui.dock;
import java.awt.CardLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.util.ArrayList;
import java.util.List;
import javax.swing.Icon;
import javax.swing.LayoutFocusTraversalPolicy;
import javax.swing.event.MouseInputListener;
import bibliothek.gui.DockController;
import bibliothek.gui.DockStation;
import bibliothek.gui.Dockable;
import bibliothek.gui.Orientation;
import bibliothek.gui.dock.action.DockAction;
import bibliothek.gui.dock.component.DockComponentRootHandler;
import bibliothek.gui.dock.dockable.AbstractDockable;
import bibliothek.gui.dock.dockable.DockableBackgroundComponent;
import bibliothek.gui.dock.dockable.DockableIcon;
import bibliothek.gui.dock.station.OrientationObserver;
import bibliothek.gui.dock.station.toolbar.ToolbarItemDockableFactory;
import bibliothek.gui.dock.station.toolbar.ToolbarStrategy;
import bibliothek.gui.dock.themes.ThemeManager;
import bibliothek.gui.dock.toolbar.expand.ExpandableStateController;
import bibliothek.gui.dock.toolbar.expand.ExpandableToolbarItem;
import bibliothek.gui.dock.toolbar.expand.ExpandableToolbarItemListener;
import bibliothek.gui.dock.toolbar.expand.ExpandedState;
import bibliothek.gui.dock.toolbar.item.ComponentItem;
import bibliothek.gui.dock.toolbar.item.DockActionItem;
import bibliothek.gui.dock.util.BackgroundAlgorithm;
import bibliothek.gui.dock.util.BackgroundPanel;
import bibliothek.gui.dock.util.ConfiguredBackgroundPanel;
import bibliothek.gui.dock.util.PropertyKey;
import bibliothek.gui.dock.util.SilentPropertyValue;
import bibliothek.gui.dock.util.Transparency;
import bibliothek.gui.dock.util.icon.DockIcon;
/**
* A {@link ToolbarItemDockable} is a {@link Dockable} that can be shown as child of a {@link ToolbarDockStation}. This
* class acts as wrapper around a {@link ToolbarItem} which can just be any kind of {@link Component}.<br>
* This class supports {@link ExpandableToolbarItem}, clients can call {@link #setItem(ToolbarItem, ExpandedState)} to
* fill up the different positions.
* @author Benjamin Sigg
*/
public class ToolbarItemDockable extends AbstractDockable implements ExpandableToolbarItem {
/** the component */
private BackgroundPanel content;
/** the layout of {@link #content} */
private CardLayout contentLayout;
/** all the {@link ExpandableToolbarItemListener}s */
private final List<ExpandableToolbarItemListener> expandableListeners = new ArrayList<ExpandableToolbarItemListener>();
/** the current state of this {@link ExpandableToolbarItem} */
private ExpandedState state = ExpandedState.SHRUNK;
/** the {@link Component}s to show in different states */
private final ToolbarItem[] items = new ToolbarItem[ExpandedState.values().length];
/** all registered {@link MouseInputListener}s */
private final List<MouseInputListener> mouseListeners = new ArrayList<MouseInputListener>();
/** the current orientation of the toolbar */
private Orientation orientation = Orientation.HORIZONTAL;
/** the background of this dockable */
private Background background = new Background();
/**
* Creates a new dockable
*/
public ToolbarItemDockable(){
this( (ToolbarItem)null, null, null );
}
/**
* Creates a new dockable
* @param icon the icon of this dockable, can be <code>null</code>
*/
public ToolbarItemDockable( Icon icon ){
this( (ToolbarItem)null, null, icon );
}
/**
* Creates a new dockable
* @param title the title of this dockable, can be <code>null</code>
*/
public ToolbarItemDockable( String title ){
this( (ToolbarItem)null, title, null );
}
/**
* Creates a new dockable
* @param action the item to show in the {@link ExpandedState#SHRUNK}, can be <code>null</code>
*/
public ToolbarItemDockable( DockAction action ){
this( action, null, null );
}
/**
* Creates a new dockable
* @param component the item to show in the {@link ExpandedState#SHRUNK}, can be <code>null</code>
*/
public ToolbarItemDockable( Component component ){
this( component, null, null );
}
/**
* Creates a new dockable
* @param item the item to show in the {@link ExpandedState#SHRUNK}, can be <code>null</code>
*/
public ToolbarItemDockable( ToolbarItem item ){
this( item, null, null );
}
/**
* Creates a new dockable
* @param action the item to show in the {@link ExpandedState#SHRUNK}, can be <code>null</code>
* @param icon the icon of this dockable, can be <code>null</code>
*/
public ToolbarItemDockable( DockAction action, Icon icon ){
this( action, null, icon );
}
/**
* Creates a new dockable
* @param component the item to show in the {@link ExpandedState#SHRUNK}, can be <code>null</code>
* @param icon the icon of this dockable, can be <code>null</code>
*/
public ToolbarItemDockable( Component component, Icon icon ){
this( component, null, icon );
}
/**
* Creates a new dockable
* @param item the item to show in the {@link ExpandedState#SHRUNK}, can be <code>null</code>
* @param icon the icon of this dockable, can be <code>null</code>
*/
public ToolbarItemDockable( ToolbarItem item, Icon icon ){
this( item, null, icon );
}
/**
* Creates a new dockable
* @param action the item to show in the {@link ExpandedState#SHRUNK}, can be <code>null</code>
* @param title the title of this dockable, can be <code>null</code>
*/
public ToolbarItemDockable( DockAction action, String title ){
this( action, title, null );
}
/**
* Creates a new dockable
* @param component the item to show in the {@link ExpandedState#SHRUNK}, can be <code>null</code>
* @param title the title of this dockable, can be <code>null</code>
*/
public ToolbarItemDockable( Component component, String title ){
this( component, title, null );
}
/**
* Creates a new dockable
* @param item the item to show in the {@link ExpandedState#SHRUNK}, can be <code>null</code>
* @param title the title of this dockable, can be <code>null</code>
*/
public ToolbarItemDockable( ToolbarItem item, String title ){
this( item, title, null );
}
/**
* Creates a new dockable
* @param action the item to show in the {@link ExpandedState#SHRUNK}, can be <code>null</code>
* @param title the title of this dockable, can be <code>null</code>
* @param icon the icon of this dockable, can be <code>null</code>
*/
public ToolbarItemDockable( DockAction action, String title, Icon icon ){
super( PropertyKey.DOCKABLE_TITLE, PropertyKey.DOCKABLE_TOOLTIP );
init( title, icon );
setAction( action, ExpandedState.SHRUNK );
}
/**
* Creates a new dockable
* @param component the item to show in the {@link ExpandedState#SHRUNK}, can be <code>null</code>
* @param title the title of this dockable, can be <code>null</code>
* @param icon the icon of this dockable, can be <code>null</code>
*/
public ToolbarItemDockable( Component component, String title, Icon icon ){
super( PropertyKey.DOCKABLE_TITLE, PropertyKey.DOCKABLE_TOOLTIP );
init( title, icon );
setComponent( component, ExpandedState.SHRUNK );
}
/**
* Creates a new dockable
* @param item the item to show in the {@link ExpandedState#SHRUNK}, can be <code>null</code>
* @param title the title of this dockable, can be <code>null</code>
* @param icon the icon of this dockable, can be <code>null</code>
*/
public ToolbarItemDockable( ToolbarItem item, String title, Icon icon ){
super( PropertyKey.DOCKABLE_TITLE, PropertyKey.DOCKABLE_TOOLTIP );
init( title, icon );
setItem( item, ExpandedState.SHRUNK );
}
private void init( String title, Icon icon ){
contentLayout = new CardLayout(){
@Override
public Dimension preferredLayoutSize( Container parent ){
synchronized( parent.getTreeLock() ) {
ToolbarItem current = getNearestComponent( state );
if( current == null ) {
return new Dimension( 10, 10 );
}
return current.getComponent().getPreferredSize();
}
}
@Override
public Dimension minimumLayoutSize( Container parent ){
synchronized( parent.getTreeLock() ) {
ToolbarItem current = getNearestComponent( state );
if( current == null ) {
return new Dimension( 10, 10 );
}
return current.getComponent().getMinimumSize();
}
}
@Override
public Dimension maximumLayoutSize( Container parent ){
synchronized( parent.getTreeLock() ) {
ToolbarItem current = getNearestComponent( state );
if( current == null ) {
return new Dimension( 10, 10 );
}
return current.getComponent().getMaximumSize();
}
}
};
content = new ConfiguredBackgroundPanel( contentLayout, Transparency.SOLID );
content.setFocusable( false );
content.setFocusTraversalPolicyProvider( true );
content.setFocusTraversalPolicy( new LayoutFocusTraversalPolicy() );
content.setBackground( background );
new ExpandableStateController( this );
new OrientationObserver( this ){
@Override
protected void orientationChanged( Orientation current ){
orientation = current;
for( ToolbarItem item : items ){
if( item != null ){
item.setOrientation( current );
}
}
}
};
setTitleIcon( icon );
setTitleText( title );
}
@Override
protected DockComponentRootHandler createRootHandler() {
return new DockComponentRootHandler( this ){
@Override
protected TraverseResult shouldTraverse( Component component ) {
if( component == content ){
return TraverseResult.EXCLUDE_CHILDREN;
}
return TraverseResult.INCLUDE_CHILDREN;
}
};
}
/**
* Gets the component associated with the nearest {@link ExpandedState} with
* regards to the <code>state</code> parameter. If two states are equally
* close, the state with minor ordinal value is returned.
*
* @param state
* the state
* @return the component in the nearest state.
*/
private ToolbarItem getNearestComponent( ExpandedState state ){
if( getController() == null ) {
return null;
}
int index = state.ordinal();
while( index >= 0 ) {
if( items[index] != null ) {
return items[index];
}
index--;
}
index = state.ordinal() + 1;
while( index < items.length ) {
if( items[index] != null ) {
return items[index];
}
index++;
}
return null;
}
/**
* Gets the nearest value of {@link ExpandedState} with regards to the
* <code>state</code> parameter.
* @param state the state
* @return the nearest state
*/
private ExpandedState getNearestState( ExpandedState state ){
ToolbarItem nearest = getNearestComponent( state );
if( nearest == null ) {
return null;
}
for( final ExpandedState next : ExpandedState.values() ) {
if( items[next.ordinal()] == nearest ) {
return next;
}
}
return null;
}
@Override
public void addMouseInputListener( MouseInputListener listener ){
super.addMouseInputListener( listener );
mouseListeners.add( listener );
ToolbarItem item = getCurrentItem();
if( item != null ) {
item.addMouseInputListener( listener );
}
}
@Override
public void removeMouseInputListener( MouseInputListener listener ){
super.removeMouseInputListener( listener );
mouseListeners.remove( listener );
ToolbarItem item = getCurrentItem();
if( item != null ) {
item.removeMouseInputListener( listener );
}
}
/**
* Sets the {@link DockAction} which should be shown if in state <code>state</code>.
* Please note that the same {@link DockAction} cannot be used for more than one state.
*
* @param action the item to set
* @param state the state in which to show <code>action</code>
*/
public void setAction( DockAction action, ExpandedState state ){
if( action == null ){
setItem( null, state );
}
else{
setItem( new DockActionItem( action ), state );
}
}
/**
* Sets the {@link Component} which should be shown if in state <code>state</code>.
* Please note that the same {@link Component} cannot be used for more than one state.
*
* @param component the item to set
* @param state the state in which to show <code>component</code>
*/
public void setComponent( Component component, ExpandedState state ){
if( component == null ){
setItem( null, state );
}
else{
setItem( new ComponentItem( component ), state );
}
}
/**
* Sets the {@link ToolbarItem} which should be shown if in state
* <code>state</code>. Please note that the same {@link ToolbarItem} cannot be
* used for more than one state.
*
* @param item the item to set
* @param state the state in which to show <code>item</code>
*/
public void setItem( ToolbarItem item, ExpandedState state ){
if( item != null ){
item.setOrientation( orientation );
}
ToolbarItem previous = items[state.ordinal()];
boolean enabled = isEnabled( state );
if( previous != item ){
if( item != null ){
item.setDockable( this );
}
if( getController() == null ) {
items[state.ordinal()] = item;
}
else {
ToolbarItem current = getCurrentItem();
if( current != null && current == previous ) {
current.setSelected( false );
for( MouseInputListener listener : mouseListeners ) {
current.removeMouseInputListener( listener );
}
}
if( previous != null ) {
content.remove( previous.getComponent() );
previous.unbind();
previous.setController( null );
}
items[state.ordinal()] = item;
if( item != null ) {
item.setController( getController() );
item.bind();
content.add( item.getComponent(), state.toString() );
current = getCurrentItem();
if( current != null && current == item ) {
current.setSelected( true );
for( MouseInputListener listener : mouseListeners ) {
current.addMouseInputListener( listener );
}
}
else{
item.setSelected( false );
}
}
ExpandedState nearest = getNearestState( this.state );
if( nearest != null ) {
contentLayout.show( content, nearest.toString() );
content.revalidate();
}
}
if( previous != null ){
previous.setDockable( null );
}
}
boolean newEnabled = isEnabled( state );
if( newEnabled != enabled ){
for( ExpandableToolbarItemListener listener : expandableListeners.toArray( new ExpandableToolbarItemListener[expandableListeners.size()] ) ) {
listener.enablementChanged( this, state, newEnabled );
}
}
}
@Override
public void setController( DockController controller ){
if( getController() != controller ) {
if( getController() != null ) {
ToolbarItem current = getCurrentItem();
if( current != null ) {
for( MouseInputListener listener : mouseListeners ) {
current.removeMouseInputListener( listener );
}
}
for( ToolbarItem item : items ) {
if( item != null ) {
content.remove( item.getComponent() );
item.unbind();
item.setController( null );
}
}
}
super.setController( controller );
background.setController( controller );
if( controller != null ) {
for( ExpandedState state : ExpandedState.values() ) {
ToolbarItem item = items[state.ordinal()];
if( item != null ) {
item.setController( controller );
item.bind();
content.add( item.getComponent(), state.toString() );
}
}
ToolbarItem current = getCurrentItem();
if( current != null ) {
for( MouseInputListener listener : mouseListeners ) {
current.addMouseInputListener( listener );
}
}
}
forceState( state );
}
}
@Override
public void setExpandedState( ExpandedState state ){
if( this.state != state ) {
forceState( state );
}
}
private void forceState( ExpandedState state ){
ExpandedState oldState = this.state;
ToolbarItem oldItem = getCurrentItem();
this.state = state;
ToolbarItem newItem = getCurrentItem();
if( oldItem != newItem ) {
if( oldItem != null ) {
oldItem.setSelected( false );
for( MouseInputListener listener : mouseListeners ) {
oldItem.removeMouseInputListener( listener );
}
}
if( newItem != null ) {
newItem.setSelected( true );
for( MouseInputListener listener : mouseListeners ) {
newItem.addMouseInputListener( listener );
}
}
}
ExpandedState nearest = getNearestState( state );
if( nearest != null ) {
contentLayout.show( content, nearest.toString() );
}
content.revalidate();
if( oldState != state ){
for( ExpandableToolbarItemListener listener : expandableListeners.toArray( new ExpandableToolbarItemListener[expandableListeners.size()] ) ) {
listener.changed( this, oldState, state );
}
}
}
/**
* Gets the {@link ComponentItem} that is currently shown on this dockable.
* @return the item that is currently shown, can be <code>null</code>
*/
private ToolbarItem getCurrentItem(){
return getNearestComponent( state );
}
@Override
public boolean isEnabled( ExpandedState state ){
return items[ state.ordinal() ] != null;
}
@Override
public ExpandedState getExpandedState(){
return state;
}
/**
* Gets the latest known orientation of this dockable
* @return the orientation, or <code>null</code> if unknown
*/
public Orientation getOrientation() {
return orientation;
}
@Override
public Component getComponent(){
return content;
}
@Override
public void addExpandableListener( ExpandableToolbarItemListener listener ){
if( listener == null ) {
throw new IllegalArgumentException( "listener must not be null" );
}
expandableListeners.add( listener );
}
@Override
public void removeExpandableListener( ExpandableToolbarItemListener listener ){
expandableListeners.remove( listener );
}
@Override
public DockStation asDockStation(){
return null;
}
@Override
public String getFactoryID(){
return ToolbarItemDockableFactory.ID;
}
@Override
protected DockIcon createTitleIcon(){
return new DockableIcon( "dockable.default", this ){
@Override
protected void changed( Icon oldValue, Icon newValue ){
fireTitleIconChanged( oldValue, newValue );
}
};
}
@Override
public boolean accept( DockStation station ){
// as this method is called during drag&drop operations a DockController
// is available
final SilentPropertyValue<ToolbarStrategy> value = new SilentPropertyValue<ToolbarStrategy>( ToolbarStrategy.STRATEGY, getController() );
final ToolbarStrategy strategy = value.getValue();
value.setProperties( (DockController) null );
return strategy.isToolbarGroupPartParent( station, this, false );
}
@Override
public boolean accept( DockStation base, Dockable neighbour ){
return false;
}
@Override
public String toString(){
return this.getClass().getSimpleName() + '@' + Integer.toHexString( hashCode() );
}
/**
* A representation of the background of this {@link Dockable}.
* @author Benjamin Sigg
*/
private class Background extends BackgroundAlgorithm implements DockableBackgroundComponent{
public Background(){
super( DockableBackgroundComponent.KIND, ThemeManager.BACKGROUND_PAINT + ".dockable.toolbar" );
}
@Override
public Component getComponent(){
return getDockable().getComponent();
}
@Override
public Dockable getDockable(){
return ToolbarItemDockable.this;
}
}
}