All Downloads are FREE. Search and download functionalities are using the official Maven repository.

bibliothek.gui.dock.extension.css.CssScheme Maven / Gradle / Ivy

The newest version!
/*
 * 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
 * [email protected]
 * CH - Switzerland
 */
package bibliothek.gui.dock.extension.css;

import java.awt.Color;
import java.awt.EventQueue;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import bibliothek.gui.dock.extension.css.path.CssPathListener;
import bibliothek.gui.dock.extension.css.property.BooleanType;
import bibliothek.gui.dock.extension.css.property.CssTransitionType;
import bibliothek.gui.dock.extension.css.property.IntegerType;
import bibliothek.gui.dock.extension.css.property.font.CssFontModifier;
import bibliothek.gui.dock.extension.css.property.font.CssFontModifierType;
import bibliothek.gui.dock.extension.css.property.font.FontModifyType;
import bibliothek.gui.dock.extension.css.property.paint.ColorType;
import bibliothek.gui.dock.extension.css.property.paint.CssPaint;
import bibliothek.gui.dock.extension.css.property.paint.CssPaintType;
import bibliothek.gui.dock.extension.css.property.shape.CssShape;
import bibliothek.gui.dock.extension.css.property.shape.CssShapeType;
import bibliothek.gui.dock.extension.css.scheme.MatchedCssRule;
import bibliothek.gui.dock.extension.css.transition.CssTransition;
import bibliothek.gui.dock.extension.css.transition.DefaultAnimatedCssRuleChain;
import bibliothek.gui.dock.extension.css.transition.TransitionalCssRuleContent;
import bibliothek.gui.dock.extension.css.transition.TransitionalCssRuleChain;
import bibliothek.gui.dock.extension.css.transition.scheduler.CssScheduler;
import bibliothek.gui.dock.extension.css.transition.scheduler.DefaultCssScheduler;
import bibliothek.gui.dock.extension.css.tree.CssTree;
import bibliothek.gui.dock.util.font.GenericFontModifier.Modify;

/**
 * Represents the contents of some css files. It is a map allowing 
 * the framework to access values read from a css file.
 * @author Benjamin Sigg
 */
public class CssScheme {
	private final Object RULES_LOCK = new Object();
	
	private List rules = new ArrayList();
	
	private Map items = new HashMap();
	private Map, CssType> types = new HashMap, CssType>();
	
	private boolean rulesAreSorted = false;
	private boolean rematchPending = false;
	
	private CssTree tree;
	private CssScheduler scheduler = new DefaultCssScheduler();
	
	private CssRuleListener selectorChangedListener = new CssRuleListener(){
		@Override
		public void selectorChanged( CssRule source ){
			rulesAreSorted = false;
			rematch();
		}
	};
	
	/**
	 * Creates a new scheme
	 */
	public CssScheme(){
		initDefaultTypes();
	}
	
	private void initDefaultTypes(){
		setConverter( CssPaint.class, new CssPaintType() );
		setConverter( CssShape.class, new CssShapeType() );
		setConverter( CssFontModifier.class, new CssFontModifierType() );
		
		setConverter( Color.class, new ColorType() );
		setConverter( Integer.class, new IntegerType() );
		setConverter( Modify.class, new FontModifyType() );
		setConverter( Boolean.class, new BooleanType() );
		
		types.put( CssTransition.class, new CssTransitionType() );
	}
	
	/**
	 * Sets the document.
	 * @param tree the elements for which this scheme is used
	 */
	public void setTree( CssTree tree ){
		this.tree = tree;
	}
	
	/**
	 * Gets the document (internal cache and factory of paths) for which this scheme
	 * is used.
	 * @return the document, can be null
	 */
	public CssTree getTree(){
		return tree;
	}
	
	/**
	 * Stores a converter for converting {@link String}s into {@link Object}s of
	 * type type. 
	 * @param type the type of the created objects
	 * @param converter the converter to use
	 */
	public  void setConverter( Class type, CssType converter ){
		if( converter == null ){
			types.remove( type );
		}
		else{
			types.put( type, converter );
		}
	}
	
