Google Tag Manager

2008/04/07

Drag and Drop the Tabs in JTabbedPane

Code

class DnDTabbedPane extends JTabbedPane {
  private static final int LINE_SIZE = 3;
  private static final int RWH = 20;
  private static final int BUTTON_SIZE = 30; // XXX 30 is magic number of scroll button size
  private final GhostGlassPane glassPane = new GhostGlassPane(this);
  protected int dragTabIndex = -1;
  protected boolean hasGhost = true;
  protected boolean isPaintScrollArea = true;

  protected Rectangle rectBackward = new Rectangle();
  protected Rectangle rectForward = new Rectangle();

  private void clickArrowButton(String actionKey) {
    JButton forwardButton = null;
    JButton backwardButton = null;
    for (Component c : getComponents()) {
      if (c instanceof JButton) {
        if (Objects.isNull(forwardButton)) {
          forwardButton = (JButton) c;
        } else {
          backwardButton = (JButton) c;
          break;
        }
      }
    }
    JButton b = "scrollTabsForwardAction".equals(actionKey) ? forwardButton : backwardButton;
    Optional.ofNullable(b)
        .filter(JButton::isEnabled)
        .ifPresent(JButton::doClick);
  }

  public void autoScrollTest(Point glassPt) {
    Rectangle r = getTabAreaBounds();
    if (isTopBottomTabPlacement(getTabPlacement())) {
      rectBackward.setBounds(r.x, r.y, RWH, r.height);
      rectForward.setBounds(r.x + r.width - RWH - BUTTON_SIZE, r.y, RWH + BUTTON_SIZE, r.height);
    } else {
      rectBackward.setBounds(r.x, r.y, r.width, RWH);
      rectForward.setBounds(r.x, r.y + r.height - RWH - BUTTON_SIZE, r.width, RWH + BUTTON_SIZE);
    }
    rectBackward = SwingUtilities.convertRectangle(getParent(), rectBackward, glassPane);
    rectForward = SwingUtilities.convertRectangle(getParent(), rectForward, glassPane);
    if (rectBackward.contains(glassPt)) {
      clickArrowButton("scrollTabsBackwardAction");
    } else if (rectForward.contains(glassPt)) {
      clickArrowButton("scrollTabsForwardAction");
    }
  }

  protected DnDTabbedPane() {
    super();
    glassPane.setName("GlassPane");
    new DropTarget(glassPane, DnDConstants.ACTION_COPY_OR_MOVE, new TabDropTargetListener(), true);
    DragSource.getDefaultDragSource().createDefaultDragGestureRecognizer(
        this, DnDConstants.ACTION_COPY_OR_MOVE, new TabDragGestureListener());
  }

  @SuppressWarnings("PMD.NPathComplexity")
  protected int getTargetTabIndex(Point glassPt) {
    int count = getTabCount();
    if (count == 0) {
      return -1;
    }
    Point tabPt = SwingUtilities.convertPoint(glassPane, glassPt, this);
    boolean isHorizontal = isTopBottomTabPlacement(getTabPlacement());
    for (int i = 0; i < count; ++i) {
      Rectangle r = getBoundsAt(i);

      // First half.
      if (isHorizontal) {
        r.width = r.width / 2 + 1;
      } else {
        r.height = r.height / 2 + 1;
      }
      if (r.contains(tabPt)) {
        return i;
      }

      // Second half.
      if (isHorizontal) {
        r.x = r.x + r.width;
      } else {
        r.y = r.y + r.height;
      }
      if (r.contains(tabPt)) {
        return i + 1;
      }
    }

    Rectangle lastRect = getBoundsAt(count - 1);
    Point d = isHorizontal ? new Point(1, 0) : new Point(0, 1);
    lastRect.translate(lastRect.width * d.x, lastRect.height * d.y);
    return lastRect.contains(tabPt) ? count : -1;
  }

  protected void convertTab(int prev, int next) {
    if (next < 0 || prev == next) {
      // This check is needed if tab content is null.
      return;
    }
    final Component cmp = getComponentAt(prev);
    final Component tab = getTabComponentAt(prev);
    final String title = getTitleAt(prev);
    final Icon icon = getIconAt(prev);
    final String tip = getToolTipTextAt(prev);
    final boolean isEnabled = isEnabledAt(prev);
    int tgtIndex = prev > next ? next : next - 1;
    remove(prev);
    insertTab(title, icon, cmp, tip, tgtIndex);
    setEnabledAt(tgtIndex, isEnabled);
    // When you drag and drop a disabled tab, it finishes enabled and selected.
    // pointed out by dlorde
    if (isEnabled) {
      setSelectedIndex(tgtIndex);
    }
    // I have a component in all tabs (JLabel with an X to close the tab)
    // and when I move a tab the component disappear.
    // pointed out by Daniel Dario Morales Salas
    setTabComponentAt(tgtIndex, tab);
  }

