View Javadoc
1   /*******************************************************************************
2    * Copyhacked (H) 2012-2025.
3    * This program and the accompanying materials
4    * are made available under no term at all, use it like
5    * you want, but share and discuss it
6    * every time possible with every body.
7    * 
8    * Contributors:
9    *      ron190 at ymail dot com - initial implementation
10   ******************************************************************************/
11  package com.jsql.view.swing.list;
12  
13  import com.jsql.util.LogLevelUtil;
14  import org.apache.logging.log4j.LogManager;
15  import org.apache.logging.log4j.Logger;
16  
17  import javax.swing.*;
18  import java.awt.*;
19  import java.awt.datatransfer.DataFlavor;
20  import java.awt.datatransfer.StringSelection;
21  import java.awt.datatransfer.Transferable;
22  import java.awt.datatransfer.UnsupportedFlavorException;
23  import java.io.File;
24  import java.io.IOException;
25  import java.util.List;
26  
27  /**
28   * Handler for processing cut/copy/paste/drag/drop action on a JList items.
29   */
30  public abstract class AbstractListTransfertHandler extends TransferHandler {
31      
32      private static final Logger LOGGER = LogManager.getRootLogger();
33  
34      /**
35       * List of cut/copy/paste/drag/drop items.
36       */
37      protected transient List<ItemList> dragPaths = null;
38      
39      protected abstract String initTransferable();
40      
41      protected abstract void parseStringDrop(TransferSupport support, DnDList list, DefaultListModel<ItemList> listModel);
42      
43      protected abstract List<Integer> initStringPaste(String clipboardText, int selectedIndex, DefaultListModel<ItemList> listModel);
44      
45      @Override
46      public int getSourceActions(JComponent c) {
47          return TransferHandler.COPY_OR_MOVE;
48      }
49      
50      @Override
51      protected Transferable createTransferable(JComponent component) {
52          DnDList list = (DnDList) component;
53          this.dragPaths = list.getSelectedValuesList();
54          var stringTransferable = this.initTransferable();
55          return new StringSelection(stringTransferable.trim());
56      }
57  
58      @SuppressWarnings("unchecked")
59      @Override
60      protected void exportDone(JComponent c, Transferable data, int action) {
61          if (action == TransferHandler.MOVE) {
62              JList<ItemList> list = (JList<ItemList>) c;
63              DefaultListModel<ItemList> model = (DefaultListModel<ItemList>) list.getModel();
64              
65              for (ItemList itemPath: this.dragPaths) {
66                  // Unhandled ArrayIndexOutOfBoundsException #56115 on remove()
67                  try {
68                      model.remove(model.indexOf(itemPath));
69                  } catch (ArrayIndexOutOfBoundsException e) {
70                      LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e.getMessage(), e);
71                  }
72              }
73              
74              this.dragPaths = null;
75          }
76      }
77  
78      @Override
79      public boolean canImport(TransferSupport support) {
80          return support.isDataFlavorSupported(DataFlavor.stringFlavor)
81              || support.isDataFlavorSupported(DataFlavor.javaFileListFlavor);
82      }
83  
84      @Override
85      public boolean importData(TransferSupport support) {
86          if (!this.canImport(support)) {
87              return false;
88          }
89  
90          DnDList list = (DnDList) support.getComponent();
91          DefaultListModel<ItemList> listModel = (DefaultListModel<ItemList>) list.getModel();
92          
93          if (support.isDrop()) {  // drop
94              if (support.isDataFlavorSupported(DataFlavor.stringFlavor)) {
95                  this.parseStringDrop(support, list, listModel);
96              } else if (support.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
97                  this.parseFileDrop(support, list);
98              }
99          } else {
100             var transferableFromClipboard = Toolkit.getDefaultToolkit().getSystemClipboard().getContents(null);
101             if (transferableFromClipboard != null) {  // paste
102                 if (transferableFromClipboard.isDataFlavorSupported(DataFlavor.stringFlavor)) {
103                     this.parseStringPaste(list, listModel, transferableFromClipboard);
104                 } else if (transferableFromClipboard.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
105                     this.parseFilePaste(list, transferableFromClipboard);
106                 }
107             }
108         }
109         return true;
110     }
111 
112     @SuppressWarnings("unchecked")
113     private void parseFileDrop(TransferSupport support, DnDList list) {
114         JList.DropLocation dropLocation = (JList.DropLocation) support.getDropLocation();
115         int childIndex = dropLocation.getIndex();
116         try {
117             list.dropPasteFile(
118                 (List<File>) support.getTransferable().getTransferData(DataFlavor.javaFileListFlavor),
119                 childIndex
120             );
121         } catch (UnsupportedFlavorException | IOException e) {
122             LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
123         }
124     }
125 
126     private void parseStringPaste(DnDList list, DefaultListModel<ItemList> listModel, Transferable transferableFromClipboard) {
127         try {
128             String clipboardText = (String) transferableFromClipboard.getTransferData(DataFlavor.stringFlavor);
129             var selectedIndexPaste = Math.max(list.getSelectedIndex(), 0);
130             list.clearSelection();
131             List<Integer> selectedIndexes = this.initStringPaste(clipboardText, selectedIndexPaste, listModel);
132             var selectedIndexesPasted = new int[selectedIndexes.size()];
133             var i = 0;
134             
135             for (Integer selectedIndex: selectedIndexes) {
136                 selectedIndexesPasted[i] = selectedIndex;
137                 i++;
138             }
139             
140             list.setSelectedIndices(selectedIndexesPasted);
141             list.scrollRectToVisible(
142                 list.getCellBounds(
143                     list.getMinSelectionIndex(),
144                     list.getMaxSelectionIndex()
145                 )
146             );
147         } catch (NullPointerException | UnsupportedFlavorException | IOException e) {
148             // Fix #8831: Multiple Exception on scrollRectToVisible()
149             LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
150         }
151     }
152 
153     @SuppressWarnings("unchecked")
154     private void parseFilePaste(DnDList list, Transferable transferableFromClipboard) {
155         try {
156             var selectedIndex = Math.max(list.getSelectedIndex(), 0);
157             list.clearSelection();
158             list.dropPasteFile(
159                 (List<File>) transferableFromClipboard.getTransferData(DataFlavor.javaFileListFlavor),
160                 selectedIndex
161             );
162         } catch (UnsupportedFlavorException | IOException e) {
163             LOGGER.log(LogLevelUtil.CONSOLE_JAVA, e, e);
164         }
165     }
166 }