/* * 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) 2008 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.extension.gui.dock.preference; import java.util.ArrayList; import java.util.List; import javax.swing.event.TreeModelEvent; import javax.swing.event.TreeModelListener; import javax.swing.tree.TreeModel; import javax.swing.tree.TreePath; import bibliothek.gui.DockController; import bibliothek.gui.dock.util.TextManager; import bibliothek.util.Path; import bibliothek.util.PathCombiner; /** * A {@link PreferenceModel} that is also a {@link TreeModel}. It contains * other {@link PreferenceModel}s and organizes them in a tree. The nodes * of this {@link TreeModel} are of the type {@link PreferenceTreeModel.Node}. * The root of this model never has a name nor a model. * @author Benjamin Sigg */ public class PreferenceTreeModel extends AbstractPreferenceModel implements TreeModel{ /** internal representation of all models */ private MergedPreferenceModel delegate; /** a listener to <code>delegate</code> */ private PreferenceModelListener delegateListener = new PreferenceModelListener(){ public void preferenceAdded( PreferenceModel model, int beginIndex, int endIndex ) { firePreferenceAdded( beginIndex, endIndex ); } public void preferenceChanged( PreferenceModel model, int beginIndex, int endIndex ) { firePreferenceChanged( beginIndex, endIndex ); } public void preferenceRemoved( PreferenceModel model, int beginIndex, int endIndex ) { firePreferenceRemoved( beginIndex, endIndex ); } }; private List<TreeModelListener> treeListeners = new ArrayList<TreeModelListener>(); private TreeNode root = new TreeNode( null, new Path() ); /** * Creates a new empty model. * @param controller the controller in whose realm this model is used */ public PreferenceTreeModel( DockController controller ){ super( controller ); delegate = new MergedPreferenceModel( controller ); } /** * Creates a new empty model. * @param combiner tells how to combine the {@link Path} of a model with * the preferences of a model. Used in {@link #getPath(int)}. Not <code>null</code>. * @param controller the controller in whose realm this model is used * @see MergedPreferenceModel#MergedPreferenceModel(PathCombiner, DockController) */ public PreferenceTreeModel( PathCombiner combiner, DockController controller ){ super( controller ); delegate = new MergedPreferenceModel( combiner, controller ); } @Override protected boolean hasListeners(){ return super.hasListeners() || treeListeners.size() > 0; } public void addTreeModelListener( TreeModelListener l ) { boolean listeners = hasListeners(); treeListeners.add( l ); if( !listeners && hasListeners() ){ delegate.addPreferenceModelListener( delegateListener ); root.updateListening( true ); } } @Override public void addPreferenceModelListener( PreferenceModelListener listener ) { boolean listeners = hasListeners(); super.addPreferenceModelListener( listener ); if( !listeners && hasListeners() ){ delegate.addPreferenceModelListener( delegateListener ); root.updateListening( true ); } } public void removeTreeModelListener( TreeModelListener l ) { boolean listeners = hasListeners(); treeListeners.remove( l ); if( listeners && !hasListeners() ){ delegate.removePreferenceModelListener( delegateListener ); root.updateListening( false ); } } @Override public void removePreferenceModelListener( PreferenceModelListener listener ) { boolean listeners = hasListeners(); super.removePreferenceModelListener( listener ); if( listeners && !hasListeners() ){ delegate.removePreferenceModelListener( delegateListener ); root.updateListening( false ); } } /** * Gets all the listeners currently known to this model. * @return the list of listeners */ protected TreeModelListener[] getTreeModelListeners(){ return treeListeners.toArray( new TreeModelListener[ treeListeners.size() ] ); } private void fireNodeAdded( TreeNode parent, TreeNode child ){ TreeModelListener[] listeners = getTreeModelListeners(); if( listeners.length > 0 ){ TreeModelEvent event = new TreeModelEvent( this, parent.getTreePath(), new int[]{ parent.indexOf( child ) }, new Object[]{ child }); for( TreeModelListener listener : listeners ){ listener.treeNodesInserted( event ); } } } private void fireNodeChanged( TreeNode node ){ TreeModelListener[] listeners = getTreeModelListeners(); if( listeners.length > 0 ){ TreeNode parent = node.getParent(); TreeModelEvent event; if( parent == null ){ event = new TreeModelEvent( this, node.getTreePath() ); } else{ event = new TreeModelEvent( parent, parent.getTreePath(), new int[]{ parent.indexOf( node ) }, new Object[]{ node }); } for( TreeModelListener listener : listeners ){ listener.treeNodesChanged( event ); } } } private void fireNodeRemoved( TreeNode parent, int[] indices, Object[] children ){ TreeModelListener[] listeners = getTreeModelListeners(); if( listeners.length > 0 ){ TreeModelEvent event = new TreeModelEvent( this, parent.getTreePath(), indices, children ); for( TreeModelListener listener : listeners ){ listener.treeNodesRemoved( event ); } } } public Node getChild( Object parent, int index ) { return ((TreeNode)parent).getChild( index ); } public int getChildCount( Object parent ) { return ((TreeNode)parent).getChildrenCount(); } public int getIndexOfChild( Object parent, Object child ) { return ((TreeNode)parent).indexOf( (TreeNode)child ); } public Node getRoot() { return root; } public boolean isLeaf( Object node ) { return ((TreeNode)node).getChildrenCount() == 0; } public void valueForPathChanged( TreePath path, Object newValue ) { // ignore } @Override public String getDescription( int index ) { return delegate.getDescription( index ); } @Override public boolean isNatural( int index ) { return delegate.isNatural( index ); } @Override public void setValueNatural( int index ) { delegate.setValueNatural( index ); } @Override public PreferenceOperation[] getOperations( int index ) { return delegate.getOperations( index ); } @Override public boolean isEnabled( int index, PreferenceOperation operation ) { return delegate.isEnabled( index, operation ); } @Override public void doOperation( int index, PreferenceOperation operation ) { delegate.doOperation( index, operation ); } @Override public void read() { delegate.read(); } @Override public void write() { delegate.write(); } public String getLabel( int index ) { return delegate.getLabel( index ); } public Path getPath( int index ) { return delegate.getPath( index ); } public int getSize() { return delegate.getSize(); } public Path getTypePath( int index ) { return delegate.getTypePath( index ); } public Object getValueInfo(int index) { return delegate.getValueInfo( index ); } public Object getValue( int index ) { return delegate.getValue( index ); } public void setValue( int index, Object value ) { delegate.setValue( index, value ); } /** * Sets the name of the node at <code>path</code>. If there is no * such node, then the node and all its parents are created. Otherwise * just the name gets exchanged. * @param path the path to the node * @param name the new name of the node */ public void putNode( Path path, String name ){ root.getNode( path, 0 ).setName( name ); } /** * Sets the name of the node at <code>path</code>. If there is no * such node, then the node and all its parents are created. Otherwise * just the name gets exchanged. * @param path the path to the node * @param nameId the new name, an identifier used for the {@link TextManager} */ public void putLinked( Path path, String nameId ){ root.getNode( path, 0 ).setNameId( nameId ); } /** * Sets the model of the node at <code>path</code>. If there is * no such node, then the node and all its parents are created. Otherwise * just the model gets exchanged. * @param path the path to change * @param model the new model */ public void putModel( Path path, PreferenceModel model ){ root.getNode( path, 0 ).setModel( model ); delegate.remove( path ); delegate.add( model, path ); } /** * Sets name and model of a given node. * @param path the path to the node * @param name the new name * @param model the new model, can be <code>null</code> * @see #putLinked(Path, String, PreferenceModel) * @see #putNode(Path, String) * @see #putModel(Path, PreferenceModel) */ public void put( Path path, String name, PreferenceModel model ){ delegate.remove( path ); if( model != null ){ delegate.add( model, path ); } root.getNode( path, 0 ).set( name, model ); } /** * Sets name and model of a given node. * @param path the path to the node * @param nameId the new name, an identifier used for a {@link TextManager}. * @param model the new model, can be <code>null</code> */ public void putLinked( Path path, String nameId, PreferenceModel model ){ delegate.remove( path ); if( model != null ){ delegate.add( model, path ); } root.getNode( path, 0 ).setLinked( nameId, model ); } /** * Gets the model which was stored using <code>path</code> as key. * @param path the key of some model * @return the model or <code>null</code> */ public PreferenceModel getModel( Path path ){ return delegate.getModel( path ); } /** * Deletes the node at <code>path</code> and all its children from the * tree. This also removes any {@link PreferenceModel} of the subtree. If * there is no node at <code>path</code>, then nothing happens * @param path the path to remove */ public void delete( Path path ){ root.delete( path, 0 ); } /** * A single node of a {@link PreferenceTreeModel}. * @author Benjamin Sigg */ public static interface Node{ /** * Gets the path of this node. * @return the path */ public Path getPath(); /** * Gets the name of this node. * @return the user readable name, might be <code>null</code> */ public String getName(); /** * Gets the model of this node. * @return the model, might be <code>null</code> */ public PreferenceModel getModel(); } private class TreeNode implements Node{ private TreeNode parent; private List<TreeNode> children; private Path path; private TreePath treePath; private PreferenceModelText name; private PreferenceModel model; public TreeNode( TreeNode parent, Path path ){ this.parent = parent; this.path = path; name = new PreferenceModelText( "null", PreferenceTreeModel.this ){ protected void changed( String oldValue, String newValue ){ fireNodeChanged( TreeNode.this ); } }; if( hasListeners() ){ name.setController( getController() ); } } public void updateListening( boolean listening ){ if( listening ){ name.setController( getController() ); } else{ name.setController( null ); } if( children != null ){ for( TreeNode child : children ){ child.updateListening( listening ); } } } public int getChildrenCount(){ return children == null ? 0 : children.size(); } public TreeNode getChild( int index ){ return children.get( index ); } public int indexOf( TreeNode child ){ return children.indexOf( child ); } /** * Gets the path to this node. * @return the path */ public TreePath getTreePath(){ if( treePath != null ) return treePath; if( parent == null ){ treePath = new TreePath( this ); } else{ treePath = parent.getTreePath().pathByAddingChild( this ); } return treePath; } public TreeNode getNode( Path path, int segment ){ if( segment == path.getSegmentCount() ) return this; if( children == null ){ children = new ArrayList<TreeNode>(); } String check = path.getSegment( segment ); for( TreeNode child : children ){ if( check.equals( child.getPath().getLastSegment() )){ return child.getNode( path, segment+1 ); } } TreeNode child = new TreeNode( this, path.subPath( 0, segment+1 ) ); children.add( child ); fireNodeAdded( this, child ); return child.getNode( path, segment+1 ); } public void delete( Path path, int segment ){ if( segment == path.getSegmentCount() ){ delete( false ); } else if( children != null ){ String check = path.getSegment( segment ); for( TreeNode child : children ){ if( check.equals( child.getPath().getLastSegment() )){ child.delete( path, segment+1 ); break; } } } } public void delete( boolean silent ){ if( children != null ){ for( TreeNode child : children ){ child.delete( true ); } } if( !silent ){ if( parent == null ){ if( children != null ){ int[] indices = new int[ children.size() ]; for( int i = 0; i < indices.length; i++ ) indices[i] = i; Object[] removed = children.toArray(); children.clear(); fireNodeRemoved( this, indices, removed ); } } else{ parent.delete( this ); } } } public void delete( TreeNode child ){ int index = indexOf( child ); children.remove( index ); fireNodeRemoved( this, new int[]{ index }, new Object[]{ child }); } public TreeNode getParent() { return parent; } @Override public String toString() { return name.value(); } public Path getPath() { return path; } public void setLinked( String name, PreferenceModel model ){ this.model = model; setNameId( name ); fireNodeChanged( this ); } public void set( String name, PreferenceModel model ){ this.model = model; setName( name ); fireNodeChanged( this ); } public void setNameId( String nameId ){ this.name.setId( nameId ); } public void setName( String name ) { this.name.setValue( name ); } public String getName() { if( !hasListeners() ){ name.update( getController().getTexts() ); } return name.value(); } public void setModel( PreferenceModel model ) { this.model = model; fireNodeChanged( this ); } public PreferenceModel getModel() { return model; } } }