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

bibliothek.gui.DockUI Maven / Gradle / Ivy

/*
 * 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
 * [email protected]
 * CH - Switzerland
 */

package bibliothek.gui;

import java.awt.AWTEvent;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.AWTEventListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.Icon;
import javax.swing.JDesktopPane;
import javax.swing.LookAndFeel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;

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.dock.DockFactory;
import bibliothek.gui.dock.action.DockAction;
import bibliothek.gui.dock.themes.BasicTheme;
import bibliothek.gui.dock.themes.NoStackTheme;
import bibliothek.gui.dock.themes.ThemeFactory;
import bibliothek.gui.dock.themes.ThemeProperties;
import bibliothek.gui.dock.themes.ThemePropertyFactory;
import bibliothek.gui.dock.util.IconManager;
import bibliothek.gui.dock.util.laf.DefaultLookAndFeelColors;
import bibliothek.gui.dock.util.laf.LookAndFeelColors;
import bibliothek.gui.dock.util.laf.LookAndFeelColorsListener;
import bibliothek.gui.dock.util.laf.Nimbus6u10;
import bibliothek.gui.dock.util.laf.Windows;
import bibliothek.util.container.Tuple;

/**
 * A list of icons, text and methods used by the framework. 
 * @author Benjamin Sigg
 */
public class DockUI {
    /** An instance of DockUI */
	private static DockUI ui;
	
	/** 
	 * Key for an {@link Icon} stored in the {@link IconManager} for the action-overflow-menu. This menu is shown if there
	 * are too many {@link DockAction}s to show. 
	 */
	public static final String OVERFLOW_MENU_ICON = "overflow.menu";
	
    /** A list of all available themes */
    private List themes = new ArrayList();
    
    /** contains regex-LookAndFeelColor pairs */
    private List> lookAndFeelColors = new ArrayList>();
    
    /** the currently used {@link LookAndFeelColors} */
    private LookAndFeelColors lookAndFeelColor;
    
    /** a list of color listeners that is called from {@link #colorsListeners} */
    private List colorsListeners = new ArrayList();
    
    /** whether this is a secure environment where global {@link AWTEventListener}s are not allowed */
    private Boolean secureEnvironment = null;
    
    /** a listener added to {@link #lookAndFeelColor} */
    private LookAndFeelColorsListener colorsListener = new LookAndFeelColorsListener(){
        public void colorChanged( String key ) {
            for( LookAndFeelColorsListener listener : colorsListeners.toArray( new LookAndFeelColorsListener[ colorsListeners.size()] ))
                listener.colorChanged( key );
        }

        public void colorsChanged() {
            for( LookAndFeelColorsListener listener : colorsListeners.toArray( new LookAndFeelColorsListener[ colorsListeners.size()] ))
                listener.colorsChanged();
        }
    };
    
    /**
     * Gets the default instance of DockUI.
     * @return the instance
     */
	public static DockUI getDefaultDockUI(){
		if( ui == null ){
		    synchronized( DockUI.class ){
		        if( ui == null ){
		            ui = new DockUI();
		        }
		    }
		}
		return ui;
	}
	
    /**
     * Creates a new DockUI
     */
    protected DockUI(){
        registerThemes();
        
        registerColors();
        
        UIManager.addPropertyChangeListener( new PropertyChangeListener(){
            public void propertyChange( PropertyChangeEvent evt ) {
                if( "lookAndFeel".equals( evt.getPropertyName() )){
                    updateUI();
                }
            }            
        });
    }
    
    /**
     * Called when the {@link LookAndFeel} changed.
     */
    protected void updateUI(){
        updateLookAndFeelColors();
    }
    
    private void registerThemes(){
        registerTheme( BasicTheme.class );
        registerTheme( FlatTheme.class );
        registerTheme( SmoothTheme.class );
        registerTheme( BubbleTheme.class );
        registerTheme( EclipseTheme.class );
        registerTheme( NoStackTheme.getFactory( BasicTheme.class ));
        registerTheme( NoStackTheme.getFactory( FlatTheme.class ));
        registerTheme( NoStackTheme.getFactory( SmoothTheme.class ));
        registerTheme( NoStackTheme.getFactory( BubbleTheme.class ));
    }
    
    private void registerColors(){
        registerColors( ".+", new DefaultLookAndFeelColors() );
        
        String jvmVersionString = System.getProperty("java.specification.version");
        int verIndex = jvmVersionString.indexOf("1.");
        if (verIndex >= 0) { // handle Java 9
            jvmVersionString = jvmVersionString.substring(verIndex + 2);
        }
        int major = Integer.parseInt(jvmVersionString);
        if( major >= 7 ){
        	registerColors( "javax\\.swing\\.plaf\\.nimbus\\.NimbusLookAndFeel", new Nimbus6u10() );
        }
        else{
        	registerColors( "com\\.sun\\.java\\.swing\\.plaf\\.nimbus\\.NimbusLookAndFeel", new Nimbus6u10() );
        }
        registerColors( "com\\.sun\\.java\\.swing\\.plaf\\.windows\\.WindowsLookAndFeel", new Windows() );
    }
    
