KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > de > schlichtherle > io > swing > JFileTree


1 /*
2  * JFileTree.java
3  *
4  * Created on 13. Februar 2006, 15:42
5  */

6 /*
7  * Copyright 2006 Schlichtherle IT Services
8  *
9  * Licensed under the Apache License, Version 2.0 (the "License");
10  * you may not use this file except in compliance with the License.
11  * You may obtain a copy of the License at
12  *
13  * http://www.apache.org/licenses/LICENSE-2.0
14  *
15  * Unless required by applicable law or agreed to in writing, software
16  * distributed under the License is distributed on an "AS IS" BASIS,
17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18  * See the License for the specific language governing permissions and
19  * limitations under the License.
20  */

21
22 package de.schlichtherle.io.swing;
23
24 import de.schlichtherle.io.swing.tree.FileTreeModel;
25 import de.schlichtherle.io.swing.tree.FileTreeCellRenderer;
26
27 import java.awt.Toolkit JavaDoc;
28 import java.io.IOException JavaDoc;
29 import java.io.InputStream JavaDoc;
30 import java.util.Enumeration JavaDoc;
31 import java.util.LinkedList JavaDoc;
32 import java.util.List JavaDoc;
33
34 import javax.swing.CellEditor JavaDoc;
35 import javax.swing.JTree JavaDoc;
36 import javax.swing.event.CellEditorListener JavaDoc;
37 import javax.swing.event.ChangeEvent JavaDoc;
38 import javax.swing.event.TreeExpansionEvent JavaDoc;
39 import javax.swing.event.TreeExpansionListener JavaDoc;
40 import javax.swing.tree.TreeCellEditor JavaDoc;
41 import javax.swing.tree.TreeCellRenderer JavaDoc;
42 import javax.swing.tree.TreeModel JavaDoc;
43 import javax.swing.tree.TreePath JavaDoc;
44
45 /**
46  * A customized {@link JTree} which browses {@link java.io.File} instances.
47  * This allows for convenient traversing of directories as well as any ZIP
48  * compatible file recognized by TrueZIP.
49  * There are a couple of file creation/modification/removal methods added
50  * which notify the tree of any changes in the file system and update the
51  * current path expansions and selection.
52  *
53  * @author Christian Schlichtherle
54  * @version @version@
55  * @since TrueZIP 5.1
56  */

