/*
* 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.AlphaComposite;
import java.awt.Color;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JComponent;
import bibliothek.extension.gui.dock.util.MouseOverListener;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.themes.color.TitleColor;
import bibliothek.gui.dock.title.AbstractDockTitle;
import bibliothek.gui.dock.title.DockTitleVersion;
import bibliothek.gui.dock.util.Transparency;
import bibliothek.util.Path;
/**
* A title that has the ability to paint a round rect as background. It also can
* apply animations when changing the state.
* @see BubbleColorAnimation
* @author Benjamin Sigg
*/
public abstract class AbstractBubbleDockTitle extends AbstractDockTitle{
/** key for the foreground color used by the animation */
protected static final String ANIMATION_KEY_TEXT = "text";
/** key for the background color at the top used by the animation */
protected static final String ANIMATION_KEY_BACKGROUND_TOP = "top";
/** key for the background color at the bottom used by the animation */
protected static final String ANIMATION_KEY_BACKGROUND_BOTTOM = "bottom";
/** An animation which can change a set of colors smoothly */
private BubbleColorAnimation animation;
/** Tells whether the mouse is over this title or not */
private MouseOverListener mouseover;
/** The size of the round edges */
private int arc = 16;
/** the colors used on this title */
private List<BubbleTitleColor> colors = new ArrayList<BubbleTitleColor>();
/**
* Creates a new title.
* @param dockable the {@link Dockable} for which this title is shown
* @param origin the creator of this title
*/
public AbstractBubbleDockTitle( Dockable dockable, DockTitleVersion origin ) {
this( dockable, origin, true );
}
/**
* Creates a new title.
* @param dockable the {@link Dockable} for which this title is shown
* @param origin the creator of this title
* @param showMiniButtons whether this title should show the {@link bibliothek.gui.dock.action.DockAction actions} or not
*/
public AbstractBubbleDockTitle( Dockable dockable, DockTitleVersion origin, boolean showMiniButtons ){
init( dockable, origin, showMiniButtons );
}
/**
* A constructor that does not do anything, subclasses should later call
* {@link #init(Dockable, DockTitleVersion, boolean)}.
*/
protected AbstractBubbleDockTitle(){
}
/**
* Initializes this title, this method should be called only once.
* @param dockable the {@link Dockable} for which this title is shown
* @param origin the creator of this title
* @param showMiniButtons whether this title should show the {@link bibliothek.gui.dock.action.DockAction actions} or not
*/
@Override
protected void init( Dockable dockable, DockTitleVersion origin, boolean showMiniButtons ){
super.init( dockable, origin, showMiniButtons );
setOpaque( false );
initAnimation();
mouseover = new MouseOverListener( getComponent() ){
@Override
protected void changed() {
updateAnimation();
}
};
}
/**
* Tells whether the mouse is currently over this title or not.
* @return <code>true</code> if the mouse is within the borders of this title
*/
public boolean isMouseOver(){
return !isDisabled() && mouseover == null ? false : mouseover.isMouseOver();
}
/**
* Registers a {@link TitleColor} width identifier <code>id</code> at this
* title.
* @param id the id of the color
* @param kind what kind of color it is (should be derived from {@link TitleColor#KIND_TITLE_COLOR}
* @param backup the standard color if nothing else is set
*/
protected void addColor( String id, Path kind, Color backup ){
BubbleTitleColor color = new BubbleTitleColor( id, kind, backup );
colors.add( color );
addColor( color );
}
/**
* Sets up the animation such that it can be started at any time.
*/
private void initAnimation(){
setTransparency( Transparency.DEFAULT );
animation = new BubbleColorAnimation();
updateAnimation();
animation.addTask( new Runnable(){
public void run() {
pulse();
}
});
setForeground( animation.getColor( "text" ));
}
@Override
public void bind() {
super.bind();
animation.kick();
}
@Override
public void setActive( boolean active ) {
if( isActive() != active ){
super.setActive( active );
updateAnimation();
}
}
@Override
protected void setDisabled( boolean disabled ){
if( isDisabled() != disabled ){
super.setDisabled( disabled );
updateAnimation();
}
}
/**
* Called when the mouse entered or left this title, or when the active
* state changed. This method has to update the animation, it should call
* {@link #updateAnimation(String, String)} for all animation-keys using
* the currently best fitting identifiers. Subclasses might want to call this
* method when some additional states changed which imply a change of the
* look of this title.
*/
protected abstract void updateAnimation();
/**
* Starts an animation for changing the color of <code>animationKey</code>
* to <code>colorId</code>.
* @param animationKey One of {@link #ANIMATION_KEY_TEXT}, {@link #ANIMATION_KEY_BACKGROUND_TOP},
* {@link #ANIMATION_KEY_BACKGROUND_BOTTOM} or if this subclasses has its
* own painting algorithm some other keys can be used.
* @param colorId One of the identifiers used on {@link #addColor(String, Path, Color)}
*/
protected void updateAnimation( String animationKey, String colorId ){
for( BubbleTitleColor color : colors ){
if( colorId.equals( color.getId() )){
animation.putColor( animationKey, color.color() );
break;
}
}
}
/**
* Gets a color for an animation that was stared with {@link #updateAnimation()}.
* @param animationKey the key for the animation
* @return the current color or <code>null</code> if not present
*/
protected Color getColor( String animationKey ){
return animation.getColor( animationKey );
}
/**
* Called every time when the colors of the animation have been changed.
*/
protected void pulse(){
setForeground( animation.getColor( ANIMATION_KEY_TEXT ));
repaint();
}
@Override
protected Insets getInnerInsets() {
int edge = arc / 4;
switch( getOrientation() ){
case EAST_SIDED: return new Insets( edge, edge/2, edge, edge );
case FREE_HORIZONTAL: return new Insets( edge, edge, edge, edge );
case FREE_VERTICAL: return new Insets( edge, edge, edge, edge );
case NORTH_SIDED: return new Insets( edge, edge, edge/2, edge );
case SOUTH_SIDED: return new Insets( edge/2, edge, edge, edge );
case WEST_SIDED: return new Insets( edge, edge, edge, edge/2 );
default: return super.getInnerInsets();
}
}
@Override
protected void paintBackground( Graphics g, JComponent component ) {
Graphics2D g2 = (Graphics2D)g.create();
g2.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
doPaintBackground( g2, component );
g2.dispose();
}
/**
* Actually paints the background with a graphics context that has special settings.
* @param g the graphics context to use
* @param component the component that is painted
*/
protected void doPaintBackground( Graphics g, JComponent component ){
Graphics2D g2 = (Graphics2D)g;
Insets insets = getInsets();
int x = 0, y = 0;
int w = component.getWidth();
int h = component.getHeight();
if( insets != null ){
x = insets.left;
y = insets.top;
w -= insets.left + insets.right;
h -= insets.top + insets.bottom;
}
// set color
Color top = animation.getColor( ANIMATION_KEY_BACKGROUND_TOP );
Color bottom = animation.getColor( ANIMATION_KEY_BACKGROUND_BOTTOM );
if( top != null && bottom != null ){
if( getOrientation().isHorizontal() )
g2.setPaint( new GradientPaint( 0, 0, top, 0, h, bottom ));
else
g2.setPaint( new GradientPaint( 0, 0, top, w, 0, bottom ));
// draw
drawRoundRect( g2, x, y, w, h );
}
}
@Override
public void paintOverlay( Graphics g ){
// draw horizon
Graphics2D g2 = (Graphics2D)g.create();
Insets insets = getInsets();
int x = 0, y = 0;
int w = getWidth();
int h = getHeight();
if( insets != null ){
x = insets.left;
y = insets.top;
w -= insets.left + insets.right;
h -= insets.top + insets.bottom;
}
Rectangle clip = g.getClipBounds();
if( clip == null ){
clip = new Rectangle( x, y, w, h );
}
// set clipping area and colors
if( getOrientation().isHorizontal() ){
g2.setPaint( new GradientPaint( 0, 0, new Color( 150, 150, 150 ), 0, h/2, Color.WHITE ));
g2.setComposite( AlphaComposite.getInstance( AlphaComposite.SRC_ATOP, 0.4f ) );
g2.setClip( 0, 0, w, h/2 );
g2.clipRect( clip.x, clip.y, clip.width, clip.height );
}
else{
g2.setPaint( new GradientPaint( 0, 0, new Color( 150, 150, 150 ), w/2, 0, Color.WHITE ));
g2.setComposite( AlphaComposite.getInstance( AlphaComposite.SRC_ATOP, 0.4f ) );
g2.setClip( 0, 0, w/2, h );
g2.clipRect( clip.x, clip.y, clip.width, clip.height );
}
drawRoundRect( g2, x, y, w, h );
g2.dispose();
}
/**
* Draws a rectangle which has some round edges within the bounds
* x,y,w,h.
* @param g2 the graphics to paint with
* @param x the x-coordinate of the bounds
* @param y the y-coordinate of the bounds
* @param w the width of the bounds
* @param h the height of the bounds
*/
private void drawRoundRect( Graphics2D g2, int x, int y, int w, int h ){
switch( getOrientation() ){
case FREE_HORIZONTAL:
case FREE_VERTICAL:
g2.fillRoundRect( x, y, w, h, arc, arc );
break;
case EAST_SIDED:
g2.fillRoundRect( x-arc, y, w+arc, h, arc, arc );
break;
case NORTH_SIDED:
g2.fillRoundRect( x, y, w, h+arc, arc, arc );
break;
case SOUTH_SIDED:
g2.fillRoundRect( x, y-arc, w, h+arc, arc, arc );
break;
case WEST_SIDED:
g2.fillRoundRect( x, y, w+arc, h, arc, arc );
break;
}
}
/**
* A color used on a {@link BubbleDockTitle}.
* @author Benjamin Sigg
*/
private class BubbleTitleColor extends TitleColor{
public BubbleTitleColor( String id, Path kind, Color backup ){
super( id, kind, AbstractBubbleDockTitle.this, backup );
}
@Override
protected void changed( Color oldColor, Color newColor ) {
updateAnimation();
}
}
}