    /**
     * Gets the default-theme to be used by all {@link DockController}s when
     * nothing else is specified.
     * @return the default-theme
     */
    public ThemeFactory getDefaultTheme(){
        return themes.get( 0 );
    }
    
    /**
     * Gets the list of all available themes.
     * @return the themes
     */
    public ThemeFactory[] getThemes(){
        return themes.toArray( new ThemeFactory[ themes.size() ] );
    }
    
    /**
     * Registers a factory for theme.
     * @param  the type of the {@link DockTheme}.
     * @param theme A class which must have the annotation 
     * {@link ThemeProperties}
     */
    public  void registerTheme( Class theme ){
        registerTheme( new ThemePropertyFactory( theme ));
    }
    
    /**
     * Stores a new theme.
     * @param factory the new theme
     */
    public void registerTheme( ThemeFactory factory ){
        if( factory == null )
            throw new IllegalArgumentException( "Theme must not be null" );
        
        themes.add( factory );
    }
    
    /**
     * Removes an earlier added factory from the set of theme-factories.
     * @param factory the factory to remove
     */
    public void unregisterTheme( ThemeFactory factory ){
        themes.remove( factory );
    }
    
    /**
     * Registers a new {@link LookAndFeelColors}. The lookAndFeelClassNameRegex
     * is a regular expression. If a {@link LookAndFeel} is active whose class name
     * {@link String#matches(String) matches} lookAndFeelClassNameRegex,
     * then colors becomes the selected source for colors. If more
     * then one regex matches, the last one that was added to this {@link DockUI}
     * is taken. So generally one would first add the most general regexes, and
     * the more detailed ones later.
     * @param lookAndFeelClassNameRegex a description of a class name
     * @param colors the new set of colors
     */
    public void registerColors( String lookAndFeelClassNameRegex, LookAndFeelColors colors ){
        if( lookAndFeelClassNameRegex == null )
            throw new IllegalArgumentException( "lookAndFeelClassNameRegex must not be null" );
            
        if( colors == null )
            throw new IllegalArgumentException( "colors must not be null" );
        
        lookAndFeelColors.add( new Tuple( lookAndFeelClassNameRegex, colors ));
        updateLookAndFeelColors();
    }
    
    /**
     * Adds a listener which gets informed when a color of the current
     * {@link LookAndFeelColors} changes. This listener gets not informed
     * about any changes when the {@link LookAndFeel} itself gets replaced.
     * This listener will automatically be transferred when another
     * {@link LookAndFeelColors} gets selected.
     * @param listener the new listener, not null
     */
    public void addLookAndFeelColorsListener( LookAndFeelColorsListener listener ){
        if( listener == null )
            throw new IllegalArgumentException( "listener must not be null" );
        
        colorsListeners.add( listener );
    }
    
    /**
     * Removes a listener from this {@link DockUI}.
     * @param listener the listener to remove
     */
    public void removeLookAndFeelColorsListener( LookAndFeelColorsListener listener ){
        colorsListeners.remove( listener );
    }
    
    /**
     * Updates the currently used {@link LookAndFeelColors} to the best
     * matching colors.
     */
    protected void updateLookAndFeelColors(){
        LookAndFeelColors next = selectBestMatchingColors();
        if( next != lookAndFeelColor ){
            if( lookAndFeelColor != null ){
                lookAndFeelColor.unbind();
                lookAndFeelColor.removeListener( colorsListener );
            }
            
            lookAndFeelColor = next;
            if( next != null ){
                next.bind();
                lookAndFeelColor.addListener( colorsListener );
            }
            colorsListener.colorsChanged();
        }
    }
    
    /**
     * Gets the {@link LookAndFeelColors} which matches the current
     * {@link LookAndFeel} best.
     * @return the current set of colors
     */
    protected LookAndFeelColors selectBestMatchingColors(){
        String className = UIManager.getLookAndFeel().getClass().getName();
        for( int i = lookAndFeelColors.size()-1; i >= 0; i-- ){
            if( className.matches( lookAndFeelColors.get( i ).getA() ))
                return lookAndFeelColors.get( i ).getB();
        }
        
        return null;
    }
    
    /**
     * Gets the current source of colors that depend on the {@link LookAndFeel}.
     * @return the current source of colors
     */
    public LookAndFeelColors getColors(){
        return lookAndFeelColor;
    }
    
