/*
* 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.common.theme;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import bibliothek.extension.gui.dock.theme.BubbleTheme;
import bibliothek.extension.gui.dock.theme.EclipseTheme;
import bibliothek.extension.gui.dock.theme.FlatTheme;
import bibliothek.extension.gui.dock.theme.SmoothTheme;
import bibliothek.gui.DockController;
import bibliothek.gui.DockTheme;
import bibliothek.gui.dock.common.CControl;
import bibliothek.gui.dock.support.util.ApplicationResource;
import bibliothek.gui.dock.support.util.ApplicationResourceManager;
import bibliothek.gui.dock.themes.BasicTheme;
import bibliothek.gui.dock.themes.ThemeFactory;
import bibliothek.gui.dock.themes.ThemePropertyFactory;
import bibliothek.util.FrameworkOnly;
import bibliothek.util.Version;
import bibliothek.util.xml.XElement;
/**
* A map of {@link ThemeFactory}s. This map can change the {@link DockTheme}
* of its associated {@link CControl}. New factories can be added or removed
* from the map.
* @author Benjamin Sigg
*/
@FrameworkOnly
public class ThemeMap {
/** standard key for the {@link CBasicTheme} */
public static final String KEY_BASIC_THEME = "basic";
/** standard key for the {@link CBubbleTheme} */
public static final String KEY_BUBBLE_THEME = "bubble";
/** standard key for the {@link CEclipseTheme} */
public static final String KEY_ECLIPSE_THEME = "eclipse";
/** standard key for the {@link CFlatTheme} */
public static final String KEY_FLAT_THEME = "flat";
/** standard key for the {@link CSmoothTheme} */
public static final String KEY_SMOOTH_THEME = "smooth";
/** modifies the themes created by this map */
private DockThemeModifier modifier;
/** the observers of this map */
private List<ThemeMapListener> listeners = new ArrayList<ThemeMapListener>();
/** the set of known factories to this map */
private List<Entry> factories = new ArrayList<Entry>();
/** the currently selected factory, can be <code>null</code> */
private Entry selected;
/**
* Creates a new empty map.
*/
public ThemeMap(){
// nothing
}
/**
* Creates a new map and wires this map to <code>control</code>. Ensures
* that the standard themes are available.<br>
* Every change of the selected factory will change the theme of <code>control</code>,
* and this map will ensure that the name of the theme is stored in the
* {@link ApplicationResourceManager}.
* @param control the control to monitor
*/
public ThemeMap( final CControl control ){
init( control );
addThemeMapListener( new ThemeMapListener(){
public void changed( ThemeMap map, int index, String key, ThemeFactory oldFactory, ThemeFactory newFactory ) {
// ignore
}
public void selectionChanged( ThemeMap map, String oldKey, String newKey ) {
ThemeFactory factory = null;
if( newKey != null )
factory = getFactory( newKey );
DockTheme theme;
if( factory == null ){
theme = new CBasicTheme( control );
}
else{
theme = factory.create( control.getController() );
}
if( modifier != null )
theme = modifier.modify( theme );
control.intern().getController().setTheme( theme );
}
});
try {
control.getResources().put( "dock.ui.ThemeMap", new ApplicationResource(){
public void read( DataInputStream in ) throws IOException {
Version.read( in ).checkCurrent();
if( in.readBoolean() ){
select( in.readUTF() );
}
else{
select( -1 );
}
}
public void readXML( XElement element ) {
String key = null;
XElement xkey = element.getElement( "key" );
if( xkey != null ){
key = xkey.getString();
}
select( key );
}
public void write( DataOutputStream out ) throws IOException {
Version.write( out, Version.VERSION_1_0_6 );
String key = getSelectedKey();
if( key == null ){
out.writeBoolean( false );
}
else{
out.writeBoolean( true );
out.writeUTF( key );
}
}
public void writeXML( XElement element ) {
String key = getSelectedKey();
if( key != null ){
element.addElement( "key" ).setString( key );
}
}
});
}
catch( IOException e ) {
e.printStackTrace();
}
if( getSelectedKey() == null ){
select( KEY_BASIC_THEME );
}
}
private void init( CControl control ){
ThemeFactory flat =
new CDockThemeFactory<FlatTheme>( new ThemePropertyFactory<FlatTheme>( FlatTheme.class ), control ){
@Override
public DockTheme create( CControl control ) {
return new CFlatTheme( control );
}
};
ThemeFactory bubble =
new CDockThemeFactory<BubbleTheme>( new ThemePropertyFactory<BubbleTheme>( BubbleTheme.class ), control ){
@Override
public DockTheme create( CControl control ) {
return new CBubbleTheme( control );
}
};
ThemeFactory eclipse = new CDockThemeFactory<EclipseTheme>( new ThemePropertyFactory<EclipseTheme>( EclipseTheme.class ), control ){
@Override
public DockTheme create( CControl control ) {
return new CEclipseTheme( control );
}
};
ThemeFactory smooth =
new CDockThemeFactory<SmoothTheme>( new ThemePropertyFactory<SmoothTheme>( SmoothTheme.class ), control ){
@Override
public DockTheme create( CControl control ) {
return new CSmoothTheme( control );
}
};
ThemeFactory basic =
new CDockThemeFactory<BasicTheme>( new ThemePropertyFactory<BasicTheme>( BasicTheme.class ), control ){
@Override
public DockTheme create( CControl control ) {
return new CBasicTheme( control );
}
};
add( KEY_BASIC_THEME, basic );
add( KEY_SMOOTH_THEME, smooth );
add( KEY_FLAT_THEME, flat );
add( KEY_BUBBLE_THEME, bubble );
add( KEY_ECLIPSE_THEME, eclipse );
}
/**
* Adds a new listener to this map. The listener will be informed when a
* factory is changed or the theme changes.
* @param listener the new listener
*/
public void addThemeMapListener( ThemeMapListener listener ){
if( listener == null )
throw new IllegalArgumentException( "listener must not be null" );
listeners.add( listener );
}
/**
* Removes a listener from this map.
* @param listener the listener to remove
*/
public void removeThemeMapListener( ThemeMapListener listener ){
listeners.remove( listener );
}
/**
* Gets an array containing all {@link ThemeMapListener}s of this map.
* @return the list of listeners
*/
protected ThemeMapListener[] listeners(){
return listeners.toArray( new ThemeMapListener[ listeners.size() ] );
}
/**
* Sets the object which will modify the {@link DockTheme} before applying
* the theme to the {@link DockController}.
* @param modifier the new modifier, can be <code>null</code>
*/
public void setModifier( DockThemeModifier modifier ) {
if( this.modifier != modifier ){
this.modifier = modifier;
String key = getSelectedKey();
select( key, true );
}
}
/**
* Gets the object which will modify the {@link DockTheme} before applying
* the theme to the {@link DockController}.
* @return the modifier, can be <code>null</code>
*/
public DockThemeModifier getModifier() {
return modifier;
}
/**
* Changes the selected factory. If there is no factory with name
* <code>key</code> or <code>key</code> is <code>null</code>, then the
* <code>null</code>-factory is selected.
* @param key the name of the newly selected factory, can be <code>null</code>
*/
public void select( String key ){
select( key, false );
}
/**
* Changes the selected factory. If there is no factory with name
* <code>key</code> or <code>key</code> is <code>null</code>, then the
* <code>null</code>-factory is selected.
* @param key the name of the newly selected factory, can be <code>null</code>
* @param force <code>true</code> if the theme is to be loaded even
* if it is already selected
*/
public void select( String key, boolean force ){
if( key == null )
select( -1, force );
else
select( indexOf( key ), force );
}
/**
* Changes the selected factory to <code>factory</code>.
* @param factory the factory to select
* @throws IllegalArgumentException if <code>factory</code> is not registered
* in this map.
*/
public void select( ThemeFactory factory ){
int index = indexOf( factory );
if( index < 0 )
throw new IllegalArgumentException( "factory not known " + factory );
select( index );
}
/**
* Changes the selected factory.
* @param index the index of the newly selected factory, -1 will deselect
* any factory
*/
public void select( int index ){
select( index, false );
}
/**
* Changes the selected factory.
* @param index the index of the newly selected factory, -1 will deselect
* any factory
* @param force <code>true</code> if an update should be forced even if
* there seems not to be a change
*/
public void select( int index, boolean force ){
Entry entry = null;
if( index >= 0 )
entry = factories.get( index );
if( entry != selected || force ){
String oldKey = selected == null ? null : selected.key;
String newKey = entry == null ? null : entry.key;
selected = entry;
for( ThemeMapListener listener : listeners() ){
listener.selectionChanged( this, oldKey, newKey );
}
}
}
/**
* Gets the name of the currently selected factory.
* @return the name or <code>null</code>
*/
public String getSelectedKey(){
return selected == null ? null : selected.key;
}
/**
* Gets the currently selected factory.
* @return the factory or <code>null</code>
*/
public ThemeFactory getSelectedFactory(){
return selected == null ? null : selected.factory;
}
/**
* Gets the number of elements of this map.
* @return the number of elements
*/
public int size(){
return factories.size();
}
/**
* Searches for an entry named <code>key</code> and changes its factory.
* If no such entry is found, then <code>factory</code> is added at the
* end of this map.
* @param key the name of the factory
* @param factory the new factory
*/
public void put( String key, ThemeFactory factory ){
if( key == null )
throw new IllegalArgumentException( "key must not be null" );
if( factory == null )
throw new IllegalArgumentException( "factory must not be null" );
int index = indexOf( key );
if( index < 0 ){
add( key, factory );
}
else{
Entry entry = factories.get( index );
ThemeFactory old = entry.factory;
entry.factory = factory;
for( ThemeMapListener listener : listeners()){
listener.changed( this, index, key, old, factory );
}
}
}
/**
* Adds <code>factory</code> at the end of this map. If there is already
* a factory named <code>key</code>, then that other factory is first removed.
* @param key the key of the new factory
* @param factory the new factory
*/
public void add( String key, ThemeFactory factory ){
insert( size(), key, factory );
}
/**
* Inserts a new factory into this map. If there is already a factory
* <code>key</code> in this map, then that other factory is removed.
* @param index the index where to insert the new factory
* @param key the key of the new factory
* @param factory the new factory
*/
public void insert( int index, String key, ThemeFactory factory ){
if( key == null )
throw new IllegalArgumentException( "key must not be null" );
if( factory == null )
throw new IllegalArgumentException( "factory must not be null" );
if( index < 0 || index > factories.size() )
throw new ArrayIndexOutOfBoundsException( index );
int remove = indexOf( key );
if( remove >= 0 ){
remove( remove );
if( index > remove )
index--;
}
Entry entry = new Entry();
entry.key = key;
entry.factory = factory;
factories.add( index, entry );
for( ThemeMapListener listener : listeners() ){
listener.changed( this, index, key, null, factory );
}
}
/**
* Removes the <code>index</code>'th entry of this map.
* @param index the name of the element to remove
*/
public void remove( int index ){
Entry entry = factories.remove( index );
for( ThemeMapListener listener : listeners() ){
listener.changed( this, index, entry.key, entry.factory, null );
}
}
/**
* Deletes the factory associated with <code>key</code>.
* @param key the name of the element to remove
* @return <code>true</code> if the element was deleted, <code>false</code>
* if no element named <code>key</code> was found
*/
public boolean remove( String key ){
int index = indexOf( key );
if( index >= 0 ){
remove( index );
return true;
}
else{
return false;
}
}
/**
* Searches for <code>factory</code> and returns its index.
* @param factory the factory to search
* @return its index or -1
*/
public int indexOf( ThemeFactory factory ){
int index = 0;
for( Entry entry : factories ){
if( entry.factory == factory )
return index;
index++;
}
return -1;
}
/**
* Searches for <code>key</code> and returns its location.
* @param key the key to search
* @return the index or -1
*/
public int indexOf( String key ){
int index = 0;
for( Entry entry : factories ){
if( entry.key.equals( key ))
return index;
index++;
}
return -1;
}
/**
* Gets the key of the <code>index</code>'th element.
* @param index the index of the element
* @return the key to that element
*/
public String getKey( int index ){
return factories.get( index ).key;
}
/**
* Gets the <code>index</code>'th factory.
* @param index the index of the factory
* @return the factory, not <code>null</code>
*/
public ThemeFactory getFactory( int index ){
return factories.get( index ).factory;
}
/**
* Searches the factory which is associated with <code>key</code>.
* @param key the unique name of a factory
* @return the factory, may be <code>null</code>
*/
public ThemeFactory getFactory( String key ){
Entry entry = getEntry( key );
if( entry == null )
return null;
return entry.factory;
}
private Entry getEntry( String key ){
for( Entry entry : factories ){
if( entry.key.equals( key ))
return entry;
}
return null;
}
/**
* An entry of this map.
* @author Benjamin Sigg
*/
private static class Entry{
/** the unique name of the entry */
public String key;
/** the factory associated to {@link #key} */
public ThemeFactory factory;
}
}