	/**
	 * Gets a converter for creating {@link Object}s of type type
	 * @param type the type of the created objects
	 * @return the converter, not null
	 * @throws IllegalStateException if there is no converter registered for type
	 */
	@SuppressWarnings("unchecked")
	public  CssType getConverter( Class type ){
		CssType result = (CssType)types.get( type );
		if( result == null ){
			throw new IllegalStateException( "missing converter for type: " + type );
		}
		return result;
	}
	
	/**
	 * Adds item as observer to this map of properties. The properties of
	 * item will be set using the best matching {@link CssRule}.
	 * @param item the new item, not null
	 * @throws IllegalArgumentException if item has already been added
	 */
	public void add( CssItem item ){
		if( items.containsKey( item )){
			throw new IllegalArgumentException( "the item is already added" );
		}
		
		Match match = new Match( item );
		items.put( item, match );
		
		match.searchRule();
	}
	
	/**
	 * Removes item from this map of properties. All properties of
	 * item will be reset to null.
	 * @param item the item to remove, not null
	 */
	public void remove( CssItem item ){
		Match match = items.remove( item );	
		if( match != null ){
			match.destroy();
		}
	}
	
	/**
	 * Searches a {@link CssRule}s whose {@link CssSelector} matches
	 * {@link CssItem}. Then collects the properties of the rule and returns them.
	 * @param item the item for which a rule is searched
	 * @return the properties of the rule, null if nothing was found
	 */
	public CssRuleContent search( CssItem item ){
		synchronized( RULES_LOCK ){
			ensureRulesSorted();
			CssRuleContentUnion result = null;
			
			for( CssRule rule : rules ){
				if( rule.getSelector().matches( item.getPath() )){
					if( result == null ){
						result = new CssRuleContentUnion();
					}
					result.add( rule.getContent() );
				}
			}
			return result;
		}
	}
	
	/**
	 * Adds rule to this scheme. This method calls {@link #rematch()}, meaning the changes
	 * will be propagated to the {@link CssItem}s later.
	 * @param rule the rule to add, not null
	 */
	public void addRule( CssRule rule ){
		if( rule == null ){
			throw new IllegalArgumentException( "rule must not be null" );
		}
		synchronized( RULES_LOCK ){
			rules.add( rule );
		}
		rulesAreSorted = false;
		rule.addRuleListener( selectorChangedListener );
		rematch();
	}
	
	/**
	 * Removes rule from this scheme. This method calls {@link #rematch()}, meaning the changes
	 * will be propagated to the {@link CssItem}s later.
	 * @param rule the rule to remove
	 */
	public void removeRule( CssRule rule ){
		synchronized( RULES_LOCK ){
			rules.remove( rule );
		}
		rule.removeRuleListener( selectorChangedListener );
		rulesAreSorted = false;
		rematch();
	}
	
	/**
	 * Replaces all the current rules with the {@link CssRule}s from rules.
	 * @param rules the new set of {@link CssRule}s
	 */
	public void setRules( Collection rules ){
		for( CssRule rule : this.rules ){
			rule.removeRuleListener( selectorChangedListener );
		}
		synchronized( RULES_LOCK ){
			this.rules.clear();
		}
		addRules( rules );
	}
	
	/**
	 * Adds all the {@link CssRule}s from rules to this scheme.
	 * @param rules a new set of {@link CssRule}s
	 */
	public void addRules( Collection rules ){
		for( CssRule rule : rules ){
			synchronized( RULES_LOCK ){
				this.rules.add( rule );
			}
			rule.addRuleListener( selectorChangedListener );
		}
		rulesAreSorted = false;
		rematch();
	}
	
	/**
	 * Schedules a call to {@link #match()}, the call will be executed later in the EDT.
	 */
	public void rematch(){
		if( !rematchPending ){
			EventQueue.invokeLater( new Runnable(){
				@Override
				public void run(){
					match();
				}
			} );
		}
	}
	
	/**
	 * Goes through all currently registered {@link CssItem}s and ensures they are matched with the correct
	 * {@link CssRule}.
	 */
	public void match(){
		rematchPending = false;
		ensureRulesSorted();
		
		for( Match match : items.values() ){
			match.searchRule();
		}
	}
	
