/* * 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.station.toolbar.group; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.Iterator; import bibliothek.gui.DockController; import bibliothek.gui.Dockable; import bibliothek.gui.dock.AbstractToolbarDockStation; import bibliothek.gui.dock.ExpandableToolbarItemStrategy; import bibliothek.gui.dock.ToolbarGroupDockStation; import bibliothek.gui.dock.action.AbstractDockActionSource; import bibliothek.gui.dock.action.DockAction; import bibliothek.gui.dock.action.DockActionSource; import bibliothek.gui.dock.action.LocationHint; import bibliothek.gui.dock.event.DockActionSourceListener; import bibliothek.gui.dock.toolbar.expand.ExpandableToolbarItemStrategyListener; import bibliothek.gui.dock.toolbar.expand.ExpandedState; import bibliothek.gui.dock.toolbar.expand.SimpleExpandAction; import bibliothek.gui.dock.toolbar.expand.SimpleExpandAction.Action; import bibliothek.gui.dock.util.PropertyValue; /** * This class uses the {@link ExpandableToolbarItemStrategy} to find out whether the items of some columns * can be expanded, and if so this class generates an appropriate {@link DockActionSource} containing actions * to expand or shrink all the items of one column. * @author Benjamin Sigg * @param <P> the type of object that represent a {@link Dockable} */ public abstract class ExpandToolbarGroupActions<P> extends AbstractToolbarGroupActions<P, ExpandToolbarGroupActions<P>.ExpandColumn> { /** * The strategy that is currently used. */ private PropertyValue<ExpandableToolbarItemStrategy> strategy = new PropertyValue<ExpandableToolbarItemStrategy>( ExpandableToolbarItemStrategy.STRATEGY ){ @Override protected void valueChanged( ExpandableToolbarItemStrategy oldValue, ExpandableToolbarItemStrategy newValue ){ if( oldValue != null ) { oldValue.removeExpandedListener( listener ); } if( newValue != null ) { newValue.addExpandedListener( listener ); } } }; private PropertyValue<Boolean> onConflictEnable = new PropertyValue<Boolean>( AbstractToolbarDockStation.ON_CONFLICT_ENABLE ){ @Override protected void valueChanged( Boolean oldValue, Boolean newValue ){ for( int i = 0, n = getColumnCount(); i < n; i++ ){ getColumn( i ).source.update(); } } }; /** * This listener is monitoring the current {@link #strategy} */ private ExpandableToolbarItemStrategyListener listener = new ExpandableToolbarItemStrategyListener(){ @Override public void stretched( Dockable item ){ update( item ); } @Override public void shrunk( Dockable item ){ update( item ); } @Override public void expanded( Dockable item ){ update( item ); } @Override public void enablementChanged( Dockable item, ExpandedState state, boolean enabled ){ update( item ); } }; /** the controller in whose realm this action is used */ private DockController controller; /** * Creates a new set of actions. * @param controller the controller in whose realm this object is used * @param station th station which uses this set of actions */ public ExpandToolbarGroupActions( DockController controller, ToolbarGroupDockStation station ){ super( station ); this.controller = controller; strategy.setProperties( controller ); onConflictEnable.setProperties( controller ); } public void destroy(){ strategy.setProperties( (DockController) null ); onConflictEnable.setProperties( (DockController)null ); } @Override protected ExpandColumn createColumn( ToolbarColumn<Dockable,P> column ){ return new ExpandColumn( column ); } /** * Gets the strategy that is currently used to decide which actions are available for which {@link Dockable}s. * @return the current strategy, can be <code>null</code> */ public ExpandableToolbarItemStrategy getStrategy(){ return strategy.getValue(); } /** * Checks the state of the column in which <code>item</code> is and updates the actions. * @param item the item whose column should be checked */ public void update( Dockable item ){ ExpandColumn column = getColumn( item ); if( column != null ){ column.source.update(); } } protected class ExpandColumn extends AbstractToolbarGroupActions<P, ExpandColumn>.Column { private ExpandSource source; public ExpandColumn( ToolbarColumn<Dockable,P> column ){ super( null ); source = new ExpandSource( this ); init( column ); } @Override protected DockActionSource createSource(){ return source; } @Override protected void inserted( int index, P item ){ source.update(); } @Override protected void removed( int index, P item ){ source.update(); } @Override protected void removed(){ // nothing to do } /** * Executes the action <code>action</code>, the exact behavior of this method depends * also on the current {@link #getState() state}. * @param action how to modify the state */ public void performAction( Action action ){ boolean[] canPerform = getEnabledStates(); ExpandedState current = getState(); ExpandedState next; switch( action ){ case LARGER: next = current.larger(); break; case LARGEST: next = ExpandedState.EXPANDED; break; case SMALLER: next = current.smaller(); break; case SMALLEST: next = ExpandedState.SHRUNK; break; default: throw new IllegalStateException( "never happens" ); } int attempts = canPerform.length; while( attempts > 0 && current != next && !canPerform[ next.ordinal() ]){ attempts--; switch( action ){ case LARGER: case SMALLEST: next = next.larger(); break; case LARGEST: case SMALLER: next = next.smaller(); break; } } if( current != next && canPerform[ next.ordinal() ]){ setState( next ); } } public void setState( ExpandedState state ){ ExpandableToolbarItemStrategy strategy = getStrategy(); for( Dockable dockable : getDockables() ){ strategy.setState( dockable, state ); } source.update(); } /** * Gets the current {@link ExpandedState} of this column. The current state is always the * state of the first child * @return the current state, not <code>null</code> */ public ExpandedState getState(){ ToolbarColumn<Dockable,P> column = getColumn(); ExpandedState result = null; if( column.getDockableCount() > 0 ){ ExpandableToolbarItemStrategy strategy = getStrategy(); if( strategy != null ){ for( int i = 0, n = column.getDockableCount(); i<n && result == null; i++){ result = strategy.getState( column.getDockable( i ) ); } } } if( result == null ){ result = ExpandedState.SHRUNK; } return result; } /** * Gets an array telling for each {@link ExpandedState} whether it is enabled or not. * @return an array, a value of <code>true</code> indicates that an {@link ExpandedState} * is enabled. */ public boolean[] getEnabledStates(){ boolean[] canPerform = new boolean[ExpandedState.values().length]; for( ExpandedState state : ExpandedState.values() ){ canPerform[state.ordinal()] = isEnabled( state ); } return canPerform; } private boolean isEnabled( ExpandedState state ){ boolean hasEnabled = false; boolean hasDisabled = false; ExpandableToolbarItemStrategy strategy = getStrategy(); if( strategy != null ){ for( Dockable dockable : getDockables() ){ if( strategy.isEnabled( dockable, state )){ hasEnabled = true; } else{ hasDisabled = true; } } } if( hasEnabled && hasDisabled ){ return onConflictEnable.getValue(); } return hasEnabled; } } /** * A {@link DockActionSource} that offers methods to update its content depending on the {@link ExpandedState} of * the {@link Dockable}s of one {@link ExpandColumn}. * @author Benjamin Sigg */ private class ExpandSource extends AbstractDockActionSource { private ExpandColumn column; private SimpleExpandAction[] actions; public ExpandSource( ExpandColumn column ){ this.column = column; } /** * Called if the <code>index</code>'th {@link SimpleExpandAction} is clicked. * @param index the index of the action that was clicked */ private void onAction( int index ){ column.performAction( actions[index].getBehavior() ); } @Override public void addDockActionSourceListener( DockActionSourceListener listener ){ if( !hasListeners() ){ findEnabledActions(); } super.addDockActionSourceListener( listener ); } private void findEnabledActions(){ boolean[] canPerform = column.getEnabledStates(); int enabledCount = 0; for( boolean can : canPerform ){ if( can ){ enabledCount++; } } boolean canExpand = canPerform[ ExpandedState.EXPANDED.ordinal() ]; boolean canShrink = canPerform[ ExpandedState.SHRUNK.ordinal() ]; boolean canStretch = canPerform[ ExpandedState.STRETCHED.ordinal() ]; int length = Math.max( 0, enabledCount-1 ); if( actions == null || actions.length != length ){ SimpleExpandAction[] next = new SimpleExpandAction[ length ]; if( actions != null ){ System.arraycopy( actions, 0, next, 0, Math.min( actions.length, next.length ) ); } for( int i = 0; i < next.length; i++ ){ if( next[i] == null ){ next[i] = new SimpleExpandAction( controller, Action.LARGEST ); final int index = i; next[i].addActionListener( new ActionListener(){ @Override public void actionPerformed( ActionEvent e ){ onAction( index ); } }); } } actions = next; } ExpandedState state = column.getState(); if( canExpand && canShrink && canStretch ){ switch( state ){ case SHRUNK: actions[0].setBehavior( Action.LARGER ); actions[1].setBehavior( Action.LARGEST ); break; case STRETCHED: actions[0].setBehavior( Action.SMALLER ); actions[1].setBehavior( Action.LARGER ); break; case EXPANDED: actions[0].setBehavior( Action.SMALLEST ); actions[1].setBehavior( Action.SMALLER ); break; } } else if( canShrink && (canExpand || canStretch) ){ switch( state ){ case SHRUNK: actions[0].setBehavior( Action.LARGER ); break; default: actions[0].setBehavior( Action.SMALLER ); break; } } else if( canStretch && canExpand ){ switch( state ){ case EXPANDED: actions[0].setBehavior( Action.SMALLER ); break; default: actions[0].setBehavior( Action.LARGER ); break; } } } /** * Updates the actions that are shown on this source. */ public void update(){ if( hasListeners() ) { final int oldCount = getDockActionCount(); findEnabledActions(); final int newCount = getDockActionCount(); if( oldCount != newCount ) { if( oldCount > 0 ) { fireRemoved( 0, oldCount - 1 ); } findEnabledActions(); if( newCount > 0 ) { fireAdded( 0, newCount - 1 ); } } } } @Override public Iterator<DockAction> iterator(){ return new Iterator<DockAction>(){ private int index = 0; @Override public boolean hasNext(){ return index < getDockActionCount(); } @Override public DockAction next(){ return getDockAction( index++ ); } @Override public void remove(){ throw new UnsupportedOperationException(); } }; } @Override public DockAction getDockAction( int index ){ if( (index < 0) || (index >= getDockActionCount()) ) { throw new IllegalArgumentException( "index out of bounds" ); } return actions[index]; } @Override public int getDockActionCount(){ if( !hasListeners() ) { findEnabledActions(); } return actions.length; } @Override public LocationHint getLocationHint(){ return new LocationHint( LocationHint.INDIRECT_ACTION, LocationHint.RIGHT ); } } }