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