View Javadoc

1   /*
2    *  XNap - A P2P framework and client.
3    *
4    *  See the file AUTHORS for copyright information.
5    *
6    *  This program is free software; you can redistribute it and/or modify
7    *  it under the terms of the GNU General Public License as published by
8    *  the Free Software Foundation.
9    *
10   *  This program is distributed in the hope that it will be useful,
11   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   *  GNU General Public License for more details.
14   *
15   *  You should have received a copy of the GNU General Public License
16   *  along with this program; if not, write to the Free Software
17   *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
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      //--- Constant(s) ---
61  
62      public static final int AUTOSCROLL_MARGIN = 14;
63  
64      //--- Data field(s) ---
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      //--- Constructor(s) ---
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      //--- Method(s) ---
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 	  public void paintComponent(Graphics g)
124 	  {
125 	  super.paintComponent(g);
126 	  Rectangle outer = getBounds();
127 	  Rectangle inner = getParent().getBounds();
128 	  g.setColor(Color.red);
129 	  g.drawRect(outer.x, outer.y, outer.width, outer.height);
130 	  g.setColor(Color.blue);
131 	  g.drawRect(inner.x, inner.y, inner.width, inner.height);
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 		// FIX: why does java 1.3 call dragExit twice before drop?
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