/*
* 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.station.flap;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JRootPane;
import javax.swing.SwingUtilities;
import javax.swing.border.BevelBorder;
import bibliothek.gui.DockController;
import bibliothek.gui.DockStation;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.FlapDockStation;
import bibliothek.gui.dock.FlapDockStation.Direction;
import bibliothek.gui.dock.security.SecureContainer;
import bibliothek.gui.dock.station.DockableDisplayer;
import bibliothek.gui.dock.station.DockableDisplayerListener;
import bibliothek.gui.dock.station.StationChildHandle;
import bibliothek.gui.dock.station.StationPaint;
import bibliothek.gui.dock.themes.ThemeManager;
import bibliothek.gui.dock.themes.border.BorderForwarder;
import bibliothek.gui.dock.title.DockTitle;
import bibliothek.gui.dock.title.DockTitleVersion;
import bibliothek.gui.dock.util.BackgroundAlgorithm;
import bibliothek.gui.dock.util.BackgroundPanel;
import bibliothek.gui.dock.util.Transparency;
/**
* This window pops up if the user presses one of the buttons of a
* {@link FlapDockStation}. The window shows one {@link Dockable}
*/
public class DefaultFlapWindow implements FlapWindow, MouseListener, MouseMotionListener {
/** the element that is shown on this window */
private StationChildHandle dockable;
/** a listener for the current {@link DockableDisplayer} */
private DockableDisplayerListener displayerListener = new DockableDisplayerListener(){
public void discard( DockableDisplayer displayer ){
discardDisplayer();
}
public void moveableElementChanged( DockableDisplayer displayer ){
// ignore
}
};
/** the border of the {@link #contentPane} */
private BorderForwarder contentBorder;
/** <code>true</code> if the mouse is currently pressed */
private boolean pressed;
/** The owner of this window */
private FlapDockStation station;
/** The buttons on the station */
private ButtonPane buttonPane;
/** Information where the user will drop or move a {@link Dockable} */
private FlapDropInfo dropInfo;
/** Information of an ongoing drag and drop operation that is removing the child of this window */
private boolean removal;
/** the panel onto which {@link #dockable} is put */
private JComponent contentPane;
/** the window on which this {@link DefaultFlapWindow} is shown */
private Parent window;
/** the parent of the {@link #contentPane} */
private SecureContainer contentContainer;
/** the background algorithm */
private Background background = new Background();
/**
* Constructs a new window.
* @param station the station which manages this window
* @param buttonPane the buttons on the station
* @param window the window on which to paint this {@link DefaultFlapWindow}
*/
public DefaultFlapWindow( FlapDockStation station, ButtonPane buttonPane, Parent window ){
this.station = station;
this.buttonPane = buttonPane;
this.window = window;
init();
}
private void init(){
contentContainer = new SecureContainer(){
@Override
protected void paintOverlay( Graphics g ){
if( removal || (dropInfo != null && dropInfo.getCombineTarget() != null) ) {
Rectangle bounds = new Rectangle(0, 0, getWidth(), getHeight());
StationPaint paint = DefaultFlapWindow.this.station.getPaint().get();
if( paint != null ){
if( dropInfo != null && dropInfo.getCombineTarget() != null ){
dropInfo.getCombineTarget().paint( g, contentContainer, paint, bounds, bounds );
}
else if( removal ){
paint.drawRemoval( g, getStation(), bounds, bounds );
}
}
}
}
};
BackgroundPanel content = new BackgroundPanel( Transparency.SOLID ){
@Override
protected void configure( Transparency transparency ){
// no support for transparency as this is a root component and can never be transparent
}
@Override
protected void setupRenderingHints( Graphics g ) {
// ignore
}
};
content.setBackground( background );
contentContainer.getBasePane().setLayout( new BorderLayout() );
contentContainer.getBasePane().add( content, BorderLayout.CENTER );
contentContainer.setContentPane( content );
window.setContentPane(contentContainer);
contentPane = contentContainer.getContentPane();
contentPane.setOpaque( false );
contentBorder = new WindowBorder( contentPane );
contentBorder.setBorder( BorderFactory.createBevelBorder(BevelBorder.RAISED) );
contentPane.addMouseListener(this);
contentPane.addMouseMotionListener(this);
contentPane.setLayout(new LayoutManager(){
public void addLayoutComponent( String name, Component comp ){
// do nothing
}
public void removeLayoutComponent( Component comp ){
// do nothing
}
public Dimension preferredLayoutSize( Container parent ){
DockableDisplayer displayer = getDisplayer();
if( displayer == null )
return new Dimension(100, 100);
return displayer.getComponent().getPreferredSize();
}
public Dimension minimumLayoutSize( Container parent ){
DockableDisplayer displayer = getDisplayer();
if( displayer == null )
return new Dimension(100, 100);
return displayer.getComponent().getMinimumSize();
}
public void layoutContainer( Container parent ){
DockableDisplayer displayer = getDisplayer();
if( displayer != null ) {
Insets insets = parent.getInsets();
insets = new Insets(insets.top, insets.left, insets.bottom, insets.right);
if( station.getDirection() == Direction.SOUTH )
insets.bottom += station.getWindowBorder();
else if( station.getDirection() == Direction.NORTH )
insets.top += station.getWindowBorder();
else if( station.getDirection() == Direction.EAST )
insets.right += station.getWindowBorder();
else
insets.left += station.getWindowBorder();
displayer.getComponent().setBounds(insets.left, insets.top, parent.getWidth() - insets.left - insets.right,
parent.getHeight() - insets.top - insets.bottom);
}
}
});
window.asComponent().addComponentListener(new ComponentListener(){
public void componentHidden( ComponentEvent e ){
// ignore
}
public void componentMoved( ComponentEvent e ){
// ignore
}
public void componentResized( ComponentEvent e ){
// ignore
}
public void componentShown( ComponentEvent e ){
if( !DefaultFlapWindow.this.station.isFlapWindow(DefaultFlapWindow.this) || getDockable() == null ) {
// This window should not be visible if it is not used
// by its former owner
SwingUtilities.invokeLater(new Runnable(){
public void run(){
destroy();
}
});
}
}
});
}
public void setWindowVisible( boolean flag ){
if( flag ) {
// Actually this should not be necessary and only
// prevents some strange bug where the size gets wrong,
// the origin of the bug remains a mystery.
updateBounds();
}
window.setVisible( flag );
}
public boolean isWindowVisible(){
return window != null && window.isVisible();
}
public Rectangle getWindowBounds(){
return window.asComponent().getBounds();
}
public void destroy(){
if( window != null ){
setController( null );
setDockable( null );
window.destroy();
window = null;
}
}
public void repaint(){
if( window != null ){
window.asComponent().repaint();
}
}
public Component getComponent() {
if( window == null ){
return null;
}
return window.asComponent();
}
/**
* Tells whether this window is still valid, e.g whether the window can be shown
* in front of its station.
* @return whether this window is still valid
*/
public boolean isWindowValid(){
return window != null && window.isParentValid();
}
public boolean containsScreenPoint( Point point ){
point = new Point(point);
Component parent = window.asComponent();
SwingUtilities.convertPointFromScreen(point, parent);
return parent.contains(point);
}
/**
* Gets the station for which this window is shown.
* @return the owner of this window
*/
public FlapDockStation getStation(){
return station;
}
/**
* Sets information where a {@link Dockable} will be dropped.
* @param dropInfo the information or <code>null</code>
*/
public void setDropInfo( FlapDropInfo dropInfo ){
this.dropInfo = dropInfo;
repaint();
}
public void setRemoval( boolean removal ){
this.removal = removal;
repaint();
}
/**
* Sets the title which should be displayed.
* @param title the title or <code>null</code>
*/
public void setDockTitle( DockTitleVersion title ){
if( dockable != null ) {
dockable.setTitleRequest(title);
}
}
/**
* Gets the title which is currently displayed
* @return the title or <code>null</code>
*/
public DockTitle getDockTitle(){
if( dockable == null )
return null;
return dockable.getTitle();
}
/**
* Gets the {@link Dockable} which is shown on this window.
* @return The {@link Dockable} or <code>null</code>
*/
public Dockable getDockable(){
if( dockable == null )
return null;
return dockable.getDockable();
}
/**
* Gets the displayer used to show a {@link Dockable}.
* @return the displayer, might be <code>null</code>
*/
public DockableDisplayer getDisplayer(){
if( dockable == null )
return null;
return dockable.getDisplayer();
}
/**
* Sets the {@link Dockable} which will be shown on this window.
* @param dockable The <code>Dockable</code> or <code>null</code>
*/
public void setDockable( Dockable dockable ){
Container content = getDisplayerParent();
if( this.dockable != null ) {
DockableDisplayer displayer = getDisplayer();
displayer.removeDockableDisplayerListener(displayerListener);
content.remove(displayer.getComponent());
this.dockable.destroy();
this.dockable = null;
}
if( dockable != null ) {
this.dockable = new StationChildHandle(station, station.getDisplayers(), dockable, station.getTitleVersion());
this.dockable.updateDisplayer();
DockableDisplayer displayer = getDisplayer();
displayer.addDockableDisplayerListener(displayerListener);
content.add(displayer.getComponent());
}
}
/**
* Replaces the current {@link DockableDisplayer} with a new instance.
*/
protected void discardDisplayer(){
if( dockable != null ) {
DockableDisplayer displayer = dockable.getDisplayer();
displayer.removeDockableDisplayerListener(displayerListener);
contentPane.remove(displayer.getComponent());
dockable.updateDisplayer();
displayer = dockable.getDisplayer();
displayer.addDockableDisplayerListener(displayerListener);
contentPane.add(displayer.getComponent());
updateBounds();
}
}
/**
* Gets the container that will become the parent of a {@link DockableDisplayer}.
* @return the parent
*/
protected Container getDisplayerParent(){
return contentPane;
}
public void setController( DockController controller ){
background.setController( controller );
contentContainer.setController( controller );
contentBorder.setController( controller );
}
/**
* Makes a guess how big the insets around the current {@link Dockable}
* of this window are.
* @return a guess of the insets
*/
public Insets getDockableInsets(){
DockableDisplayer displayer = dockable.getDisplayer();
Insets insets = displayer.getDockableInsets();
displayer.getComponent().getBounds();
Component parent = window.asComponent();
Point zero = new Point(0, 0);
zero = SwingUtilities.convertPoint(displayer.getComponent(), zero, parent);
int deltaX = zero.x;
int deltaY = zero.y;
int deltaW = parent.getWidth() - displayer.getComponent().getWidth();
int deltaH = parent.getHeight() - displayer.getComponent().getHeight();
insets.left += deltaX;
insets.top += deltaY;
insets.right += deltaW - deltaX;
insets.bottom += deltaH - deltaY;
return insets;
}
/**
* Recalculates the size and the location of this window.
*/
public void updateBounds(){
DockableDisplayer displayer = getDisplayer();
Dockable dockable = displayer == null ? null : displayer.getDockable();
if( dockable != null ) {
window.asComponent().validate();
Point location;
Dimension size;
FlapDockStation.Direction direction = station.getDirection();
int windowSize = station.getWindowSize(dockable);
Rectangle bounds = station.getExpansionBounds();
Insets insets = getDockableInsets();
if( direction == Direction.SOUTH ) {
windowSize += insets.top + insets.bottom;
location = new Point(bounds.x, bounds.height);
size = new Dimension(bounds.width, windowSize);
}
else if( direction == Direction.NORTH ) {
windowSize += insets.top + insets.bottom;
location = new Point(bounds.x, -windowSize);
size = new Dimension(bounds.width, windowSize);
}
else if( direction == Direction.WEST ) {
windowSize += insets.left + insets.right;
location = new Point(-windowSize, bounds.y);
size = new Dimension(windowSize, bounds.height);
}
else {
windowSize += insets.left + insets.right;
location = new Point(bounds.width, bounds.y);
size = new Dimension(windowSize, bounds.height);
}
SwingUtilities.convertPointToScreen(location, buttonPane);
window.setParentLocation(location);
window.setSize(size);
window.asComponent().validate();
}
}
public void mouseExited( MouseEvent e ){
if( !pressed )
window.asComponent().setCursor(Cursor.getDefaultCursor());
}
public void mouseEntered( MouseEvent e ){
if( station.getDirection() == Direction.SOUTH )
window.asComponent().setCursor(Cursor.getPredefinedCursor(Cursor.S_RESIZE_CURSOR));
else if( station.getDirection() == Direction.NORTH )
window.asComponent().setCursor(Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR));
else if( station.getDirection() == Direction.EAST )
window.asComponent().setCursor(Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR));
else
window.asComponent().setCursor(Cursor.getPredefinedCursor(Cursor.W_RESIZE_CURSOR));
}
public void mousePressed( MouseEvent e ){
pressed = true;
}
public void mouseReleased( MouseEvent e ){
pressed = false;
}
public void mouseDragged( MouseEvent e ){
if( pressed ) {
DockableDisplayer displayer = getDisplayer();
Dockable dockable = displayer == null ? null : displayer.getDockable();
if( dockable != null ) {
Point mouse = new Point(e.getX(), e.getY());
SwingUtilities.convertPointToScreen(mouse, e.getComponent());
Component flap = station.getComponent();
Point zero = new Point(0, 0);
SwingUtilities.convertPointToScreen(zero, flap);
int size = 0;
if( station.getDirection() == Direction.SOUTH )
size = mouse.y - zero.y - flap.getHeight();
else if( station.getDirection() == Direction.NORTH )
size = zero.y - mouse.y;
else if( station.getDirection() == Direction.EAST )
size = mouse.x - zero.x - flap.getWidth();
else
size = zero.x - mouse.x;
size = Math.max(size, station.getWindowMinSize());
Insets insets = getDockableInsets();
if( station.getDirection() == Direction.NORTH || station.getDirection() == Direction.SOUTH )
size -= insets.top + insets.bottom;
else
size -= insets.left + insets.right;
if( size > 0 ) {
station.setWindowSize(dockable, size);
}
}
}
}
public void mouseClicked( MouseEvent e ){
// do nothing
}
public void mouseMoved( MouseEvent e ){
// do nothing
}
/**
* Represents the border of this window
* @author Benjamin Sigg
*/
private class WindowBorder extends BorderForwarder implements FlapWindowBorder{
public WindowBorder( JComponent target ){
super( FlapWindowBorder.KIND, ThemeManager.BORDER_MODIFIER + ".flap.window", target );
}
public FlapWindow getWindow(){
return DefaultFlapWindow.this;
}
public FlapDockStation getStation(){
return DefaultFlapWindow.this.getStation();
}
}
/**
* The background algorithm of this window
* @author Benjamin Sigg
*/
private class Background extends BackgroundAlgorithm implements FlapWindowBackgroundComponent{
/**
* Creates a new algorithm
*/
public Background(){
super( FlapWindowBackgroundComponent.KIND, ThemeManager.BACKGROUND_PAINT + ".station.flap.window" );
}
public FlapWindow getWindow(){
return DefaultFlapWindow.this;
}
public DockStation getStation(){
return station;
}
public Component getComponent(){
return window.asComponent();
}
}
/**
* The parent container of a {@link DefaultFlapWindow}.
* @author Benjamin Sigg
*/
public static interface Parent {
/**
* Tells whether this window is still valid, e.g whether the window can be shown
* in front of its station.
* @return whether this parent can still be used
*/
public boolean isParentValid();
/**
* Returns <code>this</code> or the representation of <code>this</code>
* as {@link Component}.
* @return the component that shows a {@link DefaultFlapWindow}
*/
public Component asComponent();
/**
* Sets the location of this container on the screen.
* @param location the new locations in screen coordinates
*/
public void setParentLocation( Point location );
/**
* Sets the size of this container.
* @param size the new size
*/
public void setSize( Dimension size );
/**
* Tells whether the user can see this container or not.
* @return <code>true</code> if this container is visible
*/
public boolean isVisible();
/**
* Sets the visibility of this container.
* @param flag the visibility
*/
public void setVisible( boolean flag );
/**
* Tells this parent what component to paint.
* @param content the panel to show
*/
public void setContentPane( Container content );
/**
* Gets the component that is painted by this parent.
* @return the component
*/
public Container getContentPane();
/**
* Informs this parent that is it no longer needed
*/
public void destroy();
}
/**
* A parent of a {@link DefaultFlapWindow} that is a {@link JDialog}.
* @author Benjamin Sigg
*/
public static class DialogParent extends JDialog implements Parent {
/** the station for which this dialog is used */
private FlapDockStation station;
/**
* Creates a new dialog.
* @param owner the owner of this dialog
* @param station the station for which this dialog is used
*/
public DialogParent( Frame owner, FlapDockStation station ){
super(owner, false);
setUndecorated(true);
getRootPane().setWindowDecorationStyle(JRootPane.NONE);
this.station = station;
}
/**
* Creates a new dialog.
* @param owner the owner of this dialog
* @param station the station for which this dialog is used
*/
public DialogParent( Dialog owner, FlapDockStation station ){
super(owner, false);
setUndecorated(true);
getRootPane().setWindowDecorationStyle(JRootPane.NONE);
this.station = station;
}
public Component asComponent(){
return this;
}
/**
* Tells whether this window is still valid, e.g whether the window can be shown
* in front of its station.
*/
public boolean isParentValid(){
Window owner = SwingUtilities.getWindowAncestor(station.getComponent());
return getOwner() == owner;
}
public void setParentLocation( Point location ){
setLocation( location );
}
public void destroy(){
dispose();
}
}
}