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

bibliothek.extension.gui.dock.PreferenceTable 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) 2008 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.extension.gui.dock;

import java.awt.Component;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.EtchedBorder;

import bibliothek.extension.gui.dock.preference.PreferenceEditor;
import bibliothek.extension.gui.dock.preference.PreferenceEditorCallback;
import bibliothek.extension.gui.dock.preference.PreferenceEditorFactory;
import bibliothek.extension.gui.dock.preference.PreferenceModel;
import bibliothek.extension.gui.dock.preference.PreferenceModelListener;
import bibliothek.extension.gui.dock.preference.PreferenceOperation;
import bibliothek.extension.gui.dock.preference.PreferenceOperationView;
import bibliothek.extension.gui.dock.preference.PreferenceOperationViewListener;
import bibliothek.extension.gui.dock.preference.editor.BooleanEditor;
import bibliothek.extension.gui.dock.preference.editor.ChoiceEditor;
import bibliothek.extension.gui.dock.preference.editor.KeyStrokeEditor;
import bibliothek.extension.gui.dock.preference.editor.LabelEditor;
import bibliothek.extension.gui.dock.preference.editor.ModifierMaskEditor;
import bibliothek.extension.gui.dock.preference.editor.StringEditor;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.action.ActionContentModifier;
import bibliothek.gui.dock.action.DockAction;
import bibliothek.gui.dock.themes.basic.action.BasicTrigger;
import bibliothek.gui.dock.themes.basic.action.buttons.BasicMiniButton;
import bibliothek.util.Path;

/**
 * A {@link Component} that shows the entries of a {@link PreferenceModel}, the user
 * can edit those entries. Each preference is shown in a {@link PreferenceEditor}, this
 * table uses a map of {@link PreferenceEditorFactory}s to create them. 
 * @author Benjamin Sigg
 */
public class PreferenceTable extends JPanel{
    /** The factories that are available. */
    private Map> factories = new HashMap>();
    
    /** the preferences that are shown in this table */
    private PreferenceModel model;
    
    /** the visible rows */
    private List> rows = new ArrayList>();
    
    /** the panel showing the contens of this table */
    private JPanel panel;
    
    /** the layout used on this panel */
    private GridBagLayout layout;
    
    /** a listener observing {@link #model} */
    private Listener listener = new Listener();
    
    /** the operations visible on this table */
    private List operations = new ArrayList();
    
    /** all the views that are currently in use */
    private Map operationViews = new HashMap();
    
    /** whether the order of the operations should be reversed or not */
    private boolean reverseOrder = true;
    
    /**
     * Creates a new table
     */
    public PreferenceTable(){
        super( new GridBagLayout() );
        layout = new GridBagLayout();
        panel = new JPanel( layout );
        add( panel, new GridBagConstraints( 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START,
                GridBagConstraints.HORIZONTAL, new Insets( 0, 0, 0, 0 ), 0, 0 ));
        
        setEditorFactory( Path.TYPE_BOOLEAN_PATH, BooleanEditor.FACTORY );
        setEditorFactory( Path.TYPE_MODIFIER_MASK_PATH, ModifierMaskEditor.FACTORY );
        setEditorFactory( Path.TYPE_KEYSTROKE_PATH, KeyStrokeEditor.FACTORY );
        setEditorFactory( Path.TYPE_STRING_CHOICE_PATH, ChoiceEditor.FACTORY );
        setEditorFactory( Path.TYPE_LABEL, LabelEditor.FACTORY );
        setEditorFactory( Path.TYPE_STRING_PATH, StringEditor.FACTORY );
        
        addOperation( PreferenceOperation.DEFAULT );
        addOperation( PreferenceOperation.DELETE );
    }
    
    /**
     * Creates a new table
     * @param model the model shown on this table
     */
    public PreferenceTable( PreferenceModel model ){
        this();
        setModel( model );
    }
    
    /**
     * Sets the order in which the operations should be shown. Either 
     * left to right or right to left. The default value is true
     * which means right to left.
     * @param reverseOrder how to display the operations
     */
    public void setReverseOrder( boolean reverseOrder ) {
        this.reverseOrder = reverseOrder;
        
        int index = 0;
        for( Row row : rows ){
            row.setIndex( index++ );
        }
    }
    