	private void ensureRulesSorted(){
		if( !rulesAreSorted ){
			synchronized( RULES_LOCK ){
				Collections.sort( rules, new Comparator(){
					public int compare( CssRule a, CssRule b){
						return a.getSelector().getSpecificity().compareTo( b.getSelector().getSpecificity() );
					}
				} );
			}
			rulesAreSorted = true;
		}
	}
	
	/**
	 * Gets the {@link CssScheduler} which is responsible for asynchronous calls to the transitions. 
	 * @return the scheduler, not null
	 */
	public CssScheduler getScheduler(){
		return scheduler;
	}
	
	/**
	 * Sets the scheduler for asynchronous execution of transitions.
	 * @param scheduler the scheduler, not null
	 */
	public void setScheduler( CssScheduler scheduler ){
		if( scheduler == null ){
			throw new IllegalArgumentException( "scheduler must not be null" );
		}
		this.scheduler = scheduler;
	}
		
	/**
	 * Creates a new {@link TransitionalCssRuleChain} which will animate the properties of item.
	 * @param item the item to animate
	 * @return the new set of transitions
	 */
	protected TransitionalCssRuleChain createTransition( CssItem item ){
		return new DefaultAnimatedCssRuleChain( this, item );
	}
	
	/**
	 * Starts a new transition on top of the current transitions of item.
	 * @param item the item to animate
	 * @param transitionKey the name of the {@link CssProperty} describing transition
	 * @param transition the additional transition, will run alongside other currently
	 * running transition
	 * @throws IllegalArgumentException if item cannot be found, or
	 * if transition is null
	 */
	public void animate( CssItem item, CssPropertyKey transitionKey, CssTransition transition ){
		if( transition == null ){
			throw new IllegalArgumentException( "transition must not be null" );
		}
		
		Match match = items.get( item );
		if( match == null ){
			throw new IllegalArgumentException( "item not found" );
		}
		
		match.animate( transitionKey, transition );
	}
	
	/**
	 * Represents a match between a {@link CssRule} and a {@link CssItem}, this
	 * class ensures the transfer of the values from the rule ot the item.
	 * @author Benjamin Sigg
	 */
	private class Match implements CssItemListener, CssPathListener{
		private TransitionalCssRuleChain chain;
		private TransitionalCssRuleContent rule;
		private CssItem item;
		private CssPath path;
		
		private MatchedCssRule currentMatch;
		
		/**
		 * Creates a new match
		 * @param item the item to which to write properties
		 */
		public Match( CssItem item ){
			this.item = item;
			item.addItemListener( this );
			path = item.getPath();
			path.addPathListener( this );
			chain = createTransition( item );
		}
		
		public void destroy(){
			item.removeItemListener( this );
			item.getPath().removePathListener( this );
			chain.destroy();
		}
		
		private void searchRule(){
			setRule( search( item ) );
		}
		
		private void animate( CssPropertyKey transitionKey, CssTransition transition ){
			TransitionalCssRuleContent nextRule = chain.animate( transitionKey, transition );
			if( nextRule != rule ){
				replaceRule( nextRule );
			}
		}
		
		private void setRule( CssRuleContent nextRule ){
			if( rule == null || nextRule != rule.getRoot() ){
				TransitionalCssRuleContent nextAnimatedRule = chain.transition( nextRule );
				if( nextAnimatedRule != rule ){
					replaceRule( nextAnimatedRule );
				}
			}
		}
		
		private void replaceRule( TransitionalCssRuleContent nextRule ){
			boolean firstRule = currentMatch == null;
			
			if( currentMatch != null ){
				currentMatch.outdate();
			}
			rule = nextRule;
			
			currentMatch = new MatchedCssRule( CssScheme.this, item, nextRule );
			rule.onDestroyed( new Destroy( currentMatch ) );
			currentMatch.install( firstRule );
		}
		
		@Override
		public void pathChanged( CssItem source ){
			path.removePathListener( this );
			path = item.getPath();
			path.addPathListener( this );
			searchRule();
		}
		
		@Override
		public void pathChanged( CssPath path ){
			searchRule();	
		}
	}
	
	private static class Destroy implements Runnable{
		private MatchedCssRule rule;
		
		public Destroy( MatchedCssRule rule ){
			this.rule = rule;
		}
		
		@Override
		public void run(){
			rule.destroy();
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy