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

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

Tidied up the workspace and collection trees a bit more, in preparation for having special icons for some collection files.

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