    /**
     * Tells in which order the operations are shown.
     * @return the order
     * @see #setReverseOrder(boolean)
     */
    public boolean isReverseOrder() {
        return reverseOrder;
    }
    
    /**
     * Adds a new operation at the end of the set of operations. The set of
     * operations tells the order in which the operations appear on the table.
     * @param operation a new operation
     */
    public void addOperation( PreferenceOperation operation ){
        if( !operations.contains( operation )){
            operations.add( operation );
            operationViews.put( operation, new Operation( operation ) );
        }
    }
    
    /**
     * Insert an operation at the given index. The set of operations tells
     * the order in which the operations appear on the table.
     * @param index the index of the new operation
     * @param operation the new operation
     */
    public void insertOperation( int index, PreferenceOperation operation ){
        if( !operations.contains( operation )){
            operations.add( index, operation );
            operationViews.put( operation, new Operation( operation ) );
        }
    }
    
    private int getOperationIndex( PreferenceOperation operation ){
        int index = operations.indexOf( operation );
        if( index < 0 ){
            operations.add( operation );
            index = operations.size()-1;
        }
        
        if( reverseOrder )
            index = operations.size() - 1 - index;
        
        return index;
    }
    
    private void addTable( Component component ){
        panel.add( component );
    }
    
    private void removeTable( Component component ){
        panel.remove( component );
    }

    /**
     * Gets the model that is used on this table.
     * @return the model
     */
    public PreferenceModel getModel() {
        return model;
    }
    
    /**
     * Changes the model of this table.
     * @param model the new model, can be null
     */
    public void setModel( PreferenceModel model ) {
    	if( this.model != model ){
	        if( this.model != null ){
	            this.model.removePreferenceModelListener( listener );
	            listener.preferenceRemoved( this.model, 0, this.model.getSize()-1 );
	        }
	        
	        this.model = model;
	        
	        if( this.model != null ){
	            this.model.addPreferenceModelListener( listener );
	            listener.preferenceAdded( this.model, 0, this.model.getSize()-1 );
	        }
    	}
    }
    
    /**
     * Sets the factory that should be used to create an editor for some 
     * type of object.
     * @param type a path describing the type that will be edited, most
     * times the path is just the name of some class. There is a set of 
     * standard paths defined in {@link Path}
     * @param factory the new factory or null to delete a factory
     */
    public void setEditorFactory( Path type, PreferenceEditorFactory factory ){
        if( factory == null )
            factories.remove( type );
        else
            factories.put( type, factory );
    }
    
    /**
     * Gets the factory which is responsible to create editors for
     * type-objects.
     * @param  the kind of objects that get edited
     * @param type the kind of objects that get edited
     * @return the factory for type or null
     */
    @SuppressWarnings("unchecked")
    public  PreferenceEditorFactory getEditorFactory( Path type ){
        return (PreferenceEditorFactory)factories.get( type );
    }
    
    /**
     * Creates a new editor for type. 
     * @param  the kind of value that gets edited
     * @param type the kind of value that gets edited
     * @return a new editor
     */
    @SuppressWarnings("unchecked")
    protected  PreferenceEditor createEditor( Path type ){
        PreferenceEditorFactory factory = factories.get( type );
        if( factory == null )
            throw new IllegalArgumentException( "No editor defined for type '" + type.toString() + "'" );
        return (PreferenceEditor)factory.create();
    }
    
    /**
     * A wrapper around a {@link PreferenceOperation} adding support for {@link PreferenceOperationView}s.
     * @author Benjamin Sigg
     */
    private class Operation{
    	private PreferenceOperation operation;
    	private PreferenceOperationView view;
    	private int usages;
    	
    	/**
    	 * Creates a new wrapper around operation
    	 * @param operation the operation represented by this wrapper
    	 */
    	public Operation( PreferenceOperation operation ){
    		this.operation = operation;
    	}
    	
    	/**
    	 * Gets the operation which is hidden behind this wrapper.
    	 * @return the operation
    	 */
    	public PreferenceOperation getOperation(){
			return operation;
		}
    	
    	/**
    	 * Gets a view of this operation
    	 * @return the view, not null
    	 */
    	public synchronized PreferenceOperationView createView(){
    		if( view == null ){
    			view = operation.create( model );
    		}
    		usages++;
    		return view;
    	}
    	
