source: other-projects/FileTransfer-WebSocketPair/testGXTWithGreenstone/src/org/greenstone/gatherer/gui/tree/DragTree.java@ 33053

Last change on this file since 33053 was 33053, checked in by ak19, 5 years ago

I still had some stuff of Nathan Kelly's (FileTransfer-WebSocketPair) sitting on my USB. Had already commited the Themes folder at the time, 2 years back. Not sure if he wanted this additional folder commited. But I didn't want to delete it and decided it will be better off on SVN. When we use his project, if we find we didn't need this test folder, we can remove it from svn then.

File size: 30.0 KB
Line 
1package org.greenstone.gatherer.gui.tree;
2
3import java.awt.*;
4import java.awt.datatransfer.*;
5import java.awt.dnd.*;
6import java.awt.event.*;
7import java.awt.geom.AffineTransform;
8import java.awt.image.BufferedImage;
9import java.io.File;
10import java.util.*;
11import javax.swing.*;
12import javax.swing.event.*;
13import javax.swing.plaf.basic.BasicTreeUI;
14import javax.swing.tree.*;
15import org.greenstone.gatherer.Dictionary;
16import org.greenstone.gatherer.Gatherer;
17import org.greenstone.gatherer.file.FileNode;
18import org.greenstone.gatherer.file.FileSystemModel;
19import org.greenstone.gatherer.gui.Filter;
20import org.greenstone.gatherer.util.DragComponent;
21import org.greenstone.gatherer.util.DragGroup;
22import org.greenstone.gatherer.util.DragTreeSelectionModel;
23import org.greenstone.gatherer.util.Utility;
24
25public abstract class DragTree
26 extends JTree
27 implements Autoscroll, DragGestureListener, DragSourceListener, DropTargetListener, DragComponent, TreeSelectionListener {
28 /** The normal background color. */
29 private Color background_color;
30 /** The normal foreground color. */
31 private Color foreground_color;
32 /** The Group this component belongs to. */
33 private DragGroup group;
34 /** The filter for this tree. */
35 protected Filter filter = null;
36 /** The image to use for the disabled background. */
37 private ImageIcon disabled_background;
38 /** The image to use for a normal background. */
39 private ImageIcon normal_background;
40 /** 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. */
41 private ImageIcon multiple_icon = new ImageIcon("resource"+File.separator+"multiple.gif");
42 /** The default drag action, although its not that important as we provide custom icons during drags. */
43 private int drag_action = DnDConstants.ACTION_MOVE;
44 /** The location of the last ghost drawn, so that we can repair the 'spoilt' area. */
45 private Point pt_last = null;
46 /** The region borderer by the lower cue line. */
47 private Rectangle lower_cue_line;
48 /** The region covered by the drag ghost icon. */
49 private Rectangle ra_ghost = new Rectangle();
50 /** The region borderer by the upper cue line. */
51 private Rectangle upper_cue_line;
52 /** The last tree path the drag was hovered over. */
53 private TreePath previous_path = null;
54
55 static private Cursor NO_DRAG_CURSOR = null;
56
57 static private final Color TRANSPARENT_COLOR = new Color(0,0,0,0);
58 /** The distance from the edge of the current view within the scroll bar which if entered causes the view to scroll. */
59 static private final int AUTOSCROLL_MARGIN = 12;
60
61 static public final int TREE_DISPLAY_CHANGED = 0;
62 static public final int LOADED_COLLECTION_CHANGED = 1;
63 static public final int COLLECTION_CONTENTS_CHANGED = 2;
64
65
66 public DragTree(TreeModel model, boolean mixed_selection)
67 {
68 super();
69
70 // For some reason these aren't set with Java 1.4.2 and the GTK look and feel?
71 if (UIManager.get("Tree.leftChildIndent") == null) {
72 UIManager.put("Tree.leftChildIndent", new Integer(7));
73 }
74 if (UIManager.get("Tree.rightChildIndent") == null) {
75 UIManager.put("Tree.rightChildIndent", new Integer(13));
76 }
77
78 init(mixed_selection);
79 if (model != null) {
80 setModel(model);
81 }
82 }
83
84 public void init(boolean mixed_selection) {
85 if (NO_DRAG_CURSOR == null) {
86 NO_DRAG_CURSOR = DragSource.DefaultMoveNoDrop;
87 }
88
89 // Init
90 this.filter = new Filter(this, null);
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.setRootVisible(false);
99 this.setSelectionModel(new DragTreeSelectionModel(this, mixed_selection));
100 this.setShowsRootHandles(true);
101 this.setUI(new BasicTreeUI());
102
103 // Connection
104 addKeyListener(new DragTreeKeyListener());
105 addMouseListener(Gatherer.g_man.foa_listener);
106 addTreeExpansionListener(Gatherer.g_man.foa_listener);
107 addTreeSelectionListener(this);
108
109 DragTreeCellRenderer renderer = new DragTreeCellRenderer();
110 //make the renderer paint nodes as transparent when not selected
111 //renderer.setBackgroundNonSelectionColor(new Color(0,0,0,0));
112 setCellRenderer(renderer);
113 // 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).
114 // And once we have the cell renderer, use it to determine a fixed height for the rows
115 Component prototype_row = renderer.getTreeCellRendererComponent(this, "Prototype", true, true, false, 0, true);
116 this.setRowHeight(prototype_row.getSize().height);
117 prototype_row = null;
118
119
120 // Drag'n'drop Setup
121 // Drag source setup.
122 DragSource.getDefaultDragSource().createDefaultDragGestureRecognizer(this, drag_action, this);
123 // Drop destination setup.
124 new DropTarget(this, drag_action, this, true);
125 }
126
127 // Autoscroll Interface - Scroll because the mouse cursor is in our scroll zone.<br>
128 // The following code was borrowed from the book:<br>
129 // Java Swing<br>
130 // By Robert Eckstein, Marc Loy & Dave Wood<br>
131 // Paperback - 1221 pages 1 Ed edition (September 1998)<br>
132 // O'Reilly & Associates; ISBN: 156592455X<br>
133 // The relevant chapter of which can be found at:<br>
134 // http://www.oreilly.com/catalog/jswing/chapter/dnd.beta.pdf<br>
135 // But I've probably tortured it beyond all recognition anyway.
136 public void autoscroll(Point pt) {
137 // Figure out which row we're on.
138 int row = getRowForLocation(pt.x, pt.y);
139 // If we are not on a row then ignore this autoscroll request
140 if (row < 0) return;
141 Rectangle bounds = getBounds();// Yes, scroll up one row
142 // 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.
143 // Is row at top of screen?
144 if(pt.y + bounds.y <= AUTOSCROLL_MARGIN) {
145 // Yes, scroll up one row
146 if(row <= 0) {
147 row = 0;
148 }
149 else {
150 row = row - 1;
151 }
152 }
153 else {
154 // No, scroll down one row
155 if(row < getRowCount() - 1) {
156 row = row + 1;
157 }
158 }
159 this.scrollRowToVisible(row);
160 }
161
162 /** 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.
163 */
164 public void clearGhost() {
165 // Erase the last ghost image and cue line
166 paintImmediately(ra_ghost.getBounds());
167 }
168
169 /** 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.
170 * @param event A <strong>DragSourceDropEvent</strong> containing all the information about the end of the drag event.
171 */
172 public void dragDropEnd(DragSourceDropEvent event) {
173 }
174
175 /** Any implementation of DragSourceListener must include this method so we can be notified when the drag focus enters this component.
176 * @param event A <strong>DragSourceDragEvent</strong> containing all the information
177 * about the drag event.
178 */
179 public void dragEnter(DragSourceDragEvent event) {
180 // Handled elsewhere.
181 }
182 /** 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.
183 * @param event A <strong>DropTargetDragEvent</strong> containing all the information about the drag event.
184 */
185 public void dragEnter(DropTargetDragEvent event) {
186 group.grabFocus(this);
187 }
188 /** Any implementation of DragSourceListener must include this method so we can be notified when the drag focus leaves this component.
189 * @param event A <strong>DragSourceEvent</strong> containing all the information about the drag event.
190 */
191 public void dragExit(DragSourceEvent event) {
192 clearGhost();
193 }
194
195 /** Any implementation of DropTargetListener must include this method
196 * so we can be notified when the drag focus leaves this component.
197 * @param event A DropTargetEvent containing all the information
198 * about the drag event.
199 */
200 public void dragExit(DropTargetEvent event) {
201 clearGhost();
202 }
203
204 /** Any implementation of DragGestureListener must include this method
205 * so we can be notified when a drag action has been noticed, thus a
206 * drag action has begun.
207 * @param event A DragGestureEvent containing all the information about
208 * the drag event.
209 */
210 public void dragGestureRecognized(DragGestureEvent event)
211 {
212 // Check that the tree is draggable
213 if (!isDraggable()) {
214 return;
215 }
216
217 // Disable editing, unless you want to have the edit box pop-up part way through dragging.
218 this.setEditable(false);
219 // We need this to find one of the selected nodes.
220 Point origin = event.getDragOrigin();
221 TreePath path = this.getPathForLocation(origin.x, origin.y);
222 // 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.
223 if(!isPathSelected(path)) {
224 ((DragTreeSelectionModel)selectionModel).setImmediate(true);
225 setSelectionPath(path);
226 ((DragTreeSelectionModel)selectionModel).setImmediate(false);
227 }
228 if(path == null) {
229 return;
230 }
231 if (!isValidDrag()) {
232 try {
233 event.startDrag(NO_DRAG_CURSOR, new StringSelection("dummy"), this);
234 //dragging = true;
235 }
236 catch(Exception error) {
237 error.printStackTrace();
238 }
239 return;
240 }
241 // Now update the selection stored as far as the group is concerned.
242 group.setSelection(getSelectionPaths());
243 group.setSource(this);
244 // First grab ghost.
245 group.grabFocus(this);
246 // Ghost Image stuff.
247 Rectangle rect = this.getPathBounds(path);
248 group.mouse_offset = new Point(origin.x - rect.x, origin.y - rect.y);
249 // Create the ghost image.
250 // Retrieve the selected files.
251 int selection_count = getSelectionCount();
252 if(selection_count > 0) {
253 JLabel label;
254 if(selection_count == 1) {
255 TreePath node_path = getSelectionPath();
256 FileNode node = (FileNode) path.getLastPathComponent();
257 label = new JLabel(node.toString(), ((DefaultTreeCellRenderer)getCellRenderer()).getLeafIcon(), JLabel.CENTER);
258 }
259 else {
260 String title = getSelectionCount() + " " + Dictionary.get("Tree.Files");
261 label = new JLabel(title, ((DefaultTreeCellRenderer)getCellRenderer()).getClosedIcon(), JLabel.CENTER);
262 title = null;
263 }
264 // The layout manager normally does this.
265 Dimension label_size = label.getPreferredSize();
266 label.setSize(label_size);
267 label.setBackground(TRANSPARENT_COLOR);
268 label.setOpaque(true);
269 // Get a buffered image of the selection for dragging a ghost image.
270 group.image_ghost = new BufferedImage(label_size.width, label_size.height, BufferedImage.TYPE_INT_ARGB_PRE);
271 label_size = null;
272 // Get a graphics context for this image.
273 Graphics2D g2 = group.image_ghost.createGraphics();
274 // Make the image ghostlike
275 g2.setComposite(AlphaComposite.getInstance (AlphaComposite.SRC, 0.5f));
276 // Ask the cell renderer to paint itself into the BufferedImage
277 label.paint(g2);
278 g2 = null;
279 label = null;
280 try {
281 event.startDrag(new Cursor(Cursor.DEFAULT_CURSOR), group.image_ghost, group.mouse_offset, new StringSelection("dummy"), this);
282 //dragging = true;
283 }
284 catch(Exception error) {
285 error.printStackTrace();
286 }
287 }
288
289 }
290
291 /** Implementation side-effect.
292 * @param event A DragSourceDragEvent containing all the information about the drag event.
293 */
294 public void dragOver(DragSourceDragEvent event) {
295 }
296
297 /** Any implementation of DropTargetListener must include this method
298 * so we can be notified when the drag moves in this component.
299 * @param event A DropTargetDragEvent containing all the information
300 * about the drag event.
301 */
302 public void dragOver(DropTargetDragEvent event) {
303 // Draw the mouse ghost
304 Graphics2D g2 = (Graphics2D) getGraphics();
305 Point pt = event.getLocation();
306 if(pt_last != null && pt.equals(pt_last)) {
307 return;
308 }
309 pt_last = pt;
310 if(!DragSource.isDragImageSupported() && group.image_ghost != null) {
311 // Erase the last ghost image and cue line
312 paintImmediately(ra_ghost.getBounds());
313 // Remember where you are about to draw the new ghost image
314 ra_ghost.setRect(pt.x - group.mouse_offset.x, pt.y - group.mouse_offset.y, group.image_ghost.getWidth(), group.image_ghost.getHeight());
315 // Draw the ghost image
316 g2.drawImage(group.image_ghost, AffineTransform.getTranslateInstance(ra_ghost.getX(), ra_ghost.getY()), null);
317 }
318 // 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
319 TreePath target_path = this.getPathForLocation(pt.x, pt.y);
320
321 if (target_path == null) {
322 // the user has moved the mouse into background area - need to remove any existing cue lines
323 if(upper_cue_line != null && lower_cue_line != null) {
324 paintImmediately(upper_cue_line.getBounds());
325 paintImmediately(lower_cue_line.getBounds());
326 previous_path = null;
327 }
328 // don't need to display anything
329 return;
330 }
331
332 if(previous_path == null || target_path != null && !target_path.equals(previous_path)) {
333 // Immediately clear the old cue lines.
334 if(upper_cue_line != null && lower_cue_line != null) {
335 paintImmediately(upper_cue_line.getBounds());
336 paintImmediately(lower_cue_line.getBounds());
337 }
338 if(isValidDrop(target_path)) {
339 // Get the drop target's bounding rectangle
340 Rectangle raPath = getPathBounds(target_path);
341 // Cue line bounds (2 pixels beneath the drop target)
342 upper_cue_line = new Rectangle(0, raPath.y + (int)raPath.getHeight(), getWidth(), 2);
343 lower_cue_line = new Rectangle(0, raPath.y, getWidth(), 2);
344 g2.setColor(((DefaultTreeCellRenderer)cellRenderer).getBackgroundSelectionColor()); // The cue line color
345 g2.fill(upper_cue_line); // Draw the cue line
346 g2.fill(lower_cue_line);
347 }
348 else {
349 upper_cue_line = null;
350 lower_cue_line = null;
351 }
352 }
353 }
354
355 /** Any implementation of DropTargetListener must include this method
356 * so we can be notified when the drag ends, ie the transferable is
357 * dropped.
358 * @param event A DropTargetDropEvent containing all the information
359 * about the end of the drag event.
360 */
361 public void drop(DropTargetDropEvent event) {
362 ///start = System.currentTimeMillis();
363 ///ystem.err.println("Drop target drop: " + this);
364 if (!isDroppable()) {
365 return;
366 }
367
368 event.acceptDrop(drag_action);
369
370 // Determine what node we dropped over.
371 Point pt = event.getLocation();
372 TreePath target_path = this.getPathForLocation(pt.x, pt.y);
373 FileNode target = null;
374 if(target_path != null) {
375 if(isValidDrop(target_path)) {
376 target = (FileNode) target_path.getLastPathComponent();
377 } else if (isValidDropOntoFile(target_path)) {
378 target = (FileNode) target_path.getParentPath().getLastPathComponent();
379 }
380 }
381 else {
382 TreeModel m = getModel();
383 // check that we have a model and is a FileSystemModel (ie check that a collection is loaded
384 if (m != null && m instanceof FileSystemModel) {
385 target = (FileNode) m.getRoot();
386 }
387 }
388 target_path = null;
389 pt = null;
390 if(target != null) {
391 ///ystem.err.println("Valid drop.");
392 TreePath[] selection = group.getSelection();
393 if(target != null && selection != null) {
394 FileNode[] source_nodes = new FileNode[selection.length];
395 for(int i = 0; i < source_nodes.length; i++) {
396 source_nodes[i] = (FileNode) selection[i].getLastPathComponent();
397 }
398 Gatherer.f_man.action(group.getSource(), source_nodes, this, target);
399 source_nodes = null;
400 }
401 group.setSelection(null);
402 group.setSource(null);
403 selection = null;
404 target = null;
405 }
406
407 // Clear up the group.image_ghost
408 paintImmediately(ra_ghost.getBounds());
409 event.getDropTargetContext().dropComplete(true);
410 group.image_ghost = null;
411 }
412
413
414 /** Any implementation of DragSourceListener must include this method
415 * so we can be notified when the action to be taken upon drop changes.
416 * @param event A DragSourceDragEvent containing all the information
417 * about the drag event.
418 */
419 public void dropActionChanged(DragSourceDragEvent event) {
420 }
421
422 /** Any implementation of DropTargetListener must include this method
423 * so we can be notified when the action to be taken upon drop changes.
424 * @param event A DropTargetDragEvent containing all the information
425 * about the drag event.
426 */
427 public void dropActionChanged(DropTargetDragEvent event) {
428 }
429
430 /** Used to notify this component that it has gained focus. It should
431 * make some effort to inform the user of this.
432 */
433 public void gainFocus() {
434 ///ystem.err.println("Gained focus: " + this);
435 ((DragTreeCellRenderer)cellRenderer).gainFocus();
436 repaint();
437 }
438
439 /** Autoscroll Interface...
440 * The following code was borrowed from the book:
441 * Java Swing
442 * By Robert Eckstein, Marc Loy & Dave Wood
443 * Paperback - 1221 pages 1 Ed edition (September 1998)
444 * O'Reilly & Associates; ISBN: 156592455X
445 *
446 * The relevant chapter of which can be found at:
447 * http://www.oreilly.com/catalog/jswing/chapter/dnd.beta.pdf
448 * Calculate the insets for the *JTREE*, not the viewport
449 * the tree is in. This makes it a bit messy.
450 */
451 public Insets getAutoscrollInsets()
452 {
453 Rectangle raOuter = this.getBounds();
454 Rectangle raInner = this.getParent().getBounds();
455 return new Insets(raInner.y - raOuter.y + AUTOSCROLL_MARGIN,
456 raInner.x - raOuter.x + AUTOSCROLL_MARGIN,
457 raOuter.height - raInner.height - raInner.y + raOuter.y + AUTOSCROLL_MARGIN,
458 raOuter.width - raInner.width - raInner.x + raOuter.x + AUTOSCROLL_MARGIN);
459 }
460
461 public Filter getFilter() {
462 return filter;
463 }
464
465 public String getSelectionDetails() {
466 return ((DragTreeSelectionModel)selectionModel).getDetails();
467 }
468
469 public FileSystemModel getTreeModel() {
470 return (FileSystemModel) getModel();
471 }
472
473
474 protected abstract boolean isDraggable();
475
476
477 protected abstract boolean isDroppable();
478
479
480 /** This method is used to inform this component when it loses focus,
481 * and should indicate this somehow.
482 */
483 public void loseFocus() {
484 ///ystem.err.println("Lost focus: " + this);
485 ((DragTreeCellRenderer)cellRenderer).loseFocus();
486 repaint();
487 }
488
489
490 public void paint(Graphics g) {
491 if(disabled_background != null) {
492 int height = getSize().height;
493 int offset = 0;
494 ImageIcon background;
495 if(isEnabled()) {
496 background = normal_background;
497 }
498 else {
499 background = disabled_background;
500 }
501 while((height - offset) > 0) {
502 g.drawImage(background.getImage(), 0, offset, null);
503 offset = offset + background.getIconHeight();
504 }
505 background = null;
506 }
507 super.paint(g);
508 }
509
510 public void refresh(TreePath path) {
511 if (treeModel instanceof FileSystemModel) {
512 ((FileSystemModel)treeModel).refresh(path);
513 }
514 else {
515 // System.err.println("DragTree::refresh - Tree model is " + treeModel);
516 }
517 }
518
519 public void setBackgroundNonSelectionColor(Color color) {
520 background_color = color;
521 if(isEnabled()) {
522 setBackground(color);
523 ((DefaultTreeCellRenderer)cellRenderer).setBackgroundNonSelectionColor(color);
524 }
525 else {
526 setBackground(Color.lightGray);
527 ((DefaultTreeCellRenderer)cellRenderer).setBackgroundNonSelectionColor(Color.lightGray);
528 }
529 repaint();
530 }
531
532 public void setBackgroundSelectionColor(Color color) {
533 ((DefaultTreeCellRenderer)cellRenderer).setBackgroundSelectionColor(color);
534 repaint();
535 }
536
537 /** Override the normal setEnabled so the Tree exhibits a little more
538 * change, which in this instance is the background colour changing.
539 * @param state Whether this GTree should be in an enabled state.
540 */
541 public void setEnabled(boolean state) {
542 super.setEnabled(state);
543
544 // Change some colors
545 if(state) {
546 setBackground(background_color);
547 ((DefaultTreeCellRenderer)cellRenderer).setBackgroundNonSelectionColor(background_color);
548 ((DefaultTreeCellRenderer)cellRenderer).setTextNonSelectionColor(foreground_color);
549 }
550 else {
551 setBackground(Color.lightGray);
552 ((DefaultTreeCellRenderer)cellRenderer).setBackgroundNonSelectionColor(Color.lightGray);
553 ((DefaultTreeCellRenderer)cellRenderer).setTextNonSelectionColor(Color.black);
554 }
555 repaint();
556 }
557
558 public void setGroup(DragGroup group) {
559 this.group = group;
560 }
561
562 /** Determines whether the following selection attempts should go through the normal delayed selection model, or should happen immediately.*/
563 public void setImmediate(boolean state) {
564 ((DragTreeSelectionModel)selectionModel).setImmediate(state);
565 }
566
567 public void setModel(TreeModel model) {
568 super.setModel(model);
569 if(model instanceof FileSystemModel) {
570 FileSystemModel file_system_model = (FileSystemModel) model;
571 file_system_model.setTree(this);
572 removeTreeExpansionListener(file_system_model);
573 addTreeExpansionListener(file_system_model);
574 removeTreeWillExpandListener(file_system_model);
575 addTreeWillExpandListener(file_system_model);
576 file_system_model = null;
577 }
578 }
579
580 /** Ensure that that file node denoted by the given file is selected. */
581 public void setSelection(File file) {
582 // We know the file exists, and thus that it must exists somewhere in our tree hierarchy.
583 // 1. Retrieve the root node of our tree.
584 FileNode current = (FileNode) getModel().getRoot();
585 // 2. Find that node in the file parents, keeping track of each intermediate file.
586 ArrayList files = new ArrayList();
587 while(file != null && !current.toString().equals(file.getName())) {
588 files.add(0, file);
589 file = file.getParentFile();
590 }
591 if(file == null) {
592 return;
593 }
594 // 3. While there are still remaining intermediate files.
595 while(files.size() > 0) {
596 file = (File) files.remove(0);
597 // 3a. Find the next file in the current nodes children.
598 boolean found = false;
599 current.map();
600 for(int i = 0; !found && i < current.getChildCount(); i++) {
601 FileNode child = (FileNode) current.getChildAt(i);
602 if(child.toString().equals(file.getName())) {
603 // 3b. Make the current node this node (if found) and continue.
604 found = true;
605 current = child;
606 }
607 }
608 // 3c. If not found then return as this node can't exists somehow.
609 if(!found) {
610 return;
611 }
612 }
613 // 4. We should now have the desired node. Remember to make the selection immediate.
614 TreePath path = new TreePath(current.getPath());
615 setImmediate(true);
616 setSelectionPath(path);
617 setImmediate(false);
618 }
619
620 public void setTextNonSelectionColor(Color color) {
621 foreground_color = color;
622 if(isEnabled()) {
623 ((DefaultTreeCellRenderer)cellRenderer).setTextNonSelectionColor(color);
624 }
625 else {
626 ((DefaultTreeCellRenderer)cellRenderer).setTextNonSelectionColor(Color.black);
627 }
628 repaint();
629 }
630
631 public void setTextSelectionColor(Color color) {
632 ((DefaultTreeCellRenderer)cellRenderer).setTextSelectionColor(color);
633 repaint();
634 }
635
636 public void valueChanged(TreeSelectionEvent event) {
637 if(group == null) {
638 ///ystem.err.println("Oh my god, this tree has no group: " + this);
639 }
640 else {
641 group.grabFocus(this);
642 }
643 }
644
645 /** returns false for dummy nodes (ones without files), and system root
646 * nodes */
647 private boolean isValidDrag() {
648 //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
649 TreePath node_path = getSelectionPath();
650 FileNode node = (FileNode) node_path.getLastPathComponent();
651
652 if (node.getFile() == null) {
653 return false;
654 }
655 if (node.isFileSystemRoot()) {
656 return false;
657 }
658 // We also don't allow the user to select files that reside within the currently loaded collection
659 TreePath[] paths = getSelectionPaths();
660 for(int i = 0; i < paths.length; i++) {
661 FileNode child = (FileNode) paths[i].getLastPathComponent();
662 if (child.isInLoadedCollection()) {
663 return false;
664 }
665 }
666 return true;
667 }
668
669 private boolean isValidDropOntoFile(TreePath target_path) {
670 boolean valid = false;
671 if(target_path != null) {
672 FileNode target_node = (FileNode) target_path.getLastPathComponent();
673 if (target_node.isLeaf()) {
674 // check the parent node for being a valid drop
675 return isValidDrop(target_path.getParentPath());
676 }
677 }
678 return false;
679 }
680
681
682 private boolean isValidDrop(TreePath target_path) {
683 boolean valid = false;
684 if(target_path == null) {
685 previous_path = null;
686 }
687 else {
688 FileNode target_node = (FileNode) target_path.getLastPathComponent();
689 // We can only continue testing if the node is a folder.
690 if(!target_node.isLeaf()) {
691 // Now we check if the node is readonly.
692 if(!target_node.isReadOnly()) {
693 // Finally we check the target path against the paths in the selection to ensure we are not adding to our own ancestors!
694 TreePath[] selection = group.getSelection();
695 boolean failed = false;
696 for(int i = 0; !failed && selection != null && i < selection.length; i++) {
697 failed = selection[i].isDescendant(target_path);
698 }
699 // Having finally completed all the tests, we can highlight the drop target.
700 if(!failed) {
701 valid = true;
702 }
703 else {
704 ///ystem.err.println("Invalid. Target is descendant of itself.");
705 }
706 }
707 else {
708 ///ystem.err.println("Read only.");
709 }
710 }
711 else {
712 ///ystem.err.println("Leaf node. Children not allowed.");
713 }
714 previous_path = target_path;
715 }
716 return valid;
717 }
718
719
720 /** This class listens for certain key presses, such as [Enter] or [Delete], and responds appropriately. */
721 private class DragTreeKeyListener
722 extends KeyAdapter
723 {
724 private boolean vk_left_pressed = false;
725 /** Called whenever a key that was pressed is released, it is this action that will cause the desired effects (this allows for the key event itself to be processed prior to this listener dealing with it). */
726 public void keyReleased(KeyEvent event) {
727 ///ystem.err.println("Key Release detected. " + event.getKeyCode());
728 if(event.getKeyCode() == KeyEvent.VK_DELETE) {
729 // Get the selected files from the tree and removal them using the default dnd removal method.
730 // Find the active tree (you've made selections in).
731 DragTree tree = (DragTree) group.getActive();
732 // Fudge things a bit
733 group.setSource(tree);
734 // Determine the selection.
735 TreePath paths[] = tree.getSelectionPaths();
736 if(paths != null) {
737 FileNode[] source_nodes = new FileNode[paths.length];
738 for(int i = 0; i < source_nodes.length; i++) {
739 source_nodes[i] = (FileNode) paths[i].getLastPathComponent();
740 }
741 Gatherer.f_man.action(tree, source_nodes, Gatherer.recycle_bin, null);
742 source_nodes = null;
743 }
744 }
745 else if(event.getKeyCode() == KeyEvent.VK_ENTER) {
746 // Get the first selected file.
747 DragTree tree = (DragTree)event.getSource();
748 TreePath path = tree.getSelectionPath();
749 if(path != null) {
750 File file = ((FileNode)path.getLastPathComponent()).getFile();
751 if (file != null && file.isFile()) {
752 Gatherer.f_man.openFileInExternalApplication(file);
753 }
754 else {
755 if(!tree.isExpanded(path)) {
756 tree.expandPath(path);
757 }
758 else {
759 tree.collapsePath(path);
760 }
761 }
762 }
763 } else if (event.getKeyCode() == KeyEvent.VK_UP || event.getKeyCode() == KeyEvent.VK_DOWN) {
764 DragTree tree = (DragTree)event.getSource();
765 // we need to manually do the up and down selections
766 boolean up = (event.getKeyCode() == KeyEvent.VK_UP);
767 int current_row = tree.getLeadSelectionRow();
768 tree.setImmediate(true);
769 if (up) {
770 if (current_row > 0) {
771 tree.clearSelection();
772 tree.setSelectionRow(current_row-1);
773 }
774 } else {
775 if (current_row < tree.getRowCount()-1){
776 tree.clearSelection();
777 tree.setSelectionRow(current_row+1);
778 }
779 }
780 tree.setImmediate(false);
781 } else if (event.getKeyCode() == KeyEvent.VK_LEFT) {
782 // left key on a file shifts the selection to the parent folder
783 DragTree tree = (DragTree)event.getSource();
784 TreePath path = tree.getLeadSelectionPath();
785 if(path != null) {
786 File file = ((FileNode)path.getLastPathComponent()).getFile();
787 if(file != null) {
788 if (file.isFile() || vk_left_pressed) {
789 vk_left_pressed = false;
790 TreePath parent_path = path.getParentPath();
791 if (parent_path != null && parent_path.getParentPath() != null) {
792 // if this file is in the top level folder, don't move the focus
793 tree.setImmediate(true);
794 tree.clearSelection();
795 tree.setSelectionPath(parent_path);
796 tree.setImmediate(false);
797 }
798 }
799 }
800 }
801 }
802 }
803
804 // we need to watch for left clicks on an unopened folder - should shift the focus to teh parent folder. But because there is some other mysterious key listener that does opening and closing folders on right and left clicks, we must detect the situation before the other handler has done its job, and process it after.
805 public void keyPressed(KeyEvent event) {
806 if (event.getKeyCode() == KeyEvent.VK_LEFT) {
807 // left key on closed directory shifts the selection to the parent folder
808 DragTree tree = (DragTree)event.getSource();
809 TreePath path = tree.getLeadSelectionPath();
810 if(path == null) return;
811 File file = ((FileNode)path.getLastPathComponent()).getFile();
812 if(file == null) return;
813
814 if (file.isDirectory() && tree.isCollapsed(path)) {
815 vk_left_pressed = true;
816 }
817 }
818 }
819 }
820}
Note: See TracBrowser for help on using the repository browser.