/*
* 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.focus;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.Icon;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import bibliothek.gui.DockController;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.control.DockRegister;
import bibliothek.gui.dock.event.DockRegisterAdapter;
import bibliothek.gui.dock.event.DockRegisterListener;
import bibliothek.gui.dock.event.DockableAdapter;
import bibliothek.gui.dock.event.DockableListener;
/**
* This {@link DockableSelection} is also a {@link JPanel}. It implements the
* methods needed to interact with {@link DockableSelectionListener}. It uses
* the {@link #getInputMap() input map} to register actions for when an arrow
* key or a controlling key is pressed. This selection also observes the set
* of available {@link Dockable}s and their title-text/icon. Subclasses get
* automatically informed about changes.
* @author Benjamin Sigg
*/
public abstract class AbstractDockableSelection extends JPanel implements DockableSelection{
private List<DockableSelectionListener> listeners = new ArrayList<DockableSelectionListener>();
/**
* Action called when the up arrow key was pressed.
*/
protected final Action UP = new AbstractAction(){
public void actionPerformed( ActionEvent e ) {
up();
}
};
/**
* Action called when the down arrow key was pressed.
*/
protected final Action DOWN = new AbstractAction(){
public void actionPerformed( ActionEvent e ) {
down();
}
};
/**
* Action called when the left arrow key was pressed.
*/
protected final Action LEFT = new AbstractAction(){
public void actionPerformed( ActionEvent e ) {
left();
}
};
/**
* Action called when the right arrow key was pressed.
*/
protected final Action RIGHT = new AbstractAction(){
public void actionPerformed( ActionEvent e ) {
right();
}
};
/**
* Action called when the escape or return key was pressed.
*/
protected final Action CANCEL = new AbstractAction(){
public void actionPerformed( ActionEvent e ) {
cancel();
}
};
/**
* Action called when the space or enter key was pressed.
*/
protected final Action SELECT = new AbstractAction(){
public void actionPerformed( ActionEvent e ) {
select();
}
};
/** the controller this selection currently works for */
private DockController controller;
/** the current selection */
private Dockable selection;
/** the current list of selectable dockables */
private List<Dockable> dockables = new LinkedList<Dockable>();
/** a listener informing this selection when icon or title of a {@link Dockable} changes */
private DockableListener dockableListener = new DockableAdapter(){
@Override
public void titleTextChanged( Dockable dockable, String oldTitle, String newTitle ) {
int index = dockables.indexOf( dockable );
titleChanged( index, dockable );
}
@Override
public void titleIconChanged( Dockable dockable, Icon oldIcon, Icon newIcon ) {
int index = dockables.indexOf( dockable );
iconChanged( index, dockable );
}
};
/** a listener to {@link #controller} informing when dockables are added or removed */
private DockRegisterListener registerListener = new DockRegisterAdapter(){
@Override
public void dockableRegistered( DockController controller, Dockable dockable ) {
if( selectable( dockable )){
add( dockable );
}
}
@Override
public void dockableUnregistered( DockController controller, Dockable dockable ) {
remove( dockable );
}
};
/**
* Creates a new selection
*/
public AbstractDockableSelection(){
setFocusable( true );
InputMap input = getInputMap( JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT );
input.put( KeyStroke.getKeyStroke( KeyEvent.VK_UP, 0 ), "up" );
input.put( KeyStroke.getKeyStroke( KeyEvent.VK_DOWN, 0 ), "down" );
input.put( KeyStroke.getKeyStroke( KeyEvent.VK_LEFT, 0 ), "left" );
input.put( KeyStroke.getKeyStroke( KeyEvent.VK_RIGHT, 0 ), "right" );
input.put( KeyStroke.getKeyStroke( KeyEvent.VK_KP_UP, 0 ), "up" );
input.put( KeyStroke.getKeyStroke( KeyEvent.VK_KP_DOWN, 0 ), "down" );
input.put( KeyStroke.getKeyStroke( KeyEvent.VK_KP_LEFT, 0 ), "left" );
input.put( KeyStroke.getKeyStroke( KeyEvent.VK_KP_RIGHT, 0 ), "right" );
input.put( KeyStroke.getKeyStroke( KeyEvent.VK_ESCAPE, 0 ), "cancel" );
input.put( KeyStroke.getKeyStroke( KeyEvent.VK_BACK_SPACE, 0 ), "cancel" );
input.put( KeyStroke.getKeyStroke( KeyEvent.VK_ENTER, 0 ), "select" );
input.put( KeyStroke.getKeyStroke( KeyEvent.VK_SPACE, 0 ), "select" );
ActionMap action = getActionMap();
action.put( "up", UP );
action.put( "down", DOWN );
action.put( "left", LEFT );
action.put( "right", RIGHT );
action.put( "cancel", CANCEL );
action.put( "select", SELECT );
}
public void addDockableSelectionListener( DockableSelectionListener listener ) {
listeners.add( listener );
}
public void removeDockableSelectionListener( DockableSelectionListener listener ) {
listeners.remove( listener );
}
public boolean hasChoices( DockController controller ) {
DockRegister register = controller.getRegister();
int count = 0;
for( int i = 0, n = register.getDockableCount(); i<n; i++ ){
if( selectable( register.getDockable( i ) )){
count++;
if( count >= 2 )
return true;
}
}
return false;
}
public Component getComponent() {
return this;
}
public void open( DockController controller ) {
if( this.controller != null )
throw new IllegalStateException( "selection already open" );
DockRegister register = controller.getRegister();
for( int i = 0, n = register.getDockableCount(); i<n; i++ ){
Dockable dockable = register.getDockable( i );
if( selectable( dockable )){
add( dockable );
}
}
this.controller = controller;
register.addDockRegisterListener( registerListener );
Dockable focus = controller.getFocusedDockable();
if( focus != null && !selectable( focus ))
focus = null;
select( focus );
}
private void add( Dockable dockable ){
dockables.add( dockable );
dockable.addDockableListener( dockableListener );
insert( dockables.size()-1, dockable );
}
public void close() {
if( controller != null ){
controller.getRegister().removeDockRegisterListener( registerListener );
controller = null;
}
for( int i = dockables.size()-1; i >= 0; i-- ){
Dockable dockable = dockables.remove( i );
dockable.removeDockableListener( dockableListener );
remove( i, dockable );
}
selection = null;
}
private void remove( Dockable dockable ){
int index = dockables.indexOf( dockable );
if( index >= 0 ){
dockables.remove( index );
dockable.removeDockableListener( dockableListener );
remove( index, dockable );
}
}
/**
* Gets the currently used controller.
* @return the controller for which this selection shows content
*/
public DockController getController() {
return controller;
}
/**
* Decides whether <code>dockable</code> fits the requirements for
* an element that can be selected.
* @param dockable the element to check
* @return <code>true</code> if <code>dockable</code> should be shown
* on this selection
*/
protected boolean selectable( Dockable dockable ){
return dockable.asDockStation() == null && dockable.getDockParent() != null;
}
/**
* Called when an up arrow key was pressed.
*/
protected abstract void up();
/**
* Called when a down arrow key was pressed.
*/
protected abstract void down();
/**
* Called when a left arrow key was pressed.
*/
protected abstract void left();
/**
* Called when a right arrow key was pressed.
*/
protected abstract void right();
/**
* Called when a new dockable can be selected.
* @param index the location of the dockable in the list of all known dockables.
* @param dockable the new element
*/
protected abstract void insert( int index, Dockable dockable );
/**
* Called when a dockable is no longer selectable.
* @param index the index of the removed element
* @param dockable the removed element
*/
protected abstract void remove( int index, Dockable dockable );
/**
* Called when the title text of <code>dockable</code> changed.
* @param index the location of <code>dockable</code>
* @param dockable the element whose title changed
*/
protected abstract void titleChanged( int index, Dockable dockable );
/**
* Called when the icon of <code>dockable</code> changed.
* @param index the location of <code>dockable</code>
* @param dockable the element whose icon changed
*/
protected abstract void iconChanged( int index, Dockable dockable );
/**
* Called when this selection is forced to select a specific dockable.
* @param dockable the element to select, can be <code>null</code>
*/
protected abstract void select( Dockable dockable );
/**
* Informs this selection which dockable is currently selected.
* @param dockable the currently selected dockable
*/
protected void setSelection( Dockable dockable ){
selection = dockable;
for( DockableSelectionListener listener : listeners.toArray( new DockableSelectionListener[ listeners.size() ] ))
listener.considering( dockable );
}
/**
* Cancels this selection
*/
protected void cancel(){
for( DockableSelectionListener listener : listeners.toArray( new DockableSelectionListener[ listeners.size() ] ))
listener.canceled();
}
/**
* Selects the currently selected dockable.
* @see #setSelection(Dockable)
*/
protected void select(){
if( selection != null ){
Dockable dockable = this.selection;
for( DockableSelectionListener listener : listeners.toArray( new DockableSelectionListener[ listeners.size() ] ))
listener.selected( dockable );
}
}
}