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
This comment has been removed by the author.
ReplyDeleteGreat job!!! Really!
ReplyDeleteI 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.
Hi percolala.
ReplyDeleteThank 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()) {
This works great! However, I notice that if you have the setTabLayoutPolicy(
ReplyDeleteJTabbedPane.SCROLL_TAB_LAYOUT) value set that the blue rect does not get drawn on the sides of the tabs.
Hi todd.
ReplyDeleteThank 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
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.
ReplyDeleteThe 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.
Hi dlorde.
ReplyDelete> disabled tab...
This problem will be fixed soon.
Thank you for your prompt & helpful response!
Hi.
ReplyDeleteGreate 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 !!!
Hi, me again, i fixed the problem, it was easy ...
ReplyDeleteIn 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 !!!
hi daniel.
ReplyDeleteYour patch works fine.
Thank you.
Works great!!!!
ReplyDeleteOne 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!!!
Hi derek_111.
ReplyDeleteThank 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;
...
That worked perfectly!
ReplyDeletethanks!!!!
Cool stuff - thank you so much!
ReplyDeleteHi frederik.
ReplyDeleteur welcome.
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?
ReplyDeleteHi kostas. Thank you for reporting the bug.
ReplyDeleteI 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.
update auto scroll.
ReplyDeleteNice job aterai.
ReplyDeleteIn 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();
Hi daryl.
ReplyDeleteI completely forgot about (component==null).
I will fix that asap! Thank you.
i am not able to compile or run the code. Its not working. Plz give me the way of running this code.
ReplyDeleteReagards
Shamsheer,
Hi shamsheer.
ReplyDeleteI 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
Great piece of code,
ReplyDeleteI was searching for something like this and you implemented exactly the idea I had in my mind.
Thank you very much
Gianluca
Hi Gianluca. I'm glad I could help.
ReplyDeleteNice class!!
ReplyDeleteI 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
Hi, asac.cat.
ReplyDeleteThis 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.
Hey guys,
ReplyDeletethis very cool and works great. I have a question, how hard would it be to implement Drag'n'Drop between two different TabbedPanes?
Hi, SpaceLovingAlien.
ReplyDeleteHow about this:
http://terai.xrea.jp/swing/dndexporttabbedpane/src.zip
note:
ReplyDeleteBug/RFE fixed in JDK 6u21 build
http://java-swing-tips.blogspot.com/2010/02/tabtransferhandler.html
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.
ReplyDeleteHi, zammbi.
ReplyDeleteHere are some examples:
Java Swing Tips: TabTransferHandler
SharingTabsBetween2WindowsTest6(Java 1.6.0)
SharingTabsBetween2WindowsTest7(Java 1.7.0)
Hi,
ReplyDeleteThis 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 );"
Hi, Dennis.
ReplyDeleteCould you check if the `rect.x` and `rect.y` are non-negative value.
Thanks, rect.x is -2.
ReplyDeleteIs it alright if I query for "less than 0"?
A quick fix to this problem is to add this line of code: rect.x = rect.x < 0 ? 0 : rect.x;
ReplyDelete(I guess, which has something to do with a particular Look&Feel "textShiftOffset"..., but not sure)
Okay thanks :)
ReplyDeleteSir really good work there.
ReplyDeleteI'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
@Juacom99, Thank you for commenting.
DeleteYou 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.
I'm using GPLv3 for this porject is that ok with you?
DeleteRegards
According to my understanding of the MIT license, it should be no problem at all :)
DeleteHi,
ReplyDeleteI am trying to u r logic with JTabbedPane with Close button. it is not working.
please help.
Awesome project. Thank you so much for this.
ReplyDeleteThere'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).
Hi, Arjen
DeleteI was able to reproduce this problem in my environment.
I would like to fix this as soon as possible. Thank you for the report.
571575170 fix: when a tab runs rotation occurs, a tab that is not the target is dragged
DeleteThis is really awesome. Thanks for putting it up, and for maintaining it over the years.
ReplyDeleteI 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!
Hi, idarwin
DeleteThanks for pointing that out. I’ve updated the example code.
DnDTabbedPane: Drag a tab and start moving it, the tab component disappears ・ Issue #11 ・ aterai/java-swing-tips
#11 DnDTabbedPane Drag a tab and start moving it, the tab component d… by aterai ・ Pull Request #12 ・ aterai/java-swing-tips