    /**
     * Gets the color key where key is one of
     * the keys specified in {@link LookAndFeelColors}.
     * @param key the name of the color
     * @return the color or null
     */
    public static Color getColor( String key ){
        return getDefaultDockUI().getColors().getColor( key );
    }
    
    /**
     * Removes all children of station and then adds
     * the children again. Reading the children ensures that all components are
     * build up again with the current theme of the station
     * @param  the type of the station
     * @param  the type of the layout needed to describe the contents
     * of the station
     * @param station the station to update
     * @param factory a factory used to remove and to add the elements
     * @throws IOException if the factory throws an exception
     */
    public static  void updateTheme( D station, DockFactory factory ) throws IOException{
        Map children = new HashMap();
    	Map ids = new HashMap();
    	
    	for( int i = 0, n = station.getDockableCount(); i= 0; i-- ){
    		    station.drag( station.getDockable( i ));
    	    }
    	
    	    factory.setLayout( station, layout, children, null );
    	}
    	finally{
    		if( controller != null ){
    			controller.getRegister().setStalled( false );
    			controller.getHierarchyLock().setConcurrent( false );
    		}
    	}
    }
    
    /**
     * Searches the first {@link JDesktopPane} which either is component
     * or a parent of component.
     * @param component the component whose parent is searched
     * @return the parent {@link JDesktopPane} or null if not found
     */
    public static JDesktopPane getDesktopPane( Component component ){
		while( component != null ){
			if( component instanceof JDesktopPane ){
				return ((JDesktopPane)component);
			}
			component = component.getParent();
		}
		return null;
    }
    
    /**
     * Tells whether above overlaps under. This method
     * assumes that both components have a mutual parent. The method checks the location
     * and the z-order of both components.
     * @param above the component that is supposed to be above under
     * @param under the component that is supposed to be under above
     * @return true is above is overlapping under
     */
    public static boolean isOverlapping( Component above, Component under ){
    	if( SwingUtilities.isDescendingFrom( under, above )){
    		return false;
    	}
    	if( SwingUtilities.isDescendingFrom( above, under )){
    		return true;
    	}
    	if( above == under ){
    		return true;
    	}
    	
    	Container parent = above.getParent();
    	while( parent != null ){
    		if( SwingUtilities.isDescendingFrom( under, parent )){
    			// found mutual parent
    			
    			Point locationA = new Point( 0, 0 );
    			Point locationU = new Point( 0, 0 );
    			
    			locationA = SwingUtilities.convertPoint( above, locationA, parent );
    			locationU = SwingUtilities.convertPoint( under, locationU, parent );
    			
    			Rectangle boundsA = new Rectangle( locationA, above.getSize() );
    			Rectangle boundsU = new Rectangle( locationU, under.getSize() );
    			
    			if( !boundsA.intersects( boundsU )){
    				return false;
    			}
    			
    			Component pathA = firstOnPath( parent, above );
    			Component pathU = firstOnPath( parent, under );
    			
    			int zA = parent.getComponentZOrder( pathA );
    			int zU = parent.getComponentZOrder( pathU );
    			
    			return zA < zU;
    		}
    		parent = parent.getParent();
    	}
    	return false;
    }
    
    /**
     * Tells whether this application runs in a restricted environment or not. This method
     * only makes a guess and may return a false result.
     * @return whether this is a restricted environment
     */
    public boolean isSecureEnvironment(){
    	if( secureEnvironment != null ){
    		return secureEnvironment;
    	}
    	
        try{
        	Toolkit toolkit = Toolkit.getDefaultToolkit();
        	AWTEventListener listener = new AWTEventListener(){
				public void eventDispatched( AWTEvent event ){
					// ignore	
				}
			}; 
        	toolkit.addAWTEventListener( listener, AWTEvent.KEY_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK | AWTEvent.MOUSE_WHEEL_EVENT_MASK );
        	toolkit.removeAWTEventListener( listener );
			
//            SecurityManager security = System.getSecurityManager();
//            if( security != null ){
//                security.checkPermission(SecurityConstants.ALL_AWT_EVENTS_PERMISSION);
//            }
        }
        catch( SecurityException ex ){
        	secureEnvironment = true;
            return true;
        }
        
        secureEnvironment = false;
        return false;
    }
    
    /**
     * Overrides the result of {@link #isSecureEnvironment()}, any future call of that method will
     * return secureEnvironment.
     * @param secureEnvironment Whether global {@link AWTEventListener}s are allowed or not, a value of true 
     * indicates that listeners are not allowed
     */
    public void setSecureEnvironment( boolean secureEnvironment ){
		this.secureEnvironment = secureEnvironment;
	}
    
    private static Component firstOnPath( Container parent, Component child ){
    	Component result = child;
    	while( result.getParent() != parent ){
    		result = result.getParent();
    	}
    	return result;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy