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

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

Fixed up a few more refreshing problems.

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