package de.unisiegen.gtitool.ui.swing;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.awt.event.InputEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.event.MouseMotionListener;
import java.io.IOException;
import java.util.ArrayList;
import javax.swing.JComponent;
import javax.swing.JTabbedPane;
import javax.swing.TransferHandler;
import javax.swing.border.EmptyBorder;
import de.unisiegen.gtitool.ui.swing.dnd.JGTITabbedPaneComponent;
import de.unisiegen.gtitool.ui.swing.dnd.JGTITabbedPaneTransferHandler;
import de.unisiegen.gtitool.ui.swing.dnd.JGTITabbedPaneTransferable;
/**
* Special {@link JTabbedPane}.
*
* @author Christian Fehler
* @version $Id$
*/
public class JGTITabbedPane extends JTabbedPane implements DropTargetListener
{
/**
* The highlight {@link Color}.
*/
private static final Color HIGHLIGHT_COLOR = new Color ( 50, 150, 250 );
/**
* The default tab width.
*/
private static final int DEFAULT_TAB_WIDTH = 100;
/**
* The default tab height.
*/
private static final int DEFAULT_TAB_HEIGHT = 28;
/**
* The serial version uid.
*/
private static final long serialVersionUID = 5744054037095918506L;
/**
* The drag enabled value.
*/
private boolean dragEnabled = false;
/**
* The drop point.
*/
protected Point dropPoint = null;
/**
* The allowed drag and drop sources.
*/
private ArrayList < JComponent > allowedDndSources;
/**
* Flag that indicates that the drag and drop is allowed.
*/
protected boolean dragAndDropAllowed = true;
/**
* Allocates a new {@link JGTITabbedPane}.
*/
public JGTITabbedPane ()
{
super ();
init ();
}
/**
* Allocates a new {@link JGTITabbedPane}.
*
* @param tabPlacement The tab placement.
*/
public JGTITabbedPane ( int tabPlacement )
{
super ( tabPlacement );
init ();
}
/**
* Allocates a new {@link JGTITabbedPane}.
*
* @param tabPlacement The tab placement.
* @param tabLayoutPolicy The tab layout policy.
*/
public JGTITabbedPane ( int tabPlacement, int tabLayoutPolicy )
{
super ( tabPlacement, tabLayoutPolicy );
init ();
}
/**
* Adds the given {@link JComponent} to the allowed drag and drop sources.
*
* @param jComponent The {@link JComponent} to add.
*/
public final void addAllowedDndSource ( JComponent jComponent )
{
if ( !this.allowedDndSources.contains ( jComponent ) )
{
this.allowedDndSources.add ( jComponent );
}
}
/**
* Clears the allowed drag and drop sources.
*/
public final void clearAllowedDndSources ()
{
this.allowedDndSources.clear ();
}
/**
* {@inheritDoc}
*
* @see DropTargetListener#dragEnter(DropTargetDragEvent)
*/
public final void dragEnter ( DropTargetDragEvent event )
{
event.acceptDrag ( event.getDropAction () );
this.dropPoint = event.getLocation ();
repaint ();
}
/**
* {@inheritDoc}
*
* @see DropTargetListener#dragExit(DropTargetEvent)
*/
public final void dragExit (
@SuppressWarnings ( "unused" ) DropTargetEvent event )
{
this.dropPoint = null;
repaint ();
}
/**
* {@inheritDoc}
*
* @see DropTargetListener#dragOver(DropTargetDragEvent)
*/
public final void dragOver ( DropTargetDragEvent event )
{
JGTITabbedPaneComponent draggedComponent;
try
{
draggedComponent = ( JGTITabbedPaneComponent ) event.getTransferable ()
.getTransferData ( JGTITabbedPaneTransferable.dataFlavor );
if ( !this.allowedDndSources.contains ( draggedComponent.getSource () ) )
{
event.rejectDrag ();
this.dropPoint = null;
repaint ();
return;
}
// reject the drag if the mouse is not beside the tabs
if ( getTabCount () > 0 )
{
Rectangle bounds = getUI ().getTabBounds ( this, 0 );
Point point = event.getLocation ();
if ( ( point.y < bounds.y ) || ( point.y > bounds.y + bounds.height ) )
{
event.rejectDrag ();
this.dropPoint = null;
repaint ();
return;
}
}
}
catch ( UnsupportedFlavorException exc )
{
event.rejectDrag ();
this.dropPoint = null;
repaint ();
return;
}
catch ( IOException exc )
{
event.rejectDrag ();
this.dropPoint = null;
repaint ();
return;
}
event.acceptDrag ( event.getDropAction () );
this.dropPoint = event.getLocation ();
repaint ();
}
/**
* {@inheritDoc}
*
* @see DropTargetListener#drop(DropTargetDropEvent)
*/
public final void drop ( DropTargetDropEvent event )
{
event.acceptDrop ( event.getDropAction () );
try
{
Transferable transferable = event.getTransferable ();
event.dropComplete ( getTransferHandler ().importData ( this,
transferable ) );
}
catch ( RuntimeException exc )
{
event.dropComplete ( false );
}
this.dropPoint = null;
repaint ();
}
/**
* {@inheritDoc}
*
* @see DropTargetListener#dropActionChanged(DropTargetDragEvent)
*/
public final void dropActionChanged ( DropTargetDragEvent event )
{
event.acceptDrag ( event.getDropAction () );
this.dropPoint = event.getLocation ();
repaint ();
}
/**
* Returns the drag enabled value.
*
* @return The drag enabled value.
*/
public final boolean getDragEnabled ()
{
return this.dragEnabled;
}
/**
* Returns the tab index.
*
* @param x The x position.
* @param y The y position.
* @return the tab index.
*/
protected final int getTabIndex ( int x, int y )
{
return getUI ().tabForCoordinate ( this, x, y );
}
/**
* Initializes this {@link JComponent}.
*/
private final void init ()
{
this.allowedDndSources = new ArrayList < JComponent > ();
setBorder ( new EmptyBorder ( 1, 1, 1, 1 ) );
setTransferHandler ( new JGTITabbedPaneTransferHandler (
TransferHandler.MOVE )
{
/**
* The serial version uid.
*/
private static final long serialVersionUID = -7483915272887199973L;
@Override
protected boolean importComponent ( JGTITabbedPane source,
@SuppressWarnings ( "unused" ) JGTITabbedPane target,
Component component )
{
int sourceIndex = source.getSelectedIndex ();
String title = source.getTitleAt ( sourceIndex );
int targetIndex = getTabIndex ( JGTITabbedPane.this.dropPoint.x,
JGTITabbedPane.this.dropPoint.y );
source.remove ( component );
if ( targetIndex == -1 )
{
add ( title, component );
}
else
{
add ( component, targetIndex );
setTitleAt ( targetIndex, title );
}
setSelectedComponent ( component );
return true;
}
} );
// must be removed because of problems with the drag and drop
for ( MouseMotionListener current : getMouseMotionListeners () )
{
removeMouseMotionListener ( current );
}
// store the drag and drop allowed state
addMouseListener ( new MouseAdapter ()
{
@Override
public void mousePressed ( MouseEvent event )
{
JGTITabbedPane.this.dragAndDropAllowed = getTabIndex ( event
.getPoint ().x, event.getPoint ().y ) != -1;
}
} );
// swing bugfix
addMouseMotionListener ( new MouseMotionAdapter ()
{
@Override
public void mouseDragged ( MouseEvent event )
{
if ( getDragEnabled ()
&& ( ( event.getModifiers () & InputEvent.BUTTON1_MASK ) != 0 )
&& JGTITabbedPane.this.dragAndDropAllowed )
{
TransferHandler transferHandler = getTransferHandler ();
transferHandler.exportAsDrag ( JGTITabbedPane.this, event,
transferHandler.getSourceActions ( JGTITabbedPane.this ) );
event.consume ();
}
}
} );
setDropTarget ( new DropTarget ( this, this ) );
}
/**
* {@inheritDoc}
*
* @see JComponent#paintComponent(Graphics)
*/
@Override
protected final void paintComponent ( Graphics graphics )
{
super.paintComponent ( graphics );
if ( this.dropPoint != null )
{
Rectangle visibleRect = getVisibleRect ();
int vw = visibleRect.width;
int vh = visibleRect.height;
int x;
int y;
int w;
int h;
int index = getTabIndex ( this.dropPoint.x, this.dropPoint.y );
if ( index == -1 )
{
if ( getComponentCount () == 0 )
{
x = 2;
y = 3;
w = DEFAULT_TAB_WIDTH;
h = DEFAULT_TAB_HEIGHT;
}
else
{
Rectangle tabRect = getUI ().getTabBounds ( this,
getComponentCount () - 1 );
x = tabRect.x + tabRect.width - 2;
y = tabRect.y;
w = DEFAULT_TAB_WIDTH;
h = tabRect.height;
}
}
else
{
Rectangle tabRect = getUI ().getTabBounds ( this, index );
x = tabRect.x;
y = tabRect.y;
w = tabRect.width;
h = tabRect.height;
}
graphics.setColor ( HIGHLIGHT_COLOR );
// top horizontal
graphics.drawLine ( x + 1, y, x + w - 2, y );
graphics.drawLine ( x, y + 1, x + w - 1, y + 1 );
// left vertical
graphics.drawLine ( x, y + 1, x, y + h - 1 );
graphics.drawLine ( x + 1, y, x + 1, y + h - 2 );
// right vertical
graphics.drawLine ( x + w - 1, y + 1, x + w - 1, y + h - 1 );
graphics.drawLine ( x + w - 2, y, x + w - 2, y + h - 2 );
// left to tab horizontal
graphics.drawLine ( 1, y + h - 2, x - 1, y + h - 2 );
graphics.drawLine ( 0, y + h - 1, x, y + h - 1 );
// tab to right horizontal
graphics.drawLine ( x + w - 1, y + h - 2, vw - 2, y + h - 2 );
graphics.drawLine ( x + w, y + h - 1, vw - 1, y + h - 1 );
// left vertical
graphics.drawLine ( 0, y + h - 1, 0, vh - 2 );
graphics.drawLine ( 1, y + h, 1, vh - 1 );
// right vertical
graphics.drawLine ( vw - 1, y + h, vw - 1, vh - 2 );
graphics.drawLine ( vw - 2, y + h - 1, vw - 2, vh - 1 );
// bottom horizontal
graphics.drawLine ( 0, vh - 2, vw - 1, vh - 2 );
graphics.drawLine ( 1, vh - 1, vw - 2, vh - 1 );
}
}
/**
* Removes the given {@link JComponent} from the allowed drag and drop
* sources.
*
* @param jComponent The {@link JComponent} to remove.
*/
public final void removeAllowedDndSource ( JComponent jComponent )
{
this.allowedDndSources.remove ( jComponent );
}
/**
* Sets the drag enabled value.
*
* @param dragEnabled The value.
*/
public final void setDragEnabled ( boolean dragEnabled )
{
this.dragEnabled = dragEnabled;
}
}