/* * 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.themes; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.List; import javax.swing.LookAndFeel; import javax.swing.UIManager; import bibliothek.gui.DockController; import bibliothek.gui.DockStation; import bibliothek.gui.DockTheme; import bibliothek.gui.Dockable; import bibliothek.gui.dock.control.DockRegister; import bibliothek.gui.dock.control.focus.DefaultFocusRequest; import bibliothek.gui.dock.event.UIListener; import bibliothek.gui.dock.station.Combiner; import bibliothek.gui.dock.station.DisplayerFactory; import bibliothek.gui.dock.station.StationPaint; import bibliothek.gui.dock.station.span.SpanFactory; import bibliothek.gui.dock.themes.basic.action.buttons.MiniButton; import bibliothek.gui.dock.themes.border.BorderModifier; import bibliothek.gui.dock.title.DockTitleManager; import bibliothek.gui.dock.util.BackgroundPaint; import bibliothek.gui.dock.util.DockProperties; import bibliothek.gui.dock.util.Priority; import bibliothek.gui.dock.util.PropertyKey; import bibliothek.gui.dock.util.TypedPropertyUIScheme; import bibliothek.gui.dock.util.TypedUIProperties; import bibliothek.gui.dock.util.UIBridge; import bibliothek.gui.dock.util.UIValue; import bibliothek.gui.dock.util.extension.ExtensionName; import bibliothek.util.ClientOnly; import bibliothek.util.FrameworkOnly; import bibliothek.util.Path; /** * The {@link ThemeManager} is responsible for collecting properties of the current {@link DockTheme} and redistribute them. The * {@link ThemeManager} provides facilities for clients to modify and override properties of a theme without the need * to access or change the {@link DockTheme} itself. * @author Benjamin Sigg */ public class ThemeManager extends TypedUIProperties{ /** Identifier for a factory that creates {@link StationPaint}s. */ public static final Type<StationPaint> STATION_PAINT_TYPE = new Type<StationPaint>( "StationPaint" ); /** unique identifier for the basic {@link StationPaint} */ public static final String STATION_PAINT = "dock.paint"; /** Identifier for the type {@link Combiner} */ public static final Type<Combiner> COMBINER_TYPE = new Type<Combiner>( "Combiner" ); /** unique identifier for the basic {@link Combiner} */ public static final String COMBINER = "dock.combiner"; /** Identifier for the type {@link DisplayerFactory} */ public static final Type<DisplayerFactory> DISPLAYER_FACTORY_TYPE = new Type<DisplayerFactory>( "DisplayerFactory" ); /** unique identifier for the basic {@link DisplayerFactory} */ public static final String DISPLAYER_FACTORY = "dock.displayer"; /** Identifier for the type {@link BackgroundPaint} */ public static final Type<BackgroundPaint> BACKGROUND_PAINT_TYPE = new Type<BackgroundPaint>( "BackgroundPaint" ); /** unique identifier for the basic {@link BackgroundPaint} */ public static final String BACKGROUND_PAINT = "dock.background"; /** Identifier for the type {@link BorderModifier} */ public static final Type<BorderModifier> BORDER_MODIFIER_TYPE = new Type<BorderModifier>( "BorderModifier" ); /** unique identifier for the basic {@link BorderModifier} */ public static final String BORDER_MODIFIER = "dock.border"; /** Identifier for the type {@link SpanFactory} */ public static final Type<SpanFactory> SPAN_FACTORY_TYPE = new Type<SpanFactory>( "SpanFactory" ); /** unique identifier for the basic {@link SpanFactory} */ public static final String SPAN_FACTORY = "dock.spanFactory"; /** the controller owning the manager */ private DockController controller; /** the current theme */ private DockTheme theme; /** Listeners observing the ui */ private List<UIListener> uiListeners = new ArrayList<UIListener>(); /** a listener that is added to the {@link UIManager} and gets notified when the {@link LookAndFeel} changes */ private PropertyChangeListener lookAndFeelObserver = new PropertyChangeListener(){ public void propertyChange( PropertyChangeEvent evt ) { if( "lookAndFeel".equals( evt.getPropertyName() )){ updateUI(); } } }; /** items to transfer directly from {@link DockProperties} to <code>this</code> */ private TypedPropertyUIScheme transfers; /** * Creates a new object * @param controller the owner of this manager, not <code>null</code> */ public ThemeManager( DockController controller ){ super( controller ); if( controller == null ){ throw new IllegalArgumentException( "controller must not be null" ); } this.controller = controller; UIManager.addPropertyChangeListener( lookAndFeelObserver ); transfers = new TypedPropertyUIScheme( controller.getProperties() ); setScheme( Priority.THEME, transfers ); } /** * Initializes this manager, must be called exactly once. */ public void init(){ registerTypes(); link(); } private void registerTypes(){ registerType( STATION_PAINT_TYPE ); registerType( COMBINER_TYPE ); registerType( DISPLAYER_FACTORY_TYPE ); registerType( BACKGROUND_PAINT_TYPE ); registerType( BORDER_MODIFIER_TYPE ); registerType( SPAN_FACTORY_TYPE ); } private void link(){ link( DockTheme.STATION_PAINT, STATION_PAINT_TYPE, STATION_PAINT ); link( DockTheme.COMBINER, COMBINER_TYPE, COMBINER ); link( DockTheme.DISPLAYER_FACTORY, DISPLAYER_FACTORY_TYPE, DISPLAYER_FACTORY ); link( DockTheme.BACKGROUND_PAINT, BACKGROUND_PAINT_TYPE, BACKGROUND_PAINT ); link( DockTheme.BORDER_MODIFIER, BORDER_MODIFIER_TYPE, BORDER_MODIFIER ); link( DockTheme.SPAN_FACTORY, SPAN_FACTORY_TYPE, SPAN_FACTORY ); } /** * Destroys this manager and releases resources. */ @FrameworkOnly public void kill(){ theme.uninstall( controller ); UIManager.removePropertyChangeListener( lookAndFeelObserver ); } /** * Creates a link between the property <code>source</code> and the entry <code>id</code> on the * level {@link Priority#THEME}. * @param <V> the kind of property to read * @param <A> the kind of entry to write * @param source the key of the property to read * @param type the type of the entry to write * @param id the identifier of the entry to write */ public <V, A extends V> void link( PropertyKey<A> source, Type<V> type, String id ){ transfers.link( source, type, id ); } /** * Disables a link between a property and the entry <code>id</code>. * @param <V> the <code>type</code> * @param type the type of the entry * @param id the identifier of the entry to unlink * @see #link(PropertyKey, TypedUIProperties.Type, String) */ public <V> void unlink( Type<V> type, String id ){ transfers.unlink( type, id ); } /** * Adds an {@link UIListener} to this manager, the listener gets * notified when the graphical user interface needs an update because * the {@link LookAndFeel} changed. * @param listener the new listener */ public void addUIListener( UIListener listener ){ uiListeners.add( listener ); } /** * Removes a listener from this manager. * @param listener the listener to remove */ public void removeUIListener( UIListener listener ){ uiListeners.remove( listener ); } /** * Gets all the available {@link UIListener}s. * @return the list of listeners */ protected UIListener[] uiListeners(){ return uiListeners.toArray( new UIListener[ uiListeners.size() ]); } /** * Informs all registered {@link UIListener}s that the user interface * needs an update because the {@link LookAndFeel} changed. * @see #addUIListener(UIListener) * @see #removeUIListener(UIListener) */ public void updateUI(){ for( UIListener listener : uiListeners() ) listener.updateUI( controller ); } /** * Gets the current theme * @return the theme */ public DockTheme getTheme() { return theme; } /** * Sets the theme of this manager. This method fires events on registered {@link UIListener}s * and ensures that all {@link DockStation}s receive the update * @param theme the new theme */ public void setTheme( DockTheme theme ){ if( theme == null ) throw new IllegalArgumentException( "Theme must not be null" ); if( this.theme != theme ){ for( UIListener listener : uiListeners() ) listener.themeWillChange( controller, this.theme, theme ); DockRegister register = controller.getRegister(); DockTheme oldTheme = this.theme; Dockable focused = null; try{ register.setStalled( true ); focused = controller.getFocusedDockable(); if( this.theme != null ) this.theme.uninstall( controller ); this.theme = theme; ExtensionName<DockThemeExtension> name = new ExtensionName<DockThemeExtension>( DockThemeExtension.DOCK_THEME_EXTENSION, DockThemeExtension.class, DockThemeExtension.THEME_PARAMETER, theme ); List<DockThemeExtension> extensions = controller.getExtensions().load( name ); theme.install( controller, extensions.toArray( new DockThemeExtension[ extensions.size() ] ) ); controller.getDockTitleManager().registerTheme( DockTitleManager.THEME_FACTORY_ID, theme.getTitleFactory( controller ) ); // update only those station which are registered to this controller for( DockStation station : register.listDockStations() ){ if( station.getController() == controller ){ station.updateTheme(); } } } finally{ register.setStalled( false ); } controller.setFocusedDockable( new DefaultFocusRequest( focused, null, true )); for( UIListener listener : uiListeners() ) listener.themeChanged( controller, oldTheme, theme ); } } /** * Sets an algorithm to paint in the overlay panel of {@link DockStation}s. Possible * identifiers can be, but are not restricted to: * <ul> * <li>{@value #STATION_PAINT}.flap</li> * <li>{@value #STATION_PAINT}.screen</li> * <li>{@value #STATION_PAINT}.split</li> * <li>{@value #STATION_PAINT}.stack</li> * </ul> * @param id the identifier of the stations that should use <code>value</code> * @param value the new algorithm or <code>null</code> */ public void setStationPaint( String id, StationPaint value ){ put( Priority.CLIENT, id, STATION_PAINT_TYPE, value ); } /** * Sets the {@link UIBridge} that will transfer properties to those {@link UIValue}s whose kind is either * <code>kind</code> or a child of <code>kind</code>. * @param kind the kind of {@link UIValue} <code>bridge</code> will handle * @param bridge the new bridge or <code>null</code> */ public void setStationPaintBridge( Path kind, UIBridge<StationPaint, UIValue<StationPaint>> bridge ){ if( bridge == null ){ unpublish( Priority.CLIENT, kind, STATION_PAINT_TYPE ); } else{ publish( Priority.CLIENT, kind, STATION_PAINT_TYPE, bridge ); } } /** * Sets a strategy how two {@link Dockable}s can be merged into a new {@link Dockable}. * Valid identifiers may be, but are not restricted to: * <ul> * <li>{@value #COMBINER}.flap</li> * <li>{@value #COMBINER}.screen</li> * <li>{@value #COMBINER}.split</li> * <li>{@value #COMBINER}.stack</li> * </ul> * @param id the identifier of the item that uses <code>value</code> * @param value the new strategy, can be <code>null</code> */ public void setCombiner( String id, Combiner value ){ put( Priority.CLIENT, id, COMBINER_TYPE, value ); } /** * Sets the {@link UIBridge} that will transfer properties to those {@link UIValue}s whose kind is either * <code>kind</code> or a child of <code>kind</code>. * @param kind the kind of {@link UIValue} <code>bridge</code> will handle * @param bridge the new bridge or <code>null</code> */ public void setCombinerBridge( Path kind, UIBridge<Combiner, UIValue<Combiner>> bridge ){ if( bridge == null ){ unpublish( Priority.CLIENT, kind, COMBINER_TYPE ); } else{ publish( Priority.CLIENT, kind, COMBINER_TYPE, bridge ); } } /** * Sets a strategy how to display {@link Dockable}s on a {@link DockStation}. Valid * identifiers can be, but are not restricted to: * <ul> * <li>{@value #DISPLAYER_FACTORY}.flap</li> * <li>{@value #DISPLAYER_FACTORY}.screen</li> * <li>{@value #DISPLAYER_FACTORY}.split</li> * <li>{@value #DISPLAYER_FACTORY}.stack</li> * </ul> * @param id the identifier of the item that uses <code>value</code> * @param value the new strategy, can be <code>null</code> */ public void setDisplayerFactory( String id, DisplayerFactory value ){ put( Priority.CLIENT, id, DISPLAYER_FACTORY_TYPE, value ); } /** * Sets the {@link UIBridge} that will transfer properties to those {@link UIValue}s whose kind is either * <code>kind</code> or a child of <code>kind</code>. * @param kind the kind of {@link UIValue} <code>bridge</code> will handle * @param bridge the new bridge or <code>null</code> */ public void setDisplayerFactoryBridge( Path kind, UIBridge<DisplayerFactory, UIValue<DisplayerFactory>> bridge ){ if( bridge == null ){ unpublish( Priority.CLIENT, kind, DISPLAYER_FACTORY_TYPE ); } else{ publish( Priority.CLIENT, kind, DISPLAYER_FACTORY_TYPE, bridge ); } } /** * Sets a strategy to tell how to animate empty spaces when drag and dropping a {@link Dockable}. * Valid identifiers can be, but are not restricted to: * <ul> * <li>{@value #DISPLAYER_FACTORY}.flap</li> * <li>{@value #DISPLAYER_FACTORY}.split</li> * <li>{@value #DISPLAYER_FACTORY}.stack (currently not used)</li> * <li>{@value #DISPLAYER_FACTORY}.screen (currently not used)</li> * </ul> * @param id the identifier of the item that uses <code>value</code> * @param value the new strategy, can be <code>null</code> */ public void setSpanFactory( String id, SpanFactory value ){ put( Priority.CLIENT, id, SPAN_FACTORY_TYPE, value ); } /** * Sets the {@link UIBridge} that will transfer properties to those {@link UIValue}s whose kind is either * <code>kind</code> or a child of <code>kind</code>. * @param kind the kind of {@link UIValue} <code>bridge</code> will handle * @param bridge the new bridge or <code>null</code> */ public void setSpanFactoryBridge( Path kind, UIBridge<SpanFactory, UIValue<SpanFactory>> bridge ){ if( bridge == null ){ unpublish( Priority.CLIENT, kind, SPAN_FACTORY_TYPE ); } else{ publish( Priority.CLIENT, kind, SPAN_FACTORY_TYPE, bridge ); } } /** * Sets an algorithm that is used to paint the background of items which register an {@link UIValue} with * an identifier of <code>id</code>. Valid identifier can be, but are not restricted to: * <ul> * <li>{@value #BACKGROUND_PAINT}.action</li> * <li>{@value #BACKGROUND_PAINT}.displayer</li> * <li>{@value #BACKGROUND_PAINT}.dockable</li> * <li>{@value #BACKGROUND_PAINT}.station.flap</li> * <li>{@value #BACKGROUND_PAINT}.station.flap.window</li> * <li>{@value #BACKGROUND_PAINT}.station.screen</li> * <li>{@value #BACKGROUND_PAINT}.station.split</li> * <li>{@value #BACKGROUND_PAINT}.station.stack</li> * <li>{@value #BACKGROUND_PAINT}.tabPane</li> * <li>{@value #BACKGROUND_PAINT}.tabPane.child.menu</li> * <li>{@value #BACKGROUND_PAINT}.tabPane.child.tab</li> * <li>{@value #BACKGROUND_PAINT}.title</li> * </ul> * @param id the identifier of the items that should use <code>value</code> * @param value the new background algorithm, can be <code>null</code> */ @ClientOnly public void setBackgroundPaint( String id, BackgroundPaint value ){ put( Priority.CLIENT, id, BACKGROUND_PAINT_TYPE, value ); } /** * Sets the {@link UIBridge} that will transfer properties to those {@link UIValue}s whose kind is either * <code>kind</code> or a child of <code>kind</code>. * @param kind the kind of {@link UIValue} <code>bridge</code> will handle * @param bridge the new bridge or <code>null</code> */ public void setBackgroundPaintBridge( Path kind, UIBridge<BackgroundPaint, UIValue<BackgroundPaint>> bridge ){ if( bridge == null ){ unpublish( Priority.CLIENT, kind, BACKGROUND_PAINT_TYPE ); } else{ publish( Priority.CLIENT, kind, BACKGROUND_PAINT_TYPE, bridge ); } } /** * Sets a strategy that is used to modify the border of various components.<br> * Valid identifiers can be, but are not restricted to: * <ul> * <li>{@link MiniButton#BORDER_KEY_NORMAL}</li> * <li>{@link MiniButton#BORDER_KEY_NORMAL_SELECTED}</li> * <li>{@link MiniButton#BORDER_KEY_MOUSE_OVER}</li> * <li>{@link MiniButton#BORDER_KEY_MOUSE_OVER_SELECTED}</li> * <li>{@link MiniButton#BORDER_KEY_MOUSE_PRESSED}</li> * <li>{@link MiniButton#BORDER_KEY_MOUSE_PRESSED_SELECTED}</li> * <li>{@value #BORDER_MODIFIER}.displayer.basic.base</li> * <li>{@value #BORDER_MODIFIER}.displayer.basic.content</li> * <li>{@value #BORDER_MODIFIER}.displayer.bubble</li> * <li>{@value #BORDER_MODIFIER}.displayer.eclipse.no_title.out</li> * <li>{@value #BORDER_MODIFIER}.displayer.eclipse.no_title.in</li> * <li>{@value #BORDER_MODIFIER}.displayer.eclipse</li> * <li>{@value #BORDER_MODIFIER}.displayer.eclipse.content</li> * <li>{@value #BORDER_MODIFIER}.screen.window</li> * <li>{@value #BORDER_MODIFIER}.stack.eclipse</li> * <li>{@value #BORDER_MODIFIER}.stack.eclipse.content</li> * <li>{@value #BORDER_MODIFIER}.title.button</li> * <li>{@value #BORDER_MODIFIER}.title.button.flat</li> * <li>{@value #BORDER_MODIFIER}.title.button.flat.hover</li> * <li>{@value #BORDER_MODIFIER}.title.button.flat.pressed</li> * <li>{@value #BORDER_MODIFIER}.title.button.flat.selected</li> * <li>{@value #BORDER_MODIFIER}.title.button.flat.selected.hover</li> * <li>{@value #BORDER_MODIFIER}.title.button.flat.selected.pressed</li> * <li>{@value #BORDER_MODIFIER}.title.button.selected</li> * <li>{@value #BORDER_MODIFIER}.title.button.pressed</li> * <li>{@value #BORDER_MODIFIER}.title.button.selected.pressed</li> * <li>{@value #BORDER_MODIFIER}.title.eclipse.button.flat</li> * <li>{@value #BORDER_MODIFIER}.title.flat</li> * <li>{@value #BORDER_MODIFIER}.title.station.basic</li> * <li>{@value #BORDER_MODIFIER}.title.tab</li> * </ul> * @param id the identifier of the items that should use <code>modifier</code> * @param modifier the new strategy, can be <code>null</code> */ public void setBorderModifier( String id, BorderModifier modifier ){ put( Priority.CLIENT, id, BORDER_MODIFIER_TYPE, modifier ); } /** * Sets the {@link UIBridge} that will transfer properties to those {@link UIValue}s whose kind is either * <code>kind</code> or a child of <code>kind</code>. * @param kind the kind of {@link UIValue} <code>bridge</code> will handle * @param bridge the new bridge or <code>null</code> */ public void setBorderModifierBridge( Path kind, UIBridge<BorderModifier, UIValue<BorderModifier>> bridge ){ if( bridge == null ){ unpublish( Priority.CLIENT, kind, BORDER_MODIFIER_TYPE ); } else{ publish( Priority.CLIENT, kind, BORDER_MODIFIER_TYPE, bridge ); } } }