/*
* Copyright (C) 2015 Vinu K.N
*
* This program 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
// FIX ME: same icon for same named file and folder.
package org.domainmath.gui;
import java.awt.Component;
import java.awt.Desktop;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Vector;
import java.util.logging.Logger;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.Icon;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.JTree;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.ToolTipManager;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.filechooser.FileSystemView;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import org.apache.commons.io.FileUtils;
import org.domainmath.gui.dialog.Delete;
/**
* Create a File Tree Panel.
* This File Tree Panel changes according to changing folder.
* @author Vinu K.N
*/
public class FileTreePanel extends javax.swing.JPanel {
private static final long serialVersionUID = -1291143920511369926L;
private FileSystemModel fileSystemModel;
private final MainFrame frame;
java.util.ResourceBundle bundle = java.util.ResourceBundle.getBundle("org/domainmath/gui/resources/DomainMath_en");
// popup menus.
private JMenuItem openItem;
private JMenuItem runScriptItem;
private JMenuItem openOutsideItem;
private JMenuItem renameItem;
private JMenuItem deleteItem;
private JMenuItem refreshItem;
private JMenuItem newFolderItem;
private KeyStroke keyDeleteItem;
private DeleteAction deleteAction;
private final KeyStroke keyOpenItem;
private OpenAction openAction;
private File lastSelectedFile;
private final KeyStroke keyRenameItem;
private final KeyStroke keyRefreshItem;
private RefreshAction refreshAction;
/**
* Creates new form FileTreePanel
* @param frame
*/
public FileTreePanel(MainFrame frame) {
initComponents();
this.frame=frame;
fileTree.setEditable(true);
addPopupMenuToFileTree();
ToolTipManager.sharedInstance().registerComponent(fileTree);
// handle right click event on File Tree.
fileTree.addMouseListener ( new MouseAdapter (){
@Override
public void mousePressed ( MouseEvent e ){
if(e.getClickCount() == 2) {
TreePath path = fileTree.getPathForLocation ( e.getX (), e.getY () );
Rectangle pathBounds = fileTree.getUI ().getPathBounds ( fileTree, path );
if ( pathBounds != null && pathBounds.contains ( e.getX (), e.getY () ) ){
File file = (File) fileTree.getLastSelectedPathComponent();
selectFile(file);
}
}
}
});
keyDeleteItem = KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0);
keyRenameItem = KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0);
keyOpenItem = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
keyRefreshItem = KeyStroke.getKeyStroke(KeyEvent.VK_F5, 0);
}
/**
* Returns selected file(last).
* @return lastSelectedFile
*/
public File getLastSelectedFile() {
fileTree.addMouseListener ( new MouseAdapter (){
@Override
public void mousePressed ( MouseEvent e ){
TreePath path = fileTree.getPathForLocation ( e.getX (), e.getY () );
Rectangle pathBounds = fileTree.getUI ().getPathBounds ( fileTree, path );
if ( pathBounds != null && pathBounds.contains ( e.getX (), e.getY () ) ){
File file = (File) fileTree.getLastSelectedPathComponent();
lastSelectedFile = file;
}
}
});
return lastSelectedFile;
}
/**
* Set selected file(last).
* @param lastSelectedFile
*/
public void setLastSelectedFile(File lastSelectedFile) {
this.lastSelectedFile=lastSelectedFile;
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">
private void initComponents() {
jScrollPane1 = new javax.swing.JScrollPane();
fileTree = new org.domainmath.gui.FileTree();
jScrollPane1.setViewportView(fileTree);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 400, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 311, Short.MAX_VALUE)
);
}// </editor-fold>
// Variables declaration - do not modify
private org.domainmath.gui.FileTree fileTree;
private javax.swing.JScrollPane jScrollPane1;
// End of variables declaration
/**
* Update File Tree according to file f.
* @param f
*/
public void updateFileTree(File f) {
this.fileTree.setModel(null);
fileSystemModel = new FileSystemModel(f);
this.fileTree.setModel(fileSystemModel);
fileTree.setCellRenderer(new FileTreeCellRenderer());
fileTree.setSelectionRow(0);
}
/**
* Handle file selection.
* @param file
*/
private void selectFile(File file) {
if(!file.isDirectory()) {
String selectedFileToOpen = file.getName();
// user selected octave script.
if(selectedFileToOpen.endsWith(".m")) {
if(!MainFrame.fileNameList.contains(file.getAbsolutePath())) {
frame.open(file, MainFrame.FILE_TAB_INDEX);
frame.setCurrentDirFileTab(file.getParent());
}else {
System.out.println(file.getAbsolutePath()+" already open!");
}
}else{
loadFile(file);
}
}else{
// change current directory.
frame.changeDirectory(file);
updateFileTree(file);
}
}
/**
* Importing data.
* @param file
*/
private void loadFile(File file) {
String name;
name =file.getName();
if(name.endsWith(".mat")) {
MainFrame.workspace.load(file,"-mat");
}else if(name.endsWith(".hdf5")) {
MainFrame.workspace.load(file,"-hdf5");
}else if(name.endsWith(".fhdf5")) {
MainFrame.workspace.load(file,"-float-hdf5");
}else if(name.endsWith(".txt")) {
MainFrame.workspace.load(file,"-ascii");
}else if(name.endsWith(".bin")) {
MainFrame.workspace.load(file,"-binary");
}else if(name.endsWith(".fbin")) {
MainFrame.workspace.load(file,"-float-binary");
}else if(name.endsWith(".zip")) {
MainFrame.workspace.load(file,"-zip");
}else if(name.endsWith(".fis")) {
MainFrame.octavePanel.eval("pkg load fuzzy-logic-toolkit");
MainFrame.octavePanel.eval("readfis('"+file.getAbsolutePath()+"');");
}else if(name.endsWith(".csv")) {
MainFrame.workspace.loadCSV(file);
}else if(name.endsWith(".bmp") ||
name.endsWith(".gif") ||
name.endsWith(".jpg") ||
name.endsWith(".jpeg") ||
name.endsWith(".pbm") ||
name.endsWith(".pcx") ||
name.endsWith(".pgm") ||
name.endsWith(".png") ||
name.endsWith(".pnm") ||
name.endsWith(".ppm") ||
name.endsWith(".ras") ||
name.endsWith(".tif") ||
name.endsWith(".tiff") ||
name.endsWith(".xwd") ) {
MainFrame.workspace.loadImage(file.getAbsolutePath());
}else if(name.endsWith(".lin")) {
MainFrame.workspace.loadAudio1(file.getParent()+File.separator+name.substring(0, name.indexOf(".")),"lin");
}else if(name.endsWith(".raw")) {
MainFrame.workspace.loadAudio1(file.getParent()+File.separator+name.substring(0, name.indexOf(".")),"raw");
}else if(name.endsWith(".au")) {
MainFrame.workspace.loadAudio1(file.getParent()+File.separator+name.substring(0, name.indexOf(".")),"au");
}else if(name.endsWith(".mu")) {
MainFrame.workspace.loadAudio1(file.getParent()+File.separator+name.substring(0, name.indexOf(".")),"mu");
}else if(name.endsWith(".snd")) {
MainFrame.workspace.loadAudio1(file.getAbsolutePath(),"snd");
}else if(name.endsWith(".dcm")) {
MainFrame.workspace.loadDCM(file.getAbsolutePath());
}else if(name.endsWith(".wav") || name.endsWith(".riff")){
MainFrame.workspace.loadAudio2(file.getAbsolutePath());
}
MainFrame.reloadWorkspace();
}
/**
* Add popup menu.
*/
private void addPopupMenuToFileTree() {
refreshItem=new JMenuItem(bundle.getString("fileTreeRefreshItem.text"));
fileTree.addMouseListener ( new MouseAdapter (){
@Override
public void mousePressed ( MouseEvent e ){
addGlobalAction(e);
if ( SwingUtilities.isRightMouseButton ( e ) ){
TreePath path = fileTree.getPathForLocation ( e.getX (), e.getY () );
Rectangle pathBounds = fileTree.getUI ().getPathBounds ( fileTree, path );
if ( pathBounds != null && pathBounds.contains ( e.getX (), e.getY () ) ){
JPopupMenu menu = new JPopupMenu ();
File file = (File) fileTree.getLastSelectedPathComponent();
openItem = new JMenuItem (bundle.getString("fileTreeOpenItem.text"));
runScriptItem = new JMenuItem (bundle.getString("fileTreeRunItem.text"));
openOutsideItem =new JMenuItem(bundle.getString("fileTreeOpenOutsideItem.text"));
renameItem = new JMenuItem(bundle.getString("fileTreeRenameItem.text"));
deleteItem=new JMenuItem(bundle.getString("fileTreeDeleteItem.text"));
openItem.setAccelerator(keyOpenItem);
renameItem.setAccelerator(keyRenameItem);
deleteItem.setAccelerator(keyDeleteItem);
refreshItem.setAccelerator(keyRefreshItem);
menu.add (openItem);
menu.add(runScriptItem);
menu.add(openOutsideItem);
menu.addSeparator();
menu.add(renameItem);
menu.add(deleteItem);
menu.addSeparator();
menu.add(refreshItem);
fileTreeOpenItemActionPerformed(file);
fileTreeRunItemActionPerformed(file);
fileTreeOpenOutsideItemActionPerformed(file);
fileTreeRenameItemActionPerformed(fileTree.getSelectionPath());
fileTreeDeleteItemActionPerformed(fileTree.getSelectionModel().getSelectionPaths());
fileTreeRefreshItemActionPerformed((File)fileTree.getModel().getRoot());
if(file.isDirectory()) {
runScriptItem.setEnabled(false);
}else{
String name =file.getName();
if(!name.endsWith(".m")){
runScriptItem.setEnabled(false);
}
}
menu.show ( fileTree, e.getX(), e.getY() );
}
if(pathBounds == null) {
JPopupMenu menu = new JPopupMenu ();
newFolderItem = new JMenuItem(bundle.getString("fileTreeNewFolderItem.text"));
File file = (File) fileTree.getModel().getRoot();
menu.add(newFolderItem);
menu.add(refreshItem);
fileTreeNewFolderItemActionPerformed(file);
fileTreeRefreshItemActionPerformed(file);
menu.show ( fileTree, e.getX(), e.getY() );
}
}
}
});
}
/**
* Handle key events like F5,DELETE etc.
* @param e
*/
private void addGlobalAction(MouseEvent e) {
TreePath path2 = fileTree.getPathForLocation ( e.getX (), e.getY () );
Rectangle pathBounds2 = fileTree.getUI ().getPathBounds ( fileTree, path2 );
if ( pathBounds2 != null && pathBounds2.contains ( e.getX (), e.getY () ) ){
TreePath[] selectionPaths = fileTree.getSelectionModel().getSelectionPaths();
openAction = new OpenAction((File)fileTree.getLastSelectedPathComponent());
refreshAction = new RefreshAction((File)fileTree.getLastSelectedPathComponent());
deleteAction = new DeleteAction(selectionPaths);
InputMap inputMap = getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = getRootPane().getActionMap();
inputMap.put(keyDeleteItem, "DeleteAction");
actionMap.put("DeleteAction", deleteAction);
inputMap.put(keyOpenItem, "OpenAction");
actionMap.put("OpenAction", openAction);
inputMap.put(keyRefreshItem, "RefreshAction");
actionMap.put("RefreshAction", refreshAction);
}
}
/**
* Action for creating a new folder.
* @param file
*/
private void fileTreeNewFolderItemActionPerformed(final File file) {
newFolderItem.addActionListener((ActionEvent e) -> {
createUniqueFolder(file);
});
}
/**
* Create an unique folder.
* @see fileTreeNewFolderItemActionPerformed
* @param file
*/
private void createUniqueFolder(File file) {
String name ="New Folder";
File dir = new File(file.getAbsolutePath()+File.separator+name);
if(!dir.exists()) {
dir.mkdir();
updateFileTree(file);
int indexOfChild = fileSystemModel.getIndexOfChild(file, dir);
fileTree.startEditingAtPath(fileTree.getPathForRow(indexOfChild+1));
}else{
File unique=null;
int n=1;
while(unique == null) {
File d = new File(file.getAbsolutePath()+File.separator+name+"("+n+")");
if(!d.exists()) {
d.mkdir();
updateFileTree(file);
int indexOfChild = fileSystemModel.getIndexOfChild(file, d);
fileTree.startEditingAtPath(fileTree.getPathForRow(indexOfChild+1));
unique = d;
}
n++;
}
}
}
/**
* Action for refreshing File Tree.
* @param file
*/
private void fileTreeRefreshItemActionPerformed(final File file) {
refreshItem.addActionListener((ActionEvent e) -> {
updateFileTree(file);
});
}
/**
* Action for deleting selected contents from File Tree.
* @param selectionPaths
*/
private void fileTreeDeleteItemActionPerformed(final TreePath[] selectionPaths) {
deleteItem.addActionListener((ActionEvent e) -> {
confirmDeletion(selectionPaths);
});
}
/**
* Confirm removal of contents from File Tree.
* @param selectionPaths
*/
private void confirmDeletion(final TreePath[] selectionPaths) {
final Delete confirmDialog = new Delete(frame);
JButton yes = new JButton("Yes,Delete");
JButton no = new JButton("No");
confirmDialog.setSecondTitle("Are you sure to delete selected contents?");
yes.addActionListener(new ActionListener() {
Object[] path1;
String s="";
@Override
public void actionPerformed(ActionEvent e) {
for (TreePath path : selectionPaths) {
path1 = path.getPath();
for (Object path11 : path1) {
s += path11 + File.separator;
}
delete(new File(s));
s="";
}
confirmDialog.dispose();
}
});
no.addActionListener((ActionEvent e) -> {
confirmDialog.dispose();
});
confirmDialog.addButton(yes);
confirmDialog.addButton(no);
confirmDialog.setVisible(true);
}
/**
* Delete a file or folder.
* @param file
*/
private void delete(File file) {
try {
if(file.isDirectory()){
File f = file;
FileUtils.deleteDirectory(file);
updateFileTree(f.getParentFile());
}else{
File f = file;
file.delete();
updateFileTree(f.getParentFile());
}
} catch (IOException ex) {
}
}
/**
* Action for renaming selected content.
* @param selectionPath
*/
private void fileTreeRenameItemActionPerformed(final TreePath selectionPath) {
renameItem.addActionListener((ActionEvent e) -> {
fileTree.startEditingAtPath(selectionPath);
});
}
/**
* Action for opening selected contents using external application.
* @param file
*/
private void fileTreeOpenOutsideItemActionPerformed(final File file) {
openOutsideItem.addActionListener((ActionEvent e) -> {
openOutSide(file);
});
}
/**
* Opening selected contents using external application.
* @param file
*/
private void openOutSide(File file) {
try {
URI uri = Paths.get(file.getAbsolutePath()).toUri();
Desktop desktop=Desktop.getDesktop();
desktop.browse(uri);
} catch (Exception ex) {
JOptionPane.showMessageDialog(frame, ex.getMessage(),"Error", JOptionPane.ERROR_MESSAGE);
}
}
/**
* Action for running script.
* @param file
*/
private void fileTreeRunItemActionPerformed(final File file) {
runScriptItem.addActionListener((ActionEvent e) -> {
if(!file.isDirectory()) {
MainFrame.octavePanel.evalWithOutput("run('"+file.getAbsolutePath()+"');");
}
});
}
/**
* Action for opening selected file.
* @param selectionPaths
*/
private void fileTreeOpenItemActionPerformed( final File selectionPaths) {
openItem.addActionListener((ActionEvent e) -> {
selectFile(selectionPaths);
});
}
/**
* Construct an Action for handling the key event DELETE.
*/
class DeleteAction extends AbstractAction{
private static final long serialVersionUID = 50659681002151117L;
private final TreePath[] selectionPaths;
public DeleteAction(TreePath[] selectionPaths) {
this.selectionPaths=selectionPaths;
}
@Override
public void actionPerformed(ActionEvent e) {
if(fileTree.hasFocus()){
confirmDeletion(selectionPaths);
}else{
MainFrame.workspace.delete();
}
}
}
/**
* Construct an Action for handling open.
*/
class OpenAction extends AbstractAction{
private static final long serialVersionUID = -1569844444197285872L;
private final File selectionPaths;
public OpenAction(File selectionPaths) {
this.selectionPaths=selectionPaths;
}
@Override
public void actionPerformed(ActionEvent e) {
if(fileTree.hasFocus()){
selectFile(selectionPaths);
}else if(MainFrame.workspace.hasTableFocus()) {
MainFrame.reloadWorkspace();
}
}
}
/**
* Construct an Action for handling refresh.
*/
class RefreshAction extends AbstractAction{
private static final long serialVersionUID = 3005626166420504982L;
private final File selectionPaths;
public RefreshAction(File selectionPaths) {
this.selectionPaths=selectionPaths;
}
@Override
public void actionPerformed(ActionEvent e) {
if(fileTree.hasFocus()){
updateFileTree((File)fileTree.getModel().getRoot());
}else if(MainFrame.workspace.hasTableFocus()) {
MainFrame.reloadWorkspace();
}
}
}
/**
* Construct a File system according to a file.
*/
class FileSystemModel implements TreeModel {
private final File root;
private final Vector listeners = new Vector();
/**
* Construct a File System with rootDirectory.
* @param rootDirectory
*/
public FileSystemModel(File rootDirectory) {
root = rootDirectory;
}
/**
* Returns root of the File System.
* @return root of the File System.
*/
@Override
public Object getRoot() {
return root;
}
/**
* Get a child from File System.
* @param parent
* @param index
* @return
*/
@Override
public Object getChild(Object parent, int index) {
File directory = (File) parent;
String[] children = directory.list();
return new FileSystemModel.TreeFile(directory, children[index]);
}
/**
* Get total number of children of File System.
* @param parent
* @return
*/
@Override
public int getChildCount(Object parent) {
File file = (File) parent;
if (file.isDirectory()) {
String[] fileList = file.list();
if (fileList != null) {
return file.list().length;
}
}
return 0;
}
/**
* Returns true if <code>node</code> is file.
* @param node
* @return
*/
@Override
public boolean isLeaf(Object node) {
File file = (File) node;
return file.isFile();
}
/**
* Get index of a child.
* @param parent
* @param child
* @return
*/
@Override
public int getIndexOfChild(Object parent, Object child) {
File directory = (File) parent;
File file = (File) child;
String[] children = directory.list();
for (int i = 0; i < children.length; i++) {
if (file.getName().equals(children[i])) {
return i;
}
}
return -1;
}
/**
* Handle File System event.
* @param path
* @param value
*/
@Override
public void valueForPathChanged(TreePath path, Object value) {
File oldFile = (File) path.getLastPathComponent();
String fileParentPath = oldFile.getParent();
String newFileName = (String) value;
File targetFile = new File(fileParentPath, newFileName);
oldFile.renameTo(targetFile);
File parent = new File(fileParentPath);
int[] changedChildrenIndices = { getIndexOfChild(parent, targetFile) };
Object[] changedChildren = { targetFile };
fireTreeNodesChanged(path.getParentPath(), changedChildrenIndices, changedChildren);
}
/**
* Handle node change.
* @param parentPath
* @param indices
* @param children
*/
private void fireTreeNodesChanged(TreePath parentPath, int[] indices, Object[] children) {
TreeModelEvent event = new TreeModelEvent(this, parentPath, indices, children);
Iterator iterator = listeners.iterator();
TreeModelListener listener = null;
while (iterator.hasNext()) {
listener = (TreeModelListener) iterator.next();
listener.treeNodesChanged(event);
}
}
/**
* Add Tree model Listener.
* @param listener
*/
@Override
public void addTreeModelListener(TreeModelListener listener) {
listeners.add(listener);
}
/**
* Remove Tree model Listener.
* @param listener
*/
@Override
public void removeTreeModelListener(TreeModelListener listener) {
listeners.remove(listener);
}
class TreeFile extends File {
private static final long serialVersionUID = 4327413058602216070L;
public TreeFile(File parent, String child) {
super(parent, child);
}
@Override
public String toString() {
return getName();
}
}
}
/**
* Create Cell Render for File System.
* It changes the UI of default File Tree.
*/
class FileTreeCellRenderer extends DefaultTreeCellRenderer {
private static final long serialVersionUID = -8588733460532093956L;
private final FileSystemView fsv = FileSystemView.getFileSystemView();
private final Map<String, Icon> iconCache = new HashMap<>();
@Override
public Component getTreeCellRendererComponent(JTree tree, Object value,
boolean sel, boolean expanded, boolean leaf, int row,boolean hasFocus) {
File file = (File) value;
String filename = "";
if (file != null) {
filename = fsv.getSystemDisplayName(file);
}else{
Logger.getLogger(FileTreeCellRenderer.class.getName());
}
// each node is a JLabel.
JLabel result = (JLabel) super.getTreeCellRendererComponent(tree,
filename, sel, expanded, leaf, row, hasFocus);
if (file != null) {
Icon icon = this.iconCache.get(filename);
if (icon == null) {
// FIX ME: same icon for same named file and folder.
icon = fsv.getSystemIcon(file);
this.iconCache.put(filename, icon);
}
result.setIcon(icon);
}
return result;
}
}
}