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) {
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          // [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) {
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 }