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      /**
21       * Log4j logger sent to view.
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          // [JDK-6700748] Cursor flickering during D&D when using CellRendererPane with validation - Java Bug System
85          // https://bugs.openjdk.java.net/browse/JDK-6700748
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 }