  protected void initTargetLine(int next) {
    boolean isSideNeighbor = next < 0 || dragTabIndex == next || next - dragTabIndex == 1;
    if (isSideNeighbor) {
      glassPane.setTargetRect(0, 0, 0, 0);
      return;
    }
    Optional.ofNullable(getBoundsAt(Math.max(0, next - 1))).ifPresent(boundsRect -> {
      final Rectangle r = SwingUtilities.convertRectangle(this, boundsRect, glassPane);
      int a = Math.min(next, 1); // a = (next == 0) ? 0 : 1;
      if (isTopBottomTabPlacement(getTabPlacement())) {
        glassPane.setTargetRect(r.x + r.width * a - LINE_SIZE / 2, r.y, LINE_SIZE, r.height);
      } else {
        glassPane.setTargetRect(r.x, r.y + r.height * a - LINE_SIZE / 2, r.width, LINE_SIZE);
      }
    });
  }

  protected void initGlassPane(Point tabPt) {
    getRootPane().setGlassPane(glassPane);
    if (hasGhost) {
      Component c = getTabComponentAt(dragTabIndex);
      Component copy = Optional.ofNullable(c).orElseGet(() -> {
        String title = getTitleAt(dragTabIndex);
        Icon icon = getIconAt(dragTabIndex);
        JLabel label = new JLabel(title, icon, LEADING); // Nimbus?
        label.setIconTextGap(UIManager.getInt("TabbedPane.textIconGap"));
        return label;
      });
      Dimension d = copy.getPreferredSize();
      BufferedImage image = new BufferedImage(d.width, d.height, BufferedImage.TYPE_INT_ARGB);
      Graphics2D g2 = image.createGraphics();
      SwingUtilities.paintComponent(g2, copy, glassPane, 0, 0, d.width, d.height);
      g2.dispose();
      glassPane.setImage(image);
      if (c != null) {
        setTabComponentAt(dragTabIndex, c);
      }
    }
    Point glassPt = SwingUtilities.convertPoint(this, tabPt, glassPane);
    glassPane.setPoint(glassPt);
    glassPane.setVisible(true);
  }

  protected Rectangle getTabAreaBounds() {
    Rectangle tabbedRect = getBounds();
    // XXX: Rectangle compRect = getSelectedComponent().getBounds();
    // pointed out by daryl. NullPointerException: i.e. addTab("Tab", null)
    // Component comp = getSelectedComponent();
    // int idx = 0;
    // while (Objects.isNull(comp) && idx < getTabCount()) {
    //   comp = getComponentAt(idx++);
    // }

    Rectangle compRect = Optional.ofNullable(getSelectedComponent())
        .map(Component::getBounds)
        .orElseGet(Rectangle::new);
    int tabPlacement = getTabPlacement();
    if (isTopBottomTabPlacement(tabPlacement)) {
      tabbedRect.height = tabbedRect.height - compRect.height;
      if (tabPlacement == BOTTOM) {
        tabbedRect.y += compRect.y + compRect.height;
      }
    } else {
      tabbedRect.width = tabbedRect.width - compRect.width;
      if (tabPlacement == RIGHT) {
        tabbedRect.x += compRect.x + compRect.width;
      }
    }
    tabbedRect.grow(2, 2);
    return tabbedRect;
  }

  public static boolean isTopBottomTabPlacement(int tabPlacement) {
    return tabPlacement == TOP || tabPlacement == BOTTOM;
  }
}


class TabTransferable implements Transferable {
  private static final String NAME = "test";
  private final Component tabbedPane;

  protected TabTransferable(Component tabbedPane) {
    this.tabbedPane = tabbedPane;
  }

  @Override public Object getTransferData(DataFlavor flavor) {
    return tabbedPane;
  }

  @Override public DataFlavor[] getTransferDataFlavors() {
    return new DataFlavor[] {new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType, NAME)};
  }

  @Override public boolean isDataFlavorSupported(DataFlavor flavor) {
    return NAME.equals(flavor.getHumanPresentableName());
  }
}

