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

Last change on this file since 9755 was 9755, checked in by mdewsnip, 19 years ago

Replaced all the nonsense where string arrays were created for running perl scripts with array lists. This removes a lot of duplicated code where the same things were added in different cases, and the silly (and error-prone) "new String[11]"s.

  • Property svn:keywords set to Author Date Id Revision
File size: 25.4 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 if(previous_path == null || target_path != null && !target_path.equals(previous_path)) {
305 // Immediately clear the old cue lines.
306 if(upper_cue_line != null && lower_cue_line != null) {
307 paintImmediately(upper_cue_line.getBounds());
308 paintImmediately(lower_cue_line.getBounds());
309 }
310 if(isValidDrop(target_path)) {
311 ///ystem.err.println("Valid. Painting cues.");
312 // Get the drop target's bounding rectangle
313 Rectangle raPath = getPathBounds(target_path);
314 // Cue line bounds (2 pixels beneath the drop target)
315 upper_cue_line = new Rectangle(0, raPath.y + (int)raPath.getHeight(), getWidth(), 2);
316 lower_cue_line = new Rectangle(0, raPath.y, getWidth(), 2);
317 g2.setColor(((DefaultTreeCellRenderer)cellRenderer).getBackgroundSelectionColor()); // The cue line color
318 g2.fill(upper_cue_line); // Draw the cue line
319 g2.fill(lower_cue_line);
320 }
321 else {
322 upper_cue_line = null;
323 lower_cue_line = null;
324 }
325 }
326 }
327
328 /** Any implementation of DropTargetListener must include this method
329 * so we can be notified when the drag ends, ie the transferable is
330 * dropped.
331 * @param event A DropTargetDropEvent containing all the information
332 * about the end of the drag event.
333 */
334 public void drop(DropTargetDropEvent event) {
335 ///start = System.currentTimeMillis();
336 ///ystem.err.println("Drop target drop: " + this);
337 event.acceptDrop(drag_action);
338 if(!name.equals(Utility.WORKSPACE_TREE)) {
339 // Determine what node we dropped over.
340 Point pt = event.getLocation();
341 TreePath target_path = this.getPathForLocation(pt.x, pt.y);
342 FileNode target = null;
343 if(target_path != null) {
344 if(isValidDrop(target_path)) {
345 target = (FileNode) target_path.getLastPathComponent();
346 } else if (isValidDropOntoFile(target_path)) {
347 target = (FileNode) target_path.getParentPath().getLastPathComponent();
348 }
349 }
350 else {
351 TreeModel m = getModel();
352 // check that we have a model and is a FileSystemModel (ie check that a collection is loaded
353 if (m != null && m instanceof FileSystemModel) {
354 target = (FileNode) m.getRoot();
355 }
356 }
357 target_path = null;
358 pt = null;
359 if(target != null) {
360 ///ystem.err.println("Valid drop.");
361 TreePath[] selection = group.getSelection();
362 if(target != null && selection != null) {
363 FileNode[] source_nodes = new FileNode[selection.length];
364 for(int i = 0; i < source_nodes.length; i++) {
365 source_nodes[i] = (FileNode) selection[i].getLastPathComponent();
366 }
367 Gatherer.f_man.action(group.getSource(), source_nodes, this, target);
368 source_nodes = null;
369 }
370 group.setSelection(null);
371 group.setSource(null);
372 selection = null;
373 target = null;
374 }
375 }
376 // Clear up the group.image_ghost
377 paintImmediately(ra_ghost.getBounds());
378 event.getDropTargetContext().dropComplete(true);
379 group.image_ghost = null;
380 }
381
382
383 /** Any implementation of DragSourceListener must include this method
384 * so we can be notified when the action to be taken upon drop changes.
385 * @param event A DragSourceDragEvent containing all the information
386 * about the drag event.
387 */
388 public void dropActionChanged(DragSourceDragEvent event) {
389 }
390
391 /** Any implementation of DropTargetListener must include this method
392 * so we can be notified when the action to be taken upon drop changes.
393 * @param event A DropTargetDragEvent containing all the information
394 * about the drag event.
395 */
396 public void dropActionChanged(DropTargetDragEvent event) {
397 }
398
399 /** Used to notify this component that it has gained focus. It should
400 * make some effort to inform the user of this.
401 */
402 public void gainFocus() {
403 ///ystem.err.println("Gained focus: " + this);
404 ((DragTreeCellRenderer)cellRenderer).gainFocus();
405 repaint();
406 }
407
408 /** Autoscroll Interface...
409 * The following code was borrowed from the book:
410 * Java Swing
411 * By Robert Eckstein, Marc Loy & Dave Wood
412 * Paperback - 1221 pages 1 Ed edition (September 1998)
413 * O'Reilly & Associates; ISBN: 156592455X
414 *
415 * The relevant chapter of which can be found at:
416 * http://www.oreilly.com/catalog/jswing/chapter/dnd.beta.pdf
417 * Calculate the insets for the *JTREE*, not the viewport
418 * the tree is in. This makes it a bit messy.
419 */
420 public Insets getAutoscrollInsets()
421 {
422 Rectangle raOuter = this.getBounds();
423 Rectangle raInner = this.getParent().getBounds();
424 return new Insets(raInner.y - raOuter.y + AUTOSCROLL_MARGIN,
425 raInner.x - raOuter.x + AUTOSCROLL_MARGIN,
426 raOuter.height - raInner.height - raInner.y + raOuter.y + AUTOSCROLL_MARGIN,
427 raOuter.width - raInner.width - raInner.x + raOuter.x + AUTOSCROLL_MARGIN);
428 }
429
430 public String getSelectionDetails() {
431 return ((DragTreeSelectionModel)selectionModel).getDetails();
432 }
433
434 public FileSystemModel getTreeModel() {
435 return (FileSystemModel) getModel();
436 }
437
438 /** This method is used to inform this component when it loses focus,
439 * and should indicate this somehow.
440 */
441 public void loseFocus() {
442 ///ystem.err.println("Lost focus: " + this);
443 ((DragTreeCellRenderer)cellRenderer).loseFocus();
444 repaint();
445 }
446
447
448 public void paint(Graphics g) {
449 if(disabled_background != null) {
450 int height = getSize().height;
451 int offset = 0;
452 ImageIcon background;
453 if(isEnabled()) {
454 background = normal_background;
455 }
456 else {
457 background = disabled_background;
458 }
459 while((height - offset) > 0) {
460 g.drawImage(background.getImage(), 0, offset, null);
461 offset = offset + background.getIconHeight();
462 }
463 background = null;
464 }
465 super.paint(g);
466 }
467
468 public void refresh(TreePath path) {
469 if (treeModel instanceof FileSystemModel) {
470 ((FileSystemModel)treeModel).refresh(path);
471 }
472 else {
473 // System.err.println("DragTree::refresh - Tree model is " + treeModel);
474 }
475 }
476
477 public void setBackgroundNonSelectionColor(Color color) {
478 background_color = color;
479 if(isEnabled()) {
480 setBackground(color);
481 ((DefaultTreeCellRenderer)cellRenderer).setBackgroundNonSelectionColor(color);
482 }
483 else {
484 setBackground(Color.lightGray);
485 ((DefaultTreeCellRenderer)cellRenderer).setBackgroundNonSelectionColor(Color.lightGray);
486 }
487 repaint();
488 }
489
490 public void setBackgroundSelectionColor(Color color) {
491 ((DefaultTreeCellRenderer)cellRenderer).setBackgroundSelectionColor(color);
492 repaint();
493 }
494
495 /** Override the normal setEnabled so the Tree exhibits a little more
496 * change, which in this instance is the background colour changing.
497 * @param state Whether this GTree should be in an enabled state.
498 */
499 public void setEnabled(boolean state) {
500 super.setEnabled(state);
501
502 // Change some colors
503 if(state) {
504 setBackground(background_color);
505 ((DefaultTreeCellRenderer)cellRenderer).setBackgroundNonSelectionColor(background_color);
506 ((DefaultTreeCellRenderer)cellRenderer).setTextNonSelectionColor(foreground_color);
507 }
508 else {
509 setBackground(Color.lightGray);
510 ((DefaultTreeCellRenderer)cellRenderer).setBackgroundNonSelectionColor(Color.lightGray);
511 ((DefaultTreeCellRenderer)cellRenderer).setTextNonSelectionColor(Color.black);
512 }
513 repaint();
514 }
515
516 public void setGroup(DragGroup group) {
517 this.group = group;
518 }
519
520 /** Determines whether the following selection attempts should go through the normal delayed selection model, or should happen immediately.*/
521 public void setImmediate(boolean state) {
522 ((DragTreeSelectionModel)selectionModel).setImmediate(state);
523 }
524
525 public void setModel(TreeModel model) {
526 super.setModel(model);
527 if(model instanceof FileSystemModel) {
528 FileSystemModel file_system_model = (FileSystemModel) model;
529 file_system_model.setTree(this);
530 removeTreeExpansionListener(file_system_model);
531 addTreeExpansionListener(file_system_model);
532 removeTreeWillExpandListener(file_system_model);
533 addTreeWillExpandListener(file_system_model);
534 file_system_model = null;
535 }
536 }
537
538 /** Ensure that that file node denoted by the given file is selected. */
539 public void setSelection(File file) {
540 // We know the file exists, and thus that it must exists somewhere in our tree hierarchy.
541 // 1. Retrieve the root node of our tree.
542 FileNode current = (FileNode) getModel().getRoot();
543 // 2. Find that node in the file parents, keeping track of each intermediate file.
544 ArrayList files = new ArrayList();
545 while(file != null && !current.toString().equals(file.getName())) {
546 files.add(0, file);
547 file = file.getParentFile();
548 }
549 if(file == null) {
550 return;
551 }
552 // 3. While there are still remaining intermediate files.
553 while(files.size() > 0) {
554 file = (File) files.remove(0);
555 // 3a. Find the next file in the current nodes children.
556 boolean found = false;
557 current.map();
558 for(int i = 0; !found && i < current.getChildCount(); i++) {
559 FileNode child = (FileNode) current.getChildAt(i);
560 if(child.toString().equals(file.getName())) {
561 // 3b. Make the current node this node (if found) and continue.
562 found = true;
563 current = child;
564 }
565 }
566 // 3c. If not found then return as this node can't exists somehow.
567 if(!found) {
568 return;
569 }
570 }
571 // 4. We should now have the desired node. Remember to make the selection immediate.
572 TreePath path = new TreePath(current.getPath());
573 setImmediate(true);
574 setSelectionPath(path);
575 setImmediate(false);
576 }
577
578 public void setTextNonSelectionColor(Color color) {
579 foreground_color = color;
580 if(isEnabled()) {
581 ((DefaultTreeCellRenderer)cellRenderer).setTextNonSelectionColor(color);
582 }
583 else {
584 ((DefaultTreeCellRenderer)cellRenderer).setTextNonSelectionColor(Color.black);
585 }
586 repaint();
587 }
588
589 public void setTextSelectionColor(Color color) {
590 ((DefaultTreeCellRenderer)cellRenderer).setTextSelectionColor(color);
591 repaint();
592 }
593
594 public String toString() {
595 return name;
596 }
597
598 public void valueChanged(TreeSelectionEvent event) {
599 if(group == null) {
600 ///ystem.err.println("Oh my god, this tree has no group: " + this);
601 }
602 else {
603 group.grabFocus(this);
604 }
605 }
606
607 /** returns false for dummy nodes (ones without files), and system root
608 * nodes */
609 private boolean isValidDrag() {
610 //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
611 TreePath node_path = getSelectionPath();
612 FileNode node = (FileNode) node_path.getLastPathComponent();
613
614 if (node.getFile() == null) {
615 return false;
616 }
617 if (node.isFileSystemRoot()) {
618 return false;
619 }
620 // We also don't allow the user to select files that reside within the currently loaded collection
621 TreePath[] paths = getSelectionPaths();
622 for(int i = 0; i < paths.length; i++) {
623 FileNode child = (FileNode) paths[i].getLastPathComponent();
624 if (child.isInLoadedCollection()) {
625 return false;
626 }
627 }
628 return true;
629 }
630
631 private boolean isValidDropOntoFile(TreePath target_path) {
632 boolean valid = false;
633 if(target_path != null) {
634 FileNode target_node = (FileNode) target_path.getLastPathComponent();
635 if (target_node.isLeaf()) {
636 // check the parent node for being a valid drop
637 return isValidDrop(target_path.getParentPath());
638 }
639 }
640 return false;
641 }
642
643
644 private boolean isValidDrop(TreePath target_path) {
645 boolean valid = false;
646 if(target_path != null) {
647 FileNode target_node = (FileNode) target_path.getLastPathComponent();
648 // We can only continue testing if the node is a folder.
649 if(!target_node.isLeaf()) {
650 // Now we check if the node is readonly.
651 if(!target_node.isReadOnly()) {
652 // Finally we check the target path against the paths in the selection to ensure we are not adding to our own ancestors!
653 TreePath[] selection = group.getSelection();
654 boolean failed = false;
655 for(int i = 0; !failed && selection != null && i < selection.length; i++) {
656 failed = selection[i].isDescendant(target_path);
657 }
658 // Having finally completed all the tests, we can highlight the drop target.
659 if(!failed) {
660 valid = true;
661 }
662 else {
663 ///ystem.err.println("Invalid. Target is descendant of itself.");
664 }
665 }
666 else {
667 ///ystem.err.println("Read only.");
668 }
669 }
670 else {
671 ///ystem.err.println("Leaf node. Children not allowed.");
672 }
673 previous_path = target_path;
674 }
675 else {
676 if(target_path == null) {
677 previous_path = null;
678 }
679 }
680 return valid;
681 }
682}
Note: See TracBrowser for help on using the repository browser.