source: trunk/gli/src/org/greenstone/gatherer/gui/tree/DragTree.java@ 10691

Last change on this file since 10691 was 10691, checked in by kjdon, 19 years ago

fixed up the folder selection highlighting (cue lines) - the lines now disappear if the mouse is dragged off the folder

  • Property svn:keywords set to Author Date Id Revision
File size: 25.7 KB
Line 
1package org.greenstone.gatherer.gui.tree;
2
3import java.awt.*;
4import java.awt.datatransfer.*;
5import java.awt.dnd.*;
6import java.awt.geom.AffineTransform;
7import java.awt.image.BufferedImage;
8import java.io.File;
9import java.util.*;
10import javax.swing.*;
11import javax.swing.event.*;
12import javax.swing.plaf.basic.BasicTreeUI;
13import javax.swing.tree.*;
14import org.greenstone.gatherer.Dictionary;
15import org.greenstone.gatherer.Gatherer;
16import org.greenstone.gatherer.file.FileNode;
17import org.greenstone.gatherer.file.FileSystemModel;
18import org.greenstone.gatherer.util.DragComponent;
19import org.greenstone.gatherer.util.DragGroup;
20import org.greenstone.gatherer.util.DragTreeSelectionModel;
21import org.greenstone.gatherer.util.Utility;
22
23public 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}
Note: See TracBrowser for help on using the repository browser.