package jifi.ui.view.tabbedpaneview.dndtabbedpane; import java.awt.*; import java.awt.datatransfer.*; import java.awt.dnd.*; import javax.swing.*; import jifi.ui.view.tabbedpaneview.JSplitPanel; import jifi.ui.view.tabbedpaneview.TabbedPaneView; /** * A transfer handler for <code>JTabbedPane</code>s. This handler can move tabs * between tabbed panes, as well as move the position of tabs within a tabbed * pane.<p> * * If the tabbed pane receiving the drop implements the * <code>DrawDnDIndicatorTabbedPane</code> interface, it will be sent a * rectangle that it can paint to signify where the dropped tab will be placed. * * @author Robert Futrell * @version 0.5 * @see DrawDnDIndicatorTabbedPane */ public class DnDTabbedPaneTransferHandler extends TransferHandler implements DropTargetListener { private static final long serialVersionUID = 1L; /** * The "data type" that starts and ends the drag-and-drops are instances of * <code>JTabbedPane</code>. While we're actually dnd'ing a "tab" and its * contents, the component both sending and receiving is a JTabbedPane. */ private static final String mimeType = DataFlavor.javaJVMLocalObjectMimeType + ";class=javax.swing.JTabbedPane"; /** * The data flavor corresponding to the above MIME type. */ private DataFlavor tabFlavor; /** * The location of the mouse cursor throughout the drag-and-drop. This is * here because of a deficiency in TransferHandler's design; you have no way * of knowing the exact drop location in the component with a plain * TransferHandler unless you implement DropTargetListener and get it that * way. */ protected Point mouseLocation; private TabTransferable currentTransferable; /** * Constructor. */ public DnDTabbedPaneTransferHandler() { try { tabFlavor = new DataFlavor(mimeType); } catch (ClassNotFoundException e) { e.printStackTrace(); // Never happens } } /** * Overridden to include a check for a TabData flavor. */ public boolean canImport(JComponent c, DataFlavor[] flavors) { return hasTabFlavor(flavors); } protected Transferable createTransferable(JComponent c) { currentTransferable = new TabTransferable((JTabbedPane) c); return currentTransferable; } public void dragEnter(DropTargetDragEvent e) { } public void dragExit(DropTargetEvent e) { Component c = e.getDropTargetContext().getComponent(); if (c instanceof DnDTabbedPane) { ((DnDTabbedPane) c).clearDnDIndicatorRect(); } } /** * Called when a drag-and-drop operation is pending, and the mouse is * hovering over the destination component. */ public void dragOver(DropTargetDragEvent e) { mouseLocation = e.getLocation(); Component c = e.getDropTargetContext().getComponent(); JTabbedPane destTabbedPane = (JTabbedPane) c; // If the tabbed pane wants to paint the drop location, figure out // the rectangle to paint for the new tab. if (destTabbedPane instanceof DnDTabbedPane) { ((DnDTabbedPane) c).dragOver(mouseLocation); // Verify it's a tab transferable (as this class may be // subclassed to support other transferable types). TabTransferable t = currentTransferable; int tab = getDroppedTabIndex(destTabbedPane, mouseLocation); Rectangle transferredTabBounds; if (t != null) { transferredTabBounds = t.getTabBounds(); } else { transferredTabBounds = destTabbedPane.getBoundsAt(0); // // try { // TabTransferable.TabTransferData transferData = (TabTransferable.TabTransferData) e.getTransferable().getTransferData(tabFlavor); // transferredTabBounds = transferData.sourceTabbedPane.getBoundsAt(tab-1); // } catch (Exception ex) { // System.out.println(ex); // } } Rectangle iBounds = destTabbedPane. getBoundsAt(tab == 0 ? 0 : tab - 1); // Make rectangle sit on same y-axis as the target tab span. iBounds.y = iBounds.y + iBounds.height - transferredTabBounds.height; // Adjust rectangle so it is "in-between" two tabs. int tabPlacement = destTabbedPane.getTabPlacement(); switch (tabPlacement) { case JTabbedPane.TOP: case JTabbedPane.BOTTOM: iBounds.x += tab == 0 ? 0 : iBounds.width; iBounds.x -= transferredTabBounds.width / 2; break; case JTabbedPane.LEFT: case JTabbedPane.RIGHT: iBounds.y -= transferredTabBounds.height / 2; break; } // Pass the rectangle to the tabbed pane. ((DnDTabbedPane) c).setDnDIndicatorRect( iBounds.x, iBounds.y, transferredTabBounds.width, transferredTabBounds.height); } } public void drop(DropTargetDropEvent e) { Component c = e.getDropTargetContext().getComponent(); if (c instanceof DnDTabbedPane) { ((DnDTabbedPane) c).clearDnDIndicatorRect(); } } public void dropActionChanged(DropTargetDragEvent e) { } /** * Returns the index at which to add a tab if it is dropped at the mouse * location specified by <code>p</code>. * * @param tabbedPane The tabbed pane who would be receiving the tab. * @param p The mouse location. * @return The index at which to add the tab. */ protected int getDroppedTabIndex(JTabbedPane tabbedPane, Point p) { // First see if the mouse is actually in a tab. int tab = tabbedPane.indexAtLocation( mouseLocation.x, mouseLocation.y); // If it isn't, find the "closest" tab and use it. if (tab == -1) { int tabCount = tabbedPane.getTabCount(); int tabPlacement = tabbedPane.getTabPlacement(); switch (tabPlacement) { case JTabbedPane.TOP: case JTabbedPane.BOTTOM: int x = p.x; int dy = Integer.MAX_VALUE; for (int i = 0; i < tabCount; i++) { Rectangle b = tabbedPane.getBoundsAt(i); if (x >= b.x && x < (b.x + b.width)) { int dy2 = Math.abs(b.y - p.y); if (dy2 < dy) { tab = i; dy = dy2; } } } break; case JTabbedPane.LEFT: case JTabbedPane.RIGHT: int y = p.y; int dx = Integer.MAX_VALUE; for (int i = 0; i < tabCount; i++) { Rectangle b = tabbedPane.getBoundsAt(i); if (y >= b.y && y < (b.y + b.height)) { int dx2 = Math.abs(b.x - p.x); if (dx2 < dx) { tab = i; dx = dx2; } } } break; } } // If they're "off to the side" of all tabs, default to // adding to the end of the tabs. if (tab == -1) { tab = tabbedPane.getTabCount(); } return tab; } /** * We can only move tabs, we cannot copy them. * * @param c This parameter is ignored. * @return <code>TransferHandler.MOVE</code>, as we can only move tabs. */ public int getSourceActions(JComponent c) { return MOVE; } /** * Does the flavor list have a Tab flavor? */ protected boolean hasTabFlavor(DataFlavor[] flavors) { if (tabFlavor == null) { return false; } for (int i = 0; i < flavors.length; i++) { if (tabFlavor.equals(flavors[i])) { return true; } } return false; } /** * Called when the drag-and-drop operation has just completed. This creates * a new tab identical to the one "dragged" and places it in the destination * <code>JTabbedPane</code>. * * @param c The component receiving the "drop" (the instance of * <code>JTabbedPane</code>). * @param t The data being transfered (information about the tab and the * component contained by the tab). * @return Whether or not the import was successful. */ public boolean importData(JComponent c, Transferable t) { boolean successful = false; if (hasTabFlavor(t.getTransferDataFlavors()) && mouseLocation != null) { try { // Physically insert the tab. JTabbedPane tabbedPane = (JTabbedPane) c; int tab = getDroppedTabIndex(tabbedPane, mouseLocation); TabTransferable.TabTransferData td = (TabTransferable.TabTransferData) t. getTransferData(tabFlavor); JTabbedPane sourcePane = td.sourceTabbedPane; int sourceIndex = td.tabIndex; String tabName = sourcePane.getTitleAt(sourceIndex); Icon icon = sourcePane.getIconAt(sourceIndex); Component comp = sourcePane.getComponentAt(sourceIndex); String toolTip = sourcePane.getToolTipTextAt(sourceIndex); Color foreground = sourcePane.getForegroundAt(sourceIndex); if (tabbedPane instanceof DnDTabbedPane) { if (((DnDTabbedPane) tabbedPane).splitPanel()) { Component it = c; while (!(it instanceof JSplitPanel) && it != null) { it = it.getParent(); } if (it != null && it instanceof JSplitPanel) { TabbedPaneView tabbedPaneView = ((JSplitPanel) it).getTabbedPaneView(); int orientation = ((DnDTabbedPane) tabbedPane).getSplitOrientation(); if (orientation == 1) { tabbedPaneView.split(TabbedPaneView.VERTICAL, false); tabbedPaneView.get(TabbedPaneView.TOP).getDnDTabbedPane().addTab(tabName, icon, comp, toolTip); } else if (orientation == 2) { tabbedPaneView.split(TabbedPaneView.VERTICAL, true); tabbedPaneView.get(TabbedPaneView.BOTTOM).getDnDTabbedPane().addTab(tabName, icon, comp, toolTip); } else if (orientation == 3) { tabbedPaneView.split(TabbedPaneView.HORIZONTAL, false); tabbedPaneView.get(TabbedPaneView.LEFT).getDnDTabbedPane().addTab(tabName, icon, comp, toolTip); } else if (orientation == 4) { tabbedPaneView.split(TabbedPaneView.HORIZONTAL, true); tabbedPaneView.get(TabbedPaneView.RIGHT).getDnDTabbedPane().addTab(tabName, icon, comp, toolTip); } } return true; } else { tabbedPane.insertTab(tabName, icon, comp, toolTip, tab); // Here's the deal: Even though we inserted the tab, // the tabbed pane does not automatically give the tab // we just inserted focus. We must manually give the // just-dragged tab focus (on the EDT). Further, we // must figure out what tab was just dragged by // checking the contents of each tab one at a time; // this is because we may have "moved" this tab from // one location to another in the same tabbed pane, // thus messing up the value of getTabCount() (why it // may not yet be valid, I'm not sure; I guess // insertTab() doesn't actually physically add the // component to the tabbed pane until later on the // EDT?...). int count = tabbedPane.getTabCount(); for (int i = 0; i < count; i++) { Component comp2 = tabbedPane.getComponentAt(i); if (comp2 != null && comp2.equals(comp)) { tabbedPane.setForegroundAt(i, foreground); selectTab(tabbedPane, i); break; } } successful = true; if (c instanceof DnDTabbedPane) { ((DnDTabbedPane) c).clearDnDIndicatorRect(); } } } } catch (Exception e) { e.printStackTrace(); } } currentTransferable = null; return successful; } /** * Selects the specified tab in the specified tabbed pane. This method can * be overridden by subclasses to do more stuff than simply select the tab. * * @param tabbedPane The tabbed pane. * @param index The index of the tab to select. */ protected void selectTab(final JTabbedPane tabbedPane, final int index) { SwingUtilities.invokeLater(new Runnable() { public void run() { tabbedPane.setSelectedIndex(index); } }); } /** * Transferable representing a tab from a tabbed pane and its contents. */ class TabTransferable implements Transferable { private TabTransferData transferData; /** * The data remembered about the tab. */ class TabTransferData { private JTabbedPane sourceTabbedPane; private int tabIndex; TabTransferData(JTabbedPane tabbedPane, int tabIndex) { this.sourceTabbedPane = tabbedPane; this.tabIndex = tabIndex; } } TabTransferable(JTabbedPane tabbedPane) { int index = tabbedPane.getSelectedIndex(); transferData = new TabTransferData(tabbedPane, index); } public Rectangle getTabBounds() { return transferData.sourceTabbedPane.getBoundsAt(transferData.tabIndex); } public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException { if (!isDataFlavorSupported(flavor)) { throw new UnsupportedFlavorException(flavor); } return transferData; } public DataFlavor[] getTransferDataFlavors() { return new DataFlavor[]{tabFlavor}; } public boolean isDataFlavorSupported(DataFlavor flavor) { return tabFlavor.equals(flavor); } } }