概要

JTabbedPane間でタブのDrag&Dropによる移動を行います。

サンプルコード

class TabTransferHandler extends TransferHandler {
  private final DataFlavor localObjectFlavor = new DataFlavor(DnDTabData.class, "DnDTabData");
  private DnDTabbedPane source = null;

  @Override protected Transferable createTransferable(JComponent c) {
    System.out.println("createTransferable");
    if (c instanceof DnDTabbedPane) source = (DnDTabbedPane) c;
    return new Transferable() {
      @Override public DataFlavor[] getTransferDataFlavors() {
        return new DataFlavor[] {localObjectFlavor};
      }

      @Override public boolean isDataFlavorSupported(DataFlavor flavor) {
        return Objects.equals(localObjectFlavor, flavor);
      }

      @Override public Object getTransferData(DataFlavor flavor) 
            throws UnsupportedFlavorException, IOException {
        if (isDataFlavorSupported(flavor)) {
          return new DnDTabData(source);
        } else {
           throw new UnsupportedFlavorException(flavor);
        }
      }
    };
  }

  @Override public boolean canImport(TransferSupport support) {
    // System.out.println("canImport");
    if (!support.isDrop() || !support.isDataFlavorSupported(localObjectFlavor)) {
      return false;
    }
    support.setDropAction(TransferHandler.MOVE);
    DropLocation tdl = support.getDropLocation();
    Point pt = tdl.getDropPoint();
    DnDTabbedPane target = (DnDTabbedPane) support.getComponent();
    target.autoScrollTest(pt);
    DnDTabbedPane.DropLocation dl =
      (DnDTabbedPane.DropLocation) target.dropLocationForPoint(pt);
    int idx = dl.getIndex();
    boolean isDroppable = false;

    if (target == source) {
      isDroppable = target.getTabAreaBounds().contains(pt) && idx >= 0 &&
                   idx != target.dragTabIndex && idx != target.dragTabIndex + 1;
    } else {
      if (source != null && target != source.getComponentAt(source.dragTabIndex)) {
        isDroppable = target.getTabAreaBounds().contains(pt) && idx >= 0;
      }
    }

    Component c = target.getRootPane().getGlassPane();
    c.setCursor(isDroppable?DragSource.DefaultMoveDrop:DragSource.DefaultMoveNoDrop);
    if (isDroppable) {
      support.setShowDropLocation(true);
      dl.setDroppable(true);
      target.setDropLocation(dl, null, true);
      return true;
    } else {
      support.setShowDropLocation(false);
      dl.setDroppable(false);
      target.setDropLocation(dl, null, false);
      return false;
    }
  }

  private BufferedImage makeDragTabImage(DnDTabbedPane tabbedPane) {
    Rectangle rect = tabbedPane.getBoundsAt(tabbedPane.dragTabIndex);
    BufferedImage image = new BufferedImage(
      tabbedPane.getWidth(), tabbedPane.getHeight(), BufferedImage.TYPE_INT_ARGB);
    Graphics g = image.getGraphics();
    tabbedPane.paint(g);
    g.dispose();
    if (rect.x < 0) {
      rect.translate(-rect.x, 0);
    }
    if (rect.y < 0) {
      rect.translate(0, -rect.y);
    }
    if (rect.x + rect.width > image.getWidth()) {
      rect.width = image.getWidth() - rect.x;
    }
    if (rect.y + rect.height > image.getHeight()) {
      rect.height = image.getHeight() - rect.y;
    }
    return image.getSubimage(rect.x, rect.y, rect.width, rect.height);
  }

  @Override public int getSourceActions(JComponent c) {
    System.out.println("getSourceActions");
    if (c instanceof DnDTabbedPane) {
      DnDTabbedPane src = (DnDTabbedPane) c;
      c.getRootPane().setGlassPane(new GhostGlassPane(src));
      if (src.dragTabIndex < 0) {
        return TransferHandler.NONE;
      }
      setDragImage(makeDragTabImage(src));
      c.getRootPane().getGlassPane().setVisible(true);
      return TransferHandler.MOVE;
    }
    return TransferHandler.NONE;
  }

  @Override public boolean importData(TransferSupport support) {
    System.out.println("importData");

    DnDTabbedPane target = (DnDTabbedPane) support.getComponent();
    DnDTabbedPane.DropLocation dl = target.getDropLocation();
    try {
      DnDTabbedPane source = (DnDTabbedPane) support.getTransferable()
        .getTransferData(localObjectFlavor);
      int index = dl.getIndex(); //boolean insert = dl.isInsert();
      if (target == source) {
        source.convertTab(source.dragTabIndex, index);
      } else {
        source.exportTab(source.dragTabIndex, target, index);
      }
      return true;
    } catch (UnsupportedFlavorException ufe) {
      ufe.printStackTrace();
    } catch (IOException ioe) {
      ioe.printStackTrace();
    }
    return false;
  }

  @Override protected void exportDone(JComponent c, Transferable data, int action) {
    System.out.println("exportDone");
    DnDTabbedPane src = (DnDTabbedPane) c;
    c.getRootPane().getGlassPane().setVisible(false);
    src.setDropLocation(null, null, false);
  }
}
View in GitHub: Java, Kotlin

解説

上記のサンプルでは、JDK 6で導入された、TransferHandler.DropLocationを継承するDnDTabbedPane.DropLocationなどを作成して、JTabbedPane間でタブの移動ができるように設定しています。

  • 注意点
    • 自身の子として配置されているJTabbedPaneにタブを移動することはできない
    • 子コンポーネントがnull(例えばaddTab("Tab",null))のタブは移動不可
    • タブが選択不可(例えばsetEnabledAt(idx, false))の場合は移動不可
  • バグ?
    • 子コンポーネントがJTableの場合、マウスカーソルが点滅する
      • Windows環境のみ?
    • 子コンポーネントがJTextAreaなどの場合、ドラッグ中のタブゴーストが表示できない
      • JTableJTextAreaどちらもJScrollPaneが影響している?
      • Java 1.7.0-ea-b84以上で、TransferHandler#setDragImage(Image)を使用すると正常に表示される
      • textArea.setTransferHandler(null);とすれば、1.6.0でも正常に表示される
    • SCROLL_TAB_LAYOUTの場合、タブゴーストにスクロールボタンが表示される場合がある
DnDExportTabbedPane1.png

参考リンク

コメント