/* * 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; } } }