/* * 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.extension.gui.dock.theme.bubble; import java.awt.Color; import java.awt.Component; import java.awt.Graphics; import java.awt.GridLayout; import java.awt.Insets; import java.awt.event.MouseEvent; import javax.swing.JPanel; import javax.swing.border.Border; import javax.swing.event.MouseInputAdapter; import bibliothek.extension.gui.dock.theme.BubbleTheme; import bibliothek.gui.DockController; import bibliothek.gui.DockStation; import bibliothek.gui.Dockable; import bibliothek.gui.dock.DockElement; import bibliothek.gui.dock.disable.DisablingStrategy; import bibliothek.gui.dock.disable.DisablingStrategyListener; import bibliothek.gui.dock.event.DockableFocusEvent; import bibliothek.gui.dock.event.DockableFocusListener; import bibliothek.gui.dock.station.DockableDisplayer; import bibliothek.gui.dock.themes.basic.BasicDockableDisplayer; import bibliothek.gui.dock.themes.basic.BasicDockableDisplayerDecorator; import bibliothek.gui.dock.themes.basic.TabDecorator; import bibliothek.gui.dock.themes.color.DisplayerColor; import bibliothek.gui.dock.title.DockTitle; import bibliothek.gui.dock.util.PropertyValue; import bibliothek.gui.dock.util.color.ColorCodes; /** * A {@link DockableDisplayer} drawing a border around its content, but leaves * the side at which the title lies open. * @author Benjamin Sigg */ @ColorCodes({ "displayer.border.high.active", "displayer.border.high.active.mouse", "displayer.border.high.inactive", "displayer.border.high.inactive.mouse", "displayer.border.high.disabled", "displayer.border.low.active", "displayer.border.low.active.mouse", "displayer.border.low.inactive", "displayer.border.low.inactive.mouse", "displayer.border.low.disabled" }) public class BubbleDisplayer extends BasicDockableDisplayer { /** the size of the border in pixel */ private int borderSize = 2; /** the panel on which the {@link Dockable} of this displayer is shown */ private JPanel dockable; /** the animation changing the colors of this displayer */ private BubbleColorAnimation animation; private DisplayerColor borderHighActive = new BubbleDisplayerColor( "displayer.border.high.active", Color.WHITE ); private DisplayerColor borderHighActiveMouse = new BubbleDisplayerColor( "displayer.border.high.active.mouse", Color.WHITE ); private DisplayerColor borderHighInactive = new BubbleDisplayerColor( "displayer.border.high.inactive", Color.DARK_GRAY ); private DisplayerColor borderHighInactiveMouse = new BubbleDisplayerColor( "displayer.border.high.inactive.mouse", Color.DARK_GRAY ); private DisplayerColor borderHighDisabled = new BubbleDisplayerColor( "displayer.border.high.disabled", Color.WHITE ); private DisplayerColor borderLowActive = new BubbleDisplayerColor( "displayer.border.low.active", Color.LIGHT_GRAY ); private DisplayerColor borderLowActiveMouse = new BubbleDisplayerColor( "displayer.border.low.active.mouse", Color.LIGHT_GRAY ); private DisplayerColor borderLowInactive = new BubbleDisplayerColor( "displayer.border.low.inactive", Color.BLACK ); private DisplayerColor borderLowInactiveMouse = new BubbleDisplayerColor( "displayer.border.low.inactive.mouse", Color.BLACK ); private DisplayerColor borderLowDisabled = new BubbleDisplayerColor( "displayer.border.low.disabled", Color.LIGHT_GRAY ); /** <code>true</code> if the mouse is over the title of this displayer */ private boolean mouse = false; /** whether the {@link Dockable} of this displayer is disabled */ private boolean disabled = false; /** * a listener to the controller informing this displayer when the focused * {@link Dockable} has changed. */ private Listener listener = new Listener(); /** The border of this displayer */ private DisplayerBorder openBorder; /** the current {@link DisablingStrategy} */ private PropertyValue<DisablingStrategy> disablingStrategy = new PropertyValue<DisablingStrategy>( DisablingStrategy.STRATEGY ){ @Override protected void valueChanged( DisablingStrategy oldValue, DisablingStrategy newValue ){ if( oldValue != null ){ oldValue.removeDisablingStrategyListener( disablingStrategyListener ); } if( newValue != null ){ newValue.addDisablingStrategyListener( disablingStrategyListener ); setDisabled( newValue.isDisabled( getDockable() )); } else{ setDisabled( false ); } } }; /** a listener to {@link #disablingStrategy} */ private DisablingStrategyListener disablingStrategyListener = new DisablingStrategyListener(){ public void changed( DockElement item ){ if( getDockable() == item ){ setDisabled( disablingStrategy.getValue().isDisabled( item )); } } }; /** * Creates a new displayer * @param station the station for which this displayer will be used * @param dockable the {@link Dockable} which will be shown on this displayer, might be <code>null</code> * @param title the title to show on this displayer, might be <code>null</code> */ public BubbleDisplayer( DockStation station, Dockable dockable, DockTitle title ){ super( station, dockable, title ); this.dockable.setOpaque( false ); openBorder = new DisplayerBorder( this.dockable, "bubble" ); animation = new BubbleColorAnimation(); animation.addTask( new Runnable(){ public void run() { pulse(); } }); updateAnimation(); setRespectBorderHint( true ); setDefaultBorderHint( true ); setSingleTabShowInnerBorder( true ); setSingleTabShowOuterBorder( false ); } /** * Called when the {@link DisablingStrategy} changes the disabled state. * @param disabled the new state of the {@link Dockable} */ protected void setDisabled( boolean disabled ){ if( this.disabled != disabled ){ this.disabled = disabled; updateAnimation(); } } /** * Whether the {@link Dockable} is currently enabled or not * @return the disabled state */ public boolean isDisabled(){ return disabled; } /** * Sets the colors to which the animation should run. */ protected void updateAnimation(){ if( animation != null ){ DockController controller = getController(); if( isDisabled() ){ animation.putColor( "high", borderHighDisabled.value() ); animation.putColor( "low", borderLowDisabled.value() ); } else if( controller != null && controller.getFocusedDockable() == getDockable() ){ if( mouse ){ animation.putColor( "high", borderHighActiveMouse.value() ); animation.putColor( "low", borderLowActiveMouse.value() ); } else{ animation.putColor( "high", borderHighActive.value() ); animation.putColor( "low", borderLowActive.value() ); } } else{ if( mouse ){ animation.putColor( "high", borderHighInactiveMouse.value() ); animation.putColor( "low", borderLowInactiveMouse.value() ); } else{ animation.putColor( "high", borderHighInactive.value() ); animation.putColor( "low", borderLowInactive.value() ); } } } } /** * Called by the animation when the colors changed and the displayer should * be repainted. */ protected void pulse(){ dockable.repaint(); } @Override public void setController( DockController controller ) { DockController old = getController(); if( old != controller ){ if( old != null ) old.removeDockableFocusListener( listener ); if( controller != null ) controller.addDockableFocusListener( listener ); super.setController( controller ); } disablingStrategy.setProperties( controller ); openBorder.setController( controller ); borderHighActive.connect( controller ); borderHighActiveMouse.connect( controller ); borderHighInactive.connect( controller ); borderHighInactiveMouse.connect( controller ); borderHighDisabled.connect( controller ); borderLowActive.connect( controller ); borderLowActiveMouse.connect( controller ); borderLowInactive.connect( controller ); borderLowInactiveMouse.connect( controller ); borderLowDisabled.connect( controller ); animation.kick(); } @Override protected Component getComponent( Dockable dockable ) { ensureDockable(); return this.dockable; } @Override public void setTitle( DockTitle title ) { DockTitle old = getTitle(); if( old != null ) old.removeMouseInputListener( listener ); super.setTitle( title ); if( title != null ) title.addMouseInputListener( listener ); mouse = false; updateAnimation(); ensureDockable(); } @Override public void setDockable( Dockable dockable ) { super.setDockable( dockable ); ensureDockable(); this.dockable.removeAll(); if( dockable != null ){ this.dockable.add( dockable.getComponent() ); } ensureBorder(); } /** * Ensures that there is a panel for the {@link Dockable} */ private void ensureDockable(){ if( dockable == null ){ dockable = new JPanel( new GridLayout( 1, 1 )); } ensureBorder(); } private void ensureBorder(){ if( dockable != null ){ Dockable dock = getDockable(); boolean station = dock != null && dock.asDockStation() != null; if( getTitle() == null && station ) setDefaultBorderHint( false ); else setDefaultBorderHint( true ); } } @Override protected void updateBorder() { if( isRespectBorderHint() ){ if( getHints().getShowBorderHint() || getTitle() != null ){ openBorder.setBorder( getDefaultBorder() ); } else{ openBorder.setBorder( null ); } } } @Override protected Border getDefaultBorder() { return new OpenBorder(); } @Override public Insets getDockableInsets() { Insets insets = super.getDockableInsets(); Border border = dockable.getBorder(); if( border != null ){ Insets borderInsets = border.getBorderInsets( dockable ); insets.left += borderInsets.left; insets.right += borderInsets.right; insets.top += borderInsets.top; insets.bottom += borderInsets.bottom; } return insets; } @Override protected BasicDockableDisplayerDecorator createStackedDecorator(){ return createStackedDecorator( BubbleTheme.ACTION_DISTRIBUTOR ); } @Override protected TabDecorator createTabDecorator(){ return new TabDecorator( getStation(), BubbleTheme.ACTION_DISTRIBUTOR ); } /** * A listener to the controller, reacting when the focused {@link Dockable} * has changed. * @author Benjamin Sigg */ private class Listener extends MouseInputAdapter implements DockableFocusListener{ public void dockableFocused( DockableFocusEvent event ) { updateAnimation(); } @Override public void mouseEntered( MouseEvent e ) { mouse = true; updateAnimation(); } @Override public void mouseExited( MouseEvent e ) { mouse = false; updateAnimation(); } } /** * A color used on a {@link BubbleDisplayer}. * @author Benjamin Sigg */ private class BubbleDisplayerColor extends DisplayerColor{ /** * Creates a new color. * @param id the name of the color * @param backup a backup in case that no color could be read */ public BubbleDisplayerColor( String id, Color backup ) { super( id, BubbleDisplayer.this, backup ); } @Override protected void changed( Color oldColor, Color newColor ) { updateAnimation(); } } /** * The border which will be painted around the {@link BubbleDisplayer#dockable dockable}. * @author Benjamin Sigg */ public class OpenBorder implements Border{ public Insets getBorderInsets( Component c ) { if( getTitle() == null ) return new Insets( borderSize, borderSize, borderSize, borderSize ); else{ switch( getTitleLocation() ){ case BOTTOM: return new Insets( borderSize, borderSize, 0, borderSize ); case LEFT: return new Insets( borderSize, 0, borderSize, borderSize ); case RIGHT: return new Insets( borderSize, borderSize, borderSize, 0 ); case TOP: return new Insets( 0, borderSize, borderSize, borderSize ); } } // error return new Insets( 0, 0, 0, 0 ); } public boolean isBorderOpaque() { return true; } public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) { Color high = animation.getColor( "high" ); Color low = animation.getColor( "low" ); boolean noTitle = getTitle() == null; boolean top = noTitle || getTitleLocation() != Location.TOP; boolean left = noTitle || getTitleLocation() != Location.LEFT; boolean right = noTitle || getTitleLocation() != Location.RIGHT; boolean bottom = noTitle || getTitleLocation() != Location.BOTTOM; int highSize = borderSize / 2; int lowSize = borderSize - highSize; if( top ){ g.setColor( high ); g.fillRect( x, y, width, highSize ); g.setColor( low ); g.fillRect( x, y+highSize, width, lowSize ); } if( left ){ g.setColor( high ); g.fillRect( x, y, highSize, height ); g.setColor( low ); if( top ) g.fillRect( x+highSize, y+highSize, lowSize, height-highSize ); else g.fillRect( x+highSize, y, lowSize, height ); } if( right ){ g.setColor( high ); g.fillRect( x+width-borderSize, y, highSize, height ); g.setColor( low ); if( top ) g.fillRect( x+width-lowSize, y+highSize, lowSize, height-highSize ); else g.fillRect( x+width-lowSize, y, lowSize, height ); } if( bottom ){ g.setColor( high ); if( right ) g.fillRect( x, y+height-borderSize, width-borderSize, highSize ); else g.fillRect( x, y+height-borderSize, width, highSize ); g.setColor( low ); g.fillRect( x, y+height-lowSize, width, lowSize ); } } } }