/* * 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.control; import java.awt.Component; import java.awt.Container; import java.awt.event.ContainerEvent; import java.awt.event.ContainerListener; import java.util.*; import bibliothek.gui.DockController; import bibliothek.gui.Dockable; import bibliothek.gui.dock.DockElementRepresentative; import bibliothek.gui.dock.event.ComponentHierarchyObserverEvent; import bibliothek.gui.dock.event.ComponentHierarchyObserverListener; import bibliothek.gui.dock.event.DockControllerRepresentativeListener; /** * A class collecting all {@link Component}s which are somehow used on or with * the {@link Dockable}s in the realm of one {@link DockController}.<br> * A global instance of {@link ComponentHierarchyObserver} can be obtained * through {@link DockController#getComponentHierarchyObserver()}.<br> * Note that a hierarchy observer may also know {@link Component}s which are * not directly associated with {@link Dockable}s. * @author Benjamin Sigg */ public class ComponentHierarchyObserver { /** * The set of components which were explicitly added to this observer and * will not be removed implicitly. */ private Set<Component> roots = new HashSet<Component>(); /** the currently known components */ private Set<Component> components = new HashSet<Component>(); /** a listener to all {@link Container}s */ private Listener listener = new Listener(); /** the controller in whose realm this observer works */ private DockController controller; /** the observers of this {@link ComponentHierarchyObserver} */ private List<ComponentHierarchyObserverListener> listeners = new ArrayList<ComponentHierarchyObserverListener>(); /** * Creates a new observer. * @param controller the controller whose {@link Dockable}s will be observed. */ public ComponentHierarchyObserver( DockController controller ){ this.controller = controller; controller.addRepresentativeListener( new DockControllerRepresentativeListener(){ public void representativeAdded( DockController controller, DockElementRepresentative representative ) { add( representative.getComponent() ); } public void representativeRemoved( DockController controller, DockElementRepresentative representative ) { remove( representative.getComponent() ); } }); } /** * Gets a {@link Set} containing all {@link Component}s which are * used on {@link Dockable}s known in the realm of the {@link DockController} * of this observer. * @return the set of <code>Component</code>s. */ public Set<Component> getComponents() { return Collections.unmodifiableSet( components ); } /** * Gets the controller in whose realm this observer searches for * {@link Component}s. * @return the controller */ public DockController getController() { return controller; } /** * Adds a listener to this observer. * @param listener the new listener, not <code>null</code> */ public void addListener( ComponentHierarchyObserverListener listener ){ if( listener == null ) throw new NullPointerException( "listener must not be null" ); listeners.add( listener ); } /** * Removes a listener from this observer. * @param listener the listener to remove */ public void removeListener( ComponentHierarchyObserverListener listener ){ listeners.remove( listener ); } /** * Gets an array containing all listeners that are registered at this * observer. * @return the list of listeners */ protected ComponentHierarchyObserverListener[] listeners(){ return listeners.toArray( new ComponentHierarchyObserverListener[ listeners.size() ] ); } /** * Adds <code>component</code> and all its children to the set of * known {@link Component}s. Components that are already known will * not be registered twice. This observer will notice when a child of * <code>component</code> changes and update itself accordingly. * @param component the new component */ public void add( Component component ){ roots.add( component ); add( component, null ); } /** * Adds <code>component</code> and all children of it to the set of * known {@link Component}s. This will add <code>component</code> as a * root, which prevents <code>component</code> from being removed * implicitly because its parent gets removed. * @param component the new component * @param list a list to be filled with the affected {@link Component}s, * can be <code>null</code> to indicate that this method has to fire * an event. */ private void add( Component component, List<Component> list ){ boolean fire = list == null; if( fire ) list = new LinkedList<Component>(); if( components.add( component )){ list.add( component ); if( component instanceof Container ){ Container container = (Container)component; container.addContainerListener( listener ); for( int i = 0, n = container.getComponentCount(); i<n; i++ ){ add( container.getComponent( i ), list ); } } } if( fire && !list.isEmpty() ){ list = Collections.unmodifiableList( list ); ComponentHierarchyObserverEvent event = new ComponentHierarchyObserverEvent( controller, list ); for( ComponentHierarchyObserverListener listener : listeners() ) listener.added( event ); } } /** * Removes <code>component</code> and all its children from the set * of known {@link Component}s. If a child was added as a root, then * the recursion stops there because roots can't be removed implicitly. * @param component the component to remove */ public void remove( Component component ){ roots.remove( component ); remove( component, null ); } /** * Removes <code>component</code> and all its children from the set * of known {@link Component}s. * @param component the removed component. * @param list a list to be filled with the affected {@link Component}s, * can be <code>null</code> to indicate that this method has to fire * an event. */ private void remove( Component component, List<Component> list ){ if( !roots.contains( component )){ boolean fire = list == null; if( fire ) list = new LinkedList<Component>(); if( components.remove( component )){ list.add( component ); if( component instanceof Container ){ Container container = (Container)component; container.removeContainerListener( listener ); for( int i = 0, n = container.getComponentCount(); i<n; i++ ){ remove( container.getComponent( i ), list ); } } } if( fire && !list.isEmpty() ){ list = Collections.unmodifiableList( list ); ComponentHierarchyObserverEvent event = new ComponentHierarchyObserverEvent( controller, list ); for( ComponentHierarchyObserverListener listener : listeners() ) listener.removed( event ); } } } /** * A listener to {@link Container}s, triggered when {@link Component}s * are added or removed. * @author Benjamin Sigg */ private class Listener implements ContainerListener{ public void componentAdded( ContainerEvent e ) { add( e.getChild(), null ); } public void componentRemoved( ContainerEvent e ) { remove( e.getChild(), null ); } } }