package net.filebot.ui.filter; import static java.util.Collections.*; import static java.util.stream.Collectors.*; import static net.filebot.util.ui.SwingUI.*; import java.awt.event.ActionEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.io.File; import java.util.Collection; import java.util.Enumeration; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import javax.swing.AbstractAction; import javax.swing.JMenu; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; import javax.swing.JTree; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; import net.filebot.ResourceManager; import net.filebot.UserFiles; import net.filebot.ui.PanelBuilder; import net.filebot.ui.transfer.FileTransferable; import net.filebot.util.FilterIterator; import net.filebot.util.TreeIterator; import net.filebot.util.ui.SwingEventBus; public class FileTree extends JTree { public FileTree() { super(new DefaultTreeModel(new FolderNode())); getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION); setCellRenderer(new FileTreeCellRenderer()); setShowsRootHandles(true); setRootVisible(false); setRowHeight(22); setLargeModel(true); addMouseListener(new ExpandCollapsePopupListener()); } @Override public DefaultTreeModel getModel() { return (DefaultTreeModel) super.getModel(); } public List<File> getRoot() { FolderNode model = (FolderNode) getModel().getRoot(); return model.getChildren().stream().map(node -> { if (node instanceof FolderNode) { FolderNode folder = (FolderNode) node; return folder.getFile(); } if (node instanceof FileNode) { FileNode file = (FileNode) node; return file.getFile(); } return null; }).collect(toList()); } public void clear() { getModel().setRoot(new FolderNode()); getModel().reload(); } public void expandAll() { for (int row = 0; row < getRowCount(); row++) { expandRow(row); } } public void collapseAll() { for (int row = 0; row < getRowCount(); row++) { collapseRow(row); } } private class OpenExpandCollapsePopup extends JPopupMenu { public OpenExpandCollapsePopup() { Collection<File> selectedFiles = getFiles(getSelectionPaths()); if (selectedFiles != null && !selectedFiles.isEmpty()) { JMenu menu = new JMenu("Send to"); for (PanelBuilder panel : PanelBuilder.fileHandlerSequence()) { menu.add(new JMenuItem(new ImportAction(panel, selectedFiles))); } add(menu); addSeparator(); } if (selectedFiles.size() > 0) { add(new JMenuItem(new RevealAction("Reveal", selectedFiles))); add(new RevealAction("Reveal Folder", selectedFiles.stream().map(File::getParentFile).distinct().collect(toList()))); addSeparator(); } add(newAction("Expand all", ResourceManager.getIcon("tree.expand"), evt -> expandAll())); add(newAction("Collapse all", ResourceManager.getIcon("tree.collapse"), evt -> collapseAll())); } private Collection<File> getFiles(TreePath[] selection) { if (selection == null || selection.length == 0) { return emptySet(); } Set<File> files = new LinkedHashSet<File>(); for (TreePath path : selection) { collectFiles(path.getLastPathComponent(), files); } return files; } private void collectFiles(Object node, Collection<File> files) { if (node instanceof FileNode) { files.add(((FileNode) node).getFile()); } else if (node instanceof FolderNode) { for (Object it : ((FolderNode) node).getChildren()) { collectFiles(it, files); } } } private class RevealAction extends AbstractAction { private Collection<File> files; public RevealAction(String text, Collection<File> files) { super(text); this.files = files; } @Override public void actionPerformed(ActionEvent event) { UserFiles.revealFiles(files); } } private class ImportAction extends AbstractAction { private PanelBuilder panel; private Collection<File> files; public ImportAction(PanelBuilder panel, Collection<File> files) { super(panel.getName(), panel.getIcon()); this.panel = panel; this.files = files; } @Override public void actionPerformed(ActionEvent event) { // switch to panel SwingEventBus.getInstance().post(panel); // load files invokeLater(200, () -> SwingEventBus.getInstance().post(new FileTransferable(files))); } } } private class ExpandCollapsePopupListener extends MouseAdapter { @Override public void mousePressed(MouseEvent e) { maybeShowPopup(e); } @Override public void mouseReleased(MouseEvent e) { maybeShowPopup(e); } private void maybeShowPopup(MouseEvent e) { if (e.isPopupTrigger()) { TreePath path = getPathForLocation(e.getX(), e.getY()); if (!getSelectionModel().isPathSelected(path)) { // if clicked node is not selected, set selection to this node (and deselect all other currently selected nodes) setSelectionPath(path); } OpenExpandCollapsePopup popup = new OpenExpandCollapsePopup(); popup.show(e.getComponent(), e.getX(), e.getY()); } } } public static class AbstractTreeNode implements TreeNode { private TreeNode parent; @Override public TreeNode getParent() { return parent; } public void setParent(TreeNode parent) { this.parent = parent; } @Override public Enumeration<? extends TreeNode> children() { return null; } @Override public boolean getAllowsChildren() { return false; } @Override public TreeNode getChildAt(int childIndex) { return null; } @Override public int getChildCount() { return 0; } @Override public int getIndex(TreeNode node) { return -1; } @Override public boolean isLeaf() { // if we have no children, tell the UI we are a leaf, // so that it won't display any good-for-nothing expand buttons return getChildCount() == 0; } } public static class FileNode extends AbstractTreeNode { private final File file; public FileNode(File file) { this.file = file; } public File getFile() { return file; } @Override public String toString() { return file.getName(); } } public static class FolderNode extends AbstractTreeNode { private final File file; private final String title; private final List<TreeNode> children; public FolderNode() { this(emptyList()); // empty root node } public FolderNode(List<TreeNode> children) { this(null, "/", children); // root node } public FolderNode(String title, List<TreeNode> children) { this(null, title, children); // virtual node } public FolderNode(File file, String title, List<TreeNode> children) { this.file = file; this.title = title; this.children = children; } public File getFile() { return file; } @Override public String toString() { return title; } public List<TreeNode> getChildren() { return children; } @Override public Enumeration<? extends TreeNode> children() { return enumeration(children); } @Override public boolean getAllowsChildren() { return true; } @Override public TreeNode getChildAt(int childIndex) { return children.get(childIndex); } @Override public int getChildCount() { return children.size(); } @Override public int getIndex(TreeNode node) { return children.indexOf(node); } public Iterator<TreeNode> treeIterator() { return new TreeIterator<TreeNode>(this) { @Override protected Iterator<TreeNode> children(TreeNode node) { if (node instanceof FolderNode) { return ((FolderNode) node).getChildren().iterator(); } // can't step into non-folder nodes return null; } }; } public Iterator<File> fileIterator() { return new FilterIterator<TreeNode, File>(treeIterator()) { @Override protected File filter(TreeNode node) { if (node instanceof FileNode) { return ((FileNode) node).getFile(); } // filter out non-file nodes return null; } }; } } }