/*
* 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.layout;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import bibliothek.gui.DockStation;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.station.support.ConvertedPlaceholderListItem;
import bibliothek.gui.dock.station.support.PlaceholderList;
import bibliothek.gui.dock.station.support.PlaceholderList.Filter;
import bibliothek.gui.dock.station.support.PlaceholderList.Level;
import bibliothek.gui.dock.station.support.PlaceholderListItem;
import bibliothek.gui.dock.station.support.PlaceholderListItemAdapter;
import bibliothek.gui.dock.station.support.PlaceholderMap;
import bibliothek.gui.dock.station.support.PlaceholderMap.Key;
import bibliothek.gui.dock.station.support.PlaceholderStrategy;
import bibliothek.gui.dock.station.support.PlaceholderStrategyListener;
import bibliothek.gui.dock.station.toolbar.layout.GridPlaceholderList.Column;
import bibliothek.gui.dock.station.toolbar.layout.GridPlaceholderList.ColumnItem;
import bibliothek.util.Path;
/**
* A {@link PlaceholderToolbarGrid} behaves like a list of {@link PlaceholderList}s.
*
* @author Benjamin Sigg
* @param <D> the kind of object that should be treated as {@link Dockable}
* @param <S> the kind of object that should be treated as {@link DockStation}
* @param <P> the type of item which represents a {@link Dockable}
*/
public abstract class PlaceholderToolbarGrid<D, S, P extends PlaceholderListItem<D>> {
/** defines the order of all columns (visible and invisible columns) */
private GridPlaceholderList<D, S, P> columns;
/** the currently used strategy to detect invalid placeholders */
private PlaceholderStrategy strategy;
/**
* this listener is added to {@link #strategy} if {@link #bound} is
* <code>true</code>
*/
private final PlaceholderStrategyListener strategyListener = new PlaceholderStrategyListener(){
@Override
public void placeholderInvalidated( Set<Path> placeholders ){
purge();
}
};
/** tells whether this grid is currently used */
private boolean bound = false;
/**
* Initializes all fields of this object.
*/
protected void init(){
columns = createGrid();
}
/**
* Creates one column.
*
* @return a new, empty list
*/
protected abstract PlaceholderList<D, S, P> createColumn();
/**
* Creates a new {@link GridPlaceholderList}.
*
* @return the new, empty grid
*/
protected abstract GridPlaceholderList<D, S, P> createGrid();
/**
* Gets all placeholders that are associated with <code>dockable</code>.
*
* @param dockable
* some element used by this grid
* @return the placeholders
*/
protected abstract Set<Path> getPlaceholders( D dockable );
/**
* Called if <code>item</code> was added to the column <code>column</code>.
* @param column a visible column
* @param columnIndex the index of the column
* @param item a new item of <code>column</code>
* @param itemIndex the index of the new item
*/
protected abstract void onInserted( PlaceholderList<D, S, P> column, int columnIndex, P item, int itemIndex );
/**
* Called if <code>item</code> was removed to the column <code>column</code>.
* @param column a visible column
* @param columnIndex the index of the column
* @param item the removed item of <code>column</code>
* @param itemIndex the index the item had before removing
*/
protected abstract void onRemoved( PlaceholderList<D, S, P> column, int columnIndex, P item, int itemIndex );
/**
* Called if a new column <code>column</code> was added to this grid. The column may not contain any elements when
* this method is called.
* @param column the new column
* @param index the index of the new column
*/
protected abstract void onInserted( PlaceholderList<D, S, P> column, int index );
/**
* Called if the column <code>column</code> was removed from this grid. The column may still contain elements when it
* is removed.
* @param column the removed column
* @param index the index the column had before it was removed
*/
protected abstract void onRemoved( PlaceholderList<D, S, P> column, int index );
/**
* Called if all columns were inserted at the same time. This means that the grid
* previously was completely empty.
*/
protected abstract void onInserted();
/**
* Called if all columns were removed from this grid
*/
protected abstract void onRemoved();
/**
* Removes all content from this grid.
*/
public void clear(){
purge();
for( final Column<D, S, P> column : columns.dockables() ) {
column.getList().unbind();
column.getList().setStrategy( null );
}
columns.clear();
onRemoved();
}
/**
* Adds the item <code>item</code> to the non-empty column
* <code>column</code> into position <code>line</code>. This method may add
* a new column in order to store <code>item</code>.
*
* @param column
* the column in which to store <code>item</code>
* @param line
* the index within <code>column</code>
* @param item
* the item to store, not <code>null</code>
* @throws IllegalArgumentException
* if <code>item</code> is <code>null</code>
* @throws IllegalStateException
* if there is no {@link PlaceholderStrategy} set
*/
public void insert( int column, int line, P item ){
if( item == null ) {
throw new IllegalArgumentException( "item must not be null" );
}
final PlaceholderList<D, S, P> list = getColumn( column );
if( list == null ) {
insert( column, item, true );
}
else {
int index = Math.min( line, list.dockables().size() );
list.dockables().add( index, item );
ensureRemoved( list, item );
onInserted( list, column, item, index );
}
}
/**
* Adds the item <code>item</code> to a new column, the new column will have
* the index <code>columnIndex</code>. If <code>columnIndex</code> is out of
* bounds, then the new column will be added as near as possible to the
* preferred position. This method will try to reuse an empty column, if one
* is available at the desired location.
*
* @param columnIndex
* the column to add
* @param item
* the item to store, not <code>null</code>
*/
public void insert( int columnIndex, P item ){
insert( columnIndex, item, true );
}
/**
* Adds the item <code>item</code> to a new column, the new column will have
* the index <code>columnIndex</code>. If <code>columnIndex</code> is out of
* bounds, then the new column will be added as near as possible to the
* preferred position.
*
* @param columnIndex
* the column to add
* @param item
* the item to store, not <code>null</code>
* @param reuse
* if <code>false</code> then a new column will be built in any
* case, if <code>true</code> then this grid tries to reuse an
* existing yet empty column if possible
*/
public void insert( int columnIndex, P item, boolean reuse ){
final PlaceholderList<D, S, P> columnList = createColumn();
final Column<D, S, P> column = columns.createColumn( columnList );
boolean added = false;
int addedColumnIndex = -1;
if( reuse ) {
int baseIndex;
if( columns.dockables().size() > 0 ) {
baseIndex = columns.levelToBase( Math.max( 0, Math.min( columns.dockables().size() - 1, columnIndex ) ), Level.DOCKABLE );
baseIndex--;
}
else {
baseIndex = 0;
}
if( (baseIndex >= 0) && (baseIndex < columns.list().size()) ) {
final PlaceholderList<?, ?, Column<D, S, P>>.Item columnItem = columns.list().get( baseIndex );
if( columnItem.getDockable() == null ) {
final PlaceholderMap map = columnItem.getPlaceholderMap();
if( map != null ) {
columnList.read( map, columns.getConverter() );
}
columnItem.setDockable( column );
addedColumnIndex = columns.dockables().indexOf( column );
onInserted( column.getList(), addedColumnIndex );
added = true;
}
}
}
columnList.dockables().add( item );
if( added ){
onInserted( columnList, addedColumnIndex, item, columnList.dockables().size()-1 );
}
else{
int index = Math.max( 0, Math.min( columnIndex, columns.dockables().size() ) );
columns.dockables().add( index, column );
onInserted( columnList, index );
onInserted( columnList, index, item, columnList.dockables().size()-1 );
}
if( bound ) {
columnList.setStrategy( strategy );
}
ensureRemoved( columnList, item );
}
/**
* Moves the item at <code>sourceColumn/sourceLine</code> to
* <code>destinationColumn/destinationLine</code>. The operation behaves as
* if the item would first be removed from the source position, and
* afterwards inserted at the destination position.
*
* @param sourceColumn
* the column in which to find the item, only includes non-empty
* columns
* @param sourceLine
* the line in the column in which to find the item
* @param destinationColumn
* the column in which to insert the item
* @param destinationLine
* the line at which to insert the item
* @param destinationLevel
* the level at which to find <code>destinationColumn</code>,
* will be converted to an index from {@link Level#BASE}
* @throws IllegalArgumentException
* if any index is out of bounds
*/
public void move( int sourceColumn, int sourceLine, int destinationColumn, int destinationLine, Level destinationLevel ){
PlaceholderList<D, S, P> source = columns.dockables().get( sourceColumn ).getList();
final Filter<P> sourceList = source.dockables();
int destinationColumnIndex = -1;
if( destinationColumn == columns.size( destinationLevel ) ) {
destinationColumn = columns.size( Level.BASE );
}
else if( destinationColumn >= 0 ) {
destinationColumn = columns.levelToBase( destinationColumn, destinationLevel );
}
if( (sourceLine < 0) || (sourceLine >= sourceList.size()) ) {
throw new IllegalArgumentException( "sourceLine out of bounds: " + sourceLine );
}
if( (destinationColumn < -1) || (destinationColumn > columns.list().size()) ) {
throw new IllegalArgumentException( "destinationColumn out of bounds: " + destinationColumn );
}
final P value = sourceList.get( sourceLine );
PlaceholderList<D, S, P> list;
if( (destinationColumn == -1) || (destinationColumn == columns.list().size()) ) {
list = createColumn();
if( destinationLine != 0 ) {
throw new IllegalArgumentException( "destinationLine is out of bounds: " + destinationLine );
}
final PlaceholderList<ColumnItem<D, S, P>, ColumnItem<D, S, P>, Column<D, S, P>>.Item item = columns.new Item( columns.createColumn( list ) );
if( destinationColumn == -1 ) {
columns.list().add( 0, item );
}
else {
columns.list().add( item );
}
destinationColumnIndex = columns.dockables().indexOf( item.getDockable() );
onInserted( list, destinationColumnIndex );
}
else {
final PlaceholderList<?, ?, Column<D, S, P>>.Item item = columns.list().get( destinationColumn );
if( item.getDockable() == null ) {
list = createColumn();
if( destinationLine != 0 ) {
throw new IllegalArgumentException( "destinationLine is out of bounds: " + destinationLine );
}
item.setDockable( columns.createColumn( list ) );
destinationColumnIndex = columns.dockables().indexOf( item.getDockable() );
onInserted( list, destinationColumnIndex );
}
else {
list = item.getDockable().getList();
if( (destinationLine < 0) || (destinationLine > list.dockables().size()) ) {
throw new IllegalArgumentException( "destinationLine out of bounds: " + destinationLine );
}
destinationColumnIndex = columns.baseToLevel( destinationColumn, Level.DOCKABLE );
}
}
P moved = sourceList.get( sourceLine );
list.dockables().move( sourceList, sourceLine, destinationLine );
ensureRemoved( list, value );
onRemoved( source, sourceColumn, moved, sourceLine );
onInserted( list, destinationColumnIndex, moved, destinationLine );
purge();
}
/**
* Tries to put <code>item</code> into this list at location
* <code>placeholder</code>. If there is already an element at
* <code>placeholder</code>, then the old item is silently removed and the
* new item inserted. This method may create a new non-empty column if
* necessary.
*
* @param placeholder
* the name of the item
* @param item
* the item to insert
* @return <code>true</code> if insertion was a success, <code>false</code>
* otherwise
*/
public boolean put( Path placeholder, P item ){
final int listIndex = columns.getListIndex( placeholder );
if( listIndex == -1 ) {
return false;
}
final PlaceholderList<?, ?, Column<D, S, P>>.Item listItem = columns.list().get( listIndex );
Column<D, S, P> column = listItem.getDockable();
if( column != null ) {
int columnIndex = columns.dockables().indexOf( column );
// int removedIndex = column.getList().getDockableIndex( placeholder );
int replacingListIndex = column.getList().getListIndex( placeholder );
if( replacingListIndex >= 0 ){
P removed = column.getList().list().get( replacingListIndex ).getDockable();
int size = column.getList().dockables().size();
int removedIndex = column.getList().put( placeholder, item );
if( removed != null ){
onRemoved( column.getList(), columnIndex, removed, removedIndex );
if( size == 0 && item == null ){
onRemoved( column.getList(), columnIndex );
listItem.setDockable( null );
}
}
if( item != null ){
if( size == 0 ){
onInserted( column.getList(), columnIndex );
}
onInserted( column.getList(), columnIndex, item, column.getList().dockables().indexOf( item ) );
}
return true;
}
}
final PlaceholderMap map = listItem.getPlaceholderMap();
if( map != null ) {
final PlaceholderList<D, S, P> list = createColumn();
list.read( map, columns.getConverter() );
column = columns.createColumn( list );
listItem.setDockable( column );
int columnIndex = columns.dockables().indexOf( column );
onInserted( list, columnIndex );
int insertIndex = column.getList().put( placeholder, item );
if( insertIndex == -1 ) {
listItem.setDockable( null );
onRemoved( list, columnIndex );
return false;
}
else {
listItem.setPlaceholderMap( null );
ensureRemoved( list, placeholder );
onInserted( list, columnIndex, item, insertIndex );
}
return true;
}
return false;
}
/**
* Stores the placeholder <code>placeholder</code> in the designated column.
*
* @param column
* the column in which to add <code>placeholder</code>, only
* includes non-empty columns
* @param line
* the line in which to add <code>placeholder</code>
* @param placeholder
* the placeholder to store
*/
public void addPlaceholder( int column, int line, Path placeholder ){
columns.dockables().addPlaceholder( column, placeholder );
final Column<D, S, P> item = columns.dockables().get( column );
item.getList().dockables().addPlaceholder( line, placeholder );
ensureRemoved( item.getList(), placeholder );
}
/**
* Inserts <code>placeholder</code> into column <code>column</code> at <code>line</code>. This
* method may create a new column if <code>column</code> is as big as the grid.
* @param column the column into which to insert <code>placeholder</code>, includes
* empty columns.
* @param line the line into which to insert <code>placeholder</code>
* @param placeholder the new placeholder, not <code>null</code>
*/
public void insertPlaceholder( int column, int line, Path placeholder ){
if( column == columns.list().size() ){
columns.list().insertPlaceholder( column, placeholder );
}
else{
columns.list().addPlaceholder( column, placeholder );
}
PlaceholderList<ColumnItem<D, S, P>, ColumnItem<D, S, P>, Column<D, S, P>>.Item item = columns.list().get( column );
if( item.getDockable() == null ){
item.setDockable( columns.createColumn( createColumn() ));
}
Filter<PlaceholderList<D,S,P>.Item> lineList = item.getDockable().getList().list();
if( line == lineList.size() ){
lineList.insertPlaceholder( line, placeholder );
}
else{
lineList.addPlaceholder( line, placeholder );
}
}
/**
* Removes <code>item</code> from this grid, but leaves a placeholder for
* the item.
* @param item the item to remove
* @return <code>true</code> if <code>item</code> was found and removed
*/
public boolean remove( P item ){
boolean result = false;
int columnIndex = -1;
for( final Column<D, S, P> column : columns.dockables() ) {
columnIndex++;
int index = column.getList().dockables().indexOf( item );
if( index >= 0 ){
column.getList().remove( item );
onRemoved( column.getList(), columnIndex, item, index );
result = true;
}
}
purge();
return result;
}
/**
* Removes all occurences of <code>placeholder</code>.
* @param placeholder the placeholder to remove
*/
public void removePlaceholder( Path placeholder ){
Set<Path> set = new HashSet<Path>();
set.add( placeholder );
ensureRemoved( null, set );
}
private void ensureRemoved( PlaceholderList<D, S, P> ignore, P item ){
final Set<Path> placeholders = getPlaceholders( item.asDockable() );
ensureRemoved( ignore, placeholders );
}
private void ensureRemoved( PlaceholderList<D, S, P> ignore, Path placeholder ){
final Set<Path> set = new HashSet<Path>();
set.add( placeholder );
ensureRemoved( ignore, set );
}
private void ensureRemoved( PlaceholderList<D, S, P> ignore, Set<Path> placeholders ){
final Iterator<PlaceholderList<ColumnItem<D, S, P>, ColumnItem<D, S, P>, Column<D, S, P>>.Item> iter = columns.list().iterator();
while( iter.hasNext() ) {
final PlaceholderList<?, ?, Column<D, S, P>>.Item item = iter.next();
if( (item.getDockable() == null) || (item.getDockable().getList() != ignore) ) {
item.removeAll( placeholders );
if( (item.getPlaceholderSet() == null) && item.isPlaceholder() ) {
iter.remove();
}
}
}
for( final Column<D, S, P> column : columns.dockables() ) {
if( column.getList() != ignore ) {
column.getList().removeAll( placeholders );
}
}
purge();
}
/**
* Tells in which non-empty column <code>dockable</code> is.
*
* @param dockable
* the item to search
* @return the column of the dockable or <code>-1</code> if not found
*/
public int getColumn( D dockable ){
int index = 0;
final Iterator<PlaceholderList<D, S, P>> columns = columns();
while( columns.hasNext() ) {
for( final P item : columns.next().dockables() ) {
if( item.asDockable() == dockable ) {
return index;
}
}
index++;
}
return -1;
}
/**
* Gets the index of the first column that contains <code>placeholder</code>
* .
*
* @param placeholder
* the placeholder to search
* @return the first column with <code>placeholder</code> or -1 if not
* found, this includes empty columns
*/
public int getColumn( Path placeholder ){
int index = 0;
for( final PlaceholderList<?, ?, Column<D, S, P>>.Item item : columns.list() ) {
if( item.hasPlaceholder( placeholder ) ) {
return index;
}
index++;
}
return -1;
}
/**
* Tells at which position <code>dockable</code> is within its column.
*
* @param dockable
* the item to search
* @return the location of <code>dockable</code>
*/
public int getLine( D dockable ){
final int column = getColumn( dockable );
if( column == -1 ) {
return -1;
}
return getLine( column, dockable );
}
/**
* Tells at which position <code>dockable</code> is within the column
* <code>column</code>
*
* @param column
* the index of the non-empty column to search
* @param dockable
* the item to search
* @return the location of <code>dockable</code>
*/
public int getLine( int column, D dockable ){
final PlaceholderList<D, S, P> list = getColumn( column );
int index = 0;
for( final P item : list.dockables() ) {
if( item.asDockable() == dockable ) {
return index;
}
index++;
}
return -1;
}
/**
* Tells at which line <code>placeholder</code> appears in the first column
* that contains <code>placeholder</code>. This includes empty columns.
*
* @param placeholder
* the placeholder to search
* @return the line at which <code>placeholder</code> was found
*/
public int getLine( Path placeholder ){
final int column = getColumn( placeholder );
if( column == -1 ) {
return -1;
}
return getLine( column, placeholder );
}
/**
* Tells at which line <code>placeholder</code> appears in the column
* <code>column</code>.
*
* @param column
* the index of the column, this includes empty columns
* @param placeholder
* the placeholder to search
* @return the index the item would have or -1 if <code>placeholder</code>
* was not found
*/
public int getLine( int column, Path placeholder ){
final PlaceholderList<?, ?, Column<D, S, P>>.Item item = columns.list().get( column );
if( item.getDockable() == null ) {
if( item.hasPlaceholder( placeholder ) ) {
return 0;
}
else {
return -1;
}
}
else {
return item.getDockable().getList().list().indexOfPlaceholder( placeholder );
}
}
/**
* Tells whether this {@link PlaceholderToolbarGrid} knows a column which
* contains the placeholder <code>placeholder</code>, this includes empty
* columns.
*
* @param placeholder
* the placeholder to search
* @return <code>true</code> if <code>placeholder</code> was found
*/
public boolean hasPlaceholder( Path placeholder ){
final int listIndex = columns.getListIndex( placeholder );
if( listIndex == -1 ) {
return false;
}
final PlaceholderList<?, ?, Column<D, S, P>>.Item item = columns.list().get( listIndex );
final Column<D, S, P> column = item.getDockable();
if( column != null ) {
return column.getList().hasPlaceholder( placeholder );
}
final PlaceholderMap map = item.getPlaceholderMap();
if( map != null ) {
for( final Key key : map.getPlaceholders() ) {
if( key.contains( placeholder ) ) {
return true;
}
}
}
return false;
}
/**
* Gets the total count of items stored in this grid.
*
* @return the total amount of items
*/
public int size(){
int sum = 0;
final Iterator<PlaceholderList<D, S, P>> iter = columns();
while( iter.hasNext() ) {
sum += iter.next().dockables().size();
}
return sum;
}
/**
* Gets the <code>index</code>'th item of this grid.
*
* @param index
* the index of the item
* @return the item
* @throws IllegalArgumentException
* if <code>index</code> is not valid
*/
public P get( int index ){
if( index < 0 ) {
throw new IllegalArgumentException( "index must not be < 0" );
}
final Iterator<PlaceholderList<D, S, P>> iter = columns();
while( iter.hasNext() ) {
final Filter<P> dockables = iter.next().dockables();
final int size = dockables.size();
if( index < size ) {
return dockables.get( index );
}
else {
index -= size;
}
}
throw new IllegalArgumentException( "index must not be >= size" );
}
/**
* Gets the item that represents <code>dockable</code>
*
* @param dockable
* the dockable to search
* @return the item that represents <code>dockable</code> or
* <code>null</code> if not found
*/
public P get( D dockable ){
final Iterator<P> iter = items();
while( iter.hasNext() ) {
final P next = iter.next();
if( next.asDockable() == dockable ) {
return next;
}
}
return null;
}
/**
* Searches the item which is at the location of <code>placeholder</code>.
*
* @param placeholder
* some placeholder that may or may not be known to this grid
* @return the item at <code>placeholder</code> or <code>null</code> either
* if <code>placeholder</code> was not found or if there is no item
* stored
*/
public P get( Path placeholder ){
final int listIndex = columns.getListIndex( placeholder );
if( listIndex == -1 ) {
return null;
}
final PlaceholderList<?, ?, Column<D, S, P>>.Item item = columns.list().get( listIndex );
final Column<D, S, P> column = item.getDockable();
if( column == null ) {
return null;
}
return column.getList().getDockableAt( placeholder );
}
/**
* Gets an iterator over all columns, including the columns with no content.
* This does not include columns with no list (columns that consist only of
* placeholders).
*
* @return all columns
*/
protected Iterator<PlaceholderList<D, S, P>> allColumns(){
return new Iterator<PlaceholderList<D, S, P>>(){
private final Iterator<GridPlaceholderList.Column<D, S, P>> items = columns.dockables().iterator();
private PlaceholderList<D, S, P> current;
private int currentIndex = -1;
@Override
public boolean hasNext(){
return items.hasNext();
};
@Override
public PlaceholderList<D, S, P> next(){
current = items.next().getList();
currentIndex++;
return current;
}
@Override
public void remove(){
items.remove();
onRemoved( current, currentIndex-- );
}
};
}
/**
* Gets an iterator over all non-empty columns. The iterator does not
* support modifications nor is it concurrent.
*
* @return the iterator
*/
protected Iterator<PlaceholderList<D, S, P>> columns(){
return new Iterator<PlaceholderList<D, S, P>>(){
private final Iterator<GridPlaceholderList.Column<D, S, P>> items = columns.dockables().iterator();
private PlaceholderList<D, S, P> next;
private void forward(){
next = null;
while( (next == null) && items.hasNext() ) {
final PlaceholderList<D, S, P> column = items.next().getList();
if( column.dockables().size() > 0 ) {
next = column;
}
}
}
@Override
public boolean hasNext(){
if( (next == null) && items.hasNext() ) {
forward();
}
return next != null;
}
@Override
public PlaceholderList<D, S, P> next(){
final PlaceholderList<D, S, P> result = next;
forward();
return result;
}
@Override
public void remove(){
throw new UnsupportedOperationException();
}
};
}
/**
* Gets an iterator that visits all items of this grid.
*
* @return the iterator
*/
public Iterator<P> items(){
return new Iterator<P>(){
private final Iterator<PlaceholderList<D, S, P>> columns = columns();
private Iterator<P> items = null;
private PlaceholderList<D, S, P> currentList;
private int currentListIndex = -1;
private P currentItem;
private int currentItemIndex = -1;
private boolean requiresdPurge = false;
private void validate(){
while( ((items == null) || !items.hasNext()) && columns.hasNext() ) {
currentList = columns.next();
currentItemIndex = -1;
currentListIndex++;
items = currentList.dockables().iterator();
}
}
@Override
public boolean hasNext(){
validate();
boolean result = (items != null) && items.hasNext();
if( !result ){
if( requiresdPurge ){
purge();
requiresdPurge = false;
}
}
return result;
}
@Override
public P next(){
validate();
currentItem = items.next();
return currentItem;
}
@Override
public void remove(){
if( items == null ) {
throw new IllegalStateException( "no item selected" );
}
items.remove();
onRemoved( currentList, currentListIndex, currentItem, currentItemIndex-- );
requiresdPurge = true;
}
};
}
/**
* Gets the number of columns that are currently stored in this grid. Empty
* columns are excluded.
*
* @return the total number of non-empty columns
*/
public int getColumnCount(){
return columns.dockables().size();
}
/**
* Tells how many items are currently stored at the non-empty column with
* index <code>column</code>.
*
* @param column
* the index of a non-empty column
* @return the size of the column
*/
public int getLineCount( int column ){
return columns.dockables().get( column ).getList().dockables().size();
}
/**
* Gets the total number of columns, this includes empty columns.
*
* @return the total number of columns
*/
public int getTotalColumnCount(){
return columns.list().size();
}
/**
* Gets an iterator over the contents of the <code>index</code>'th non-empty
* column.
*
* @param index
* the index of the non-empty column
* @return the content of the non-empty column
*/
public Iterator<P> getColumnContent( final int index ){
final PlaceholderList<D, S, P> list = getColumn( index );
if( list == null ) {
throw new IllegalArgumentException( "index is out of bounds" );
}
return new Iterator<P>(){
private Iterator<P> delegate = list.dockables().iterator();
private P current;
private int currentIndex = -1;
private boolean requiresPurge = false;
@Override
public boolean hasNext(){
boolean result = delegate.hasNext();
if( !result ){
if( requiresPurge ){
purge();
requiresPurge = false;
}
}
return result;
}
@Override
public P next(){
current = delegate.next();
currentIndex++;
return current;
}
@Override
public void remove(){
delegate.remove();
onRemoved( list, index, current, currentIndex-- );
}
};
}
/**
* Gets the non-empty column with index <code>index</code>. Subclasses should not modify the returned list.
*
* @param index
* the index of the column
* @return the non-empty column or <code>null</code> if no such column
* exists
*/
protected PlaceholderList<D, S, P> getColumn( int index ){
if( index < 0 ) {
return null;
}
final Filter<Column<D, S, P>> dockables = columns.dockables();
if( index >= dockables.size() ) {
return null;
}
return dockables.get( index ).getList();
}
/**
* Informs this grid that it is actually used and that it should be allowed
* to add observers to various resources.
*/
public void bind(){
columns.bind();
final Iterator<PlaceholderList<D, S, P>> columns = allColumns();
while( columns.hasNext() ) {
columns.next().bind();
}
if( !bound ) {
bound = true;
if( strategy != null ) {
strategy.addListener( strategyListener );
purge();
}
}
}
/**
* Informs this grid that it is no longer used and that is should remove any
* observers.
*/
public void unbind(){
columns.unbind();
final Iterator<PlaceholderList<D, S, P>> columns = allColumns();
while( columns.hasNext() ) {
columns.next().unbind();
}
if( bound ) {
bound = false;
if( strategy != null ) {
strategy.removeListener( strategyListener );
}
}
}
/**
* Sets the {@link PlaceholderStrategy} which is to be used by this grid.
*
* @param strategy
* the new strategy, can be <code>null</code>
*/
public void setStrategy( PlaceholderStrategy strategy ){
if( (this.strategy != null) && bound ) {
this.strategy.removeListener( strategyListener );
}
this.strategy = strategy;
columns.setStrategy( strategy );
final Iterator<PlaceholderList<D, S, P>> columns = allColumns();
while( columns.hasNext() ) {
columns.next().setStrategy( strategy );
}
if( (this.strategy != null) && bound ) {
this.strategy.addListener( strategyListener );
purge();
}
}
/**
* Gets the {@link PlaceholderStrategy} that is currently used by this grid.
*
* @return the strategy, can be <code>null</code>
*/
public PlaceholderStrategy getStrategy(){
return strategy;
}
/**
* Removes any dead element from {@link #columns}.
*/
private void purge(){
purge( false );
}
/**
* Removes any dead element from {@link #columns}.
* @param silent if <code>true</code> then no events are fired when a column is removed
*/
private void purge( boolean silent ){
int index = -1;
for( final PlaceholderList<ColumnItem<D, S, P>, ColumnItem<D, S, P>, Column<D, S, P>>.Item item : columns.list() ) {
final Column<D, S, P> column = item.getDockable();
if( column != null ) {
index++;
PlaceholderList<D, S, P> list = column.getList();
if( list.dockables().size() == 0 ) {
item.setPlaceholderMap( list.toMap( new PlaceholderListItemAdapter<D, PlaceholderListItem<D>>(){
@Override
public ConvertedPlaceholderListItem convert( int index, PlaceholderListItem<D> dockable ){
throw new IllegalStateException( "the list is supposed to have no children, so this conversion method must never be called" );
}
} ) );
item.setDockable( null );
if( !silent ){
onRemoved( list, index-- );
}
}
}
}
}
/**
* Called by {@link #toMap(Map)}, this method should read persistent data
* from <code>dockable</code> and write that data into <code>item</code>.
*
* @param dockable
* the dockable to read
* @param item
* the item to write into
*/
protected abstract void fill( D dockable, ConvertedPlaceholderListItem item );
/**
* Converts this grid into a {@link PlaceholderMap} using
* <code>identifiers</code> to remember which {@link Dockable} was a which
* position.
*
* @param identifiers
* identifiers for all children of the {@link DockStation} using
* this grid
* @return the map that persistently stores all data of this grid
*/
public PlaceholderMap toMap( final Map<D, Integer> identifiers ){
columns.setConverter( new PlaceholderListItemAdapter<D, P>(){
@Override
public ConvertedPlaceholderListItem convert( int index, P dockable ){
final Integer id = identifiers.get( dockable.asDockable() );
if( id == null ) {
return null;
}
final ConvertedPlaceholderListItem item = new ConvertedPlaceholderListItem();
item.putInt( "index", index );
item.putInt( "id", id.intValue() );
fill( dockable.asDockable(), item );
return item;
}
} );
try {
return columns.toMap( new PlaceholderListItemAdapter<ColumnItem<D, S, P>, Column<D, S, P>>(){
@Override
public ConvertedPlaceholderListItem convert( int index, Column<D, S, P> dockable ){
final ConvertedPlaceholderListItem item = new ConvertedPlaceholderListItem();
item.putInt( "index", index );
item.setPlaceholderMap( dockable.getPlaceholders() );
return item;
}
} );
}
finally {
columns.setConverter( null );
}
}
public void fromMap( PlaceholderMap map, final Map<Integer, D> identifiers, final PlaceholderToolbarGridConverter<D, P> converter ){
clear();
columns.setConverter( new PlaceholderListItemAdapter<D, P>(){
@Override
public P convert( ConvertedPlaceholderListItem item ){
final Integer id = item.getInt( "id" );
final D dockable = identifiers.get( id );
if( dockable == null ) {
return null;
}
return converter.convert( dockable, item );
}
} );
try {
columns.read( map, new PlaceholderListItemAdapter<GridPlaceholderList.ColumnItem<D, S, P>, GridPlaceholderList.Column<D, S, P>>(){
@Override
public GridPlaceholderList.Column<D, S, P> convert( ConvertedPlaceholderListItem item ){
final PlaceholderList<D, S, P> list = createColumn();
final PlaceholderMap map = item.getPlaceholderMap();
if( map == null ) {
return null;
}
list.read( map, columns.getConverter() );
return columns.createColumn( list );
}
@Override
public void added( GridPlaceholderList.Column<D, S, P> dockable ){
for( final P item : dockable.getList().dockables() ) {
converter.added( item );
}
};
} );
purge(true);
}
finally {
columns.setConverter( null );
onInserted();
}
}
/**
* Converts this grid into a {@link PlaceholderMap}, if possible any
* {@link Dockable} is converted into a placeholder.
*
* @return the converted map
*/
public PlaceholderMap toMap(){
columns.setConverter( new PlaceholderListItemAdapter<D, P>(){
@Override
public ConvertedPlaceholderListItem convert( int index, P dockable ){
final ConvertedPlaceholderListItem item = new ConvertedPlaceholderListItem();
fill( dockable.asDockable(), item );
if( item.getPlaceholder() == null && item.getPlaceholderMap() == null ) {
return null;
}
return item;
}
} );
try {
return columns.toMap( new PlaceholderListItemAdapter<ColumnItem<D, S, P>, Column<D, S, P>>(){
@Override
public ConvertedPlaceholderListItem convert( int index, Column<D, S, P> dockable ){
final ConvertedPlaceholderListItem item = new ConvertedPlaceholderListItem();
item.putInt( "index", index );
item.setPlaceholderMap( dockable.getPlaceholders() );
return item;
}
} );
}
finally {
columns.setConverter( null );
}
}
/**
* Replaces the content of this grid by a map that was written earlier using
* {@link #toMap()} or {@link #toMap(Map)}.
*
* @param map
* the map to read, not <code>null</code>
*/
public void fromMap( PlaceholderMap map ){
clear();
columns.read( map, new PlaceholderListItemAdapter<ColumnItem<D, S, P>, Column<D, S, P>>(){
@Override
public Column<D, S, P> convert( ConvertedPlaceholderListItem item ){
final PlaceholderMap map = item.getPlaceholderMap();
if( map == null ) {
return null;
}
final PlaceholderList<D, S, P> content = createColumn();
content.read( map, columns.getConverter() );
return columns.createColumn( content );
}
} );
purge(true);
}
}