View Javadoc
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 dnDTabbedPane) {
33              this.source = dnDTabbedPane;
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          // [JDK-6700748] Cursor flickering during D&D when using CellRendererPane with validation - Java Bug System
82          // https://bugs.openjdk.java.net/browse/JDK-6700748
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 src) {
120             c.getRootPane().setGlassPane(new GhostGlassPane(src));
121             
122             if (src.dragTabIndex < 0) {
123                 return TransferHandler.NONE;
124             }
125             
126             this.setDragImage(this.makeDragTabImage(src));
127             c.getRootPane().getGlassPane().setVisible(true);
128             
129             return TransferHandler.MOVE;
130         }
131         return TransferHandler.NONE;
132     }
133     
134     @Override
135     public boolean importData(TransferHandler.TransferSupport support) {
136         if (!this.canImport(support)) {
137             return false;
138         }
139 
140         DnDTabbedPane target = (DnDTabbedPane) support.getComponent();
141         DnDTabbedPane.DnDDropLocation dl = target.getDropLocation();
142         
143         try {
144             DnDTabData data = (DnDTabData) support.getTransferable().getTransferData(this.localObjectFlavor);
145             DnDTabbedPane src = data.tabbedPane;
146             int index = dl.getIndex();
147             
148             if (target.equals(src)) {
149                 src.convertTab(src.dragTabIndex, index);
150             } else {
151                 src.exportTab(src.dragTabIndex, target, index);
152             }
153             return true;
154         } catch (UnsupportedFlavorException | IOException e) {
155             LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
156         }
157         
158         return false;
159     }
160     
161     @Override
162     protected void exportDone(JComponent c, Transferable data, int action) {
163         DnDTabbedPane src = (DnDTabbedPane) c;
164         src.getRootPane().getGlassPane().setVisible(false);
165         src.setDropLocation(null, false);
166         src.repaint();
167         src.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
168     }
169 }