1 | package org.greenstone.gatherer.gui.tree;
|
---|
2 |
|
---|
3 | import java.awt.*;
|
---|
4 | import java.awt.datatransfer.*;
|
---|
5 | import java.awt.dnd.*;
|
---|
6 | import java.awt.geom.AffineTransform;
|
---|
7 | import java.awt.image.BufferedImage;
|
---|
8 | import java.io.File;
|
---|
9 | import java.util.*;
|
---|
10 | import javax.swing.*;
|
---|
11 | import javax.swing.event.*;
|
---|
12 | import javax.swing.plaf.basic.BasicTreeUI;
|
---|
13 | import javax.swing.tree.*;
|
---|
14 | import org.greenstone.gatherer.Dictionary;
|
---|
15 | import org.greenstone.gatherer.Gatherer;
|
---|
16 | import org.greenstone.gatherer.file.FileNode;
|
---|
17 | import org.greenstone.gatherer.file.FileSystemModel;
|
---|
18 | import org.greenstone.gatherer.util.DragComponent;
|
---|
19 | import org.greenstone.gatherer.util.DragGroup;
|
---|
20 | import org.greenstone.gatherer.util.DragTreeSelectionModel;
|
---|
21 | import org.greenstone.gatherer.util.Utility;
|
---|
22 |
|
---|
23 | public abstract class DragTree
|
---|
24 | extends JTree
|
---|
25 | implements Autoscroll, DragGestureListener, DragSourceListener, DropTargetListener, DragComponent, TreeSelectionListener {
|
---|
26 | /** The normal background color. */
|
---|
27 | private Color background_color;
|
---|
28 | /** The normal foreground color. */
|
---|
29 | private Color foreground_color;
|
---|
30 | /** The Group this component belongs to. */
|
---|
31 | private DragGroup group;
|
---|
32 | /** The image to use for the disabled background. */
|
---|
33 | private ImageIcon disabled_background;
|
---|
34 | /** The image to use for a normal background. */
|
---|
35 | private ImageIcon normal_background;
|
---|
36 | /** The icon to use for multiple node drag'n'drops. We decided against using the windows paradigm or a block of x horizontal lines for x files. */
|
---|
37 | private ImageIcon multiple_icon = new ImageIcon("resource"+File.separator+"multiple.gif");
|
---|
38 | /** The default drag action, although its not that important as we provide custom icons during drags. */
|
---|
39 | private int drag_action = DnDConstants.ACTION_MOVE;
|
---|
40 | /** The location of the last ghost drawn, so that we can repair the 'spoilt' area. */
|
---|
41 | private Point pt_last = null;
|
---|
42 | /** The region borderer by the lower cue line. */
|
---|
43 | private Rectangle lower_cue_line;
|
---|
44 | /** The region covered by the drag ghost icon. */
|
---|
45 | private Rectangle ra_ghost = new Rectangle();
|
---|
46 | /** The region borderer by the upper cue line. */
|
---|
47 | private Rectangle upper_cue_line;
|
---|
48 | /** The identifying name of this Tree. */
|
---|
49 | private String name;
|
---|
50 | /** The last tree path the drag was hovered over. */
|
---|
51 | private TreePath previous_path = null;
|
---|
52 |
|
---|
53 | static private Cursor NO_DRAG_CURSOR = null;
|
---|
54 |
|
---|
55 | static private final Color TRANSPARENT_COLOR = new Color(0,0,0,0);
|
---|
56 | /** The distance from the edge of the current view within the scroll bar which if entered causes the view to scroll. */
|
---|
57 | static private final int AUTOSCROLL_MARGIN = 12;
|
---|
58 |
|
---|
59 | static public final int TREE_DISPLAY_CHANGED = 0;
|
---|
60 | static public final int LOADED_COLLECTION_CHANGED = 1;
|
---|
61 | static public final int COLLECTION_CONTENTS_CHANGED = 2;
|
---|
62 |
|
---|
63 |
|
---|
64 | public DragTree(String name, TreeModel model, boolean mixed_selection)
|
---|
65 | {
|
---|
66 | super();
|
---|
67 | init(name, mixed_selection);
|
---|
68 | if (model != null) {
|
---|
69 | setModel(model);
|
---|
70 | }
|
---|
71 | }
|
---|
72 |
|
---|
73 | public void init(String name, boolean mixed_selection) {
|
---|
74 | if (NO_DRAG_CURSOR == null) {
|
---|
75 | NO_DRAG_CURSOR = DragSource.DefaultMoveNoDrop;
|
---|
76 | }
|
---|
77 |
|
---|
78 | // Init
|
---|
79 | this.name = name;
|
---|
80 |
|
---|
81 | // Creation
|
---|
82 | this.putClientProperty("JTree.lineStyle", "Angled");
|
---|
83 | this.setAutoscrolls(true);
|
---|
84 | this.setEditable(false);
|
---|
85 | this.setLargeModel(true);
|
---|
86 | this.setOpaque(true);
|
---|
87 | this.setSelectionModel(new DragTreeSelectionModel(this, mixed_selection));
|
---|
88 | this.setShowsRootHandles(true);
|
---|
89 | this.setUI(new BasicTreeUI());
|
---|
90 | // Connection
|
---|
91 | addTreeSelectionListener(this);
|
---|
92 |
|
---|
93 | DragTreeCellRenderer renderer = new DragTreeCellRenderer();
|
---|
94 | //make the renderer paint nodes as transparent when not selected
|
---|
95 | //renderer.setBackgroundNonSelectionColor(new Color(0,0,0,0));
|
---|
96 | setCellRenderer(renderer);
|
---|
97 | // It turns out VariableHeightLayoutCache is buggy under MacOS, so I'll force it to use FixedHeightLayoutCache. To do that I have to set a cell height, ala below, and set the large model property to true, ala above. And buggy VariableHeightLayoutCache goes away. Plus this actually provides a minor performance boost as the layout manager doesn't have to calculate new bounds each time (well... I suppose any gain is actually pretty much eclipsed by the time taken for file access while we determine what a certain nodes children are).
|
---|
98 | // And once we have the cell renderer, use it to determine a fixed height for the rows
|
---|
99 | Component prototype_row = renderer.getTreeCellRendererComponent(this, "Prototype", true, true, false, 0, true);
|
---|
100 | this.setRowHeight(prototype_row.getSize().height);
|
---|
101 | prototype_row = null;
|
---|
102 |
|
---|
103 |
|
---|
104 | // Drag'n'drop Setup
|
---|
105 | // Drag source setup.
|
---|
106 | DragSource.getDefaultDragSource().createDefaultDragGestureRecognizer(this, drag_action, this);
|
---|
107 | // Drop destination setup.
|
---|
108 | new DropTarget(this, drag_action, this, true);
|
---|
109 | }
|
---|
110 |
|
---|
111 | // Autoscroll Interface - Scroll because the mouse cursor is in our scroll zone.<br>
|
---|
112 | // The following code was borrowed from the book:<br>
|
---|
113 | // Java Swing<br>
|
---|
114 | // By Robert Eckstein, Marc Loy & Dave Wood<br>
|
---|
115 | // Paperback - 1221 pages 1 Ed edition (September 1998)<br>
|
---|
116 | // O'Reilly & Associates; ISBN: 156592455X<br>
|
---|
117 | // The relevant chapter of which can be found at:<br>
|
---|
118 | // http://www.oreilly.com/catalog/jswing/chapter/dnd.beta.pdf<br>
|
---|
119 | // But I've probably tortured it beyond all recognition anyway.
|
---|
120 | public void autoscroll(Point pt) {
|
---|
121 | // Figure out which row we're on.
|
---|
122 | int row = getRowForLocation(pt.x, pt.y);
|
---|
123 | // If we are not on a row then ignore this autoscroll request
|
---|
124 | if (row < 0) return;
|
---|
125 | Rectangle bounds = getBounds();// Yes, scroll up one row
|
---|
126 | // Now decide if the row is at the top of the screen or at the bottom. We do this to make the previous row (or the next row) visible as appropriate. If we're at the absolute top or bottom, just return the first or last row respectively.
|
---|
127 | // Is row at top of screen?
|
---|
128 | if(pt.y + bounds.y <= AUTOSCROLL_MARGIN) {
|
---|
129 | // Yes, scroll up one row
|
---|
130 | if(row <= 0) {
|
---|
131 | row = 0;
|
---|
132 | }
|
---|
133 | else {
|
---|
134 | row = row - 1;
|
---|
135 | }
|
---|
136 | }
|
---|
137 | else {
|
---|
138 | // No, scroll down one row
|
---|
139 | if(row < getRowCount() - 1) {
|
---|
140 | row = row + 1;
|
---|
141 | }
|
---|
142 | }
|
---|
143 | this.scrollRowToVisible(row);
|
---|
144 | }
|
---|
145 |
|
---|
146 | /** In order for the appearance to be consistant, given we may be in the situation where the pointer has left our focus but the ghost remains, this method allows other members of the GGroup to tell this component to clear its ghost.
|
---|
147 | */
|
---|
148 | public void clearGhost() {
|
---|
149 | // Erase the last ghost image and cue line
|
---|
150 | paintImmediately(ra_ghost.getBounds());
|
---|
151 | }
|
---|
152 |
|
---|
153 | /** Any implementation of DragSourceListener must include this method so we can be notified when the drag event ends (somewhere else), which will in turn remove actions.
|
---|
154 | * @param event A <strong>DragSourceDropEvent</strong> containing all the information about the end of the drag event.
|
---|
155 | */
|
---|
156 | public void dragDropEnd(DragSourceDropEvent event) {
|
---|
157 | if(event.getDropSuccess()) {
|
---|
158 | // Do whatever I do when the drop is successful.
|
---|
159 | }
|
---|
160 | }
|
---|
161 | /** Any implementation of DragSourceListener must include this method so we can be notified when the drag focus enters this component.
|
---|
162 | * @param event A <strong>DragSourceDragEvent</strong> containing all the information
|
---|
163 | * about the drag event.
|
---|
164 | */
|
---|
165 | public void dragEnter(DragSourceDragEvent event) {
|
---|
166 | // Handled elsewhere.
|
---|
167 | }
|
---|
168 | /** Any implementation of DropTargetListener must include this method so we can be notified when the drag focus enters this component, which in this case is to grab focus from within our group.
|
---|
169 | * @param event A <strong>DropTargetDragEvent</strong> containing all the information about the drag event.
|
---|
170 | */
|
---|
171 | public void dragEnter(DropTargetDragEvent event) {
|
---|
172 | group.grabFocus(this);
|
---|
173 | }
|
---|
174 | /** Any implementation of DragSourceListener must include this method so we can be notified when the drag focus leaves this component.
|
---|
175 | * @param event A <strong>DragSourceEvent</strong> containing all the information about the drag event.
|
---|
176 | */
|
---|
177 | public void dragExit(DragSourceEvent event) {
|
---|
178 | clearGhost();
|
---|
179 | }
|
---|
180 |
|
---|
181 | /** Any implementation of DropTargetListener must include this method
|
---|
182 | * so we can be notified when the drag focus leaves this component.
|
---|
183 | * @param event A DropTargetEvent containing all the information
|
---|
184 | * about the drag event.
|
---|
185 | */
|
---|
186 | public void dragExit(DropTargetEvent event) {
|
---|
187 | clearGhost();
|
---|
188 | }
|
---|
189 |
|
---|
190 | /** Any implementation of DragGestureListener must include this method
|
---|
191 | * so we can be notified when a drag action has been noticed, thus a
|
---|
192 | * drag action has begun.
|
---|
193 | * @param event A DragGestureEvent containing all the information about
|
---|
194 | * the drag event.
|
---|
195 | */
|
---|
196 | public void dragGestureRecognized(DragGestureEvent event) {
|
---|
197 | // Can never drag from Enrich tree
|
---|
198 | if(name.equals("Enrich")) {
|
---|
199 | return;
|
---|
200 | }
|
---|
201 | // Disable editing, unless you want to have the edit box pop-up part way through dragging.
|
---|
202 | this.setEditable(false);
|
---|
203 | // We need this to find one of the selected nodes.
|
---|
204 | Point origin = event.getDragOrigin();
|
---|
205 | TreePath path = this.getPathForLocation(origin.x, origin.y);
|
---|
206 | // Taking into account our delayed model of selection, it is possible the user has performed a select and drag in one click. Here we utilize the Windows paradigm like so: If the node at the origin of the drag and drop is already in our selection then we perform multiple drag and drop. Otherwise we recognise that this is a distinct drag-drop and move only the origin node.
|
---|
207 | if(!isPathSelected(path)) {
|
---|
208 | ((DragTreeSelectionModel)selectionModel).setImmediate(true);
|
---|
209 | setSelectionPath(path);
|
---|
210 | ((DragTreeSelectionModel)selectionModel).setImmediate(false);
|
---|
211 | }
|
---|
212 | if(path == null) {
|
---|
213 | return;
|
---|
214 | }
|
---|
215 | if (!isValidDrag()) {
|
---|
216 | try {
|
---|
217 | event.startDrag(NO_DRAG_CURSOR, new StringSelection("dummy"), this);
|
---|
218 | //dragging = true;
|
---|
219 | }
|
---|
220 | catch(Exception error) {
|
---|
221 | error.printStackTrace();
|
---|
222 | }
|
---|
223 | return;
|
---|
224 | }
|
---|
225 | // Now update the selection stored as far as the group is concerned.
|
---|
226 | group.setSelection(getSelectionPaths());
|
---|
227 | group.setSource(this);
|
---|
228 | // First grab ghost.
|
---|
229 | group.grabFocus(this);
|
---|
230 | // Ghost Image stuff.
|
---|
231 | Rectangle rect = this.getPathBounds(path);
|
---|
232 | group.mouse_offset = new Point(origin.x - rect.x, origin.y - rect.y);
|
---|
233 | // Create the ghost image.
|
---|
234 | // Retrieve the selected files.
|
---|
235 | int selection_count = getSelectionCount();
|
---|
236 | if(selection_count > 0) {
|
---|
237 | JLabel label;
|
---|
238 | if(selection_count == 1) {
|
---|
239 | TreePath node_path = getSelectionPath();
|
---|
240 | FileNode node = (FileNode) path.getLastPathComponent();
|
---|
241 | label = new JLabel(node.toString(), ((DefaultTreeCellRenderer)getCellRenderer()).getLeafIcon(), JLabel.CENTER);
|
---|
242 | }
|
---|
243 | else {
|
---|
244 | String title = getSelectionCount() + Dictionary.get("Tree.Files");
|
---|
245 | label = new JLabel(title, ((DefaultTreeCellRenderer)getCellRenderer()).getClosedIcon(), JLabel.CENTER);
|
---|
246 | title = null;
|
---|
247 | }
|
---|
248 | // The layout manager normally does this.
|
---|
249 | Dimension label_size = label.getPreferredSize();
|
---|
250 | label.setSize(label_size);
|
---|
251 | label.setBackground(TRANSPARENT_COLOR);
|
---|
252 | label.setOpaque(true);
|
---|
253 | // Get a buffered image of the selection for dragging a ghost image.
|
---|
254 | group.image_ghost = new BufferedImage(label_size.width, label_size.height, BufferedImage.TYPE_INT_ARGB_PRE);
|
---|
255 | label_size = null;
|
---|
256 | // Get a graphics context for this image.
|
---|
257 | Graphics2D g2 = group.image_ghost.createGraphics();
|
---|
258 | // Make the image ghostlike
|
---|
259 | g2.setComposite(AlphaComposite.getInstance (AlphaComposite.SRC, 0.5f));
|
---|
260 | // Ask the cell renderer to paint itself into the BufferedImage
|
---|
261 | label.paint(g2);
|
---|
262 | g2 = null;
|
---|
263 | label = null;
|
---|
264 | try {
|
---|
265 | event.startDrag(new Cursor(Cursor.DEFAULT_CURSOR), group.image_ghost, group.mouse_offset, new StringSelection("dummy"), this);
|
---|
266 | //dragging = true;
|
---|
267 | }
|
---|
268 | catch(Exception error) {
|
---|
269 | error.printStackTrace();
|
---|
270 | }
|
---|
271 | }
|
---|
272 |
|
---|
273 | }
|
---|
274 |
|
---|
275 | /** Implementation side-effect.
|
---|
276 | * @param event A DragSourceDragEvent containing all the information about the drag event.
|
---|
277 | */
|
---|
278 | public void dragOver(DragSourceDragEvent event) {
|
---|
279 | }
|
---|
280 |
|
---|
281 | /** Any implementation of DropTargetListener must include this method
|
---|
282 | * so we can be notified when the drag moves in this component.
|
---|
283 | * @param event A DropTargetDragEvent containing all the information
|
---|
284 | * about the drag event.
|
---|
285 | */
|
---|
286 | public void dragOver(DropTargetDragEvent event) {
|
---|
287 | // Draw the mouse ghost
|
---|
288 | Graphics2D g2 = (Graphics2D) getGraphics();
|
---|
289 | Point pt = event.getLocation();
|
---|
290 | if(pt_last != null && pt.equals(pt_last)) {
|
---|
291 | return;
|
---|
292 | }
|
---|
293 | pt_last = pt;
|
---|
294 | if(!DragSource.isDragImageSupported() && group.image_ghost != null) {
|
---|
295 | // Erase the last ghost image and cue line
|
---|
296 | paintImmediately(ra_ghost.getBounds());
|
---|
297 | // Remember where you are about to draw the new ghost image
|
---|
298 | ra_ghost.setRect(pt.x - group.mouse_offset.x, pt.y - group.mouse_offset.y, group.image_ghost.getWidth(), group.image_ghost.getHeight());
|
---|
299 | // Draw the ghost image
|
---|
300 | g2.drawImage(group.image_ghost, AffineTransform.getTranslateInstance(ra_ghost.getX(), ra_ghost.getY()), null);
|
---|
301 | }
|
---|
302 | // Now we highlight the target node if it is a valid drop target. Of course we don't bother if we are still over a node which has already been identified as to whether its a drop target
|
---|
303 | TreePath target_path = this.getPathForLocation(pt.x, pt.y);
|
---|
304 |
|
---|
305 | if (target_path == null) {
|
---|
306 | // the user has moved the mouse into background area - need to remove any existing cue lines
|
---|
307 | if(upper_cue_line != null && lower_cue_line != null) {
|
---|
308 | paintImmediately(upper_cue_line.getBounds());
|
---|
309 | paintImmediately(lower_cue_line.getBounds());
|
---|
310 | previous_path = null;
|
---|
311 | }
|
---|
312 | // don't need to display anything
|
---|
313 | return;
|
---|
314 | }
|
---|
315 |
|
---|
316 | if(previous_path == null || target_path != null && !target_path.equals(previous_path)) {
|
---|
317 | // Immediately clear the old cue lines.
|
---|
318 | if(upper_cue_line != null && lower_cue_line != null) {
|
---|
319 | paintImmediately(upper_cue_line.getBounds());
|
---|
320 | paintImmediately(lower_cue_line.getBounds());
|
---|
321 | }
|
---|
322 | if(isValidDrop(target_path)) {
|
---|
323 | // Get the drop target's bounding rectangle
|
---|
324 | Rectangle raPath = getPathBounds(target_path);
|
---|
325 | // Cue line bounds (2 pixels beneath the drop target)
|
---|
326 | upper_cue_line = new Rectangle(0, raPath.y + (int)raPath.getHeight(), getWidth(), 2);
|
---|
327 | lower_cue_line = new Rectangle(0, raPath.y, getWidth(), 2);
|
---|
328 | g2.setColor(((DefaultTreeCellRenderer)cellRenderer).getBackgroundSelectionColor()); // The cue line color
|
---|
329 | g2.fill(upper_cue_line); // Draw the cue line
|
---|
330 | g2.fill(lower_cue_line);
|
---|
331 | }
|
---|
332 | else {
|
---|
333 | upper_cue_line = null;
|
---|
334 | lower_cue_line = null;
|
---|
335 | }
|
---|
336 | }
|
---|
337 | }
|
---|
338 |
|
---|
339 | /** Any implementation of DropTargetListener must include this method
|
---|
340 | * so we can be notified when the drag ends, ie the transferable is
|
---|
341 | * dropped.
|
---|
342 | * @param event A DropTargetDropEvent containing all the information
|
---|
343 | * about the end of the drag event.
|
---|
344 | */
|
---|
345 | public void drop(DropTargetDropEvent event) {
|
---|
346 | ///start = System.currentTimeMillis();
|
---|
347 | ///ystem.err.println("Drop target drop: " + this);
|
---|
348 | event.acceptDrop(drag_action);
|
---|
349 | if(!name.equals(Utility.WORKSPACE_TREE)) {
|
---|
350 | // Determine what node we dropped over.
|
---|
351 | Point pt = event.getLocation();
|
---|
352 | TreePath target_path = this.getPathForLocation(pt.x, pt.y);
|
---|
353 | FileNode target = null;
|
---|
354 | if(target_path != null) {
|
---|
355 | if(isValidDrop(target_path)) {
|
---|
356 | target = (FileNode) target_path.getLastPathComponent();
|
---|
357 | } else if (isValidDropOntoFile(target_path)) {
|
---|
358 | target = (FileNode) target_path.getParentPath().getLastPathComponent();
|
---|
359 | }
|
---|
360 | }
|
---|
361 | else {
|
---|
362 | TreeModel m = getModel();
|
---|
363 | // check that we have a model and is a FileSystemModel (ie check that a collection is loaded
|
---|
364 | if (m != null && m instanceof FileSystemModel) {
|
---|
365 | target = (FileNode) m.getRoot();
|
---|
366 | }
|
---|
367 | }
|
---|
368 | target_path = null;
|
---|
369 | pt = null;
|
---|
370 | if(target != null) {
|
---|
371 | ///ystem.err.println("Valid drop.");
|
---|
372 | TreePath[] selection = group.getSelection();
|
---|
373 | if(target != null && selection != null) {
|
---|
374 | FileNode[] source_nodes = new FileNode[selection.length];
|
---|
375 | for(int i = 0; i < source_nodes.length; i++) {
|
---|
376 | source_nodes[i] = (FileNode) selection[i].getLastPathComponent();
|
---|
377 | }
|
---|
378 | Gatherer.f_man.action(group.getSource(), source_nodes, this, target);
|
---|
379 | source_nodes = null;
|
---|
380 | }
|
---|
381 | group.setSelection(null);
|
---|
382 | group.setSource(null);
|
---|
383 | selection = null;
|
---|
384 | target = null;
|
---|
385 | }
|
---|
386 | }
|
---|
387 | // Clear up the group.image_ghost
|
---|
388 | paintImmediately(ra_ghost.getBounds());
|
---|
389 | event.getDropTargetContext().dropComplete(true);
|
---|
390 | group.image_ghost = null;
|
---|
391 | }
|
---|
392 |
|
---|
393 |
|
---|
394 | /** Any implementation of DragSourceListener must include this method
|
---|
395 | * so we can be notified when the action to be taken upon drop changes.
|
---|
396 | * @param event A DragSourceDragEvent containing all the information
|
---|
397 | * about the drag event.
|
---|
398 | */
|
---|
399 | public void dropActionChanged(DragSourceDragEvent event) {
|
---|
400 | }
|
---|
401 |
|
---|
402 | /** Any implementation of DropTargetListener must include this method
|
---|
403 | * so we can be notified when the action to be taken upon drop changes.
|
---|
404 | * @param event A DropTargetDragEvent containing all the information
|
---|
405 | * about the drag event.
|
---|
406 | */
|
---|
407 | public void dropActionChanged(DropTargetDragEvent event) {
|
---|
408 | }
|
---|
409 |
|
---|
410 | /** Used to notify this component that it has gained focus. It should
|
---|
411 | * make some effort to inform the user of this.
|
---|
412 | */
|
---|
413 | public void gainFocus() {
|
---|
414 | ///ystem.err.println("Gained focus: " + this);
|
---|
415 | ((DragTreeCellRenderer)cellRenderer).gainFocus();
|
---|
416 | repaint();
|
---|
417 | }
|
---|
418 |
|
---|
419 | /** Autoscroll Interface...
|
---|
420 | * The following code was borrowed from the book:
|
---|
421 | * Java Swing
|
---|
422 | * By Robert Eckstein, Marc Loy & Dave Wood
|
---|
423 | * Paperback - 1221 pages 1 Ed edition (September 1998)
|
---|
424 | * O'Reilly & Associates; ISBN: 156592455X
|
---|
425 | *
|
---|
426 | * The relevant chapter of which can be found at:
|
---|
427 | * http://www.oreilly.com/catalog/jswing/chapter/dnd.beta.pdf
|
---|
428 | * Calculate the insets for the *JTREE*, not the viewport
|
---|
429 | * the tree is in. This makes it a bit messy.
|
---|
430 | */
|
---|
431 | public Insets getAutoscrollInsets()
|
---|
432 | {
|
---|
433 | Rectangle raOuter = this.getBounds();
|
---|
434 | Rectangle raInner = this.getParent().getBounds();
|
---|
435 | return new Insets(raInner.y - raOuter.y + AUTOSCROLL_MARGIN,
|
---|
436 | raInner.x - raOuter.x + AUTOSCROLL_MARGIN,
|
---|
437 | raOuter.height - raInner.height - raInner.y + raOuter.y + AUTOSCROLL_MARGIN,
|
---|
438 | raOuter.width - raInner.width - raInner.x + raOuter.x + AUTOSCROLL_MARGIN);
|
---|
439 | }
|
---|
440 |
|
---|
441 | public String getSelectionDetails() {
|
---|
442 | return ((DragTreeSelectionModel)selectionModel).getDetails();
|
---|
443 | }
|
---|
444 |
|
---|
445 | public FileSystemModel getTreeModel() {
|
---|
446 | return (FileSystemModel) getModel();
|
---|
447 | }
|
---|
448 |
|
---|
449 | /** This method is used to inform this component when it loses focus,
|
---|
450 | * and should indicate this somehow.
|
---|
451 | */
|
---|
452 | public void loseFocus() {
|
---|
453 | ///ystem.err.println("Lost focus: " + this);
|
---|
454 | ((DragTreeCellRenderer)cellRenderer).loseFocus();
|
---|
455 | repaint();
|
---|
456 | }
|
---|
457 |
|
---|
458 |
|
---|
459 | public void paint(Graphics g) {
|
---|
460 | if(disabled_background != null) {
|
---|
461 | int height = getSize().height;
|
---|
462 | int offset = 0;
|
---|
463 | ImageIcon background;
|
---|
464 | if(isEnabled()) {
|
---|
465 | background = normal_background;
|
---|
466 | }
|
---|
467 | else {
|
---|
468 | background = disabled_background;
|
---|
469 | }
|
---|
470 | while((height - offset) > 0) {
|
---|
471 | g.drawImage(background.getImage(), 0, offset, null);
|
---|
472 | offset = offset + background.getIconHeight();
|
---|
473 | }
|
---|
474 | background = null;
|
---|
475 | }
|
---|
476 | super.paint(g);
|
---|
477 | }
|
---|
478 |
|
---|
479 | public void refresh(TreePath path) {
|
---|
480 | if (treeModel instanceof FileSystemModel) {
|
---|
481 | ((FileSystemModel)treeModel).refresh(path);
|
---|
482 | }
|
---|
483 | else {
|
---|
484 | // System.err.println("DragTree::refresh - Tree model is " + treeModel);
|
---|
485 | }
|
---|
486 | }
|
---|
487 |
|
---|
488 | public void setBackgroundNonSelectionColor(Color color) {
|
---|
489 | background_color = color;
|
---|
490 | if(isEnabled()) {
|
---|
491 | setBackground(color);
|
---|
492 | ((DefaultTreeCellRenderer)cellRenderer).setBackgroundNonSelectionColor(color);
|
---|
493 | }
|
---|
494 | else {
|
---|
495 | setBackground(Color.lightGray);
|
---|
496 | ((DefaultTreeCellRenderer)cellRenderer).setBackgroundNonSelectionColor(Color.lightGray);
|
---|
497 | }
|
---|
498 | repaint();
|
---|
499 | }
|
---|
500 |
|
---|
501 | public void setBackgroundSelectionColor(Color color) {
|
---|
502 | ((DefaultTreeCellRenderer)cellRenderer).setBackgroundSelectionColor(color);
|
---|
503 | repaint();
|
---|
504 | }
|
---|
505 |
|
---|
506 | /** Override the normal setEnabled so the Tree exhibits a little more
|
---|
507 | * change, which in this instance is the background colour changing.
|
---|
508 | * @param state Whether this GTree should be in an enabled state.
|
---|
509 | */
|
---|
510 | public void setEnabled(boolean state) {
|
---|
511 | super.setEnabled(state);
|
---|
512 |
|
---|
513 | // Change some colors
|
---|
514 | if(state) {
|
---|
515 | setBackground(background_color);
|
---|
516 | ((DefaultTreeCellRenderer)cellRenderer).setBackgroundNonSelectionColor(background_color);
|
---|
517 | ((DefaultTreeCellRenderer)cellRenderer).setTextNonSelectionColor(foreground_color);
|
---|
518 | }
|
---|
519 | else {
|
---|
520 | setBackground(Color.lightGray);
|
---|
521 | ((DefaultTreeCellRenderer)cellRenderer).setBackgroundNonSelectionColor(Color.lightGray);
|
---|
522 | ((DefaultTreeCellRenderer)cellRenderer).setTextNonSelectionColor(Color.black);
|
---|
523 | }
|
---|
524 | repaint();
|
---|
525 | }
|
---|
526 |
|
---|
527 | public void setGroup(DragGroup group) {
|
---|
528 | this.group = group;
|
---|
529 | }
|
---|
530 |
|
---|
531 | /** Determines whether the following selection attempts should go through the normal delayed selection model, or should happen immediately.*/
|
---|
532 | public void setImmediate(boolean state) {
|
---|
533 | ((DragTreeSelectionModel)selectionModel).setImmediate(state);
|
---|
534 | }
|
---|
535 |
|
---|
536 | public void setModel(TreeModel model) {
|
---|
537 | super.setModel(model);
|
---|
538 | if(model instanceof FileSystemModel) {
|
---|
539 | FileSystemModel file_system_model = (FileSystemModel) model;
|
---|
540 | file_system_model.setTree(this);
|
---|
541 | removeTreeExpansionListener(file_system_model);
|
---|
542 | addTreeExpansionListener(file_system_model);
|
---|
543 | removeTreeWillExpandListener(file_system_model);
|
---|
544 | addTreeWillExpandListener(file_system_model);
|
---|
545 | file_system_model = null;
|
---|
546 | }
|
---|
547 | }
|
---|
548 |
|
---|
549 | /** Ensure that that file node denoted by the given file is selected. */
|
---|
550 | public void setSelection(File file) {
|
---|
551 | // We know the file exists, and thus that it must exists somewhere in our tree hierarchy.
|
---|
552 | // 1. Retrieve the root node of our tree.
|
---|
553 | FileNode current = (FileNode) getModel().getRoot();
|
---|
554 | // 2. Find that node in the file parents, keeping track of each intermediate file.
|
---|
555 | ArrayList files = new ArrayList();
|
---|
556 | while(file != null && !current.toString().equals(file.getName())) {
|
---|
557 | files.add(0, file);
|
---|
558 | file = file.getParentFile();
|
---|
559 | }
|
---|
560 | if(file == null) {
|
---|
561 | return;
|
---|
562 | }
|
---|
563 | // 3. While there are still remaining intermediate files.
|
---|
564 | while(files.size() > 0) {
|
---|
565 | file = (File) files.remove(0);
|
---|
566 | // 3a. Find the next file in the current nodes children.
|
---|
567 | boolean found = false;
|
---|
568 | current.map();
|
---|
569 | for(int i = 0; !found && i < current.getChildCount(); i++) {
|
---|
570 | FileNode child = (FileNode) current.getChildAt(i);
|
---|
571 | if(child.toString().equals(file.getName())) {
|
---|
572 | // 3b. Make the current node this node (if found) and continue.
|
---|
573 | found = true;
|
---|
574 | current = child;
|
---|
575 | }
|
---|
576 | }
|
---|
577 | // 3c. If not found then return as this node can't exists somehow.
|
---|
578 | if(!found) {
|
---|
579 | return;
|
---|
580 | }
|
---|
581 | }
|
---|
582 | // 4. We should now have the desired node. Remember to make the selection immediate.
|
---|
583 | TreePath path = new TreePath(current.getPath());
|
---|
584 | setImmediate(true);
|
---|
585 | setSelectionPath(path);
|
---|
586 | setImmediate(false);
|
---|
587 | }
|
---|
588 |
|
---|
589 | public void setTextNonSelectionColor(Color color) {
|
---|
590 | foreground_color = color;
|
---|
591 | if(isEnabled()) {
|
---|
592 | ((DefaultTreeCellRenderer)cellRenderer).setTextNonSelectionColor(color);
|
---|
593 | }
|
---|
594 | else {
|
---|
595 | ((DefaultTreeCellRenderer)cellRenderer).setTextNonSelectionColor(Color.black);
|
---|
596 | }
|
---|
597 | repaint();
|
---|
598 | }
|
---|
599 |
|
---|
600 | public void setTextSelectionColor(Color color) {
|
---|
601 | ((DefaultTreeCellRenderer)cellRenderer).setTextSelectionColor(color);
|
---|
602 | repaint();
|
---|
603 | }
|
---|
604 |
|
---|
605 | public String toString() {
|
---|
606 | return name;
|
---|
607 | }
|
---|
608 |
|
---|
609 | public void valueChanged(TreeSelectionEvent event) {
|
---|
610 | if(group == null) {
|
---|
611 | ///ystem.err.println("Oh my god, this tree has no group: " + this);
|
---|
612 | }
|
---|
613 | else {
|
---|
614 | group.grabFocus(this);
|
---|
615 | }
|
---|
616 | }
|
---|
617 |
|
---|
618 | /** returns false for dummy nodes (ones without files), and system root
|
---|
619 | * nodes */
|
---|
620 | private boolean isValidDrag() {
|
---|
621 | //because you cant select nodes that are children of another selection, and we use a contiguous selection model, we just test the first selection path
|
---|
622 | TreePath node_path = getSelectionPath();
|
---|
623 | FileNode node = (FileNode) node_path.getLastPathComponent();
|
---|
624 |
|
---|
625 | if (node.getFile() == null) {
|
---|
626 | return false;
|
---|
627 | }
|
---|
628 | if (node.isFileSystemRoot()) {
|
---|
629 | return false;
|
---|
630 | }
|
---|
631 | // We also don't allow the user to select files that reside within the currently loaded collection
|
---|
632 | TreePath[] paths = getSelectionPaths();
|
---|
633 | for(int i = 0; i < paths.length; i++) {
|
---|
634 | FileNode child = (FileNode) paths[i].getLastPathComponent();
|
---|
635 | if (child.isInLoadedCollection()) {
|
---|
636 | return false;
|
---|
637 | }
|
---|
638 | }
|
---|
639 | return true;
|
---|
640 | }
|
---|
641 |
|
---|
642 | private boolean isValidDropOntoFile(TreePath target_path) {
|
---|
643 | boolean valid = false;
|
---|
644 | if(target_path != null) {
|
---|
645 | FileNode target_node = (FileNode) target_path.getLastPathComponent();
|
---|
646 | if (target_node.isLeaf()) {
|
---|
647 | // check the parent node for being a valid drop
|
---|
648 | return isValidDrop(target_path.getParentPath());
|
---|
649 | }
|
---|
650 | }
|
---|
651 | return false;
|
---|
652 | }
|
---|
653 |
|
---|
654 |
|
---|
655 | private boolean isValidDrop(TreePath target_path) {
|
---|
656 | boolean valid = false;
|
---|
657 | if(target_path == null) {
|
---|
658 | previous_path = null;
|
---|
659 | }
|
---|
660 | else {
|
---|
661 | FileNode target_node = (FileNode) target_path.getLastPathComponent();
|
---|
662 | // We can only continue testing if the node is a folder.
|
---|
663 | if(!target_node.isLeaf()) {
|
---|
664 | // Now we check if the node is readonly.
|
---|
665 | if(!target_node.isReadOnly()) {
|
---|
666 | // Finally we check the target path against the paths in the selection to ensure we are not adding to our own ancestors!
|
---|
667 | TreePath[] selection = group.getSelection();
|
---|
668 | boolean failed = false;
|
---|
669 | for(int i = 0; !failed && selection != null && i < selection.length; i++) {
|
---|
670 | failed = selection[i].isDescendant(target_path);
|
---|
671 | }
|
---|
672 | // Having finally completed all the tests, we can highlight the drop target.
|
---|
673 | if(!failed) {
|
---|
674 | valid = true;
|
---|
675 | }
|
---|
676 | else {
|
---|
677 | ///ystem.err.println("Invalid. Target is descendant of itself.");
|
---|
678 | }
|
---|
679 | }
|
---|
680 | else {
|
---|
681 | ///ystem.err.println("Read only.");
|
---|
682 | }
|
---|
683 | }
|
---|
684 | else {
|
---|
685 | ///ystem.err.println("Leaf node. Children not allowed.");
|
---|
686 | }
|
---|
687 | previous_path = target_path;
|
---|
688 | }
|
---|
689 | return valid;
|
---|
690 | }
|
---|
691 | }
|
---|