    	/**
    	 * Informs this operation that its view is no longer required
    	 */
    	public synchronized void freeView(){
    		usages--;
    		if( usages == 0 ){
    			view.destroy();
    			view = null;
    		}
    	}
    }
    
    /**
     * Represents a single row in a {@link PreferenceTable}.
     * @author Benjamin Sigg
     *
     * @param  the kind of value in this row
     */
    private class Row implements PreferenceEditorCallback{
        private int index;
        
        private JLabel label;
        private PreferenceEditor editor;
        
        private Map editorOperations;
        private Map modelOperations;
        
        private boolean initialized = false;
        
        public Row( PreferenceEditor editor, String label, String description ){
            this.editor = editor;
            this.label = new JLabel( label );
            this.label.setToolTipText( description );
            
            addTable( this.label );
            
            if( editor != null ){
                editor.setCallback( this );
            }
            
            addTable( editor.getComponent() );
        }
        
        public void setIndex( int index ) {
            this.index = index;
            
            layout.setConstraints( label,
                    new GridBagConstraints( 0, index, 1, 1, 1.0, 1.0,
                            GridBagConstraints.LINE_START, GridBagConstraints.NONE,
                            new Insets( 1, 1, 1, 1 ), 0, 0 ) );
            
            if( editor != null ){
                layout.setConstraints( editor.getComponent(),
                        new GridBagConstraints( 1, index, 1, 1, 100.0, 1.0,
                                GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL,
                                new Insets( 1, 1, 1, 1 ), 0, 0 ) );
            }
            
            if( !initialized ){
                initialized = true;
                initialize();
            }
            
            if( modelOperations != null ){
                for( Map.Entry entry : modelOperations.entrySet() ){
                    int location = 2 + getOperationIndex( entry.getKey() );
                    
                    layout.setConstraints( entry.getValue(),
                            new GridBagConstraints( location, index, 1, 1, 1.0, 1.0,
                                    GridBagConstraints.LINE_END, GridBagConstraints.NONE,
                                    new Insets( 1, 0, 1, 0 ), 0, 0 ) );
                }
            }
            
            if( editorOperations != null ){
                for( Map.Entry entry : editorOperations.entrySet() ){
                    int location = 2 + getOperationIndex( entry.getKey() );
                    
                    layout.setConstraints( entry.getValue(),
                            new GridBagConstraints( location, index, 1, 1, 1.0, 1.0,
                                    GridBagConstraints.LINE_END, GridBagConstraints.NONE,
                                    new Insets( 1, 0, 1, 0 ), 0, 0 ) );
                }
            }
            
            revalidate();
        }
        
        public void setOperation( PreferenceOperation operation, boolean enabled ) {
            setOperation( operation, enabled, true );
        }
        
        @SuppressWarnings("unchecked")
        private void initialize(){
            PreferenceOperation[] operations = model.getOperations( index );
            if( operations != null ){
                for( PreferenceOperation operation : operations ){
                    if( editorOperations == null || !editorOperations.containsKey( operation )){
                    	setOperation( operation, model.isEnabled( index, operation ), false );
                    }
                }
            }
            if( editor != null ){
            	editor.setValueInfo( model.getValueInfo( index ));
                editor.setValue( (V)model.getValue( index ) );
            }
        }
        
        private void setOperation( final PreferenceOperation operation, boolean enabled, final boolean editor ) {
            Map operations;
            
            if( editor ){
                if( editorOperations == null ){
                    editorOperations = new HashMap();
                }   
                operations = editorOperations;
            }
            else{
                if( modelOperations == null ){
                    modelOperations = new HashMap();
                }   
                operations = modelOperations;                
            }
            
            Button button = operations.get( operation );
            if( button == null ){
            	Operation view = operationViews.get( operation );
            	if( view == null ){
            		view = new Operation( operation );
            	}
            	
                button = new Button( view, editor );
                operations.put( operation, button );
                addTable( button );
                
                int location = 2 + getOperationIndex( operation );
                
                layout.setConstraints( button,
                        new GridBagConstraints( location, index, 1, 1, 1.0, 1.0,
                                GridBagConstraints.LINE_END, GridBagConstraints.NONE,
                                new Insets( 1, 0, 1, 0 ), 0, 0 ) );
                
                revalidate();
            }
            
            button.getModel().setEnabled( enabled );
        }

