1 package com.jsql.view.swing.tab.dnd;
2
3 import com.jsql.util.LogLevelUtil;
4 import org.apache.logging.log4j.LogManager;
5 import org.apache.logging.log4j.Logger;
6
7 import javax.swing.*;
8 import java.awt.*;
9 import java.awt.datatransfer.DataFlavor;
10 import java.awt.datatransfer.Transferable;
11 import java.awt.datatransfer.UnsupportedFlavorException;
12 import java.awt.dnd.DragSource;
13 import java.awt.image.BufferedImage;
14 import java.io.IOException;
15 import java.util.Objects;
16 import java.util.Optional;
17
18 public class TabTransferHandler extends TransferHandler {
19
20 private static final Logger LOGGER = LogManager.getRootLogger();
21
22 protected final DataFlavor localObjectFlavor;
23
24 protected DnDTabbedPane source;
25
26 public TabTransferHandler() {
27 this.localObjectFlavor = new DataFlavor(DnDTabData.class, "DnDTabData");
28 }
29
30 @Override
31 protected Transferable createTransferable(JComponent c) {
32 if (c instanceof DnDTabbedPane) {
33 this.source = (DnDTabbedPane) c;
34 }
35
36 return new Transferable() {
37 @Override
38 public DataFlavor[] getTransferDataFlavors() {
39 return new DataFlavor[] { TabTransferHandler.this.localObjectFlavor };
40 }
41 @Override
42 public boolean isDataFlavorSupported(DataFlavor flavor) {
43 return Objects.equals(TabTransferHandler.this.localObjectFlavor, flavor);
44 }
45 @Override
46 public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException {
47 if (this.isDataFlavorSupported(flavor)) {
48 return new DnDTabData(TabTransferHandler.this.source);
49 } else {
50 throw new UnsupportedFlavorException(flavor);
51 }
52 }
53 };
54 }
55
56 @Override
57 public boolean canImport(TransferHandler.TransferSupport support) {
58 if (!support.isDrop() || !support.isDataFlavorSupported(this.localObjectFlavor)) {
59 return false;
60 }
61
62 support.setDropAction(TransferHandler.MOVE);
63 var tdl = support.getDropLocation();
64 var pt = tdl.getDropPoint();
65
66 DnDTabbedPane target = (DnDTabbedPane) support.getComponent();
67
68 target.autoScrollTest(pt);
69 DnDTabbedPane.DnDDropLocation dl = target.dropLocationForPointDnD(pt);
70 int idx = dl.getIndex();
71
72 var isDroppable = false;
73 boolean isAreaContains = target.getTabAreaBounds().contains(pt) && idx >= 0;
74
75 if (target.equals(this.source)) {
76 isDroppable = isAreaContains && idx != target.dragTabIndex && idx != target.dragTabIndex + 1;
77 } else {
78 isDroppable = Optional.ofNullable(this.source).map(c -> !c.isAncestorOf(target)).orElse(false) && isAreaContains;
79 }
80
81
82
83 Cursor cursor = isDroppable ? DragSource.DefaultMoveDrop : DragSource.DefaultMoveNoDrop;
84 Component glassPane = target.getRootPane().getGlassPane();
85 glassPane.setCursor(cursor);
86 target.setCursor(cursor);
87
88 support.setShowDropLocation(isDroppable);
89 dl.setDroppable(isDroppable);
90 target.setDropLocation(dl, isDroppable);
91 return isDroppable;
92 }
93
94 private BufferedImage makeDragTabImage(DnDTabbedPane tabbedPane) {
95 Rectangle rect = tabbedPane.getBoundsAt(tabbedPane.dragTabIndex);
96 var image = new BufferedImage(tabbedPane.getWidth(), tabbedPane.getHeight(), BufferedImage.TYPE_INT_ARGB);
97 Graphics g2 = image.createGraphics();
98 tabbedPane.paint(g2);
99 g2.dispose();
100
101 if (rect.x < 0) {
102 rect.translate(-rect.x, 0);
103 }
104 if (rect.y < 0) {
105 rect.translate(0, -rect.y);
106 }
107 if (rect.x + rect.width > image.getWidth()) {
108 rect.width = image.getWidth() - rect.x;
109 }
110 if (rect.y + rect.height > image.getHeight()) {
111 rect.height = image.getHeight() - rect.y;
112 }
113
114 return image.getSubimage(rect.x, rect.y, rect.width, rect.height);
115 }
116
117 @Override
118 public int getSourceActions(JComponent c) {
119 if (c instanceof DnDTabbedPane) {
120 DnDTabbedPane src = (DnDTabbedPane) c;
121 c.getRootPane().setGlassPane(new GhostGlassPane(src));
122
123 if (src.dragTabIndex < 0) {
124 return TransferHandler.NONE;
125 }
126
127 this.setDragImage(this.makeDragTabImage(src));
128 c.getRootPane().getGlassPane().setVisible(true);
129
130 return TransferHandler.MOVE;
131 }
132 return TransferHandler.NONE;
133 }
134
135 @Override
136 public boolean importData(TransferHandler.TransferSupport support) {
137 if (!this.canImport(support)) {
138 return false;
139 }
140
141 DnDTabbedPane target = (DnDTabbedPane) support.getComponent();
142 DnDTabbedPane.DnDDropLocation dl = target.getDropLocation();
143
144 try {
145 DnDTabData data = (DnDTabData) support.getTransferable().getTransferData(this.localObjectFlavor);
146 DnDTabbedPane src = data.tabbedPane;
147 int index = dl.getIndex();
148
149 if (target.equals(src)) {
150 src.convertTab(src.dragTabIndex, index);
151 } else {
152 src.exportTab(src.dragTabIndex, target, index);
153 }
154 return true;
155 } catch (UnsupportedFlavorException | IOException e) {
156 LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
157 }
158
159 return false;
160 }
161
162 @Override
163 protected void exportDone(JComponent c, Transferable data, int action) {
164 DnDTabbedPane src = (DnDTabbedPane) c;
165 src.getRootPane().getGlassPane().setVisible(false);
166 src.setDropLocation(null, false);
167 src.repaint();
168 src.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
169 }
170 }