My example got a bit bigger than anticipated :twisted: On the other hand this “example” is an application that you can copy and test for yourself.
You do not necessarily need to understand what is going on to use this code in your application. Just copy all the inner interfaces/classes into new files, add the line “control.getController().addActionOffer( new VisibilityActionOffer() );” to your application, and you have a visibility property.
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.action.AbstractDockActionSource;
import bibliothek.gui.dock.action.ActionOffer;
import bibliothek.gui.dock.action.DefaultActionOffer;
import bibliothek.gui.dock.action.DockAction;
import bibliothek.gui.dock.action.DockActionSource;
import bibliothek.gui.dock.action.LocationHint;
import bibliothek.gui.dock.common.CControl;
import bibliothek.gui.dock.common.DefaultSingleCDockable;
import bibliothek.gui.dock.common.action.CAction;
import bibliothek.gui.dock.common.action.CButton;
import bibliothek.gui.dock.common.action.core.CommonDockAction;
import bibliothek.gui.dock.event.DockActionSourceListener;
/**
* This example application is going to show, how we can customize the framework in order to add a "visibility" property
* to CActions. While this application has only one top-level class, all the inner classes are static and could easily
* be moved to their own files. People interested in using this example are highly encouraged to extract all the inner
* classes and interfaces!
* <br><br>
* The example consists of:
* <ul>
* <li>{@link TestDockable}: A dockable that offers some buttons to add and remove actions. </li>
* <li>{@link CVisibleAction}: An interface any CAction with the visibility property has to implement. Any action
* without this interface is always visible.</li>
* <li>{@link ActionVisibilityListener}: A listener that is added to a {@link CVisibleAction}, it is informed whenever
* the visibility state changes. </li>
* <li>{@link CustomButton}: A {@link CButton} that implements {@link CVisibleAction}, we add and remove some of these
* buttons to show that the code works.</li>
* <li>{@link ActionVisibility}: Not really necessary, but this class helps making the implementation of {@link CustomButton} easier.</li>
* <li>{@link VisibilityFilteredActionSource}: This is the hard part. This {@link DockActionSource} contains all the actions that belong
* to {@link TestDockable}, and it has a filter searching for the visibility property. Even more, it adds an {@link ActionVisibilityListener} to
* all actions, and thus any change of the visibility property has an immediate effect. </li>
* <li>{@link VisibilityActionOffer}: This class is needed to override the default behavior of how actions for a
* dockable are collected. In our case the default behavior needs just a little update: we need to introduce
* our {@link VisibilityFilteredActionSource} as wrapper around the original list of actions.</li>
* </ul>
*
* @author Beni
*
*/
public class Dock02 {
public static void main( String[] args ){
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
frame.setBounds( 20, 20, 400, 400 );
CControl control = new CControl( frame );
// This is an important line, here we install our custom ActionOffer
control.getController().addActionOffer( new VisibilityActionOffer() );
frame.add( control.getContentArea() );
TestDockable dockable = new TestDockable();
control.addDockable( dockable );
dockable.setVisible( true );
frame.setVisible( true );
}
/**
* This dockable offers controls for adding, removing and modifying {@link CVisibleAction}s.
*/
private static class TestDockable extends DefaultSingleCDockable{
private ActionModel model = new ActionModel();
private JTable table;
public TestDockable(){
super( "test", "Test" );
table = new JTable( model );
JButton addVisible = new JButton("Add visible");
JButton addInvisible = new JButton("Add invisible");
JButton remove = new JButton("Remove selection");
Insets insets = new Insets( 0, 0, 0, 0 );
setLayout( new GridBagLayout() );
add( new JScrollPane( table ), new GridBagConstraints( 0, 0, 1, 1, 100.0, 100.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, insets, 0, 0 ));
JPanel buttons = new JPanel( new GridLayout( 1, 3 ));
add( buttons, new GridBagConstraints( 0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.LAST_LINE_END, GridBagConstraints.VERTICAL, insets, 0, 0 ));
buttons.add( addVisible );
buttons.add( addInvisible );
buttons.add( remove );
addVisible.addActionListener( new ActionListener(){
@Override
public void actionPerformed( ActionEvent e ){
int index = table.getSelectedRow()+1;
model.insert( index, true );
table.getSelectionModel().setSelectionInterval( index, index );
}
});
addInvisible.addActionListener( new ActionListener(){
@Override
public void actionPerformed( ActionEvent e ){
int index = table.getSelectedRow()+1;
model.insert( index, false );
table.getSelectionModel().setSelectionInterval( index, index );
}
});
remove.addActionListener( new ActionListener(){
@Override
public void actionPerformed( ActionEvent e ){
int index = table.getSelectedRow();
if( index != -1 ){
model.remove( index );
}
}
});
}
/**
* This {@link Icon} is added to the {@link CustomButton}s.
*/
private class ActionIcon implements Icon{
private Color color;
public ActionIcon( Color color ){
this.color = color;
}
@Override
public void paintIcon( Component c, Graphics g, int x, int y ){
g.setColor( color );
g.fillOval( x, y, 16, 16 );
}
@Override
public int getIconWidth(){
return 16;
}
@Override
public int getIconHeight(){
return 16;
}
}
/**
* This {@link TableModel} shows all the {@link CustomButton}s that we added to this
* {@link TestDockable}, it also offers methods to add and remove buttons.
*/
private class ActionModel extends AbstractTableModel{
private List<CustomButton> buttons = new ArrayList<CustomButton>();
public void insert( int index, boolean visible ){
Color color = new Color( (int)(0xFFFFFF * Math.random()));
CustomButton button = new CustomButton();
button.setIcon( new ActionIcon( color ) );
button.setText( color.getRed() + " " + color.getGreen() + " " + color.getBlue() );
button.setVisible( visible );
insertAction( index, button );
buttons.add( index, button );
fireTableRowsInserted( index, index );
}
public void remove( int index ){
removeAction( index );
buttons.remove( index );
fireTableRowsDeleted( index, index );
}
@Override
public int getRowCount(){
return buttons.size();
}
@Override
public int getColumnCount(){
return 3;
}
@Override
public String getColumnName( int column ){
switch( column ){
case 0: return "";
case 1: return "";
case 2: return "Name";
default: throw new IllegalArgumentException();
}
}
@Override
public Class<?> getColumnClass( int column ){
switch( column ){
case 0: return Boolean.class;
case 1: return Icon.class;
case 2: return String.class;
default: throw new IllegalArgumentException();
}
}
@Override
public boolean isCellEditable( int rowIndex, int columnIndex ){
return columnIndex == 0;
}
@Override
public void setValueAt( Object aValue, int rowIndex, int columnIndex ){
CustomButton button = buttons.get( rowIndex );
button.setVisible( (Boolean)aValue );
fireTableCellUpdated( rowIndex, columnIndex );
}
@Override
public Object getValueAt( int rowIndex, int columnIndex ){
CustomButton button = buttons.get( rowIndex );
switch( columnIndex ){
case 0: return button.isVisible();
case 1: return button.getIcon();
case 2: return button.getText();
default: throw new IllegalArgumentException();
}
}
}
}
/**
* This {@link ActionOffer} wrapps each {@link DockActionSource} in a {@link VisibilityFilteredActionSource} and
* thus enables support for the visibility property of {@link CVisibleAction}.
*/
public static class VisibilityActionOffer extends DefaultActionOffer{
@Override
public DockActionSource getSource( Dockable dockable, DockActionSource source, DockActionSource[] guards, DockActionSource parent, DockActionSource[] parents ){
DockActionSource result = super.getSource( dockable, source, guards, parent, parents );
return new VisibilityFilteredActionSource( result );
}
}
/**
* This {@link DockActionSource} shows only those {@link CVisibleAction}s whose visibility property
* is set to <code>true</code>
*/
public static class VisibilityFilteredActionSource extends AbstractDockActionSource{
/** all the actions that could be shown, even the invisble ones */
private DockActionSource source;
/**
* This listener is added to {@link #source} and to any {@link CVisibleAction} if there
* are listeners attached to <code>this</code>.
*/
private Listener listener;
public VisibilityFilteredActionSource( DockActionSource source ){
this.source = source;
}
/**
* Tells whether <code>action</code> should be shown.
* @param action some action of {@link #source}
* @return whether <code>action</code> should be shown
*/
public boolean isVisible( DockAction action ){
if( action instanceof CommonDockAction ){
CAction caction = ((CommonDockAction)action).getAction();
if( caction instanceof CVisibleAction ){
return ((CVisibleAction)caction).isVisible();
}
}
return true;
}
@Override
public LocationHint getLocationHint(){
return source.getLocationHint();
}
@Override
public int getDockActionCount(){
int sum = 0;
for( DockAction action : source ){
if( isVisible( action )){
sum++;
}
}
return sum;
}
@Override
public DockAction getDockAction( int index ){
int count = 0;
for( DockAction action : source ){
if( isVisible( action )){
if( count == index ){
return action;
}
else{
count++;
}
}
}
throw new IndexOutOfBoundsException();
}
@Override
public Iterator<DockAction> iterator(){
return new Iterator<DockAction>(){
private Iterator<DockAction> iterator = source.iterator();
private DockAction next;
private void forward(){
next = null;
while( next == null && iterator.hasNext() ){
DockAction action = iterator.next();
if( isVisible( action )){
next = action;
}
}
}
@Override
public boolean hasNext(){
if( next == null ){
forward();
}
return next != null;
}
@Override
public DockAction next(){
if( !hasNext() ){
throw new NoSuchElementException();
}
DockAction result = next;
next = null;
return result;
}
@Override
public void remove(){
iterator.remove();
}
};
}
@Override
public void addDockActionSourceListener( DockActionSourceListener listener ){
if( !hasListeners() ){
setListening( true );
}
super.addDockActionSourceListener( listener );
}
@Override
public void removeDockActionSourceListener( DockActionSourceListener listener ){
super.removeDockActionSourceListener( listener );
if( !hasListeners() ){
setListening( false );
}
}
/**
* Either install or deinstalls {@link #listener} to all {@link CVisibleAction}s.
* @param listening whether to install or to deinstall the listener
*/
private void setListening( boolean listening ){
if( listening ){
if( listener == null ){
listener = new Listener();
source.addDockActionSourceListener( listener );
for( DockAction action : source ){
setListening( action, true );
}
}
}
else{
if( listener != null ){
source.removeDockActionSourceListener( listener );
for( DockAction action : source ){
setListening( action, false );
}
listener = null;
}
}
}
/**
* Either adds or removes {@link #listener} from the {@link CVisibleAction} that is represented
* by <code>action</code>.
* @param action the action which may or may not need a listener
* @param listening whether to add or to remove {@link #listener}
*/
private void setListening( DockAction action, boolean listening ){
if( action instanceof CommonDockAction ){
CAction caction = ((CommonDockAction)action).getAction();
if( caction instanceof CVisibleAction ){
CVisibleAction visibleAction = (CVisibleAction)caction;
if( listening ){
visibleAction.addVisibilityListener( listener );
}
else{
visibleAction.removeVisibilityListener( listener );
}
}
}
}
private class Listener implements DockActionSourceListener, ActionVisibilityListener{
private List<DockAction> cache = new ArrayList<DockAction>();
public Listener(){
for( DockAction action : source ){
cache.add( action );
}
}
@Override
public void visibilityChanged( CVisibleAction caction, boolean visible ){
DockAction action = caction.asAction().intern();
int index = 0;
for( DockAction next : cache ){
if( next == action ){
break;
}
if( isVisible( next )){
index++;
}
}
if( visible ){
fireAdded( index, index );
}
else{
fireRemoved( index, index );
}
}
@Override
public void actionsAdded( DockActionSource source, int firstIndex, int lastIndex ){
// find index of first and last new action
int firstVisibleIndex = -1;
int lastVisibleIndex = -1;
int count = 0;
int index = 0;
for( DockAction action : source ){
if( index >= firstIndex && index <= lastIndex ){
cache.add( index, action );
setListening( action, true );
}
if( isVisible( action )){
if( index >= firstIndex ){
if( firstVisibleIndex == -1 ){
firstVisibleIndex = count;
}
lastVisibleIndex = count;
}
count++;
}
index++;
if( index > lastIndex ){
break;
}
}
if( firstVisibleIndex != -1 && lastVisibleIndex != -1 ){
fireAdded( firstVisibleIndex, lastVisibleIndex );
}
}
@Override
public void actionsRemoved( DockActionSource source, int firstIndex, int lastIndex ){
// find index of first and last removed action
int firstVisibleIndex = -1;
int lastVisibleIndex = -1;
int count = 0;
int index = 0;
for( DockAction action : cache ){
if( isVisible( action )){
if( index >= firstIndex ){
if( firstVisibleIndex == -1 ){
firstVisibleIndex = count;
}
lastVisibleIndex = count;
}
count++;
}
index++;
if( index > lastIndex ){
break;
}
}
for( int i = lastIndex; i >= firstIndex; i-- ){
setListening( cache.remove( i ), false );
}
if( firstVisibleIndex != -1 && lastVisibleIndex != -1 ){
fireRemoved( firstVisibleIndex, lastVisibleIndex );
}
}
}
}
/**
* The {@link ActionVisibilityListener} is added to {@link CVisibleAction}s, this observer
* is informed if the visibility property of the action changes
*/
private interface ActionVisibilityListener {
public void visibilityChanged( CVisibleAction action, boolean visible );
}
/**
* The {@link CVisibleAction} interface can be added to any {@link CAction}, it
* allows to make the action visible or invisible
*/
private interface CVisibleAction {
public CAction asAction();
public void setVisible( boolean visible );
public boolean isVisible();
public void addVisibilityListener( ActionVisibilityListener listener );
public void removeVisibilityListener( ActionVisibilityListener listener );
}
/**
* A helper class to manage the visibility property of a {@link CVisibleAction}
* and the {@link ActionVisibilityListener}s.
*/
public static class ActionVisibility{
private boolean visible = true;
private List<ActionVisibilityListener> listeners = new ArrayList<ActionVisibilityListener>();
private CVisibleAction action;
public ActionVisibility( CVisibleAction action ){
this.action = action;
}
public void addVisibilityListener( ActionVisibilityListener listener ){
listeners.add( listener );
}
public void removeVisibilityListener( ActionVisibilityListener listener ){
listeners.remove( listener );
}
public boolean isVisible(){
return visible;
}
public void setVisible( boolean visible ){
if( this.visible != visible ){
this.visible = visible;
for( ActionVisibilityListener listener : listeners ){
listener.visibilityChanged( action, visible );
}
}
}
}
/**
* A CButton that has an additional visibility property
*/
public static class CustomButton extends CButton implements CVisibleAction{
private ActionVisibility visibility;
public CustomButton(){
visibility = new ActionVisibility( this );
}
@Override
public CAction asAction(){
return this;
}
@Override
public void setVisible( boolean visible ){
visibility.setVisible( visible );
}
@Override
public boolean isVisible(){
return visibility.isVisible();
}
@Override
public void addVisibilityListener( ActionVisibilityListener listener ){
visibility.addVisibilityListener( listener );
}
@Override
public void removeVisibilityListener( ActionVisibilityListener listener ){
visibility.removeVisibilityListener( listener );
}
}
}