/**
* OrbisGIS is a java GIS application dedicated to research in GIScience.
* OrbisGIS is developed by the GIS group of the DECIDE team of the
* Lab-STICC CNRS laboratory, see <http://www.lab-sticc.fr/>.
*
* The GIS group of the DECIDE team is located at :
*
* Laboratoire Lab-STICC – CNRS UMR 6285
* Equipe DECIDE
* UNIVERSITÉ DE BRETAGNE-SUD
* Institut Universitaire de Technologie de Vannes
* 8, Rue Montaigne - BP 561 56017 Vannes Cedex
*
* OrbisGIS is distributed under GPL 3 license.
*
* Copyright (C) 2007-2014 CNRS (IRSTV FR CNRS 2488)
* Copyright (C) 2015-2017 CNRS (Lab-STICC UMR CNRS 6285)
*
* This file is part of OrbisGIS.
*
* OrbisGIS 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 3 of the License, or (at your option) any later
* version.
*
* OrbisGIS 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
* OrbisGIS. If not, see <http://www.gnu.org/licenses/>.
*
* For more information, please consult: <http://www.orbisgis.org/>
* or contact directly:
* info_at_ orbisgis.org
*/
package org.orbisgis.sif.components.fstree;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.ActionListener;
import java.beans.EventHandler;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.swing.*;
import javax.swing.TransferHandler.TransferSupport;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeNode;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.orbisgis.commons.progress.SwingWorkerPM;
import org.orbisgis.sif.UIFactory;
import org.orbisgis.sif.common.MenuCommonFunctions;
import org.orbisgis.sif.icons.SifIcon;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xnap.commons.i18n.I18n;
import org.xnap.commons.i18n.I18nFactory;
/**
* Represent a folder in the file system.
* @author Nicolas Fortin
*/
public class TreeNodeFolder extends AbstractTreeNodeContainer implements PopupTreeNode, TreeNodePath, DropDestinationTreeNode, DragTreeNode {
private File folderPath;
protected static final Logger LOGGER = LoggerFactory.getLogger("gui." + TreeNodeFolder.class);
protected static final I18n I18N = I18nFactory.getI18n(TreeNodeFolder.class);
private TreeNodeFileFactoryManager factoryManager;
private boolean loaded = false;
/**
* @param folderPath
* @param factoryManager
* @throws IllegalArgumentException If the provided path represent a file
*/
public TreeNodeFolder(File folderPath,TreeNodeFileFactoryManager factoryManager) {
this.factoryManager = factoryManager;
this.folderPath = folderPath;
if(!folderPath.isDirectory()) {
throw new IllegalArgumentException("The file path must be a directory");
}
setLabel(folderPath.getName());
}
@Override
public void setParent(MutableTreeNode mtn) {
super.setParent(mtn);
// Only sub folder can be renamed
setEditable(mtn instanceof TreeNodeFolder);
}
/**
* @param loaded If false, it will be refresh on request
*/
public void setLoaded(boolean loaded) {
this.loaded = loaded;
}
protected TreeNodeFolder createInstance(File folderPath,
TreeNodeFileFactoryManager factoryManager) {
return new TreeNodeFolder(folderPath, factoryManager);
}
/**
* Read the file system and insert the new files and folders
*/
public void updateTree() {
loaded = true;
List<String> fsList;
try {
fsList = new ArrayList<String>(Arrays.asList(getFilePath().list()));
} catch( SecurityException ex) {
LOGGER.error(I18N.tr("Cannot list the directory content"),ex);
return;
}
// Find deleted sub-elements, and update existing sub-folders
List<MutableTreeNode> childrenToRemove = new ArrayList<MutableTreeNode>(children.size());
for(MutableTreeNode child : children) {
if(child instanceof TreeNodePath) {
String childFileName = ((TreeNodePath)child).getFilePath()
.getName();
if(!fsList.contains(childFileName)) {
childrenToRemove.add(child);
} else {
fsList.remove(childFileName);
if(child instanceof TreeNodeFolder) {
((TreeNodeFolder)child).setLoaded(false);
model.nodeChanged(child);
}
}
}
}
// Effective children removal
for(MutableTreeNode child : childrenToRemove) {
model.removeNodeFromParent(child);
}
// Add the new children, and update new sub-folders
for(String childPath : fsList) {
File newChild = new File(getFilePath(), childPath);
if (newChild.isDirectory()) {
TreeNodeFolder subDir =
createInstance(newChild, factoryManager);
model.insertNodeInto(subDir, this, children.size());
} else {
AbstractTreeNode child = factoryManager.create(newChild);
if (child != null) {
model.insertNodeInto(child, this, children.size());
}
}
}
}
@Override
public int getChildCount() {
if(!loaded) {
// Load from disk
updateTree();
}
return super.getChildCount();
}
@Override
public void setUserObject(Object o) {
//User set the folder name
File curPath = getFilePath();
File dest = new File(curPath.getParentFile(),o.toString());
if(dest.exists()) {
LOGGER.error("The specified folder already exists");
return;
}
if(curPath.renameTo(dest)) {
folderPath = dest;
setLabel(folderPath.getName());
} else {
LOGGER.error("The specified folder name is not correct");
}
}
/**
* File&Folder deletion
*/
public void onDeleteFolder() {
if(getFilePath().exists()) {
int result = JOptionPane.showConfirmDialog(UIFactory.getMainFrame(),
I18N.tr("Are you sure you want to delete permanently the selected files ?"),
I18N.tr("Remove the files"),
JOptionPane.YES_NO_OPTION,JOptionPane.WARNING_MESSAGE);
if(result == JOptionPane.YES_OPTION) {
try {
FileUtils.deleteDirectory(getFilePath());
} catch(IOException ex) {
LOGGER.error(I18N.tr("Cannot remove the folder {0}",getFilePath().getName()),ex);
}
} else {
return;
}
}
model.removeNodeFromParent(this);
}
/**
* Copy the folder path to the ClipBoard
*/
public void onCopyPath() {
StringSelection pathString = new StringSelection(getFilePath().getAbsolutePath());
try {
Clipboard clipboard =
Toolkit.getDefaultToolkit().getSystemClipboard();
clipboard.setContents(pathString, pathString);
LOGGER.info(I18N.tr("The path {0} has been copied to the clipboard",getFilePath().getAbsolutePath()));
} catch (Throwable ex) {
LOGGER.error(I18N.tr("Could not copy the folder path to the clipboard"),ex);
}
}
@Override
public void feedPopupMenu(JPopupMenu menu) {
if (menu.getComponentCount() > 0) {
menu.addSeparator();
}
// Open the folder
JMenuItem copyPathMenu = new JMenuItem(I18N.tr("Copy the path"));
copyPathMenu.setToolTipText(I18N.tr("Copy the folder path in the clipboard"));
copyPathMenu.setActionCommand("TreeNodeFolder:CopyPath");
copyPathMenu.addActionListener(
EventHandler.create(ActionListener.class,
this, "onCopyPath"));
MenuCommonFunctions.updateOrInsertMenuItem(menu, copyPathMenu);
// Read the file system to update the tree
JMenuItem updateMenu = new JMenuItem(I18N.tr("Update"),
SifIcon.getIcon("refresh"));
updateMenu.setToolTipText(I18N.tr("Update the content of this folder from the file system"));
updateMenu.setActionCommand("Update");
updateMenu.addActionListener(
EventHandler.create(ActionListener.class,
this, "updateTree"));
MenuCommonFunctions.updateOrInsertMenuItem(menu, updateMenu);
// Add a new Sub Folder
JMenuItem newSubFolder = new JMenuItem(I18N.tr("New folder"),
SifIcon.getIcon("folder_add"));
newSubFolder.setToolTipText(I18N.tr("Create a sub-folder"));
newSubFolder.setActionCommand("TreeNodeFolder:newSubFolder");
newSubFolder.addActionListener(
EventHandler.create(ActionListener.class,
this, "onNewSubFolder"));
MenuCommonFunctions.updateOrInsertMenuItem(menu, newSubFolder);
// Remove the folder
//The root folder cannot be removed
if(parent instanceof TreeNodeFolder) {
JMenuItem folderRemove = new JMenuItem(I18N.tr("Delete"),
SifIcon.getIcon("remove"));
folderRemove.setToolTipText(I18N.tr("Remove permanently the folder"));
folderRemove.setActionCommand("delete");
folderRemove.addActionListener(
EventHandler.create(ActionListener.class,
this, "onDeleteFolder"));
MenuCommonFunctions.updateOrInsertMenuItem(menu,folderRemove,true);
}
}
/**
* Create a sub folder, the folder name is given through an input dialog
*/
public void onNewSubFolder() {
String folderName = JOptionPane.showInputDialog(UIFactory.getMainFrame(), I18N.tr("Enter the folder name"), I18N.tr("Folder"));
if(folderName!=null) {
File newFolderPath = getUniqueFileName(new File(getFilePath(),folderName));
try {
if(!newFolderPath.mkdir()) {
LOGGER.error(I18N.tr("The folder creation has failed"));
} else {
updateTree();
}
} catch(Throwable ex) {
LOGGER.error(I18N.tr("The folder creation has failed"),ex);
}
}
}
@Override
public File getFilePath() {
if(parent instanceof TreeNodePath) {
File parentPath = ((TreeNodePath)parent).getFilePath();
return new File(parentPath,folderPath.getName());
} else {
return folderPath;
}
}
/**
* Return the children of this folder
* @return
*/
public List<AbstractTreeNode> getChildren() {
List<AbstractTreeNode> childrenRet = new ArrayList<AbstractTreeNode>(getChildCount());
for(MutableTreeNode child : children) {
childrenRet.add((AbstractTreeNode)child);
}
return childrenRet;
}
@Override
public boolean canImport(TransferSupport ts) {
return ts.isDataFlavorSupported(TransferableNodePaths.PATHS_FLAVOR) || ts.isDataFlavorSupported(TransferableFileContent.FILE_CONTENT_FLAVOR);
}
/**
* Extract all components of the path to compare with the second argument
* @param path
* @param compPath Absolute path
* @return
*/
private static boolean hasAParent(File path,String compPath) {
File parent = path.getParentFile();
while(parent!=null) {
if(compPath.equals(parent.getAbsolutePath())) {
return true;
}
parent = parent.getParentFile();
}
return false;
}
/**
* Extract all components of the path to compare with the second argument
* @param path
* @param paths Set of Absolute path
* @return
*/
private static boolean hasAParentInPathList(File path,Set<String> paths) {
File parent = path.getParentFile();
while(parent!=null) {
if(paths.contains(parent.getAbsolutePath())) {
return true;
}
parent = parent.getParentFile();
}
return false;
}
/**
* Return the appropriate file path to not overwrite existing files
* @param fullPath Path of the file
* @return Non existing file path
*/
private static File getUniqueFileName(final File fullPath) {
int cpt = 0;
final String fileNameWithExt = fullPath.getName();
final File basePath = fullPath.getParentFile();
final String extension = FilenameUtils.getExtension(fileNameWithExt);
final String baseName = FilenameUtils.getBaseName(fileNameWithExt);
File fileName = fullPath;
while (fileName.exists()) {
String newFileName = baseName
+"_"+(cpt++)+"."
+ extension;
fileName = new File(basePath,newFileName);
}
return fileName;
}
/**
* Create a file from a Reader instance in this folder
* @param tf Transferable instance
* @param flavor DataFlavor
* @return If the file has been created
*/
private boolean importReader(Transferable tf, DataFlavor flavor) {
try {
// From this transferable, a file can be created
Object transferData = tf.getTransferData(flavor);
if(!(transferData instanceof Reader)) {
return false;
}
BufferedReader br = new BufferedReader((Reader)transferData );
File fileName;
if (transferData instanceof TransferableFileContent) {
// The filename is given by the drag source
fileName = new File(getFilePath(), ((TransferableFileContent) transferData).getFileNameHint());
} else {
// The filename must be given by the user
String contentFileName = JOptionPane.showInputDialog(UIFactory.getMainFrame(), I18N.tr("Enter the folder name"), I18N.tr("Folder"));
if (contentFileName != null) {
fileName = new File(getFilePath(), contentFileName);
} else {
return false;
}
}
// If the file exists found a new one
fileName = getUniqueFileName(fileName);
FileWriter writer = new FileWriter(fileName);
String res = br.readLine();
while (res != null) {
writer.write(res+"\n");
res = br.readLine();
}
writer.close();
return true;
} catch (UnsupportedFlavorException ex) {
LOGGER.error(ex.getLocalizedMessage(), ex);
return false;
} catch (IOException ex) {
LOGGER.error(ex.getLocalizedMessage(), ex);
return false;
}
}
@Override
public boolean importData(TransferSupport ts) {
if(ts.isDataFlavorSupported(TransferableNodePaths.PATHS_FLAVOR)) {
// Move Nodes and Move Files
new DropTransferable(ts.getTransferable(), this).execute();
return true;
} else {
DataFlavor[] flavors = ts.getDataFlavors();
for(DataFlavor flavor : flavors) {
if(flavor.isRepresentationClassReader()) {
new DropTransferable(ts.getTransferable(), this).execute();
return true;
}
}
return false;
}
}
@Override
public boolean completeTransferable(TransferableList transferable) {
if(parent instanceof TreeNodeFolder) {
transferable.addTransferable(new TransferableNodePaths(this));
return true;
} else {
return false;
}
}
private void transferTreeNodePath(List<TreeNodePath> nodePath) {
// Moved paths set
Set<String> paths = new HashSet<String>();
for (TreeNodePath treeNode : nodePath) {
File treeFilePath = treeNode.getFilePath();
paths.add(treeFilePath.getAbsolutePath());
}
// Process folder and files moves
for (TreeNodePath treeNode : nodePath) {
// Ignore if a parent path is already on the list
// or if this folder node is the child of a transfered path
File treeFilePath = treeNode.getFilePath();
if (!hasAParentInPathList(treeFilePath, paths)
&& !hasAParent(getFilePath(), treeFilePath.getAbsolutePath())) {
try {
File dest = new File(getFilePath(), treeFilePath.getName());
if (!dest.exists()) {
// Move the folder
FileUtils.copyDirectory(treeFilePath, getFilePath(), false);
FileUtils.deleteDirectory(treeFilePath);
} else {
LOGGER.warn(I18N.tr("Destination file {0} already exists, cannot move {1}", dest, treeFilePath));
}
} catch (IOException ex) {
LOGGER.error(ex.getLocalizedMessage(), ex);
return;
}
}
}
}
/**
* Process drop operation on this node
*/
private static class DropTransferable extends SwingWorkerPM {
Transferable transferable;
TreeNodeFolder folderNode;
public DropTransferable(Transferable transferable, TreeNodeFolder folderNode) {
this.transferable = transferable;
this.folderNode = folderNode;
setTaskName(I18N.tr("Drop the content in the folder.."));
}
@Override
protected Object doInBackground() throws Exception {
Thread.currentThread().setName(this.getClass().getSimpleName());
if (transferable.isDataFlavorSupported(TransferableNodePaths.PATHS_FLAVOR)) {
// Move Nodes and Move Files
List<TreeNodePath> nodePath = new ArrayList<TreeNodePath>();
try {
Object objTrans = transferable.
getTransferData(TransferableNodePaths.PATHS_FLAVOR);
if (objTrans instanceof List) {
nodePath = (List<TreeNodePath>) objTrans;
}
} catch (UnsupportedFlavorException ex) {
LOGGER.error(ex.getLocalizedMessage(), ex);
return null;
} catch (IOException ex) {
LOGGER.error(ex.getLocalizedMessage(), ex);
return null;
}
folderNode.transferTreeNodePath(nodePath);
} else {
DataFlavor[] flavors = transferable.getTransferDataFlavors();
for (DataFlavor flavor : flavors) {
if (flavor.isRepresentationClassReader()) {
folderNode.importReader(transferable, flavor);
break;
}
}
}
return null;
}
@Override
protected void done() {
// Retrieve the top folder
TreeNodeFolder topFolder = folderNode;
TreeNode cursor = folderNode;
while(cursor instanceof TreeNodeFolder) {
topFolder = (TreeNodeFolder) cursor;
cursor = cursor.getParent();
}
topFolder.updateTree();
}
}
}