57 public class JFileTree extends JTree JavaDoc {
58     
59     /** The name of the property <code>displayingSuffixes</code>. */
60     private static final String JavaDoc PROPERTY_DISPLAYING_SUFFIXES = "displayingSuffixes";
61
62     /** The name of the property <code>editingSuffixes</code>. */
63     private static final String JavaDoc PROPERTY_EDITING_SUFFIXES = "editingSuffixes";
64
65     /** The name of the property <code>defaultSuffix</code>. */
66     private static final String JavaDoc PROPERTY_DEFAULT_SUFFIX = "defaultSuffix";
67
68     private final TreeExpansionListener JavaDoc tel = new TreeExpansionListener JavaDoc() {
69         public void treeCollapsed(TreeExpansionEvent JavaDoc evt) {
70             ((FileTreeModel) getModel()).forget(
71                     (java.io.File JavaDoc) evt.getPath().getLastPathComponent());
72         }
73
74         public void treeExpanded(TreeExpansionEvent JavaDoc evt) {
75         }
76     };
77
78     /**
79      * Creates an empty <code>JFileTree</code> with no root.
80      * You shouldn't use this constructor.
81      * It's only provided to implement the JavaBean pattern.
82      */

83     public JFileTree() {
84         this(new FileTreeModel());
85     }
86
87     /**
88      * Creates a new <code>JFileTree</code> which traverses the given
89      * root <code>File</code>.
90      * The ZipDetector of the given file is used to detect and configure any
91      * ZIP compatible files in this directory tree.
92      *
93      * @see de.schlichtherle.io.File#getDefaultArchiveDetector()
94      * @see de.schlichtherle.io.File#setDefaultArchiveDetector(ArchiveDetector)
95      */

96     public JFileTree(java.io.File JavaDoc root) {
97         this(new FileTreeModel(root));
98     }
99
100     /**
101      * Creates a new <code>JFileTree</code> which traverses the given
102      * {@link FileTreeModel}.
103      */

104     public JFileTree(FileTreeModel model) {
105         super(model);
106
107         setCellRenderer(createTreeCellRenderer());
108         addTreeExpansionListener(tel);
109     }
110
111     protected TreeCellRenderer createTreeCellRenderer() {
112         return new FileTreeCellRenderer(this);
113     }
114
115     //
116
// Properties.
117
//
118

119     /**
120      * @throws ClassCastException If <code>model</code> is not an instance
121      * of {@link FileTreeModel}.
122      */

123     public void setModel(TreeModel model) {
124         super.setModel((FileTreeModel) model);
125     }
126
127     public void setEditable(final boolean editable) {
128         if (editable) {
129             super.setEditable(true);
130             getCellEditor().addCellEditorListener(cel);
131         } else {
132             final CellEditor JavaDoc ce = getCellEditor();
133             if (ce != null)
134                 ce.removeCellEditorListener(cel);
135             super.setEditable(false);
136         }
137     }
138
139     private final CellEditorListener JavaDoc cel = new CellEditorListener JavaDoc() {
140         public void editingCanceled(ChangeEvent JavaDoc evt) {
141         }
142
143         public void editingStopped(ChangeEvent JavaDoc evt) {
144             onEditingStopped(evt);
145         }
146     };
147
148     /**
149      * Holds value of property displayingSuffixes.
150      */

151     private boolean displayingSuffixes = true;
152
153     /**
154      * Getter for bound property displayingSuffixes.
155      *
156      * @return Value of property displayingSuffixes.
157      */

158     public boolean isDisplayingSuffixes() {
159         return this.displayingSuffixes;
160     }
161
162     /**
163      * Setter for bound property displayingSuffixes.
164      * If this is <code>false</code>, the suffix of files will not be displayed
165      * in this tree.
166      * Defaults to <code>true</code>.
167      *
168      *
169      * @param displayingSuffixes New value of property displayingSuffixes.
170      */

171     public void setDisplayingSuffixes(boolean displayingSuffixes) {
172         boolean oldDisplayingSuffixes = this.displayingSuffixes;
173         this.displayingSuffixes = displayingSuffixes;
174         firePropertyChange(PROPERTY_DISPLAYING_SUFFIXES,
175                 oldDisplayingSuffixes, displayingSuffixes);
176     }
177
178     /**
179      * Holds value of property editingSuffixes.
180      */

181     private boolean editingSuffixes = true;
182
183     /**
184      * Getter for bound property editingSuffixes.
185      *
186      * @return Value of property editingSuffixes.
187      */

188     public boolean isEditingSuffixes() {
189         return this.editingSuffixes;
190     }
191
192     /**
193      * Setter for bound property editingSuffixes.
194      * If this is <code>false</code>, the suffix of a file will be truncated
195      * before editing its name starts.
196      * Defaults to <code>true</code>.
197      *
198      * @param editingSuffixes New value of property editingSuffixes.
199      */

200     public void setEditingSuffixes(boolean editingSuffixes) {
201         boolean oldEditingSuffixes = this.editingSuffixes;
202         this.editingSuffixes = editingSuffixes;
203         firePropertyChange(PROPERTY_EDITING_SUFFIXES,
204                 oldEditingSuffixes, editingSuffixes);
205     }
206
207     /**
208      * Holds value of property defaultSuffix.
209      */

210     private String JavaDoc defaultSuffix;
211
212     /**
213      * Getter for bound property defaultSuffix.
214      *
215      * @return Value of property defaultSuffix.
216      */

217     public String JavaDoc getDefaultSuffix() {
218         return this.defaultSuffix;
219     }
220
221     /**
222      * Setter for bound property defaultSuffixes.
223      * Sets the default suffix to use when suffixes are shown and allowed to
224      * be edited, but the user did not provide a suffix when editing a file
225      * name.
226      * This property defaults to <code>null</code> and is ignored for
227      * directories.
228      *
229      * @param defaultSuffix The new default suffix.
230      * If not <code>null</code>, this parameter is fixed to always
231      * start with a <code>'.'</code>.
232      */

233     public void setDefaultSuffix(String JavaDoc defaultSuffix) {
234         final String JavaDoc oldDefaultSuffix = this.defaultSuffix;
235         if (defaultSuffix != null) {
236             defaultSuffix = defaultSuffix.trim();
237             if (defaultSuffix.length() <= 0)
238                 defaultSuffix = null;
239             else if (defaultSuffix.charAt(0) != '.')
240                 defaultSuffix = "." + defaultSuffix;
241         }
242         this.defaultSuffix = defaultSuffix;
243         firePropertyChange(PROPERTY_DEFAULT_SUFFIX,
244                 oldDefaultSuffix, defaultSuffix);
245     }
246
247     //
248
// Editing.
249
//
250

251     private java.io.File JavaDoc editedNode;
252
253     /**
254      * Returns the node that is currently edited, if any.
255      * This method is not intended for public use - do not use it!
256      */

257     public java.io.File JavaDoc getEditedNode() {
258         return editedNode;
259     }
260
261     public boolean isEditing() {
262         return editedNode != null;
263     }
264
265     public void startEditingAtPath(TreePath JavaDoc path) {
266         editedNode = (java.io.File JavaDoc) path.getLastPathComponent();
267         super.startEditingAtPath(path);
268     }
269
270     public void cancelEditing() {
271         editedNode = null;
272         super.cancelEditing();
273     }
274
275     public boolean stopEditing() {
276         final boolean stop = super.stopEditing();
277         if (stop)
278             editedNode = null;
279         return stop;
280     }
281
282     /**
283      * Called when the editing of a cell has been stopped.
284      * The implementation in this class will rename the edited file,
285      * obeying the rules for suffix handling and updating the expanded and
286      * selected paths accordingly.
287      *
288      * @param evt The change event passed to
289      * {@link CellEditorListener#editingStopped(ChangeEvent)}.
290      */

291     protected void onEditingStopped(final ChangeEvent JavaDoc evt) {
292         final TreeCellEditor JavaDoc tce = (TreeCellEditor JavaDoc) evt.getSource();
293         String JavaDoc base = tce.getCellEditorValue().toString().trim();
294         final java.io.File JavaDoc oldNode
295                 = (java.io.File JavaDoc) getLeadSelectionPath().getLastPathComponent();
296         final java.io.File JavaDoc parent = oldNode.getParentFile();
297         assert parent != null;
298         if (!oldNode.isDirectory()) {
299             if (isDisplayingSuffixes() && isEditingSuffixes()) {
300                 final String JavaDoc suffix = getSuffix(base);
301                 if (suffix == null) {
302                     final String JavaDoc defaultSuffix = getDefaultSuffix();
303                     if (defaultSuffix != null)
304                         base += defaultSuffix;
305                 }
306             } else {
307                 final String JavaDoc suffix = getSuffix(oldNode.getName());
308                 if (suffix != null)
309                     base += suffix;
310             }
311         }
312         final java.io.File JavaDoc node = new de.schlichtherle.io.File(parent, base);
313
314         if (!renameTo(oldNode, node))
315             Toolkit.getDefaultToolkit().beep();
316     }
317
318     private String JavaDoc getSuffix(final String JavaDoc base) {
319         final int i = base.lastIndexOf('.');
320         return i != -1 ? base.substring(i) : null;
321     }
322
323     //
324
// Rendering.
325
//
326

327     public String JavaDoc convertValueToText(
328             final Object JavaDoc value,
329             final boolean selected,
330             final boolean expanded,
331             final boolean leaf,
332             final int row,
333             final boolean hasFocus) {
334         final java.io.File JavaDoc node = (java.io.File JavaDoc) value;
335         final java.io.File JavaDoc editedNode = getEditedNode();
336         if (node != editedNode && !node.exists()) {
337             // You will see this occur for files which have been deleted
338
// concurrently or which are returned by File.listFiles(), but do
339
// not actually File.exists(), such as "C:\hiberfile.sys" on the
340
// Windows platform.
341
return "?";
342         }
343
344         final String JavaDoc base = node.getName();
345         if (base.length() <= 0)
346             return node.getPath(); // This is a file system root.
347
if (node.isDirectory() ||
348                 isDisplayingSuffixes()
349                 && (!node.equals(editedNode) || isEditingSuffixes()))
350             return base;
351         final int i = base.lastIndexOf('.');
352         return i != -1 ? base.substring(0, i) : base;
353     }
354
355     //
356
// Refreshing.
357
//
358

359     /**
360      * Refreshes the entire tree,
361      * restores the expanded and selected paths and scrolls to the lead
362      * selection path if necessary.
363      */

364     public void refresh() {
365         final FileTreeModel ftm = (FileTreeModel) getModel();
366         final TreePath JavaDoc path = ftm.getTreePath((java.io.File JavaDoc) ftm.getRoot());
367         if (path != null)
368             refresh(new TreePath JavaDoc[] { path });
369     }
370
371     /**
372      * Refreshes the subtree for the given node,
373      * restores the expanded and selected paths and scrolls to the lead
374      * selection path if necessary.
375      *
376      * @param node The file or directory to refresh.
377      * This may <em>not</em> be <code>null</code>.
378      *
379      */

380     public void refresh(final java.io.File JavaDoc node) {
381         if (node == null)
382             throw new NullPointerException JavaDoc();
383
384         final FileTreeModel ftm = (FileTreeModel) getModel();
385         final TreePath JavaDoc path = ftm.getTreePath(node);
386         if (path != null)
387             refresh(new TreePath JavaDoc[] { path });
388     }
389
390     /**
391      * Refreshes the subtree for the given paths,
392      * restores the expanded and selected paths and scrolls to the lead
393      * selection path if necessary.
394      *
395      * @param paths The array of <code>TreePath</code>s to refresh.
396      * This may be <code>null</code>.
397      */

398     public void refresh(final TreePath JavaDoc paths[]) {
399         if (paths == null || paths.length <= 0)
400             return;
401
402         final FileTreeModel ftm = (FileTreeModel) getModel();
403
404         final TreePath JavaDoc lead = getLeadSelectionPath();
405         final TreePath JavaDoc anchor = getAnchorSelectionPath();
406         final TreePath JavaDoc[] selections = getSelectionPaths();
407         
408         for (int i = 0, l = paths.length; i < l; i++) {
409             final TreePath JavaDoc path = paths[i];
410             final Enumeration JavaDoc expansions = getExpandedDescendants(path);
411             ftm.refresh((java.io.File JavaDoc) path.getLastPathComponent());
412             setExpandedDescendants(expansions);
413         }
414
415         setSelectionPaths(selections);
416         setAnchorSelectionPath(anchor);
417         setLeadSelectionPath(lead);
418         scrollPathToVisible(lead);
419     }
420
421     private void setExpandedDescendants(final Enumeration JavaDoc expansions) {
422         if (expansions == null)
423             return;
424         while (expansions.hasMoreElements())
425             setExpandedState((TreePath JavaDoc) expansions.nextElement(), true);
426     }
427
428     //
429
// file methods.
430
//
431

432     /**
433      * Forwards the call to the {@link FileTreeModel}
434      * and scrolls the tree so that the newly created file
435      * is selected and visible.
436      * If you would like to create a new file with initial content, please
437      * check {@link #copyFrom(de.schlichtherle.io.File, InputStream)}.
438      */

439     public boolean createNewFile(final java.io.File JavaDoc node) throws IOException JavaDoc {
440         final FileTreeModel ftm = (FileTreeModel) getModel();
441         final TreePath JavaDoc path = ftm.getTreePath(node);
442         if (path == null)
443             return false;
444
445         if (!ftm.createNewFile(node))
446             return false;
447
448         setSelectionPath(path);
449         scrollPathToVisible(path);
450
451         return true;
452     }
453
454     /**
455      * Forwards the call to the {@link FileTreeModel}
456      * and scrolls the tree so that the newly created directory
457      * is selected and visible.
458      */

459     public boolean mkdir(final java.io.File JavaDoc node) {
460         final FileTreeModel ftm = (FileTreeModel) getModel();
461         final TreePath JavaDoc path = ftm.getTreePath(node);
462         if (path == null)
463             return false;
464
465         if (!ftm.mkdir(node))
466             return false;
467
468         setSelectionPath(path);
469         scrollPathToVisible(path);
470
471         return true;
472     }
473
474     /**
475      * Forwards the call to the {@link FileTreeModel}
476      * and scrolls the tree so that the newly created directory
477      * is selected and visible.
478      */

479     public boolean mkdirs(final java.io.File JavaDoc node) {
480         final FileTreeModel ftm = (FileTreeModel) getModel();
481         final TreePath JavaDoc path = ftm.getTreePath(node);
482         if (path == null)
483             return false;
484
485         if (!ftm.mkdirs(node))
486             return false;
487
488         setSelectionPath(path);
489         scrollPathToVisible(path);
490
491         return true;
492     }
493
494     /**
495      * Forwards the call to the {@link FileTreeModel}
496      * and scrolls the tree so that the copied node
497      * is selected and visible.
498      */

499     public boolean copyFrom(final de.schlichtherle.io.File node, final InputStream JavaDoc in) {
500         final FileTreeModel ftm = (FileTreeModel) getModel();
501         final TreePath JavaDoc path = ftm.getTreePath(node);
502         if (path == null)
503             return false;
504
505         if (!ftm.copyFrom(node, in))
506             return false;
507
508         setSelectionPath(path);
509         scrollPathToVisible(path);
510
511         return true;
512     }
513
514     /**
515      * Forwards the call to the {@link FileTreeModel}
516      * and scrolls the tree so that the copied node
517      * is selected and visible.
518      */

519     public boolean copyTo(final de.schlichtherle.io.File oldNode, final java.io.File JavaDoc node) {
520         final FileTreeModel ftm = (FileTreeModel) getModel();
521         final TreePath JavaDoc path = ftm.getTreePath(node);
522         if (path == null)
523             return false;
524
525         if (!ftm.copyTo(oldNode, node))
526             return false;
527
528         setSelectionPath(path);
529         scrollPathToVisible(path);
530
531         return true;
532     }
533
534     /**
535      * Forwards the call to the {@link FileTreeModel}
536      * and scrolls the tree so that the recursively copied node
537      * is selected and visible.
538      */

539     public boolean copyAllTo(final de.schlichtherle.io.File oldNode, final java.io.File JavaDoc node) {
540         final FileTreeModel ftm = (FileTreeModel) getModel();
541         final TreePath JavaDoc path = ftm.getTreePath(node);
542         if (path == null)
543             return false;
544
545         if (!ftm.copyAllTo(oldNode, node))
546             return false;
547
548         setSelectionPath(path);
549         scrollPathToVisible(path);
550
551         return true;
552     }
553
554     /**
555      * Forwards the call to the {@link FileTreeModel}
556      * and scrolls the tree so that the copied node
557      * is selected and visible.
558      */

559     public boolean archiveCopyTo(final de.schlichtherle.io.File oldNode, final java.io.File JavaDoc node) {
560         final FileTreeModel ftm = (FileTreeModel) getModel();
561         final TreePath JavaDoc path = ftm.getTreePath(node);
562         if (path == null)
563             return false;
564
565         if (!ftm.archiveCopyTo(oldNode, node))
566             return false;
567
568         setSelectionPath(path);
569         scrollPathToVisible(path);
570
571         return true;
572     }
573
574     /**
575      * Forwards the call to the {@link FileTreeModel}
576      * and scrolls the tree so that the recursively copied node
577      * is selected and visible.
578      */

579     public boolean archiveCopyAllTo(final de.schlichtherle.io.File oldNode, final java.io.File JavaDoc node) {
580         final FileTreeModel ftm = (FileTreeModel) getModel();
581         final TreePath JavaDoc path = ftm.getTreePath(node);
582         if (path == null)
583             return false;
584
585         if (!ftm.archiveCopyAllTo(oldNode, node))
586             return false;
587
588         setSelectionPath(path);
589         scrollPathToVisible(path);
590
591         return true;
592     }
593
594     /**
595      * Forwards the call to the {@link FileTreeModel},
596      * restores the expanded paths, selects <code>node</code> and scrolls to
597      * it if necessary.
598      */

599     public boolean renameTo(final java.io.File JavaDoc oldNode, final java.io.File JavaDoc node) {
600         final FileTreeModel ftm = (FileTreeModel) getModel();
601         final TreePath JavaDoc path = ftm.getTreePath(node);
602         if (path == null)
603             return false;
604
605         final Enumeration JavaDoc expansions;
606         final TreePath JavaDoc oldPath = ftm.getTreePath(oldNode);
607         if (oldPath != null)
608             expansions = getExpandedDescendants(oldPath);
609         else
610             expansions = null;
611
612         if (!ftm.renameTo(oldNode, node))
613             return false;
614
615         if (expansions != null)
616             while (expansions.hasMoreElements())
617                 setExpandedState(
618                         substPath((TreePath JavaDoc) expansions.nextElement(),
619                             oldPath, path),
620                         true);
621         setSelectionPath(path);
622         scrollPathToVisible(path);
623
624         return true;
625     }
626
627     private TreePath JavaDoc substPath(
628             final TreePath JavaDoc tp,
629             final TreePath JavaDoc oldPath,
630             final TreePath JavaDoc path) {
631         final java.io.File JavaDoc file = (java.io.File JavaDoc) tp.getLastPathComponent();
632         if (file.equals(oldPath.getLastPathComponent())) {
633             return path;
634         } else {
635             final TreePath JavaDoc parent = substPath(tp.getParentPath(), oldPath, path);
636             return parent.pathByAddingChild(
637                     new de.schlichtherle.io.File((java.io.File JavaDoc) parent.getLastPathComponent(),
638                              file.getName()));
639         }
640     }
641
642     /**
643      * Forwards the call to the {@link FileTreeModel}
644      * and scrolls the tree so that the successor to the deleted node
645      * is selected and visible.
646      */

647     public boolean delete(final java.io.File JavaDoc node) {
648         final FileTreeModel ftm = (FileTreeModel) getModel();
649         final TreePath JavaDoc path = ftm.getTreePath(node);
650         if (path == null)
651             return false;
652         scrollPathToVisible(path);
653         final int row = getRowForPath(path);
654
655         if (!ftm.delete(node))
656             return false;
657
658         setSelectionRow(row);
659
660         return true;
661     }
662
663     /**
664      * Forwards the call to the {@link FileTreeModel}
665      * and scrolls the tree so that the successor to the deleted node
666      * is selected and visible.
667      */

668     public boolean deleteAll(final de.schlichtherle.io.File node) {
669         final FileTreeModel ftm = (FileTreeModel) getModel();
670         final TreePath JavaDoc path = ftm.getTreePath(node);
671         if (path == null)
672             return false;
673         scrollPathToVisible(path);
674         final int row = getRowForPath(path);
675
676         if (!ftm.deleteAll(node))
677             return false;
678
679         setSelectionRow(row);
680
681         return true;
682     }
683
684     public void setSelectionNode(final java.io.File JavaDoc node) {
685         final FileTreeModel ftm = (FileTreeModel) getModel();
686         final TreePath JavaDoc path = ftm.getTreePath(node);
687         if (path != null) {
688             setSelectionPath(path);
689         }
690     }
691
692     public void setSelectionNodes(final java.io.File JavaDoc[] nodes) {
693         final FileTreeModel ftm = (FileTreeModel) getModel();
694
695         final List JavaDoc list = new LinkedList JavaDoc();
696         TreePath JavaDoc lastPath = null;
697         for (int i = 0, l = nodes.length; i < l; i++) {
698             lastPath = ftm.getTreePath(nodes[i]);
699             if (lastPath != null)
700                 list.add(lastPath);
701         }
702
703         final int size = list.size();
704         if (size > 0) {
705             final TreePath JavaDoc[] paths = new TreePath JavaDoc[size];
706             list.toArray(paths);
707             setSelectionPaths(paths);
708         }
709     }
710
711     public void scrollNodeToVisible(final java.io.File JavaDoc node) {
712         final FileTreeModel ftm = (FileTreeModel) getModel();
713         final TreePath JavaDoc path = ftm.getTreePath(node);
714         if (path != null) {
715             scrollPathToVisible(path);
716         }
717     }
718 }
719
Popular Tags