1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.xnap.gui.tree;
21
22 import java.awt.Component;
23 import java.awt.Insets;
24 import java.awt.Point;
25 import java.awt.Rectangle;
26 import java.awt.datatransfer.Transferable;
27 import java.awt.datatransfer.UnsupportedFlavorException;
28 import java.awt.dnd.Autoscroll;
29 import java.awt.dnd.DnDConstants;
30 import java.awt.dnd.DropTarget;
31 import java.awt.dnd.DropTargetDragEvent;
32 import java.awt.dnd.DropTargetDropEvent;
33 import java.awt.dnd.DropTargetEvent;
34 import java.awt.dnd.DropTargetListener;
35 import java.awt.event.ActionEvent;
36 import java.awt.event.ActionListener;
37 import java.io.File;
38 import java.io.IOException;
39
40 import javax.swing.JTree;
41 import javax.swing.Timer;
42 import javax.swing.event.TreeSelectionEvent;
43 import javax.swing.tree.TreeModel;
44 import javax.swing.tree.TreePath;
45
46 import org.apache.log4j.Logger;
47 import org.xnap.gui.Dialogs;
48 import org.xnap.gui.util.FileArray;
49 import org.xnap.gui.util.TransferableFile;
50 import org.xnap.util.FileHelper;
51
52 /***
53 * Provides a {@link JTree} aware of drops performed on its nodes. Drop objects
54 * have to be of flavor {@link TransferableFile#FILE_FLAVOR} to be accepted.
55 */
56 public class DroppableTree extends JTree
57 implements DropTargetListener, Autoscroll
58 {
59
60
61
62 public static final int AUTOSCROLL_MARGIN = 14;
63
64
65
66 protected DropTarget dropTarget = new DropTarget(this, this);
67 protected TreePath originalPath = null;
68 protected Timer delayTimer;
69 protected Component parent;
70 protected boolean dndActive = false;
71
72 private TreePath lastPath;
73
74 private static Logger logger = Logger.getLogger(DroppableTree.class);
75
76
77
78 public DroppableTree(TreeModel tm, Component parent)
79 {
80 super(tm);
81 this.parent = parent;
82 delayTimer = new Timer(500, new HoverListener());
83 delayTimer.setRepeats(false);
84 }
85
86
87
88 public void autoscroll(Point p)
89 {
90 int row = getClosestRowForLocation(p.x, p.y);
91
92 if (row < 0)
93 return;
94
95 Rectangle bounds = getBounds();
96
97 if (p.y + bounds.y <= AUTOSCROLL_MARGIN) {
98 row--;
99 }
100 else {
101 row++;
102 }
103
104 if (row >= 0 && row < getRowCount()) {
105 scrollRowToVisible(row);
106 }
107 }
108
109 public Insets getAutoscrollInsets()
110 {
111 Rectangle outer = getBounds();
112 Rectangle inner = getParent().getBounds();
113
114 return new Insets(inner.y - outer.y + AUTOSCROLL_MARGIN,
115 inner.x - outer.x + AUTOSCROLL_MARGIN,
116 outer.height - inner.height
117 - inner.y + outer.y + AUTOSCROLL_MARGIN,
118 outer.width - inner.width - inner.x
119 + outer.x + AUTOSCROLL_MARGIN);
120 }
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135 public void dragEnter(DropTargetDragEvent event)
136 {
137 originalPath = getSelectionPath();
138 dndActive = true;
139 }
140
141 public void dragExit(DropTargetEvent event)
142 {
143 logger.debug("dragExit!");
144
145
146 if (lastPath == null) {
147 lastPath = getSelectionPath();
148 logger.debug("path: " + lastPath);
149 }
150
151 cleanUp();
152 }
153
154 public void dragOver(DropTargetDragEvent event)
155 {
156 Point p = event.getLocation();
157 TreePath path = getClosestPathForLocation(p.x, p.y);
158 if (path != getSelectionPath()) {
159 delayTimer.restart();
160 setSelectionPath(path);
161 }
162 }
163
164 /***
165 * After DND this method should be called to set the tree in its original
166 * state.
167 */
168 protected void cleanUp()
169 {
170 logger.debug("cleanUp!");
171
172 dndActive = false;
173 if (originalPath != null) {
174 setSelectionPath(originalPath);
175 scrollPathToVisible(originalPath);
176 }
177 delayTimer.stop();
178 }
179
180 /***
181 * Only issue TreeSelectionEvents if the tree is not in DND mode.
182 */
183 protected void fireValueChanged(TreeSelectionEvent e)
184 {
185 if (!dndActive) {
186 super.fireValueChanged(e);
187 }
188 }
189
190 public void drop(DropTargetDropEvent event)
191 {
192 logger.debug("dropped!");
193
194 delayTimer.stop();
195
196 try {
197 Transferable t = event.getTransferable();
198
199 if (!t.isDataFlavorSupported(TransferableFile.FILE_FLAVOR)) {
200 event.rejectDrop();
201 cleanUp();
202 return;
203 }
204
205 event.acceptDrop(DnDConstants.ACTION_MOVE);
206
207 FileArray f =
208 (FileArray)t.getTransferData(TransferableFile.FILE_FLAVOR);
209
210 TreePath selected
211 = (getSelectionPath().equals(originalPath)) ? lastPath :
212 getSelectionPath();
213 lastPath = null;
214 if (selected == null) {
215 event.rejectDrop();
216 cleanUp();
217 return;
218 }
219
220 Object o = selected.getLastPathComponent();
221 if (o instanceof File) {
222 File dir = (File)o;
223 File files[] = Dialogs.showMoveDialog(parent, f.getFiles(),
224 dir);
225
226 if (files == null) {
227 event.dropComplete(false);
228 cleanUp();
229 return;
230 }
231
232 for (int i = 0; i < files.length; i++) {
233 try {
234 FileHelper.moveUnique(files[i], dir.getAbsolutePath());
235 }
236 catch (IOException ie) {
237 logger.debug("drag move failed", ie);
238 }
239 }
240 event.dropComplete(true);
241 }
242 }
243 catch (IOException ie) {
244 logger.debug(ie);
245 }
246 catch (UnsupportedFlavorException ue) {
247 logger.debug(ue);
248 }
249 cleanUp();
250 }
251
252 public void dropActionChanged(DropTargetDragEvent event)
253 {
254 }
255
256 protected class HoverListener implements ActionListener
257 {
258 public void actionPerformed(ActionEvent event)
259 {
260 TreePath path = getSelectionPath();
261
262 if (path == null)
263 return;
264
265 if (isExpanded(path)) {
266 collapsePath(path);
267 }
268 else {
269 scrollPathToVisible(path);
270 expandPath(path);
271 }
272 }
273 }
274 }
275