/* Copyright (C) 2006 Christian Schneider
*
* This file is part of Nomad.
*
* Nomad is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Nomad is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Nomad; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/*
* Created on Oct 29, 2006
*/
package net.sf.nmedit.nomad.core.swing.explorer;
import java.awt.Component;
import java.awt.Event;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.NoSuchElementException;
import javax.swing.Icon;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import net.sf.nmedit.nmutils.FileSort;
import net.sf.nmedit.nmutils.Platform;
import net.sf.nmedit.nmutils.io.FileUtils;
import net.sf.nmedit.nmutils.swing.WorkIndicator;
import net.sf.nmedit.nomad.core.Nomad;
public class FileNode implements ETreeNode, MouseListener,
Transferable
{
private final static FileNode[] EMPTY = new FileNode[0];
private File file;
private FileNode[] children = null;
private TreeNode parent;
private boolean nailed = false;
public FileNode(TreeNode parent, File file)
{
this.parent = parent;
this.file = file;
}
public File getFile()
{
return file;
}
public void setFile(File file)
{
this.file = file;
updateChildrenNodes(true);
}
protected TreeNode[] getPathToRoot(TreeNode aNode, int depth) {
TreeNode[] retNodes;
/* Check for null, in case someone passed in a null node, or
they passed in an element that isn't rooted at root. */
if(aNode == null) {
if(depth == 0)
return null;
else
retNodes = new TreeNode[depth];
}
else {
depth++;
retNodes = getPathToRoot(aNode.getParent(), depth);
retNodes[retNodes.length - depth] = aNode;
}
return retNodes;
}
public TreeNode[] getPath() {
return getPathToRoot(this, 0);
}
public void processEvent(Event event)
{
if (parent!=null && parent instanceof ETreeNode)
((ETreeNode) parent).processEvent(event);
}
public FileFilter getFileFilter()
{
if (parent != null && parent instanceof FileNode)
{
return ((FileNode) parent).getFileFilter();
}
else
{
return null;
}
}
public boolean updateChildrenNodes(boolean deep) {
boolean updated = false;
if (children != null) {
File files[] = getChildrenFiles();
Hashtable<String, File> fileNames = new Hashtable<String, File>();
Hashtable<String, FileNode> childrenNames = new Hashtable<String, FileNode>();
ArrayList<FileNode> newChildren = new ArrayList<FileNode>();
for (File fn : files) {
try {
fileNames.put(fn.getCanonicalPath(), fn);
} catch (Throwable e) {
updated = true;
}
}
for (FileNode child : children) {
try {
String name = child.getFile().getCanonicalPath();
childrenNames.put(name, child);
if (fileNames.containsKey(name)) {
newChildren.add(child);
} else {
updated = true;
}
} catch (Throwable e) {
updated = true;
}
}
for (File f: files) {
try {
String name = f.getCanonicalPath();
if (!childrenNames.containsKey(name)) {
newChildren.add(new FileNode(this, f));
updated = true;
}
} catch (Throwable e) {
updated = true;
}
}
if (updated) {
Collections.sort(newChildren, new Comparator<FileNode>() {
public int compare(FileNode o1, FileNode o2) {
return o1.getFile().getName().compareTo(o2.getFile().getName());
}
});
children = new FileNode[newChildren.size()];
int i = 0;
for (FileNode child : newChildren) {
children[i++] = child;
}
}
if (deep)
{
for (FileNode child : children) {
if (child.getChildCount() > 0) {
if (child.updateChildrenNodes(deep)) {
updated = true;
}
}
}
}
}
return updated;
}
public void notifyDropChildren()
{
children = null;
}
private void updateChildrenArray(int removed) {
FileNode[] a = new FileNode[children.length-removed];
int ai = 0;
for (int i=0;i<children.length;i++)
if (children[i] != null)
a[ai++] = children[i];
children = a;
}
public void notifyChildFilesRemoved(ExplorerTree et)
{
if (children != null)
{
int removed = 0;
for (int i=children.length-1;i>=0;i--)
{
FileNode fn = children[i];
if (!fn.getFile().exists())
{
children[i] = null;
removed ++;
}
}
if (removed>0)
{
updateChildrenArray(removed);
et.fireNodeStructureChanged(this);
}
}
}
private File[] getChildrenFiles() {
FileFilter filter = getFileFilter();
File[] files = filter == null ? this.file.listFiles() : this.file.listFiles(filter);
return files;
}
private TreeNode[] getChildrenArray()
{
if (children == null)
{
File[] files = getChildrenFiles();
if (files != null && files.length>0)
{
FileSort.sortDirectoriesFiles(files);
children = new FileNode[files.length];
for (int i=files.length-1;i>=0;i--)
children[i] = new FileNode(this, files[i]);
}
else
{
children = EMPTY;
}
}
return children;
}
public Collection<? extends TreeNode> getChildren() {
ArrayList<TreeNode> result = new ArrayList<TreeNode>();
for (TreeNode child : getChildrenArray()) {
result.add(child);
}
return result;
}
public TreeNode getChildAt( int childIndex )
{
return getChildrenArray()[childIndex];
}
public int getChildCount()
{
return file.isFile() ? 0 : getChildrenArray().length;
}
public TreeNode getParent()
{
return parent;
}
public int getIndex( TreeNode node )
{
TreeNode[] children = getChildrenArray();
for (int i=children.length-1;i>=0;i--)
if (children[i]==node)
return i;
return -1;
}
public boolean getAllowsChildren()
{
return file.isDirectory();
}
public boolean isLeaf()
{
return getChildCount() == 0;
}
public Enumeration children()
{
if (getChildCount()==0)
return DefaultMutableTreeNode.EMPTY_ENUMERATION;
else
return new Enumeration()
{
TreeNode[] items = getChildrenArray();
int index = 0;
public boolean hasMoreElements()
{
return index<items.length;
}
public Object nextElement()
{
if (!hasMoreElements())
throw new NoSuchElementException();
return items[index++];
}
};
}
public String toString()
{
return file.getName();
}
public Icon getIcon()
{
return file.isFile() ?
ExplorerTreeUI.DefaultFileIcon : null;
}
public String getToolTipText()
{
File f = getFile();
return f.getAbsolutePath();
}
public void processEvent(MouseEvent e)
{
EventDispatcher.dispatchEvent(this, e);
}
public void mouseClicked(MouseEvent e)
{
// no op
}
public void mouseEntered(MouseEvent e)
{
// no op
}
public void mouseExited(MouseEvent e)
{
// no op
}
private void openAction(MouseEvent e)
{
if (Platform.isLeftMouseButtonOnly(e) && e.getClickCount()==2)
{
justOpenIt(e.getComponent());
}
}
private void justOpenIt(Component c)
{
Runnable run = new Runnable()
{
public void run()
{
Nomad.sharedInstance().openOrSelect(file);
}
};
run = WorkIndicator.create(c, run);
SwingUtilities.invokeLater(run);
}
public void mousePressed(MouseEvent e)
{
ExplorerTree et = (ExplorerTree) e.getComponent();
/*
if (handlePopupTrigger(e))
return;*/
if (!Platform.isFlavor(Platform.OS.MacOSFlavor))
openAction(e);
}
public void mouseReleased(MouseEvent e)
{
if (Platform.isFlavor(Platform.OS.MacOSFlavor))
openAction(e);
/*
if (Platform.couldBePopupTrigger(e) && filePopup != null)
{
((ExplorerTree)e.getComponent()).cancelEditing();
e.consume();
}
if (Platform.isFlavor(Platform.OS.MacOSFlavor) && Platform.couldBePopupTrigger(e))
e.consume();*/
}
public boolean isActionCommandPossible(ExplorerTree tree, String command)
{
if (nailed && ((command == FileExplorerTree.ACTION_ENTRY_REMOVE) ||
(command == FileExplorerTree.ACTION_ITEM_DELETE) ||
(command == FileExplorerTree.ACTION_RENAME)))
return false;
if (this instanceof FileContext)
{
return command == FileExplorerTree.ACTION_DIR_NEWDIR
|| command == FileExplorerTree.ACTION_ITEM_DELETE
|| command == FileExplorerTree.ACTION_REFRESH
|| command == FileExplorerTree.ACTION_RENAME
|| command == FileExplorerTree.ACTION_ENTRY_REMOVE;
}
if (file.isDirectory())
{
return command == FileExplorerTree.ACTION_DIR_NEWDIR
|| command == FileExplorerTree.ACTION_ITEM_DELETE
|| command == FileExplorerTree.ACTION_REFRESH
|| command == FileExplorerTree.ACTION_RENAME;
}
else if (file.isFile())
{
return command == FileExplorerTree.ACTION_ITEM_OPEN
|| command == FileExplorerTree.ACTION_ITEM_DELETE
|| command == FileExplorerTree.ACTION_RENAME;
}
return false;
}
public void actionCommandPerformed(ExplorerTree tree, String command)
{
ExplorerTree et = tree;
FileNode node = this;
if (command == FileExplorerTree.ACTION_ITEM_OPEN)
{
justOpenIt(tree);
}
else if (command == FileExplorerTree.ACTION_REFRESH) {
if (node.updateChildrenNodes(true)) {
et.fireNodeStructureChanged(node);
}
} else if (command == FileExplorerTree.ACTION_ITEM_DELETE) {
Nomad n = Nomad.sharedInstance();
File f = node.getFile();
if (f.isFile()) {
int result = JOptionPane.showConfirmDialog(n.getWindow().getRootPane(),
"Are you sure you want to delete " + f.getName() + " ?", "", JOptionPane.OK_CANCEL_OPTION
);
if (result == JOptionPane.OK_OPTION) {
if (f.delete())
{
if (node.getParent() instanceof FileNode)
((FileNode)node.getParent()).notifyChildFilesRemoved(et);
}
}
} else if (f.isDirectory())
{
int result = JOptionPane.showConfirmDialog(n.getWindow().getRootPane(),
"Are you sure you want to delete " + f.getName() + " and all its contents ?", "", JOptionPane.OK_CANCEL_OPTION
);
if (result == JOptionPane.OK_OPTION) {
FileUtils.deleteDirectory(f);
boolean rootChanged = false;
for (FileNode rNode : et.getRootFileNodes()) {
File rFile = rNode.getFile();
if (FileUtils.isFileParent(f, rFile)) {
et.getRoot().remove(rNode);
rootChanged = true;
}
}
if (node.getParent() instanceof FileNode)
((FileNode)node.getParent()).notifyChildFilesRemoved(et);
if (rootChanged)
et.fireRootChanged();
}
}
}
else if (command == FileExplorerTree.ACTION_ENTRY_REMOVE)
{
if (node.getParent() == et.getRoot())
{
Nomad n = Nomad.sharedInstance();
File f = node.getFile();
int result = JOptionPane.showConfirmDialog(n.getWindow().getRootPane(),
"Are you sure you want to remove " + f.getName() + " from the tree ?", "", JOptionPane.OK_CANCEL_OPTION
);
if (result == JOptionPane.OK_OPTION) {
et.getRoot().remove(node);
et.fireRootChanged();
}
}
} else if (command == FileExplorerTree.ACTION_RENAME) {
et.startEditingAtPath(new TreePath(node.getPath()));
} else if (command == FileExplorerTree.ACTION_DIR_NEWDIR) {
File f = node.getFile();
try {
File newDir = FileUtils.newFileWithPrefix(f, "dir", "");
newDir.mkdir();
node.updateChildrenNodes(true);
((ExplorerTree)et).updateParentRootNodes(node);
et.expandPath(new TreePath(node.getPath()));
((ExplorerTree)et).fireNodeStructureChanged(node);
for (TreeNode child : node.getChildren()) {
if (child instanceof FileNode && ((FileNode)child).getFile().getCanonicalPath().equals(newDir.getCanonicalPath())) {
et.startEditingAtPath(new TreePath(((FileNode)child).getPath()));
break;
}
}
} catch (IOException e1) {
// no op
e1.printStackTrace();
}
}
}
protected static DataFlavor fileFlavor = new DataFlavor(File.class, "File");
protected static DataFlavor fileNodeFlavor = new DataFlavor(FileNode.class, "FileNode");
protected static DataFlavor uriFlavor =
new DataFlavor("text/uri-list; charset=utf-16", "uri list");
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException
{
if (fileFlavor.match(flavor))
return file;
if (uriFlavor.match(flavor))
{
String path;
if (file == null) {
path = null;
} else {
path = file.getAbsoluteFile().toURI().toString();
}
return new ByteArrayInputStream(path.getBytes("utf-16"));
}
if (fileNodeFlavor.match(flavor))
return this;
throw new UnsupportedFlavorException(flavor);
}
public DataFlavor[] getTransferDataFlavors()
{
DataFlavor[] flavors = {fileFlavor, uriFlavor, fileNodeFlavor};
return flavors;
}
public boolean isDataFlavorSupported(DataFlavor flavor)
{
for (DataFlavor f: getTransferDataFlavors())
if (f.equals(flavor))
return true;
return false;
}
public void setNailed(boolean nailed) {
this.nailed = nailed;
}
public boolean isNailed() {
return nailed;
}
public boolean renameTo(ExplorerTree etree, File realNewFile) {
File oldFile = getFile();
if (oldFile.renameTo(realNewFile)) {
setFile(realNewFile);
boolean rootChanged = false;
for (FileNode rNode : etree.getRootFileNodes()) {
File rFile = rNode.getFile();
if (FileUtils.isFileParent(oldFile, rFile)) {
try {
String oldPath = oldFile.getCanonicalPath();
String newPath = realNewFile.getCanonicalPath();
String renPath = rFile.getCanonicalPath();
String newRenPath = newPath + renPath.substring(oldPath.length());
rNode.setFile(new File(newRenPath));
etree.updateParentRootNodes(rNode);
etree.fireNodeStructureChanged(rNode.getParent());
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
rootChanged = true;
} else if (FileUtils.isFileParent(rFile, oldFile)) {
rNode.updateChildrenNodes(true);
}
}
if (rootChanged)
etree.fireRootChanged();
}
return true;
}
}