/*
* 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.Component;
import java.awt.Rectangle;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import bibliothek.gui.Dockable;
import bibliothek.gui.Orientation;
import bibliothek.gui.dock.ToolbarGroupDockStation;
import bibliothek.gui.dock.action.DockActionSource;
import bibliothek.gui.dock.station.toolbar.title.ColumnDockActionSource;
import bibliothek.gui.dock.station.toolbar.title.ColumnDockActionSourceListener;
/**
* A class that can create one {@link DockActionSource} for each {@link ToolbarColumn} a {@link ToolbarGroupDockStation}
* has. This class is built such that subclasses can easily access or modify the {@link DockActionSource}s.
*
* @author Benjamin Sigg
* @param <P> the type of object that represents a {@link Dockable}
* @param <C> the type of this subclass
*/
public abstract class AbstractToolbarGroupActions<P, C extends AbstractToolbarGroupActions<P, C>.Column> implements ColumnDockActionSource {
/**
* The model that is currently observed
*/
private ToolbarColumnModel<Dockable,P> model;
/**
* All the columns that are currently used
*/
private List<C> columns = new ArrayList<C>();
/** all the listeners that were added to this {@link ColumnDockActionSource} */
private List<ColumnDockActionSourceListener> listeners = new ArrayList<ColumnDockActionSourceListener>();
/** the station using this object */
private ToolbarGroupDockStation station;
/**
* This listener is added to the current {@link #model}
*/
private ToolbarColumnModelListener<Dockable,P> modelListener = new ToolbarColumnModelListener<Dockable,P>(){
@Override
public void removed( ToolbarColumnModel<Dockable,P> model, ToolbarColumn<Dockable,P> column, int index ){
Column col = columns.remove( index );
for( ColumnDockActionSourceListener listener : listeners() ){
listener.removed( AbstractToolbarGroupActions.this, col.getSource(), index );
}
col.destroy();
}
@Override
public void inserted( ToolbarColumnModel<Dockable,P> model, ToolbarColumn<Dockable,P> column, int index ){
C col = createColumn( column );
columns.add( index, col );
for( ColumnDockActionSourceListener listener : listeners() ){
listener.inserted( AbstractToolbarGroupActions.this, col.getSource(), index );
}
for( int i = 0, n = column.getDockableCount(); i < n; i++ ) {
col.inserted( i, column.getItem( i ) );
}
}
};
/**
* This listener is added to {@link Component}s on which the boundaries of the columns depend.
*/
private ComponentListener componentListener = new ComponentListener(){
@Override
public void componentShown( ComponentEvent e ){
// ignore
}
@Override
public void componentHidden( ComponentEvent e ){
// ignore
}
@Override
public void componentResized( ComponentEvent e ){
for( ColumnDockActionSourceListener listener : listeners() ){
listener.reshaped( AbstractToolbarGroupActions.this );
}
}
@Override
public void componentMoved( ComponentEvent e ){
for( ColumnDockActionSourceListener listener : listeners() ){
listener.reshaped( AbstractToolbarGroupActions.this );
}
}
};
/**
* Creates a new object
* @param station the station which uses this set of actions
*/
public AbstractToolbarGroupActions( ToolbarGroupDockStation station ){
this.station = station;
}
@Override
public void addListener( ColumnDockActionSourceListener listener ){
if( listener == null ){
throw new IllegalArgumentException( "listener must not be null" );
}
listeners.add( listener );
}
@Override
public void removeListener( ColumnDockActionSourceListener listener ){
listeners.remove( listener );
}
/**
* Gets all the listeners that are currently registered.
* @return all the listeners
*/
protected ColumnDockActionSourceListener[] listeners(){
return listeners.toArray( new ColumnDockActionSourceListener[ listeners.size() ] );
}
/**
* Sets the model which should be observed by this {@link AbstractToolbarGroupActions}, all existing columns
* will be removed by this method.
* @param model the new model or <code>null</code>
*/
public void setModel( ToolbarColumnModel<Dockable,P> model ){
if( this.model != model ) {
if( this.model != null ) {
this.model.removeListener( modelListener );
for( int i = columns.size()-1; i >= 0; i-- ){
Column column = columns.get( i );
modelListener.removed( model, column.getColumn(), i );
}
}
this.model = model;
if( model != null ) {
model.addListener( modelListener );
for( int i = 0, n = model.getColumnCount(); i < n; i++ ) {
modelListener.inserted( model, model.getColumn( i ), i );
}
}
}
}
/**
* Gets the model which is currently observed by this object
* @return the mode, can be <code>null</code>
*/
public ToolbarColumnModel<Dockable,P> getModel(){
return model;
}
/**
* Gets all the column that are currently known to this object.
* @return all the columns, the list is not modifiable
*/
protected List<C> getColumns(){
return Collections.unmodifiableList( columns );
}
/**
* Gets the current number of columns.
* @return the number of columns
*/
public int getColumnCount(){
return columns.size();
}
@Override
public int getSourceCount(){
return getColumnCount();
}
@Override
public DockActionSource getSource( int index ){
return getColumn( index ).getSource();
}
@Override
public Orientation getOrientation(){
return station.getOrientation();
}
/**
* Gets the <code>index</code>'th column.
* @param index the location of the column
* @return the column, never <code>null</code>
*/
public C getColumn( int index ){
return columns.get( index );
}
/**
* Searches the column which contains <code>dockable</code>.
* @param dockable the element to search
* @return the column with <code>dockable</code> or <code>null</code> if not found
*/
protected C getColumn( Dockable dockable ){
for( C column : columns ){
if( column.getDockables().contains( dockable )){
return column;
}
}
return null;
}
@Override
public int getSourceOffset( int index ){
Rectangle boundaries = getColumn( index ).getColumnBoundaries();
if( boundaries == null ){
return -1;
}
if( getOrientation() == Orientation.VERTICAL ){
return boundaries.x;
}
else{
return boundaries.y;
}
}
@Override
public int getSourceLength( int index ){
Rectangle boundaries = getColumn( index ).getColumnBoundaries();
if( boundaries == null ){
return 0;
}
if( getOrientation() == Orientation.VERTICAL ){
return boundaries.width;
}
else{
return boundaries.height;
}
}
/**
* Creates a new, empty {@link Column} which will be filled with content later.
* @param column the column that is represented by the new object
* @return the new column, must not be <code>null</code>
*/
protected abstract C createColumn( ToolbarColumn<Dockable,P> column );
/**
* Gets the bounds of the {@link Component} <code>item</code>.
* @param item the item whose boundaries are required
* @return the boundaries
*/
protected abstract Rectangle getBoundaries( P item );
/**
* Installs the {@link ComponentListener} <code>listener</code> such that changes on <code>item</code> that lead to
* columns shifting position or size are recognized.
* @param item the item which gets a new listener
* @param listener the new listener
*/
protected abstract void installListener( P item, ComponentListener listener );
/**
* Removes the listener <code>listener</code> from <code>item</code>.
* @param item the item where the listener is to be removed
* @param listener the listener to remove
*/
protected abstract void uninstallListener( P item, ComponentListener listener );
/**
* Represents one column of the {@link ToolbarGroupDockStation}.
* @author Benjamin Sigg
*/
protected abstract class Column {
private ToolbarColumn<Dockable,P> column;
private boolean created = false;
private DockActionSource source;
private List<P> items = new ArrayList<P>();
private List<Dockable> dockables = new ArrayList<Dockable>();
private ToolbarColumnListener<Dockable,P> listener = new ToolbarColumnListener<Dockable,P>(){
@Override
public void removed( ToolbarColumn<Dockable,P> column, P item, Dockable dockable, int index ){
items.remove( index );
dockables.remove( index );
uninstallListener( item, componentListener );
Column.this.removed( index, item );
}
@Override
public void inserted( ToolbarColumn<Dockable,P> column, P item, Dockable dockable, int index ){
items.add( index, item );
dockables.add( index, dockable );
installListener( item, componentListener );
Column.this.inserted( index, item );
}
};
/**
* Creates a new column.
* @param column the column that is represented by this object. Subclasses can
* set this argument to <code>null</code>, but have to call {@link #init(ToolbarColumn)} later.
*/
public Column( ToolbarColumn<Dockable,P> column ){
if( column != null ){
init( column );
}
}
/**
* Initializes all fields of this object
* @param column the column that is represented by this object, not <code>null</code>
*/
protected void init( ToolbarColumn<Dockable,P> column ){
this.column = column;
this.column.addListener( listener );
for( int i = 0, n = column.getDockableCount(); i<n; i++ ){
listener.inserted( column, column.getItem( i ), column.getDockable( i ), i );
}
}
/**
* Gets the {@link DockActionSource} which is associated with this column.
* @return the source, can be <code>null</code>
*/
public DockActionSource getSource(){
if( !created ) {
created = true;
source = createSource();
}
return source;
}
/**
* Gets all the items of this column.
* @return all the items, the list is not modifiable
*/
public List<P> getItems(){
return Collections.unmodifiableList( items );
}
/**
* Gets all the items of this column.
* @return all the items, the list is not modifiable
*/
public List<Dockable> getDockables(){
return Collections.unmodifiableList( dockables );
}
/**
* Gets the column which is represented by this object
* @return the underlying data structure
*/
public ToolbarColumn<Dockable,P> getColumn(){
return column;
}
/**
* Gets the current boundaries of this column.
* @return the boundaries in respect to the {@link ToolbarGroupDockStation}, <code>null</code> if there
* are no items in this column
*/
public Rectangle getColumnBoundaries(){
Rectangle result = null;
for( P item : getItems() ){
Rectangle next = getBoundaries( item );
if( result == null ){
result = new Rectangle( next );
}
else{
result = result.union( next );
}
}
return result;
}
/**
* Creates a {@link DockActionSource} that will be stored in this {@link Column}. This method is called
* lazily, the first time when {@link #getSource()} is executed.
* @return the new source or <code>null</code>
*/
protected abstract DockActionSource createSource();
/**
* Called after an item was added to this column
* @param index the index of the new item
* @param item the item that was added
*/
protected abstract void inserted( int index, P item );
/**
* Called after an item was removed from this column
* @param index the index of the removed item
* @param item the item that was removed
*/
protected abstract void removed( int index, P item );
private void destroy(){
column.removeListener( listener );
for( P item : getItems() ){
uninstallListener( item, componentListener );
}
removed();
}
/**
* Called if this column is no longer used, all resources should be released
*/
protected abstract void removed();
}
}