class TabDragSourceListener implements DragSourceListener {
  @Override public void dragEnter(DragSourceDragEvent e) {
    e.getDragSourceContext().setCursor(DragSource.DefaultMoveDrop);
  }

  @Override public void dragExit(DragSourceEvent e) {
    e.getDragSourceContext().setCursor(DragSource.DefaultMoveNoDrop);
    // glassPane.setTargetRect(0, 0, 0, 0);
    // glassPane.setPoint(new Point(-1000, -1000));
    // glassPane.repaint();
  }

  @Override public void dragOver(DragSourceDragEvent e) {
    // Point glassPt = e.getLocation();
    // JComponent glassPane = (JComponent) e.getDragSourceContext();
    // SwingUtilities.convertPointFromScreen(glassPt, glassPane);
    // int targetIdx = getTargetTabIndex(glassPt);
    // if (getTabAreaBounds().contains(glassPt) && targetIdx >= 0
    //     && targetIdx != dragTabIndex && targetIdx != dragTabIndex + 1) {
    //   e.getDragSourceContext().setCursor(DragSource.DefaultMoveDrop);
    //   glassPane.setCursor(DragSource.DefaultMoveDrop);
    // } else {
    //   e.getDragSourceContext().setCursor(DragSource.DefaultMoveNoDrop);
    //   glassPane.setCursor(DragSource.DefaultMoveNoDrop);
    // }
  }

  @Override public void dragDropEnd(DragSourceDropEvent e) {
    // System.out.println("dragDropEnd");
    // dragTabIndex = -1;
    // glassPane.setVisible(false);
    Component c = e.getDragSourceContext().getComponent();
    if (c instanceof JComponent) {
      JRootPane rp = ((JComponent) c).getRootPane();
      Optional.ofNullable(rp.getGlassPane()).ifPresent(gp -> gp.setVisible(false));
    }
  }

  @Override public void dropActionChanged(DragSourceDragEvent e) {
    /* not needed */
  }
}

class TabDragGestureListener implements DragGestureListener {
  private final DragSourceListener handler = new TabDragSourceListener();

  @Override public void dragGestureRecognized(DragGestureEvent e) {
    Optional.ofNullable(e.getComponent())
        .filter(DnDTabbedPane.class::isInstance)
        .map(DnDTabbedPane.class::cast)
        .filter(t -> t.getTabCount() > 1)
        .ifPresent(t -> startDrag(e, t));
  }

  private void startDrag(DragGestureEvent e, DnDTabbedPane tabs) {
    Point tabPt = e.getDragOrigin();
    int idx = tabs.indexAtLocation(tabPt.x, tabPt.y);
    int selIdx = tabs.getSelectedIndex();
    // When a tab runs rotation occurs, a tab that is not the target is dragged.
    // pointed out by Arjen
    boolean isTabRunsRotated = !(tabs.getUI() instanceof MetalTabbedPaneUI)
        && tabs.getTabLayoutPolicy() == JTabbedPane.WRAP_TAB_LAYOUT
        && idx != selIdx;
    tabs.dragTabIndex = isTabRunsRotated ? selIdx : idx;
    if (tabs.dragTabIndex >= 0 && tabs.isEnabledAt(tabs.dragTabIndex)) {
      tabs.initGlassPane(tabPt);
      try {
        e.startDrag(DragSource.DefaultMoveDrop, new TabTransferable(tabs), handler);
      } catch (InvalidDnDOperationException ex) {
        throw new IllegalStateException(ex);
      }
    }
  }
}

class TabDropTargetListener implements DropTargetListener {
  private static final Point HIDDEN_POINT = new Point(0, -1000);

  private static Optional<GhostGlassPane> getGhostGlassPane(Component c) {
    Class<GhostGlassPane> clz = GhostGlassPane.class;
    return Optional.ofNullable(c).filter(clz::isInstance).map(clz::cast);
  }

  @Override public void dragEnter(DropTargetDragEvent e) {
    getGhostGlassPane(e.getDropTargetContext().getComponent()).ifPresent(glassPane -> {
      // DnDTabbedPane tabbedPane = glassPane.tabbedPane;
      Transferable t = e.getTransferable();
      DataFlavor[] f = e.getCurrentDataFlavors();
      if (t.isDataFlavorSupported(f[0])) { // && tabbedPane.dragTabIndex >= 0) {
        e.acceptDrag(e.getDropAction());
      } else {
        e.rejectDrag();
      }
    });
  }

