/*
* 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) 2007 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
*
* Benjamin Sigg
* benjamin_sigg@gmx.ch
* CH - Switzerland
*/
package bibliothek.gui.dock.action.actions;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.swing.Icon;
import javax.swing.KeyStroke;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.action.ActionContentModifier;
import bibliothek.gui.dock.action.ActionType;
import bibliothek.gui.dock.action.DockAction;
import bibliothek.gui.dock.action.StandardDockAction;
import bibliothek.gui.dock.event.StandardDockActionListener;
import bibliothek.util.container.Tuple;
/**
* A {@link DockAction} which classifies its {@link Dockable Dockables}
* in groups. Every {@link Dockable} must be in one group. The groups
* itself are completely independent, except that they all must have
* the same {@link ActionType}.<br>
* A {@link Dockable} may change its group at any time.
* The method {@link #setGroup(Object, Dockable) setGroup} is used for that.<br>
* If a new {@link Dockable} is {@link #bind(Dockable) bound} to this
* action, the {@link #createGroupKey(Dockable) createGroupKey}-method
* determines the group where the {@link Dockable} will be added.<br>
* When a group is completely empty, it is removed. This behavior can
* be changed by the method {@link #setRemoveEmptyGroups(boolean) setRemoveEmptyGroups}.
* @author Benjamin Sigg
* @param <K> the type of the key used to distinguish between groups
* @param <D> the internal representation of one group
*
* @see #createGroupKey(Dockable)
* @see #setRemoveEmptyGroups(boolean)
*/
public abstract class GroupedDockAction<K, D extends SimpleDockAction> extends AbstractStandardDockAction {
/** the groups of this action */
private Map<K, D> groups = new HashMap<K, D>();
/** tells which Dockable is part of which group */
private Map<Dockable, Tuple<K, D>> dockActions = new HashMap<Dockable, Tuple<K, D>>();
/** a listener to the groups */
private Listener listener = new Listener();
/** whether empty groups should be removed automatically or not */
private boolean removeEmptyGroups = true;
/** a generator for keys for unknown Dockables */
private GroupKeyGenerator<? extends K> generator;
/**
* Creates a new action.
* @param generator the generator that will be used to get a key for
* Dockables which do not yet have a key. The generator can be <code>null</code>
* and set later through the method {@link #setGenerator(GroupKeyGenerator)}
*/
public GroupedDockAction( GroupKeyGenerator<? extends K> generator ){
super( true );
this.generator = generator;
}
/**
* Sets the generator that is used to create keys for unknown Dockables.
* @param generator the generator
* @see #createGroupKey(Dockable)
*/
public void setGenerator( GroupKeyGenerator<? extends K> generator ){
this.generator = generator;
}
/**
* Gets the generator that is used to create keys for unknown Dockables.
* @return the generator
* @see #createGroupKey(Dockable)
*/
public GroupKeyGenerator<? extends K> getGenerator(){
return generator;
}
/**
* If <code>true</code>, groups with no {@link Dockable} associated
* to, will be deleted automatically.
* @return <code>true</code> if empty groups are deleted
*/
public boolean isRemoveEmptyGroups() {
return removeEmptyGroups;
}
/**
* Sets whether empty groups should be removed automatically.<br>
* A group is a set of {@link Dockable Dockables}. A group can become
* empty if all it's <code>Dockables</code> are {@link #setGroup(Object, Dockable) transferred}
* to another group, or removed through an {@link #unbound(Dockable) unbound}.
* @param removeEmptyGroups <code>true</code> if empty groups should
* be deleted, <code>false</code> if the should remain in memory and
* be used again.
*/
public void setRemoveEmptyGroups( boolean removeEmptyGroups ) {
this.removeEmptyGroups = removeEmptyGroups;
}
public Icon getIcon( Dockable dockable, ActionContentModifier modifier ){
return getGroup( dockable ).getIcon( dockable, modifier );
}
public ActionContentModifier[] getIconContexts( Dockable dockable ){
return getGroup( dockable ).getIconContexts( dockable );
}
public String getText( Dockable dockable ) {
return getGroup( dockable ).getText( dockable );
}
public String getTooltipText( Dockable dockable ) {
return getGroup( dockable ).getTooltipText( dockable );
}
@Override
public boolean isEnabled( Dockable dockable ) {
return getGroup( dockable ).isEnabled( dockable ) && super.isEnabled( dockable );
}
@Override
public void bound( Dockable dockable ) {
K key = createGroupKey( dockable );
if( key == null )
throw new IllegalStateException( "null-key generated, a null-key is not allowed" );
super.bound(dockable);
D action = ensureGroup( key );
action.bind( dockable );
dockActions.put( dockable, new Tuple<K, D>( key, action ));
}
@Override
public void unbound( Dockable dockable ) {
super.unbound(dockable);
Tuple<K, D> action = dockActions.remove( dockable );
action.getB().unbind( dockable );
removeIfEmpty( action.getA() );
}
private void removeIfEmpty( K key ){
if( removeEmptyGroups ){
D group = getGroup( key );
if( group.getBoundDockables().isEmpty() ){
groups.remove( key );
group.removeDockActionListener( listener );
}
}
}
/**
* Sets the <code>icon</code> of the group named <code>key</code>.
* If this group does not exist, it will be created.
* @param key the name of the group
* @param icon the new icon of the group, may be <code>null</code>
*/
public void setIcon( K key, Icon icon ){
ensureGroup( key ).setIcon( icon );
}
/**
* Gets the icon of the group named <code>key</code>.
* @param key The name of the group
* @return The icon of the group, may be <code>null</code>
* @throws IllegalArgumentException If the group does not exist
* @see #setIcon(Object, Icon)
*/
public Icon getIcon( Object key ){
SimpleDockAction action = groups.get( key );
if( action == null )
throw new IllegalArgumentException( "There is no such group" );
return action.getIcon();
}
/**
* Sets the <code>icon</code> of the group named <code>key</code>.
* If this group does not exist, it will be created.
* @param key the name of the group
* @param modifier tells in which context the icon is used
* @param icon the new icon of the group, may be <code>null</code>
*/
public void setIcon( K key, ActionContentModifier modifier, Icon icon ){
ensureGroup( key ).setIcon( modifier, icon );
}
/**
* Gets the icon of the group named <code>key</code>.
* @param key The name of the group
* @param modifier tells in which context the icon is used
* @return The icon of the group, may be <code>null</code>
* @throws IllegalArgumentException If the group does not exist
* @see #setIcon(Object, Icon)
*/
public Icon getIcon( Object key, ActionContentModifier modifier ){
SimpleDockAction action = groups.get( key );
if( action == null )
throw new IllegalArgumentException( "There is no such group" );
return action.getIcon( modifier );
}
/**
* Sets the <code>icon</code> which will be shown, when the group
* named <code>key</code> is disabled. If the group <code>key</code>
* does not exist, it will be created.
* @param key The name of the group
* @param icon The new icon for the disabled-state of the group,
* may be <code>null</code>
*/
public void setDisabledIcon( K key, Icon icon ){
ensureGroup( key ).setDisabledIcon( icon );
}
public Icon getDisabledIcon( Dockable dockable ){
return getGroup( dockable ).getDisabledIcon();
}
/**
* Gets the icon that is shown, when the group <code>key</code>
* is disabled.
* @param key The name of the group
* @return The disabled-icon, may be <code>null</code>
* @throws IllegalArgumentException If the group does not exist
* @see #setDisabledIcon(Object, Icon)
*/
public Icon getDisabledIcon( Object key ){
SimpleDockAction action = groups.get( key );
if( action == null )
throw new IllegalArgumentException( "There is no such group" );
return action.getDisabledIcon();
}
/**
* Sets the <code>text</code> for group <code>key</code>. If the
* group does not exist, it will be created.
* @param key The name of the group
* @param text The text of the group
*/
public void setText( K key, String text ){
ensureGroup( key ).setText( text );
}
/**
* Gets the text of the the group <code>key</code>.
* @param key the key of the group
* @return the text of the group
* @throws IllegalArgumentException if the group does not exist
* @see #setText(Object, String)
*/
public String getText( Object key ){
SimpleDockAction action = groups.get( key );
if( action == null )
throw new IllegalArgumentException( "There is no such group" );
return action.getText();
}
/**
* Sets the tooltip of the group <code>key</code>. If the
* group does not exist, it will be created. The tooltip is the first
* part of the tooltip text. The whole tooltip text may contain additional
* information like the {@link #setAccelerator(Object, KeyStroke) accelerator}.
* @param key The name of the group
* @param text The tooltip of the group
*/
public void setTooltip( K key, String text ){
ensureGroup( key ).setTooltip( text );
}
/**
* Gets the tooltip text of the group <code>key</code>. The tooltip text is
* the text which is really shown on the tooltip.
* @param key The name of the group
* @return The tooltip
* @throws IllegalArgumentException If the group does not exist
* @see #setTooltip(Object, String)
* @see #getTooltip(Object)
*/
public String getTooltipText( Object key ){
SimpleDockAction action = groups.get( key );
if( action == null )
throw new IllegalArgumentException( "There is no such group" );
return action.getTooltipText();
}
/**
* Gets the tooltip of the group <code>key</code>. The tooltip if the
* first part of the tooltip text.
* @param key The name of the group
* @return The tooltip
* @throws IllegalArgumentException If the group does not exist
* @see #setTooltip(Object, String)
*/
public String getTooltip( Object key ){
SimpleDockAction action = groups.get( key );
if( action == null )
throw new IllegalArgumentException( "There is no such group" );
return action.getTooltip();
}
/**
* Sets the enabled-state of the group <code>key</code>. This action
* can only be triggered, if the associated {@link Dockable} is
* in a group with <code>true</code> enable-state. If the group
* does not exist, it will be created.
* @param key The name of the group
* @param enabled The state of the group
*/
public void setEnabled( K key, boolean enabled ){
ensureGroup( key ).setEnabled( enabled );
}
/**
* Gets the enabled-state of the group <code>key</code>.
* @param key The name of the group
* @return The enabled-state
* @throws IllegalArgumentException If the group does not exist
* @see #setEnabled(Object, boolean)
*/
public boolean isEnabled( Object key ){
D action = getGroup( key );
if( action == null )
throw new IllegalArgumentException( "There is no such group" );
return action.isEnabled();
}
public Dockable getDockableRepresentation( Dockable dockable ){
return getGroup( dockable ).getDockableRepresentation( dockable );
}
/**
* Gets the {@link Dockable} which is represented by this {@link DockAction}.
* @param key the name of the group
* @return the element, can be <code>null</code>
* @throws IllegalArgumentException if the group does not exist
*/
public Dockable getDockableRepresentation( K key ){
SimpleDockAction action = groups.get( key );
if( action == null )
throw new IllegalArgumentException( "There is no such group" );
return action.getDockableRepresentation();
}
/**
* Sets the {@link Dockable} that is represented by this group.
* @param key the name of the group
* @param dockable the new represented {@link Dockable}, can be <code>null</code>
*/
public void setDockableRepresentation( K key, Dockable dockable ){
ensureGroup( key ).setDockableRepresentation( dockable );
}
/**
* Set the type of {@link java.awt.event.KeyEvent} that will trigger
* an action.
* @param key the group of actions whose trigger might be affected
* @param accelerator the new key-trigger or <code>null</code>
*/
public void setAccelerator( K key, KeyStroke accelerator ){
ensureGroup( key ).setAccelerator( accelerator );
}
/**
* Gets the type of {@link java.awt.event.KeyEvent} that will
* trigger an action.
* @param key the affected group
* @return the type of event or <code>null</code>
* @throws IllegalArgumentException if <code>key</code> is unknown
*/
public KeyStroke getAccelerator( K key ){
SimpleDockAction action = groups.get( key );
if( action == null )
throw new IllegalArgumentException( "There is no such group" );
return action.getAccelerator();
}
/**
* Ensures that there exist a group with the name <code>key</code>.
* @param key The name of the group
* @return The group with the name <code>key</code>. This may be
* a newly created group, or a group that already existed.
*/
protected D ensureGroup( K key ){
if( key == null )
throw new IllegalArgumentException( "The key must be a non-null value" );
D action = groups.get( key );
if( action == null ){
action = createGroup( key );
action.addDockActionListener( listener );
groups.put( key, action );
}
return action;
}
/**
* Gets the group in which <code>dockable</code> is.
* @param dockable the Dockable whose group is searched
* @return the group or <code>null</code>
*/
protected D getGroup( Dockable dockable ){
Tuple<K, D> group = dockActions.get( dockable );
return group == null ? null : group.getB();
}
/**
* Gets the group with the given key.
* @param key the key of the group
* @return the group or <code>null</code>
*/
protected D getGroup( Object key ){
return groups.get( key );
}
/**
* Creates a new group.
* @param key the key of the new group
* @return the new group
*/
protected abstract D createGroup( K key );
/**
* Returns <code>true</code> if a group with the name of <code>key</code>
* exists, return <code>false</code> otherwise.
* @param key The group that is searched
* @return <code>true</code> if <code>key</code> was found, <code>false</code>
* otherwise
*/
public boolean groupExists( Object key ){
return groups.containsKey( key );
}
/**
* Removes a group but only if the group is empty (no {@link Dockable Dockables}
* are registered in that group).
* @param key The name of the group
* @return <code>true</code> if there is no longer a group with name
* <code>key</code> (also <code>true</code> if there never existed
* a group with that name), or <code>false</code> if the group
* was not deleted because it was not empty.
*/
public boolean removeGroup( Object key ){
SimpleDockAction group = groups.get( key );
if( group == null )
return true;
if( group.getBoundDockables().isEmpty() ){
group.removeDockActionListener( listener );
groups.remove( key );
return true;
}
else
return false;
}
/**
* Calculates the name of the group to which the <code>dockable</code>
* should be added.<br>
* Every {@link Dockable} is member of one group. The membership
* determines text, icon, etc. for the dockable. Whenever a
* dockable is {@link #bound(Dockable) bound} to this action,
* the group will be determined by this method. Later on, the group
* can be changed by the method {@link #setGroup(Object, Dockable) setGroup}.<br>
* The default implementation uses the {@link #getGenerator() generator} of
* this action.
* @param dockable The {@link Dockable} whose group has to be
* found
* @return the name of the dockable's group. That can be an existing
* or a non existing group. <code>null</code> is not a valid result.
*/
protected K createGroupKey( Dockable dockable ){
return generator.generateKey( dockable );
}
/**
* Assigns the <code>dockable</code> to the group with the given <code>key</code>.
* @param key The name of the new group
* @param dockable The {@link Dockable} whose membership will be changed.
* The dockable must already be in a group of this action.
* @throws IllegalArgumentException if the {@link Dockable} is not
* in a group, or if <code>key</code> is <code>null</code>
* @see #createGroupKey(Dockable)
*/
public void setGroup( K key, Dockable dockable ){
if( key == null )
throw new IllegalArgumentException( "Key must not be null" );
Tuple<K, D> old = dockActions.get( dockable );
if( old == null )
throw new IllegalArgumentException( "Dockable was not registered" );
D put = ensureGroup( key );
old.getB().unbind( dockable );
removeIfEmpty( old.getA() );
put.bind( dockable );
dockActions.put( dockable, new Tuple<K, D>( key, put ) );
Set<Dockable> set = new HashSet<Dockable>();
set.add( dockable );
fireActionEnabledChanged( set );
fireActionIconChanged( null, set );
fireActionTextChanged( set );
fireActionTooltipTextChanged( set );
fireActionRepresentativeChanged( set );
}
/**
* Tells whether the <code>dockable</code> is {@link #bound(Dockable) bound}
* to this action, or not. If the <code>dockable</code> was {@link #unbind(Dockable) unbound},
* then this method will return <code>false</code>.
* @param dockable the {@link Dockable} to search
* @return <code>true</code> if the {@link Dockable} is bound
*/
public boolean isKnown( Dockable dockable ){
return dockActions.containsKey( dockable );
}
public boolean trigger( Dockable dockable ) {
return getGroup( dockable ).trigger( dockable );
}
/**
* A listener to the groups.
* @author Benjamin Sigg
*/
private class Listener implements StandardDockActionListener{
public void actionTooltipTextChanged( StandardDockAction action, Set<Dockable> dockables ) {
fireActionTooltipTextChanged( dockables );
}
public void actionTextChanged( StandardDockAction action, Set<Dockable> dockables ) {
fireActionTextChanged( dockables );
}
public void actionIconChanged( StandardDockAction action, ActionContentModifier modifier, Set<Dockable> dockables ){
fireActionIconChanged( modifier, dockables );
}
public void actionEnabledChanged( StandardDockAction action, Set<Dockable> dockables ) {
fireActionEnabledChanged( dockables );
}
public void actionRepresentativeChanged( StandardDockAction action, Set<Dockable> dockables ){
fireActionRepresentativeChanged( dockables );
}
}
}