/* * 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) 2012 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.extension.css.scheme; import java.util.HashMap; import java.util.Map; import bibliothek.gui.dock.extension.css.CssItem; import bibliothek.gui.dock.extension.css.CssProperty; import bibliothek.gui.dock.extension.css.CssPropertyContainer; import bibliothek.gui.dock.extension.css.CssPropertyContainerListener; import bibliothek.gui.dock.extension.css.CssPropertyKey; import bibliothek.gui.dock.extension.css.CssRule; import bibliothek.gui.dock.extension.css.CssRuleContent; import bibliothek.gui.dock.extension.css.CssRuleContentListener; import bibliothek.gui.dock.extension.css.CssScheme; /** * This class monitors a {@link CssPropertyContainer} and sets the values of its * {@link CssProperty}s using values from a {@link CssRuleContent}. * @author Benjamin Sigg */ public class PropertyForwarder { /** the source of all values */ private CssRuleContent source; /** where to insert all the values */ private CssPropertyContainer target; /** additional information required to convert data from {@link #source} to {@link #target} */ private CssScheme scheme; /** monitors {@link CssRule} and target {@link CssPropertyContainer}s */ private Listener listener = new Listener(); /** all the properties that are currently monitored */ private Map<CssPropertyKey, CssProperty<?>> properties = new HashMap<CssPropertyKey, CssProperty<?>>(); /** * Creates a new forwarder. * @param source the source of all values * @param target the target for all values * @param scheme conversion information for values */ public PropertyForwarder( CssRuleContent source, CssPropertyContainer target, CssScheme scheme ){ this.source = source; this.target = target; this.scheme = scheme; target.addPropertyContainerListener( listener ); source.addRuleContentListener( listener ); } /** * Finds out which key <code>container</code> and <code>key</code> form. * @param container the container, which may or may not be a {@link CssProperty} whose * key is stored. * @param key the appendix to the key of <code>container</code> * @return the new key representing <code>key</code> as child of <code>container</code> */ protected CssPropertyKey combinedKey( CssPropertyContainer container, String key ){ for( Map.Entry<CssPropertyKey, CssProperty<?>> entry : properties.entrySet() ){ if( entry.getValue() == container ){ return entry.getKey().append( key ); } } return new CssPropertyKey( key ); } /** * Takes all the known {@link CssProperty}s from the <code>source</code> and adds * them for monitoring. * @param firstRule whether this is the first rule forwarder for a {@link CssItem}, the first * forwarder has to call {@link CssProperty#setScheme(CssScheme, CssPropertyKey)} */ public void install( boolean firstRule ){ for( String key : target.getPropertyKeys() ){ addProperty( new CssPropertyKey( key ), target.getProperty( key ), firstRule ); } } protected void ignoreTarget(){ target.removePropertyContainerListener( listener ); } /** * This forwarded stops listening to <code>source</code> or <code>target</code> and * sets all properties back to <code>null</code> */ public void destroy(){ target.removePropertyContainerListener( listener ); source.removeRuleContentListener( listener ); for( CssProperty<?> property : properties.values() ){ property.removePropertyContainerListener( listener ); } for( CssProperty<?> property : properties.values() ){ property.set( null ); property.setScheme( null, null ); } properties.clear(); } /** * Called if a <code>property</code> is to be monitored. * @param key the name of <code>property</code> * @param property the additional property to monitor * @param firstRule whether this is the first rule forwarder for a {@link CssItem}, the first * forwarder has to call {@link CssProperty#setScheme(CssScheme, CssPropertyKey)} */ protected <T> void addProperty( CssPropertyKey key, CssProperty<T> property, boolean firstRule ){ if( properties.containsKey( key )){ throw new IllegalStateException( "property with name '" + key + "' already exists" ); } if( firstRule ){ property.setScheme( scheme, key ); } if( source != null ){ T value = source.getProperty( property.getType( scheme ), key ); property.set( value ); } properties.put( key, property ); property.addPropertyContainerListener( listener ); for( String name : property.getPropertyKeys() ){ CssProperty<?> subProperty = property.getProperty( name ); if( subProperty != null ){ CssPropertyKey subKey = key.append( name ); if( !properties.containsKey( subKey )){ addProperty( subKey, subProperty, true ); } } } } /** * Removes <code>property</code> from this forwarder. * @param key the name of the property * @param property the property to remove * @param fullRemoval whether the value and {@link CssScheme} should be set to <code>null</code> */ protected void removeProperty( CssPropertyKey key, CssProperty<?> property, boolean fullRemoval ){ property.removePropertyContainerListener( listener ); if( fullRemoval ){ for( String name : property.getPropertyKeys() ){ removeProperty( key.append( name ), property.getProperty( name ), fullRemoval ); } } properties.remove( key ); if( fullRemoval ){ property.set( null ); property.setScheme( null, null ); } } protected CssPropertyKey[] getKeys(){ return properties.keySet().toArray( new CssPropertyKey[ properties.size() ] ); } protected CssProperty<?> getProperty( CssPropertyKey key ){ return properties.get( key ); } private class Listener implements CssRuleContentListener, CssPropertyContainerListener{ @Override public void propertyChanged( CssRuleContent source, CssPropertyKey key ){ CssProperty<?> sink = properties.get( key ); if( sink != null ){ resetProperty( sink, key ); } } @Override public void propertiesChanged( CssRuleContent source ){ for( Map.Entry<CssPropertyKey, CssProperty<?>> entry : properties.entrySet() ){ resetProperty( entry.getValue(), entry.getKey() ); } } private <T> void resetProperty( CssProperty<T> property, CssPropertyKey key ){ T value; if( source == null ){ value = null; } else{ value = source.getProperty( property.getType( scheme ), key ); } property.set( value ); } @Override public void propertyAdded( CssPropertyContainer source, String key, CssProperty<?> property ){ if( source != null ){ CssPropertyKey cssKey = combinedKey( source, key ); addProperty( cssKey, property, true ); } } @Override public void propertyRemoved( CssPropertyContainer source, String key, CssProperty<?> property ){ if( source != null ){ CssPropertyKey cssKey = combinedKey( source, key ); removeProperty( cssKey, property, true ); } } } }