/* * 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) 2010 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.common.intern.layout; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import bibliothek.gui.Dockable; import bibliothek.gui.DockFrontend.DockInfo; import bibliothek.gui.dock.common.CControl; import bibliothek.gui.dock.common.MultipleCDockable; import bibliothek.gui.dock.common.MultipleCDockableFactory; import bibliothek.gui.dock.common.MultipleCDockableLayout; import bibliothek.gui.dock.common.SingleCDockable; import bibliothek.gui.dock.common.event.CVetoClosingEvent; import bibliothek.gui.dock.common.intern.CDockable; import bibliothek.gui.dock.common.intern.CommonDockable; import bibliothek.gui.dock.common.intern.CommonMultipleDockableLayout; import bibliothek.gui.dock.frontend.DefaultLayoutChangeStrategy; import bibliothek.gui.dock.frontend.DockFrontendInternals; import bibliothek.gui.dock.frontend.Setting; import bibliothek.gui.dock.layout.DockLayout; import bibliothek.gui.dock.layout.DockLayoutComposition; import bibliothek.gui.dock.layout.DockLayoutInfo; import bibliothek.gui.dock.layout.DockSituation; import bibliothek.gui.dock.layout.PredefinedDockSituation; import bibliothek.gui.dock.station.support.PlaceholderStrategy; import bibliothek.util.FrameworkOnly; /** * Strategy that pays attention to {@link MultipleCDockableFactory#match(bibliothek.gui.dock.common.MultipleCDockable, bibliothek.gui.dock.common.MultipleCDockableLayout)} * and that fires {@link CVetoClosingEvent}s for {@link MultipleCDockable}s as well. * @author Benjamin Sigg */ @FrameworkOnly public class CLayoutChangeStrategy extends DefaultLayoutChangeStrategy { /** the control in whose realm this strategy is used */ private CControl control; /** the internal name of the {@link ReplacementDockFactory} */ private static final String REPLACEMENT_FACTORY_ID = PredefinedDockSituation.convertFactoryID( ReplacementDockFactory.REPLACEMENT_FACTORY_ID ); /** * Creates a new strategy. * @param control the control in whose realm this strategy will be used */ public CLayoutChangeStrategy( CControl control ){ this.control = control; } @Override protected PredefinedDockSituation createSituation( DockFrontendInternals frontend, boolean entry, boolean onSetLayout ){ PredefinedDockSituation situation = super.createSituation( frontend, entry, onSetLayout ); if( onSetLayout ){ situation.add( new ReplacementDockFactory() ); } situation.setPlaceholderStrategy( control.getProperty( PlaceholderStrategy.PLACEHOLDER_STRATEGY ) ); return situation; } @Override protected boolean shouldPredefine( Dockable dockable ){ if( dockable instanceof CommonDockable ){ return ((CommonDockable)dockable).getDockable() instanceof SingleCDockable; } else{ return true; } } @Override protected Set<Dockable> estimateVisible( DockFrontendInternals frontend, DockSituation situation, DockLayoutComposition layout ){ if( situation instanceof PredefinedDockSituation ){ Set<Dockable> allDockables = new HashSet<Dockable>(); for( DockInfo info : frontend.getDockables() ){ Dockable dockable = info.getDockable(); if( dockable != null ){ allDockables.add( dockable ); } } for( MultipleCDockable dockable : control.getRegister().getMultipleDockables() ){ allDockables.add( dockable.intern() ); } PredefinedDockSituation predefined = (PredefinedDockSituation)situation; Set<Dockable> visible = predefined.listVisible( allDockables, layout ); findVisible( visible, layout ); return visible; } return null; } @Override protected Collection<Dockable> getClosingDockables( DockFrontendInternals frontend, Set<Dockable> visible ){ Collection<Dockable> result = super.getClosingDockables( frontend, visible ); for( MultipleCDockable dockable : control.getRegister().getMultipleDockables() ){ CommonDockable intern = dockable.intern(); if( !visible.contains( intern )){ result.add( intern ); } } return result; } private void findVisible( Set<Dockable> visible, DockLayoutComposition layout ){ DockLayoutInfo info = layout.getLayout(); if( info != null ){ DockLayout<?> data = info.getDataLayout(); if( data != null ){ if( REPLACEMENT_FACTORY_ID.equals( data.getFactoryID() )){ CDockable dockable = (CDockable)data.getData(); visible.add( dockable.intern() ); } } } for( DockLayoutComposition child : layout.getChildren() ){ findVisible( visible, child ); } } /** * Checks the {@link DockLayout} that is associated with <code>composition</code> and may replace * the layout by another layout if a {@link MultipleCDockableFactory} finds a match between an existing * item and meta-data about an item. * @param frontend the caller of this method * @param setting the layout that is about to be applied * @param composition the composition to modify, may be <code>null</code> * @return the replacement composition or <code>null</code> */ protected DockLayoutComposition replaceMultipleDockables( DockFrontendInternals frontend, CSettingAccess setting, DockLayoutComposition composition ){ if( composition == null ){ return null; } DockLayoutInfo info = composition.getLayout(); if( info != null ){ DockLayout<?> layout = info.getDataLayout(); if( layout != null ){ MultipleCDockable match = setting.findMatch( layout ); if( match != null ){ DockLayout<?> newLayout = new DockLayout<MultipleCDockable>( REPLACEMENT_FACTORY_ID, match ); DockLayoutInfo newInfo = new DockLayoutInfo( newLayout ); newInfo.setLocation( info.getLocation() ); info = newInfo; } } } List<DockLayoutComposition> oldChildren = composition.getChildren(); List<DockLayoutComposition> newChildren = new ArrayList<DockLayoutComposition>( oldChildren.size() ); for( DockLayoutComposition child : oldChildren ){ newChildren.add( replaceMultipleDockables( frontend, setting, child ) ); } return new DockLayoutComposition( info, composition.getAdjacent(), newChildren, composition.isIgnoreChildren() ); } @Override protected SettingAccess createAccess( DockFrontendInternals frontend, Setting setting ){ return new CSettingAccess( frontend, setting ); } /** * A {@link bibliothek.gui.dock.frontend.DefaultLayoutChangeStrategy.SettingAccess} that modifies the roots by calling * {@link CLayoutChangeStrategy#replaceMultipleDockables(DockFrontendInternals, CSettingAccess, DockLayoutComposition)}. * @author Benjamin Sigg */ protected class CSettingAccess extends SettingAccess{ private DockFrontendInternals frontend; private Map<String, DockLayoutComposition> modifiedRoots = new HashMap<String, DockLayoutComposition>(); /** all the factories that might be used to create a match */ private Map<String, MultipleCDockableFactory<?, ?>> factories; /** the dockables which have not yet been paired off ordered by their factories */ private Map<String, List<MultipleCDockable>> remainingDockables; public CSettingAccess( DockFrontendInternals frontend, Setting setting ){ super( setting ); this.frontend = frontend; Map<String, MultipleCDockableFactory<?, ?>> factories = control.getRegister().getFactories(); this.factories = new HashMap<String, MultipleCDockableFactory<?,?>>(); for( Map.Entry<String, MultipleCDockableFactory<?, ?>> entry : factories.entrySet() ){ this.factories.put( PredefinedDockSituation.convertFactoryID( entry.getKey() ), entry.getValue() ); } remainingDockables = new HashMap<String, List<MultipleCDockable>>(); for( MultipleCDockable dockable : control.getRegister().getMultipleDockables() ){ for( Map.Entry<String, MultipleCDockableFactory<?, ?>> entry : factories.entrySet() ){ if( entry.getValue() == dockable.getFactory() ){ String key = PredefinedDockSituation.convertFactoryID( entry.getKey() ); List<MultipleCDockable> list = remainingDockables.get( key ); if( list == null ){ list = new LinkedList<MultipleCDockable>(); remainingDockables.put( key, list ); } list.add( dockable ); break; } } } } /** * Searches a match for the meta-data <code>layout</code>. The result of this method will * never be result again for this {@link CSettingAccess}. * @param layout the element whose match is searched * @return the match or <code>null</code> if none was found */ @SuppressWarnings("unchecked") public MultipleCDockable findMatch( DockLayout<?> layout ){ String factoryId = layout.getFactoryID(); Object data = layout.getData(); if( data instanceof CommonMultipleDockableLayout ){ MultipleCDockableLayout multipleLayout = ((CommonMultipleDockableLayout) data).getLayout(); MultipleCDockableFactory<MultipleCDockable, MultipleCDockableLayout> factory = (MultipleCDockableFactory<MultipleCDockable, MultipleCDockableLayout>) factories.get( factoryId ); if( factory != null ){ List<MultipleCDockable> list = remainingDockables.get( factoryId ); if( list != null ){ Iterator<MultipleCDockable> iterator = list.iterator(); while( iterator.hasNext() ){ MultipleCDockable next = iterator.next(); if( factory.match( next, multipleLayout )){ iterator.remove(); if( list.isEmpty() ){ remainingDockables.remove( factoryId ); } return next; } } } } } return null; } @Override public DockLayoutComposition getRoot( String root ){ DockLayoutComposition result = modifiedRoots.get( root ); if( result == null ){ result = replaceMultipleDockables( frontend, this, super.getRoot( root ) ); modifiedRoots.put( root, result ); } return result; } } }