  @Override public void dragExit(DropTargetEvent e) {
    // Component c = e.getDropTargetContext().getComponent();
    // System.out.println("DropTargetListener#dragExit: " + c.getName());
    getGhostGlassPane(e.getDropTargetContext().getComponent()).ifPresent(glassPane -> {
      // XXX: glassPane.setVisible(false);
      glassPane.setPoint(HIDDEN_POINT);
      glassPane.setTargetRect(0, 0, 0, 0);
      glassPane.repaint();
    });
  }

  @Override public void dropActionChanged(DropTargetDragEvent e) {
    /* not needed */
  }

  @Override public void dragOver(DropTargetDragEvent e) {
    Component c = e.getDropTargetContext().getComponent();
    getGhostGlassPane(c).ifPresent(glassPane -> {
      Point glassPt = e.getLocation();

      DnDTabbedPane tabbedPane = glassPane.tabbedPane;
      tabbedPane.initTargetLine(tabbedPane.getTargetTabIndex(glassPt));
      tabbedPane.autoScrollTest(glassPt);

      glassPane.setPoint(glassPt);
      glassPane.repaint();
    });
  }

  @Override public void drop(DropTargetDropEvent e) {
    Component c = e.getDropTargetContext().getComponent();
    getGhostGlassPane(c).ifPresent(glassPane -> {
      DnDTabbedPane tabbedPane = glassPane.tabbedPane;
      Transferable t = e.getTransferable();
      DataFlavor[] f = t.getTransferDataFlavors();
      int prev = tabbedPane.dragTabIndex;
      int next = tabbedPane.getTargetTabIndex(e.getLocation());
      if (t.isDataFlavorSupported(f[0]) && prev != next) {
        tabbedPane.convertTab(prev, next);
        e.dropComplete(true);
      } else {
        e.dropComplete(false);
      }
      glassPane.setVisible(false);
      // tabbedPane.dragTabIndex = -1;
    });
  }
}

class GhostGlassPane extends JComponent {
  public final DnDTabbedPane tabbedPane;
  private final Rectangle lineRect = new Rectangle();
  private final Color lineColor = new Color(0, 100, 255);
  private final Point location = new Point();
  private transient BufferedImage draggingGhost;

  protected GhostGlassPane(DnDTabbedPane tabbedPane) {
    super();
    this.tabbedPane = tabbedPane;
    // [JDK-6700748]
    // Cursor flickering during D&D when using CellRendererPane with validation - Java Bug System
    // https://bugs.openjdk.org/browse/JDK-6700748
    // setCursor(null);
  }

  public void setTargetRect(int x, int y, int width, int height) {
    lineRect.setBounds(x, y, width, height);
  }

  public void setImage(BufferedImage draggingImage) {
    this.draggingGhost = draggingImage;
  }

  public void setPoint(Point pt) {
    this.location.setLocation(pt);
  }

  @Override public boolean isOpaque() {
    return false;
  }

  @Override public void setVisible(boolean v) {
    super.setVisible(v);
    if (!v) {
      setTargetRect(0, 0, 0, 0);
      setImage(null);
    }
  }

  @Override protected void paintComponent(Graphics g) {
    Graphics2D g2 = (Graphics2D) g.create();
    g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .5f));
    boolean b = tabbedPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT;
    if (b && tabbedPane.isPaintScrollArea) {
      g2.setPaint(Color.RED);
      g2.fill(tabbedPane.rectBackward);
      g2.fill(tabbedPane.rectForward);
    }
    if (draggingGhost != null) {
      double xx = location.getX() - draggingGhost.getWidth(this) / 2d;
      double yy = location.getY() - draggingGhost.getHeight(this) / 2d;
      g2.drawImage(draggingGhost, (int) xx, (int) yy, this);
    }
    g2.setPaint(lineColor);
    g2.fill(lineRect);
    g2.dispose();
  }
}

References

