/* * 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.Component; import java.util.ArrayList; import java.util.List; import javax.swing.JPanel; import bibliothek.gui.DockController; import bibliothek.gui.DockStation; import bibliothek.gui.Dockable; import bibliothek.gui.Orientation; import bibliothek.gui.dock.event.DockStationAdapter; import bibliothek.gui.dock.station.AbstractDockableStation; import bibliothek.gui.dock.station.DisplayerCollection; import bibliothek.gui.dock.station.DockableDisplayer; import bibliothek.gui.dock.station.OrientationObserver; import bibliothek.gui.dock.station.OrientedDockStation; import bibliothek.gui.dock.station.OrientingDockStationEvent; import bibliothek.gui.dock.station.OrientingDockStationListener; import bibliothek.gui.dock.station.StationBackgroundComponent; import bibliothek.gui.dock.station.StationDragOperation; import bibliothek.gui.dock.station.StationPaint; import bibliothek.gui.dock.station.ToolbarTabDockStation; import bibliothek.gui.dock.station.toolbar.ToolbarStrategy; import bibliothek.gui.dock.themes.DefaultDisplayerFactoryValue; import bibliothek.gui.dock.themes.DefaultStationPaintValue; import bibliothek.gui.dock.title.DockTitle; import bibliothek.gui.dock.title.DockTitleFactory; import bibliothek.gui.dock.title.DockTitleVersion; import bibliothek.gui.dock.toolbar.expand.ExpandableToolbarItem; import bibliothek.gui.dock.toolbar.expand.ExpandableToolbarItemListener; import bibliothek.gui.dock.toolbar.expand.ExpandableToolbarItemStrategyListener; import bibliothek.gui.dock.toolbar.expand.ExpandedState; import bibliothek.gui.dock.util.BackgroundAlgorithm; import bibliothek.gui.dock.util.BackgroundComponent; import bibliothek.gui.dock.util.ConfiguredBackgroundPanel; import bibliothek.gui.dock.util.PropertyKey; import bibliothek.gui.dock.util.PropertyValue; import bibliothek.gui.dock.util.SilentPropertyValue; import bibliothek.gui.dock.util.Transparency; import bibliothek.gui.dock.util.property.ConstantPropertyFactory; import bibliothek.util.FrameworkOnly; /** * Base class of a {@link DockStation} behaving like a typical toolbar: the * children are ordered in a list, an optional title and border may be shown. * * @author Benjamin Sigg * @author Herve Guillaume */ public abstract class AbstractToolbarDockStation extends AbstractDockableStation implements OrientedDockStation, ExpandableToolbarItem{ /** * If it is not clear whether an {@link ExpandedState} is enabled, because the involved {@link Dockable}s offer different * enabled-states, then the value of this <code>boolean</code> is the result of the operation. */ public static final PropertyKey<Boolean> ON_CONFLICT_ENABLE = new PropertyKey<Boolean>( "ExpandableToolbarGroupActions.on_conflict_enable", new ConstantPropertyFactory<Boolean>( Boolean.TRUE ), true ); /** * a helper class ensuring that all properties of the * {@link DockableDisplayer}s are set correctly */ protected DisplayerCollection displayers; /** * a factory used by {@link #displayers} to create new * {@link DockableDisplayer}s */ protected DefaultDisplayerFactoryValue displayerFactory; /** a factory creating new {@link DockTitle}s */ protected DockTitleVersion title; /** A paint to draw lines */ protected DefaultStationPaintValue paint; /** Alignment of the content of this station */ protected Orientation orientation = Orientation.HORIZONTAL; /** all registered {@link OrientingDockStationListener}s. */ private final List<OrientingDockStationListener> orientingListeners = new ArrayList<OrientingDockStationListener>(); /** all registered {@link ExpandableToolbarItemListener}s */ private final List<ExpandableToolbarItemListener> expandableListeners = new ArrayList<ExpandableToolbarItemListener>(); /** the current behavior of this station */ private ExpandedState state = ExpandedState.SHRUNK; /** added to the current {@link #expandableStategy} */ private ExpandableListener expandableListener = new ExpandableListener(); /** the current strategy to handle {@link ExpandableToolbarItem}s */ private PropertyValue<ExpandableToolbarItemStrategy> expandableStategy = new PropertyValue<ExpandableToolbarItemStrategy>( ExpandableToolbarItemStrategy.STRATEGY ){ @Override protected void valueChanged( ExpandableToolbarItemStrategy oldValue, ExpandableToolbarItemStrategy newValue ){ if( oldValue != null ){ oldValue.removeExpandedListener( expandableListener ); } if( newValue != null ){ newValue.addExpandedListener( expandableListener ); } fireEnablementChanged(); } }; /** tells what happens when there are conflicts in the enabled state of {@link ExpandedState} */ private PropertyValue<Boolean> onConflictEnable = new PropertyValue<Boolean>( ON_CONFLICT_ENABLE ){ @Override protected void valueChanged( Boolean oldValue, Boolean newValue ){ fireEnablementChanged(); } }; private boolean[] expandedEnablementStateCache = new boolean[ ExpandedState.values().length ]; /** the Dockable that is currently removed */ private Dockable removal; private Background background; // ######################################################## // ############ Initialization Managing ################### // ######################################################## /** * Constructs a new ToolbarDockStation. Subclasses must call {@link #init()} * once the constructor has been executed. */ public AbstractToolbarDockStation(){ new OrientationObserver( this ){ @Override protected void orientationChanged( Orientation current ){ if( current != null ){ setOrientation( current ); } } }; addDockStationListener( new DockStationAdapter(){ @Override public void dockableAdded( DockStation station, Dockable dockable ){ fireEnablementChanged(); } @Override public void dockableRemoved( DockStation station, Dockable dockable ){ fireEnablementChanged(); } }); } @Override public void setController( DockController controller ){ super.setController( controller ); expandableStategy.setProperties( controller ); onConflictEnable.setProperties( controller ); background.setController( controller ); } /** * Initializes the properties that depend on the subclasses * @param backgroundId the identifier used for registering a {@link BackgroundComponent} */ protected void init( String backgroundId ){ background = new Background( backgroundId ); } /** * Creates a new {@link JPanel} which uses the {@link #getBackgroundAlgorithm() background algorithm} to * paint its content. * @return the new panel */ protected JPanel createBackgroundPanel(){ ConfiguredBackgroundPanel panel = new ConfiguredBackgroundPanel( Transparency.DEFAULT ); panel.setBackground( background ); return panel; } /** * Gets the algorithm which should be used to paint this station. * @return the background algorithm, <code>null</code> until {@link #init(String)} was called */ protected BackgroundAlgorithm getBackgroundAlgorithm(){ return background; } // ######################################################## // ############ General DockStation Managing ############## // ######################################################## @Override public Dockable getFrontDockable(){ // there's no child which is more important than another return null; } @Override public void setFrontDockable( Dockable dockable ){ // there's no child which is more important than another } @Override public String toString(){ return this.getClass().getSimpleName() + '@' + Integer.toHexString(hashCode()); } // ######################################################## // ################### Class Utilities #################### // ######################################################## /** * Gets the location of <code>dockable</code> in the component-panel. * * @param dockable * the {@link Dockable} to search * @return the location or -1 if the child was not found */ public int indexOf( Dockable dockable ){ for (int i = 0; i < getDockableCount(); i++){ if (getDockable(i) == dockable){ return i; } } return -1; } // ######################################################## // ############ Orientation Managing ###################### // ######################################################## @Override public Orientation getOrientation(){ return orientation; } @Override public void addOrientingDockStationListener( OrientingDockStationListener listener ){ orientingListeners.add(listener); } @Override public void removeOrientingDockStationListener( OrientingDockStationListener listener ){ orientingListeners.remove(listener); } @Override public Orientation getOrientationOf( Dockable child ){ return getOrientation(); } /** * Fires an {@link OrientingDockStationEvent}. */ protected void fireOrientingEvent(){ final OrientingDockStationEvent event = new OrientingDockStationEvent( this); for (final OrientingDockStationListener listener : orientingListeners .toArray(new OrientingDockStationListener[orientingListeners .size()])){ listener.changed(event); } } // ######################################################## // ############ Expanded State Managing ################### // ######################################################## @Override public ExpandedState getExpandedState(){ return state; } /** * Sets the {@link ExpandedState} of this station. * * @param state * the new state, not <code>null</code> * @param action * if <code>true</code>, then * {@link #setExpandedState(ExpandedState)} is called. Otherwise * the property is changed without actually performing any * actions. The later option should only be used while loading a * layout. */ @FrameworkOnly public void setExpandedState( ExpandedState state, boolean action ){ if (action){ setExpandedState(state); } else{ this.state = state; } } @Override public void setExpandedState( ExpandedState state ){ if (this.state != state){ final DockController controller = getController(); if (controller != null){ controller.freezeLayout(); } try{ final ExpandedState oldState = this.state; this.state = state; if (oldState != ExpandedState.SHRUNK){ shrink(oldState); } if (state == ExpandedState.EXPANDED){ expand(); } else if (state == ExpandedState.STRETCHED){ stretch(); } for (final ExpandableToolbarItemListener listener : expandableListeners){ listener.changed(this, oldState, state); } } finally{ if (controller != null){ controller.meltLayout(); } } } } @Override public boolean isEnabled( ExpandedState state ){ ExpandableToolbarItemStrategy strategy = expandableStategy.getValue(); if( strategy == null ){ return false; } boolean hasEnabled = false; boolean hasDisabled = false; DockStation station = null; if( getExpandedState() == ExpandedState.EXPANDED && getDockableCount() == 1 ){ station = getDockable( 0 ).asDockStation(); } if( station == null ){ station = this; } for( int i = 0, n = station.getDockableCount(); i<n; i++ ){ if( strategy.isEnabled( station.getDockable( i ), state )){ hasEnabled = true; } else{ hasDisabled = true; } } if( hasEnabled && hasDisabled ){ onConflictEnable.getValue(); } return hasEnabled; } private void expand(){ // state is "shrunk" final DockController controller = getController(); Dockable focused = null; final Dockable[] children = new Dockable[getDockableCount()]; for (int i = 0; i < children.length; i++){ children[i] = getDockable(i); if ((controller != null) && controller.isFocused(children[i])){ focused = children[i]; } } for (int i = children.length - 1; i >= 0; i--){ remove(getDockable(i)); } final ToolbarTabDockStation station = new ToolbarTabDockStation(); for (final Dockable child : children){ station.drop(child); } drop(station); if (focused != null){ station.setFrontDockable(focused); controller.setFocusedDockable(focused, true); } } public void stretch(){ // state is "shrunk" } public void shrink(ExpandedState state){ if (state == ExpandedState.EXPANDED){ final DockController controller = getController(); final DockStation child = getDockable(0).asDockStation(); final Dockable focused = child.getFrontDockable(); remove(getDockable(0)); final Dockable[] children = new Dockable[child.getDockableCount()]; for (int i = 0; i < children.length; i++){ children[i] = child.getDockable(i); } for (int i = children.length - 1; i >= 0; i--){ child.drag(children[i]); } for (final Dockable next : children){ drop(next); } if ((focused != null) && (controller != null)){ controller.setFocusedDockable(focused, true); } } } @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); } /** * Gets all the {@link ExpandableToolbarItemListener}s that are currently * registered. * * @return all the listeners */ protected ExpandableToolbarItemListener[] expandableListeners(){ return expandableListeners .toArray(new ExpandableToolbarItemListener[expandableListeners .size()]); } private void fireEnablementChanged(){ for( ExpandedState state : ExpandedState.values() ){ fireEnablementChanged( state ); } } private void fireEnablementChanged( ExpandedState state ){ boolean enabled = isEnabled( state ); if( enabled != expandedEnablementStateCache[ state.ordinal() ]){ expandedEnablementStateCache[ state.ordinal() ] = enabled; for( ExpandableToolbarItemListener listener : expandableListeners() ){ listener.enablementChanged( this, state, enabled ); } } } // ######################################################## // ############### Drop/Move Managing ##################### // ######################################################## /** * Gets the {@link ToolbarStrategy} that is currently used by this station. * * @return the strategy, never <code>null</code> */ public ToolbarStrategy getToolbarStrategy(){ final SilentPropertyValue<ToolbarStrategy> value = new SilentPropertyValue<ToolbarStrategy>( ToolbarStrategy.STRATEGY, getController()); final ToolbarStrategy result = value.getValue(); value.setProperties((DockController) null); return result; } @Override public boolean canDrag( Dockable dockable ){ if (getExpandedState() == ExpandedState.EXPANDED){ DockStation child = dockable.asDockStation(); return child != null && child.getDockableCount() == 0; } return true; } /** * Removes <code>dockable</code> from this station.<br> * Note: clients may need to invoke {@link DockController#freezeLayout()} * and {@link DockController#meltLayout()} to ensure none else adds or * removes <code>Dockable</code>s. * * @param dockable * the child to remove */ protected abstract void remove( Dockable dockable ); @Override public boolean canReplace( Dockable old, Dockable next ){ if (old.getClass() == next.getClass()){ return true; } else{ return false; } } @Override public void replace( DockStation old, Dockable next ){ replace(old.asDockable(), next); } @Override public StationDragOperation prepareDrag( Dockable dockable ){ removal = dockable; getComponent().repaint(); return new StationDragOperation(){ @Override public void succeeded(){ removal = null; getComponent().repaint(); } @Override public void canceled(){ removal = null; getComponent().repaint(); } }; } /** * Gets the child of this station that is about to be removed. * @return the child that is involved in a drag and drop operation, can be <code>null</code> */ protected Dockable getRemoval(){ return removal; } // ######################################################## // ###################### UI Managing ##################### // ######################################################## /** * Creates a new {@link DefaultDisplayerFactoryValue}, a factory used to * create new {@link DockableDisplayer}s. * * @return the new factory, must not be <code>null</code> */ protected abstract DefaultDisplayerFactoryValue createDisplayerFactory(); /** * Gets a {@link StationPaint} which is used to paint some lines onto this * station. Use a {@link DefaultStationPaintValue#setDelegate(StationPaint) * delegate} to exchange the paint. * * @return the paint */ public DefaultStationPaintValue getPaint(){ return paint; } /** * Registers the default {@link DockTitleFactory} of this station at * <code>controller</code> and returns the associated * {@link DockTitleVersion}. * * @param controller * the controller at which the default title factory has to be * registered * @return the version of the title */ protected abstract DockTitleVersion registerTitle( DockController controller ); /** * Replaces <code>displayer</code> with a new {@link DockableDisplayer}. * * @param displayer * the displayer to replace * @throws IllegalArgumentException * if <code>displayer</code> is not a child of this station */ protected abstract void discard( DockableDisplayer displayer ); /** * The background algorithm of this {@link ToolbarContainerDockStation}. * @author Benjamin Sigg */ private class Background extends BackgroundAlgorithm implements StationBackgroundComponent{ public Background( String backgroundId ){ super( StationBackgroundComponent.KIND, backgroundId ); } @Override public Component getComponent(){ return AbstractToolbarDockStation.this.getComponent(); } @Override public DockStation getStation(){ return AbstractToolbarDockStation.this; } } private class ExpandableListener implements ExpandableToolbarItemStrategyListener{ @Override public void expanded( Dockable item ){ // ignore } @Override public void stretched( Dockable item ){ // ignore } @Override public void shrunk( Dockable item ){ // ignore } @Override public void enablementChanged( Dockable item, ExpandedState state, boolean enabled ){ if( item.getDockParent() == AbstractToolbarDockStation.this ){ fireEnablementChanged( state ); } } } }