        @SuppressWarnings("unchecked")
        public void changed(){
            if( editor != null ){
                editor.setValue( (V)model.getValue( index ) );
            }
            
            label.setText( model.getLabel( index ) );
            label.setToolTipText( model.getDescription( index ) );
            
            if( modelOperations != null ){
                for( Map.Entry entry : modelOperations.entrySet() ){
                    boolean enabled = model.isEnabled( index, entry.getKey() );
                    entry.getValue().getModel().setEnabled( enabled );
                }
            }
        }
        
        /**
         * Destroys this row.
         */
        public void destroy(){
            removeTable( label );
            if( editor != null ){
                editor.setCallback( null );
                editor.setValue( null );
                removeTable( editor.getComponent() );
                editor.setValueInfo( null );
            }
            if( editorOperations != null ){
                for( Button button : editorOperations.values() ){
                	button.destroy();
                    removeTable( button );
                }
            }
            if( modelOperations != null ){
                for( Button button : modelOperations.values() ){
                	button.destroy();
                    removeTable( button );
                }
            }
        }
        
        @SuppressWarnings("unchecked")
        public V get() {
            return (V)model.getValue( index );
        }
        
        public void set( V value ) {
            model.setValue( index, value );
        }

        private void doOperation( PreferenceOperation operation ){
            model.doOperation( index, operation );
        }
        
        public PreferenceModel getModel(){
        	return model;
        }
        
        /**
         * A small button that can trigger an operation 
         * @author Benjamin Sigg
         */
        private class Button extends BasicMiniButton implements PreferenceOperationViewListener{
        	private Operation operation;
        	private PreferenceOperationView view;
        	
            public Button( final Operation operation, final boolean editorOperation ){
                super( new BasicTrigger(){
                    public void triggered() {
                        if( editorOperation )
                            editor.doOperation( operation.getOperation() );
                        else
                            doOperation( operation.getOperation() );
                    }
                    public DockAction getAction(){
                    	return null;
                    }
                    public Dockable getDockable(){
                    	return null;
                    }
                }, null );
                
                this.operation = operation;
                view = operation.createView();
                
                view.addListener( this );
                
                getModel().setIcon( ActionContentModifier.NONE_HORIZONTAL, view.getIcon() );
                getModel().setToolTipText( view.getDescription() );
   
                setMouseOverBorder( BorderFactory.createEtchedBorder( EtchedBorder.LOWERED ) );
                setMousePressedBorder( BorderFactory.createLoweredBevelBorder() );
            }

            @Override
            public void setEnabled( boolean enabled ) {
                super.setEnabled( enabled );
                setFocusable( enabled );
            }
            
            public void iconChanged( PreferenceOperationView operation, Icon oldIcon, Icon newIcon ){
            	getModel().setIcon( ActionContentModifier.NONE_HORIZONTAL, newIcon );
            }
            
            public void descriptionChanged( PreferenceOperationView operation, String oldDescription, String newDescription ){
            	getModel().setToolTipText( newDescription );
            }
            
            public void destroy(){
            	view.removeListener( this );
            	operation.freeView();
            }
        }
    }
    
    
    /**
     * Listens to {@link PreferenceTable#model} and updates this table when necessary.
     * @author Benjamin Sigg
     *
     */
    private class Listener implements PreferenceModelListener{
        @SuppressWarnings("unchecked")
        public void preferenceAdded( PreferenceModel model, int beginIndex, int endIndex ){
            for( int index = beginIndex; index <= endIndex; index++ ){
                PreferenceEditor editor = createEditor( model.getTypePath( index ) );
                Row row = new Row( editor, model.getLabel( index ), model.getDescription( index ));
                rows.add( index, row );
                row.setIndex( index );
            }
            
            for( int i = beginIndex, n = rows.size(); i row = rows.get( i );
                row.changed();
            }
            
            revalidate();
        }

        public void preferenceRemoved( PreferenceModel model, int beginIndex, int endIndex ){
            for( int i = endIndex; i >= beginIndex; i-- ){
                Row row = rows.remove( i );
                row.destroy();
            }
            
            for( int i = beginIndex, n = rows.size(); i




© 2015 - 2024 Weber Informatics LLC | Privacy Policy