46 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. Great job!!! Really!

    I have tried several tabbed pane in a same frame and in a same tab and it doesn't work... Maybe you could fix it or told me how to do this?

    Thanks in advance.

    ReplyDelete
  3. Hi percolala.

    Thank you for reporting the bug, which will now be fixed.
    - http://terai.xrea.jp/swing/dndtabbedpane/src.zip

    - frame.setGlassPane(glassPane);
    }

    private void initGlassPane(Component c, Point tabPt) {
    + getRootPane().setGlassPane(glassPane);
    if(hasGhost()) {

    ReplyDelete
  4. This works great! However, I notice that if you have the setTabLayoutPolicy(
    JTabbedPane.SCROLL_TAB_LAYOUT) value set that the blue rect does not get drawn on the sides of the tabs.

    ReplyDelete
  5. Hi todd.
    Thank you for your report and sorry for not being able to write to you sooner.
    "no effect on scrolling" is pointed out in percolala's mail and is investigating it now.
    so here is under construction example(work only "setTabPlacement(JTabbedPane.TOP)").
    DnDTabbedPane_AutoScroll_alpha.zip

    ReplyDelete
  6. Nice piece of code. Only it doesn't allow for disabled tabs. When you drag'n'drop a disabled tab, it finishes enabled and selected.

    The fix is to (in the tab move handler) set a flag for the tab enabled state, and keep a reference to the original selected tab component.

    When the tab move is done, set the selected component reference to the moved component if the tab was enabled, set the moved tab enable state according to the flag, and then select the tab using the selected component reference.

    ReplyDelete
  7. Hi dlorde.
    > disabled tab...
    This problem will be fixed soon.
    Thank you for your prompt & helpful response!

    ReplyDelete
  8. Hi.

    Greate code, it works awesome ...

    I have just one problem. I have a component (tabbedPane.setTabComponentAt(tabCount, cerrar);) in all tabs (jlabel with an X to close the tab) and when i move a tab the component disappear. Do you know how can i fix this ??

    thanks !!!

    ReplyDelete
  9. Hi, me again, i fixed the problem, it was easy ...

    In convertTab(int prev, int next) method under CDropTargetListener class, just added the next to code lines:

    Component tabCmp = getTabComponentAt(prev);

    setTabComponentAt(tgtindex, tabCmp);

    and that's all.

    greetings !!!

    ReplyDelete
  10. hi daniel.
    Your patch works fine.
    Thank you.

    ReplyDelete
  11. Works great!!!!
    One thing, if there is only one tab in the pane, it would be nice if it didnt start the drag operation since there is no place to move it to.
    thanks!!!

    ReplyDelete
  12. Hi derek_111.
    Thank you for your comment.

    > there is only one tab in the pane, didn't start the drag operation
    You can try to add this in void dragGestureRecognized(DragGestureEvent e) method:
    public void dragGestureRecognized(DragGestureEvent e) {
    // IE7, Chroeme like?
    if(getTabCount()<=1) return;
    ...

    ReplyDelete
  13. That worked perfectly!
    thanks!!!!

    ReplyDelete
  14. Another bug: Put a few tables in a few tabs, then take a tab and drag and drop to reorder it. Go back into any of the tabs and try to resize a table column. The cursor does not change to the resize cursor when you hover over the point between column headers. Weird?

    ReplyDelete
  15. Hi kostas. Thank you for reporting the bug.

    I think that may be a bug of this:
    JTable on a disabled JPanel get no Resize Cursor
    or (If you on Windows)
    Cursor flickering during D&D when using CellRendererPane with validation

    I'm not sure, bat changed the logic to use repaint() method.

    ReplyDelete
  16. Nice job aterai.

    In getTabAreaBounds(), if no component has been used (i.e., addTab("Tab",null)), then the statement:

    scr = getSelectedComponent().getBounds();

    fails. I worked around this by adding the following code:

    int idx = getSelectedIndex();
    Component comp = getSelectedComponent();
    comp = getTabComponentAt(idx);
    if (comp == null)
    scr = getBoundsAt(idx);
    else scr = getSelectedComponent().getBounds();

    ReplyDelete
  17. Hi daryl.
    I completely forgot about (component==null).
    I will fix that asap! Thank you.

    ReplyDelete
  18. i am not able to compile or run the code. Its not working. Plz give me the way of running this code.
    Reagards
    Shamsheer,

    ReplyDelete
  19. Hi shamsheer.
    I am using JDK 1.6.
    In order to run this sample with JDK 1.5 you need to remove or comment out line 283, 298:
    ////MainPanel.java:283: cannot find symbol
    //Component tab = getTabComponentAt(prev);
    ////MainPanel.java:298: cannot find symbol
    //setTabComponentAt(tgtindex, tab);

    Thanks

    ReplyDelete
  20. Great piece of code,
    I was searching for something like this and you implemented exactly the idea I had in my mind.
    Thank you very much

    Gianluca

    ReplyDelete
  21. Hi Gianluca. I'm glad I could help.

    ReplyDelete
  22. Nice class!!

    I have a problem: I have tab components with mouse listeners. The mouse listeners react to double-click to maximize the tab, and bring up context menus. However, they eat the mouse event and there is not drag event anymore.

    Is it possible to have both, mouse listeners on the tab components and dragging?

    Thanks, Alex

    ReplyDelete
  23. Hi, asac.cat.
    This might work:

    final JPopupMenu popup = new JPopupMenu();
    popup.add(new AbstractAction("test") {
    @Override public void actionPerformed(ActionEvent e) {
    System.out.println("maximize: "+tabbedPane.getSelectedIndex());
    }
    });
    //--------
    //How about using the dispatchEvent method:
    JLabel l = new JLabel("JTree 00");
    MouseAdapter ma = new MouseAdapter() {
    public void mousePressed(MouseEvent e) {
    if(e.getClickCount() >= 2) {
    popup.show(e.getComponent(), e.getX(), e.getY());
    }else{
    tabbedPane.dispatchEvent(SwingUtilities.convertMouseEvent(e.getComponent(), e, tabbedPane));
    }
    }
    public void mouseDragged(MouseEvent e) {
    tab.dispatchEvent(SwingUtilities.convertMouseEvent(e.getComponent(), e, tabbedPane));
    }
    };
    JLabel tab0 = new JLabel("tab0");
    tab0.addMouseListener(ma);
    tab0.addMouseMotionListener(ma);
    tabbedPane.setTabComponentAt(0, tab0);

    //--------
    //Or, as an alternate solution(use JTabbedPane#addMouseListener):
    tabbedPane.addMouseListener(new MouseAdapter() {
    public void mousePressed(MouseEvent e) {
    if(e.getClickCount() >= 2) popup.show(e.getComponent(), e.getX(), e.getY());
    }
    });

    Greetings and a happy new year.

    ReplyDelete
  24. Hey guys,

    this very cool and works great. I have a question, how hard would it be to implement Drag'n'Drop between two different TabbedPanes?

    ReplyDelete
  25. Hi, SpaceLovingAlien.
    How about this:
    http://terai.xrea.jp/swing/dndexporttabbedpane/src.zip

    ReplyDelete
  26. note:
    Bug/RFE fixed in JDK 6u21 build
    http://java-swing-tips.blogspot.com/2010/02/tabtransferhandler.html

    ReplyDelete
  27. Great work. What would be needed to change to get it working sharing tabs between 2 windows of the same application running twice? Like how Chrome does its tabs.

    ReplyDelete
  28. Hi,
    This is a good extension, but I get a "RasterFormatException" in the "initGlassPane()"-Method, if i have many Tabs and click the leftmost.
    It concerns the calling of "image.getSubimage( rect.x, rect.y, rect.width, rect.height );"

    ReplyDelete
  29. Hi, Dennis.
    Could you check if the `rect.x` and `rect.y` are non-negative value.

    ReplyDelete
  30. Thanks, rect.x is -2.
    Is it alright if I query for "less than 0"?

    ReplyDelete
  31. A quick fix to this problem is to add this line of code: rect.x = rect.x < 0 ? 0 : rect.x;
    (I guess, which has something to do with a particular Look&Feel "textShiftOffset"..., but not sure)

    ReplyDelete
  32. Sir really good work there.
    I'm doing an open source project and i'm needing somethinbg like this.
    I was wondering if i can use it on my project

    Regards

    ReplyDelete
    Replies
    1. @Juacom99, Thank you for commenting.

      You can freely use, modify, distribute and sell a software licensed under the MIT License(LICENSE.txt) without worrying about the use of software: personal, internal or commercial.

      Delete
    2. I'm using GPLv3 for this porject is that ok with you?

      Regards

      Delete
    3. According to my understanding of the MIT license, it should be no problem at all :)

      Delete
  33. Hi,

    I am trying to u r logic with JTabbedPane with Close button. it is not working.
    please help.

    ReplyDelete
  34. Awesome project. Thank you so much for this.

    There's one thing that's confusing our users, however.
    We use WRAP_TAB_LAYOUT, so we have two rows of tabs at the top of the pane.
    If we want to drag a tab on the top row, that row immediately flips to the bottom.
    And instead of dragging the tab we wanted, we're dragging the tab that was on the other row (which is now the top row).

    ReplyDelete
  35. This is really awesome. Thanks for putting it up, and for maintaining it over the years.

    I have the same problem as "Jack" who had a close button issue but gave no useful info. My issue is this: Dragging tabs works fine, but after the drag, the tab has its title but not its close button! Rather than cutting snippets out of context, I'll refer you to the complete project, at https://github.com/IanDarwin/pdfshow. See docs/code.* for a quick overview before diving in. Many thanks if you have time to look into this!

    ReplyDelete