diff --git a/source/net/sourceforge/filebot/ui/FileBotTree.java b/source/net/sourceforge/filebot/ui/FileBotTree.java deleted file mode 100644 index f733b230..00000000 --- a/source/net/sourceforge/filebot/ui/FileBotTree.java +++ /dev/null @@ -1,241 +0,0 @@ - -package net.sourceforge.filebot.ui; - - -import java.awt.Desktop; -import java.awt.Font; -import java.awt.event.ActionEvent; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.io.File; -import java.util.ArrayList; -import java.util.Enumeration; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; - -import javax.swing.AbstractAction; -import javax.swing.JMenuItem; -import javax.swing.JPopupMenu; -import javax.swing.JTree; -import javax.swing.SwingUtilities; -import javax.swing.tree.DefaultMutableTreeNode; -import javax.swing.tree.DefaultTreeModel; -import javax.swing.tree.TreeNode; -import javax.swing.tree.TreePath; -import javax.swing.tree.TreeSelectionModel; - -import net.sourceforge.filebot.ResourceManager; - - -public class FileBotTree extends JTree { - - public FileBotTree() { - super(new DefaultTreeModel(new DefaultMutableTreeNode())); - getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION); - setCellRenderer(new FileBotTreeCellRenderer()); - setShowsRootHandles(true); - setRootVisible(false); - setRowHeight(22); - - addMouseListener(new ExpandCollapsePopupListener()); - } - - - @Override - public DefaultTreeModel getModel() { - return (DefaultTreeModel) super.getModel(); - } - - - public void clear() { - DefaultMutableTreeNode root = (DefaultMutableTreeNode) getModel().getRoot(); - root.removeAllChildren(); - - getModel().reload(root); - } - - - public List convertToList() { - TreeNode node = (TreeNode) getModel().getRoot(); - - return convertToList(node); - } - - - public List convertToList(TreeNode node) { - ArrayList list = new ArrayList(); - - convertToListImpl((DefaultMutableTreeNode) node, list); - - return list; - } - - - private void convertToListImpl(DefaultMutableTreeNode node, List list) { - if (node.isLeaf()) { - if (node.getUserObject() instanceof File) { - File file = (File) node.getUserObject(); - - if (file.isFile()) - list.add(file); - } - } else { - for (int i = 0; i < node.getChildCount(); i++) { - DefaultMutableTreeNode child = (DefaultMutableTreeNode) node.getChildAt(i); - convertToListImpl(child, list); - } - } - } - - - @Override - public String convertValueToText(Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { - if (value instanceof DefaultMutableTreeNode) { - DefaultMutableTreeNode node = (DefaultMutableTreeNode) value; - - Object userObject = node.getUserObject(); - - if ((userObject != null) && (userObject instanceof File)) { - File file = (File) node.getUserObject(); - return file.getName(); - } - } - - return value.toString(); - } - - - public void expandOrCollapseAll(boolean expand) { - TreeNode node = (TreeNode) getModel().getRoot(); - Enumeration e = node.children(); - - while (e.hasMoreElements()) { - TreeNode n = (TreeNode) e.nextElement(); - TreePath path = new TreePath(node).pathByAddingChild(n); - expandOrCollapseAllImpl(path, expand); - } - } - - - private void expandOrCollapseAllImpl(TreePath parent, boolean expand) { - // Traverse children - TreeNode node = (TreeNode) parent.getLastPathComponent(); - if (node.getChildCount() >= 0) - for (Enumeration e = node.children(); e.hasMoreElements();) { - TreeNode n = (TreeNode) e.nextElement(); - TreePath path = parent.pathByAddingChild(n); - expandOrCollapseAllImpl(path, expand); - } - - // Expansion or collapse must be done bottom-up - if (expand) - expandPath(parent); - else - collapsePath(parent); - } - - - private class ExpandCollapsePopup extends JPopupMenu { - - public ExpandCollapsePopup() { - if (getSelectionCount() >= 1) { - Object pathComponent = getLastSelectedPathComponent(); - - if (pathComponent instanceof DefaultMutableTreeNode) { - DefaultMutableTreeNode node = (DefaultMutableTreeNode) pathComponent; - - Object userObject = node.getUserObject(); - - if (userObject instanceof File) { - File file = (File) userObject; - - if (file.isFile()) { - JMenuItem openItem = new JMenuItem(new OpenAction("Open", file)); - openItem.setFont(openItem.getFont().deriveFont(Font.BOLD)); - - add(openItem); - add(new OpenAction("Open Folder", file.getParentFile())); - addSeparator(); - } - } - } - } - - add(new ExpandOrCollapseAction(true)); - add(new ExpandOrCollapseAction(false)); - } - - - private class OpenAction extends AbstractAction { - - private File file; - - - public OpenAction(String text, File file) { - super(text); - this.file = file; - - if (file == null) - setEnabled(false); - } - - - public void actionPerformed(ActionEvent event) { - try { - Desktop.getDesktop().open(file); - } catch (Exception e) { - MessageManager.showWarning(e.getMessage()); - Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.WARNING, e.getMessage()); - } - } - } - - - private class ExpandOrCollapseAction extends AbstractAction { - - private boolean expand; - - - public ExpandOrCollapseAction(boolean expand) { - this.expand = expand; - - if (expand) { - putValue(SMALL_ICON, ResourceManager.getIcon("tree.expand")); - putValue(NAME, "Expand all"); - } else { - putValue(SMALL_ICON, ResourceManager.getIcon("tree.collapse")); - putValue(NAME, "Collapse all"); - } - } - - - public void actionPerformed(ActionEvent e) { - expandOrCollapseAll(expand); - } - } - - } - - - private class ExpandCollapsePopupListener extends MouseAdapter { - - @Override - public void mouseReleased(MouseEvent e) { - if (SwingUtilities.isRightMouseButton(e)) { - ExpandCollapsePopup popup = new ExpandCollapsePopup(); - popup.show(e.getComponent(), e.getX(), e.getY()); - } - } - - - @Override - public void mousePressed(MouseEvent e) { - if (SwingUtilities.isRightMouseButton(e)) { - TreePath path = getPathForLocation(e.getX(), e.getY()); - setSelectionPath(path); - } - } - } - -} diff --git a/source/net/sourceforge/filebot/ui/panel/analyze/AnalyzePanel.java b/source/net/sourceforge/filebot/ui/panel/analyze/AnalyzePanel.java index f8aca1ea..8dcc5c2c 100644 --- a/source/net/sourceforge/filebot/ui/panel/analyze/AnalyzePanel.java +++ b/source/net/sourceforge/filebot/ui/panel/analyze/AnalyzePanel.java @@ -4,8 +4,6 @@ package net.sourceforge.filebot.ui.panel.analyze; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; -import java.io.File; -import java.util.List; import javax.swing.BorderFactory; import javax.swing.JTabbedPane; @@ -15,9 +13,6 @@ import net.miginfocom.swing.MigLayout; import net.sourceforge.filebot.ResourceManager; import net.sourceforge.filebot.ui.FileBotPanel; import net.sourceforge.filebot.ui.FileTransferableMessageHandler; -import net.sourceforge.filebot.ui.panel.analyze.tools.SplitPanel; -import net.sourceforge.filebot.ui.panel.analyze.tools.ToolPanel; -import net.sourceforge.filebot.ui.panel.analyze.tools.TypePanel; import net.sourceforge.tuned.MessageHandler; @@ -26,7 +21,7 @@ public class AnalyzePanel extends FileBotPanel { private final FileTreePanel fileTreePanel = new FileTreePanel(); private final JTabbedPane toolsPanel = new JTabbedPane(SwingConstants.TOP, JTabbedPane.SCROLL_TAB_LAYOUT); - private final MessageHandler messageHandler = new FileTransferableMessageHandler(this, fileTreePanel.getFileTree().getTransferablePolicy()); + private final MessageHandler messageHandler = new FileTransferableMessageHandler(this, fileTreePanel.getTransferablePolicy()); public AnalyzePanel() { @@ -36,13 +31,18 @@ public class AnalyzePanel extends FileBotPanel { setLayout(new MigLayout("insets 0, gapx 50, fill")); - add(fileTreePanel, "grow"); - add(toolsPanel, "grow"); + add(fileTreePanel, "grow, sizegroup column"); + add(toolsPanel, "grow, sizegroup column"); - addTool(new TypePanel()); - addTool(new SplitPanel()); + addTool(new TypeTool()); + addTool(new SplitTool()); - fileTreePanel.getFileTree().addPropertyChangeListener(FileTree.CONTENT_PROPERTY, fileTreeChangeListener); + fileTreePanel.addPropertyChangeListener("filetree", filetreeListener); + } + + + private void addTool(Tool tool) { + toolsPanel.addTab(tool.getName(), tool); } @@ -51,20 +51,13 @@ public class AnalyzePanel extends FileBotPanel { return messageHandler; } - - private void addTool(ToolPanel toolPanel) { - toolsPanel.addTab(toolPanel.getToolName(), toolPanel); - } - - private PropertyChangeListener fileTreeChangeListener = new PropertyChangeListener() { + private final PropertyChangeListener filetreeListener = new PropertyChangeListener() { - @SuppressWarnings("unchecked") public void propertyChange(PropertyChangeEvent evt) { - List files = (List) evt.getNewValue(); - + // stopped loading, refresh tools for (int i = 0; i < toolsPanel.getTabCount(); i++) { - ToolPanel toolPanel = (ToolPanel) toolsPanel.getComponentAt(i); - toolPanel.update(files); + Tool tool = (Tool) toolsPanel.getComponentAt(i); + tool.setSourceModel(fileTreePanel.getFileTree().getRoot()); } } }; diff --git a/source/net/sourceforge/filebot/ui/panel/analyze/FileTree.java b/source/net/sourceforge/filebot/ui/panel/analyze/FileTree.java index aefd0db5..e4f2c749 100644 --- a/source/net/sourceforge/filebot/ui/panel/analyze/FileTree.java +++ b/source/net/sourceforge/filebot/ui/panel/analyze/FileTree.java @@ -2,122 +2,422 @@ package net.sourceforge.filebot.ui.panel.analyze; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; +import java.awt.Desktop; +import java.awt.Font; +import java.awt.event.ActionEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; import java.io.File; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; -import javax.swing.SwingWorker; -import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.AbstractAction; +import javax.swing.Action; +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.sourceforge.filebot.ui.FileBotTree; -import net.sourceforge.filebot.ui.transfer.DefaultTransferHandler; -import net.sourceforge.filebot.ui.transfer.FileTransferablePolicy; +import net.sourceforge.filebot.ResourceManager; +import net.sourceforge.filebot.ui.MessageManager; +import net.sourceforge.tuned.FilterIterator; +import net.sourceforge.tuned.TreeIterator; -class FileTree extends FileBotTree { - - public static final String LOADING_PROPERTY = "loading"; - public static final String CONTENT_PROPERTY = "content"; - - private PostProcessor postProcessor; - - private final FileTreeTransferablePolicy transferablePolicy; - +public class FileTree extends JTree { public FileTree() { - transferablePolicy = new FileTreeTransferablePolicy(this); - transferablePolicy.addPropertyChangeListener(LOADING_PROPERTY, new LoadingPropertyChangeListener()); + super(new DefaultTreeModel(new FolderNode())); + getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION); + setCellRenderer(new FileTreeCellRenderer()); + setShowsRootHandles(true); + setRootVisible(false); - setTransferHandler(new DefaultTransferHandler(transferablePolicy, null)); - } - - - public FileTransferablePolicy getTransferablePolicy() { - return transferablePolicy; - } - - - public void removeTreeItems(TreePath paths[]) { - List changedNodes = new ArrayList(paths.length); + setRowHeight(22); + setLargeModel(true); - for (TreePath element : paths) { - DefaultMutableTreeNode node = (DefaultMutableTreeNode) (element.getLastPathComponent()); - - if (!node.isRoot()) { - changedNodes.add(node.getParent()); - node.removeFromParent(); - } - } - - for (TreeNode treeNode : changedNodes) { - getModel().reload(treeNode); - } - - contentChanged(); + addMouseListener(new ExpandCollapsePopupListener()); } @Override + public DefaultTreeModel getModel() { + return (DefaultTreeModel) super.getModel(); + } + + + public FolderNode getRoot() { + return (FolderNode) getModel().getRoot(); + } + + public void clear() { - transferablePolicy.reset(); - - super.clear(); - contentChanged(); + getRoot().clear(); + getModel().reload(); } - private synchronized void contentChanged() { - if (postProcessor != null) - postProcessor.cancel(true); + public void removeTreeNode(TreePath... paths) { + Set dirtyNodes = new HashSet(); - postProcessor = new PostProcessor(); - postProcessor.execute(); - }; + for (TreePath path : paths) { + AbstractTreeNode node = (AbstractTreeNode) (path.getLastPathComponent()); + + FolderNode parent = (FolderNode) node.getParent(); + if (parent != null) { + parent.remove(node); + dirtyNodes.add(parent); + } + } + + for (TreeNode dirtyNode : dirtyNodes) { + getModel().reload(dirtyNode); + } + } + + + 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 LoadingPropertyChangeListener implements PropertyChangeListener { + private class OpenExpandCollapsePopup extends JPopupMenu { + + public OpenExpandCollapsePopup() { + TreePath[] selectionPaths = getSelectionPaths(); + Set selectionFiles = null; + + if (selectionPaths != null) { + selectionFiles = new LinkedHashSet(selectionPaths.length); + + for (TreePath treePath : selectionPaths) { + Object node = treePath.getLastPathComponent(); + + if (node instanceof FileNode) { + selectionFiles.add(((FileNode) node).getFile()); + } + } + } + + if (selectionFiles != null && !selectionFiles.isEmpty()) { + JMenuItem openItem = new JMenuItem(new OpenAction("Open", selectionFiles)); + openItem.setFont(openItem.getFont().deriveFont(Font.BOLD)); + add(openItem); + + Set selectionParentFolders = new LinkedHashSet(selectionFiles.size()); + for (File file : selectionFiles) { + selectionParentFolders.add(file.getParentFile()); + } + + add(new OpenAction("Open Folder", selectionParentFolders)); + addSeparator(); + } + + add(expandAction); + add(collapseAction); + } + + + private class OpenAction extends AbstractAction { + + public OpenAction(String text, File... files) { + this(text, Arrays.asList(files)); + } + + + public OpenAction(String text, Collection files) { + super(text); + putValue("files", files); + } + + + @SuppressWarnings("unchecked") + public void actionPerformed(ActionEvent event) { + try { + for (File file : (Collection) getValue("files")) { + Desktop.getDesktop().open(file); + } + } catch (Exception e) { + MessageManager.showWarning(e.getMessage()); + Logger.getLogger("global").log(Level.SEVERE, e.getMessage(), e); + } + } + } + + private final Action expandAction = new AbstractAction("Expand all", ResourceManager.getIcon("tree.expand")) { + + @Override + public void actionPerformed(ActionEvent e) { + expandAll(); + } + + }; + + private final Action collapseAction = new AbstractAction("Collapse all", ResourceManager.getIcon("tree.collapse")) { + + @Override + public void actionPerformed(ActionEvent e) { + collapseAll(); + } + + }; + + } + + + private class ExpandCollapsePopupListener extends MouseAdapter { @Override - public void propertyChange(PropertyChangeEvent evt) { - Boolean loading = (Boolean) evt.getNewValue(); - - firePropertyChange(FileTree.LOADING_PROPERTY, null, loading); - - if (!loading) { - getModel().reload(); - contentChanged(); + 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()); } } } - private class PostProcessor extends SwingWorker, Void> { + public static class AbstractTreeNode implements TreeNode { + + private TreeNode parent; + @Override - protected List doInBackground() throws Exception { - return convertToList(); + public TreeNode getParent() { + return parent; + } + + + public void setParent(TreeNode parent) { + this.parent = parent; } @Override - protected void done() { - if (isCancelled()) - return; - - try { - List files = get(); - FileTree.this.firePropertyChange(CONTENT_PROPERTY, null, files); - } catch (Exception e) { - Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, e.toString(), e); + public Enumeration 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 List children; + private String title; + + + /** + * Creates a root node (no parent, no title, empty list of children) + */ + public FolderNode() { + this(null, 5); + } + + + public FolderNode(String title, int initialCapacity) { + this.title = title; + this.children = new ArrayList(initialCapacity); + } + + + public void setTitle(String title) { + this.title = title; + } + + + @Override + public String toString() { + return title; + } + + + public List getChildren() { + return Collections.unmodifiableList(children); + } + + + public void add(AbstractTreeNode node) { + if (children.add(node)) { + // node added, set parent + node.setParent(this); } } + + public void remove(AbstractTreeNode node) { + if (children.remove(node)) { + // node removed, reset parent + node.setParent(null); + } + } + + + public void clear() { + // reset parent of all children + for (AbstractTreeNode node : children) { + node.setParent(null); + } + + // clear children + children.clear(); + } + + + @Override + public Enumeration children() { + return Collections.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 treeIterator() { + return new TreeIterator(this) { + + @Override + protected Iterator children(AbstractTreeNode node) { + if (node instanceof FolderNode) + return ((FolderNode) node).getChildren().iterator(); + + // can't step into non-folder nodes + return null; + } + + }; + } + + + public Iterator fileIterator() { + return new FilterIterator(treeIterator()) { + + @Override + protected File filter(AbstractTreeNode node) { + if (node instanceof FileNode) + return ((FileNode) node).getFile(); + + // filter out non-file nodes + return null; + } + }; + } } } diff --git a/source/net/sourceforge/filebot/ui/FileBotTreeCellRenderer.java b/source/net/sourceforge/filebot/ui/panel/analyze/FileTreeCellRenderer.java similarity index 52% rename from source/net/sourceforge/filebot/ui/FileBotTreeCellRenderer.java rename to source/net/sourceforge/filebot/ui/panel/analyze/FileTreeCellRenderer.java index 04923f73..e0343e42 100644 --- a/source/net/sourceforge/filebot/ui/FileBotTreeCellRenderer.java +++ b/source/net/sourceforge/filebot/ui/panel/analyze/FileTreeCellRenderer.java @@ -1,22 +1,22 @@ -package net.sourceforge.filebot.ui; +package net.sourceforge.filebot.ui.panel.analyze; import java.awt.Component; -import java.io.File; import javax.swing.JTree; -import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.TreeNode; import net.sourceforge.filebot.ResourceManager; import net.sourceforge.tuned.ui.FancyTreeCellRenderer; import net.sourceforge.tuned.ui.GradientStyle; -public class FileBotTreeCellRenderer extends FancyTreeCellRenderer { +public class FileTreeCellRenderer extends FancyTreeCellRenderer { - public FileBotTreeCellRenderer() { + public FileTreeCellRenderer() { super(GradientStyle.TOP_TO_BOTTOM); + openIcon = ResourceManager.getIcon("tree.open"); closedIcon = ResourceManager.getIcon("tree.closed"); leafIcon = ResourceManager.getIcon("tree.leaf"); @@ -25,23 +25,22 @@ public class FileBotTreeCellRenderer extends FancyTreeCellRenderer { @Override public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) { - if (leaf && isFolder(value)) - super.getTreeCellRendererComponent(tree, value, sel, true, false, row, hasFocus); - else - super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus); + if (leaf && isFolder(value)) { + // make leafs that are empty folders look like expanded nodes + expanded = true; + leaf = false; + } + + super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus); return this; } private boolean isFolder(Object value) { - DefaultMutableTreeNode node = (DefaultMutableTreeNode) value; - Object object = node.getUserObject(); - - if (object instanceof File) - return ((File) object).isDirectory(); + if (((TreeNode) value).getAllowsChildren()) + return true; return false; } - } diff --git a/source/net/sourceforge/filebot/ui/panel/analyze/FileTreeExportHandler.java b/source/net/sourceforge/filebot/ui/panel/analyze/FileTreeExportHandler.java new file mode 100644 index 00000000..24092264 --- /dev/null +++ b/source/net/sourceforge/filebot/ui/panel/analyze/FileTreeExportHandler.java @@ -0,0 +1,59 @@ + +package net.sourceforge.filebot.ui.panel.analyze; + + +import java.awt.datatransfer.Transferable; +import java.io.File; +import java.util.Iterator; +import java.util.LinkedHashSet; + +import javax.swing.JComponent; +import javax.swing.TransferHandler; +import javax.swing.tree.TreeNode; +import javax.swing.tree.TreePath; + +import net.sourceforge.filebot.ui.panel.analyze.FileTree.FileNode; +import net.sourceforge.filebot.ui.panel.analyze.FileTree.FolderNode; +import net.sourceforge.filebot.ui.transfer.FileTransferable; +import net.sourceforge.filebot.ui.transfer.TransferableExportHandler; + + +class FileTreeExportHandler implements TransferableExportHandler { + + @Override + public Transferable createTransferable(JComponent c) { + FileTree tree = (FileTree) c; + + LinkedHashSet files = new LinkedHashSet(); + + for (TreePath path : tree.getSelectionPaths()) { + TreeNode node = (TreeNode) path.getLastPathComponent(); + + if (node instanceof FileNode) { + files.add(((FileNode) node).getFile()); + } else if (node instanceof FolderNode) { + for (Iterator iterator = ((FolderNode) node).fileIterator(); iterator.hasNext();) { + files.add(iterator.next()); + } + } + } + + if (!files.isEmpty()) + return new FileTransferable(files); + + return null; + } + + + @Override + public void exportDone(JComponent source, Transferable data, int action) { + + } + + + @Override + public int getSourceActions(JComponent c) { + return TransferHandler.COPY; + } + +} diff --git a/source/net/sourceforge/filebot/ui/panel/analyze/FileTreePanel.java b/source/net/sourceforge/filebot/ui/panel/analyze/FileTreePanel.java index 55f4ed2f..bc3a7f17 100644 --- a/source/net/sourceforge/filebot/ui/panel/analyze/FileTreePanel.java +++ b/source/net/sourceforge/filebot/ui/panel/analyze/FileTreePanel.java @@ -3,35 +3,43 @@ package net.sourceforge.filebot.ui.panel.analyze; import java.awt.event.ActionEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; import javax.swing.AbstractAction; import javax.swing.BorderFactory; import javax.swing.JButton; -import javax.swing.JPanel; +import javax.swing.JComponent; import javax.swing.JScrollPane; import javax.swing.KeyStroke; import net.miginfocom.swing.MigLayout; import net.sourceforge.filebot.ResourceManager; +import net.sourceforge.filebot.ui.transfer.DefaultTransferHandler; import net.sourceforge.filebot.ui.transfer.LoadAction; import net.sourceforge.tuned.ui.LoadingOverlayPane; import net.sourceforge.tuned.ui.TunedUtil; -class FileTreePanel extends JPanel { +class FileTreePanel extends JComponent { private FileTree fileTree = new FileTree(); + private FileTreeTransferablePolicy transferablePolicy = new FileTreeTransferablePolicy(fileTree); + public FileTreePanel() { - super(new MigLayout("insets 0, nogrid, fill", "align center")); + fileTree.setTransferHandler(new DefaultTransferHandler(transferablePolicy, null)); setBorder(BorderFactory.createTitledBorder("File Tree")); - add(new LoadingOverlayPane(new JScrollPane(fileTree), ResourceManager.getIcon("loading")), "grow, wrap 1.2mm"); + setLayout(new MigLayout("insets 0, nogrid, fill", "align center")); + add(new LoadingOverlayPane(new JScrollPane(fileTree), this), "grow, wrap 1.2mm"); add(new JButton(loadAction)); add(new JButton(clearAction), "gap 1.2mm, wrap 1.2mm"); + transferablePolicy.addPropertyChangeListener("loading", loadingListener); + // Shortcut DELETE TunedUtil.putActionForKeystroke(fileTree, KeyStroke.getKeyStroke("pressed DELETE"), removeAction); } @@ -41,12 +49,19 @@ class FileTreePanel extends JPanel { return fileTree; } - private final LoadAction loadAction = new LoadAction(fileTree.getTransferablePolicy()); + + public FileTreeTransferablePolicy getTransferablePolicy() { + return transferablePolicy; + } + + private final LoadAction loadAction = new LoadAction(transferablePolicy); private final AbstractAction clearAction = new AbstractAction("Clear", ResourceManager.getIcon("action.clear")) { public void actionPerformed(ActionEvent e) { + transferablePolicy.reset(); fileTree.clear(); + fireFileTreeChange(); } }; @@ -58,7 +73,7 @@ class FileTreePanel extends JPanel { int row = fileTree.getMinSelectionRow(); - fileTree.removeTreeItems(fileTree.getSelectionPaths()); + fileTree.removeTreeNode(fileTree.getSelectionPaths()); int maxRow = fileTree.getRowCount() - 1; @@ -66,6 +81,27 @@ class FileTreePanel extends JPanel { row = maxRow; fileTree.setSelectionRow(row); + + fireFileTreeChange(); + } + }; + + + private void fireFileTreeChange() { + firePropertyChange("filetree", null, fileTree); + } + + private final PropertyChangeListener loadingListener = new PropertyChangeListener() { + + public void propertyChange(PropertyChangeEvent evt) { + boolean loading = (Boolean) evt.getNewValue(); + + // relay loading property changes for loading overlay + firePropertyChange(evt.getPropertyName(), evt.getOldValue(), evt.getNewValue()); + + if (!loading) { + fireFileTreeChange(); + } } }; diff --git a/source/net/sourceforge/filebot/ui/panel/analyze/FileTreeTransferablePolicy.java b/source/net/sourceforge/filebot/ui/panel/analyze/FileTreeTransferablePolicy.java index 7f418ad6..8cf66de3 100644 --- a/source/net/sourceforge/filebot/ui/panel/analyze/FileTreeTransferablePolicy.java +++ b/source/net/sourceforge/filebot/ui/panel/analyze/FileTreeTransferablePolicy.java @@ -5,14 +5,14 @@ package net.sourceforge.filebot.ui.panel.analyze; import java.io.File; import java.util.List; -import javax.swing.tree.DefaultMutableTreeNode; -import javax.swing.tree.DefaultTreeModel; - -import net.sourceforge.filebot.FileBotUtil; +import net.sourceforge.filebot.ui.panel.analyze.FileTree.AbstractTreeNode; +import net.sourceforge.filebot.ui.panel.analyze.FileTree.FileNode; +import net.sourceforge.filebot.ui.panel.analyze.FileTree.FolderNode; import net.sourceforge.filebot.ui.transfer.BackgroundFileTransferablePolicy; +import net.sourceforge.tuned.FileUtil; -class FileTreeTransferablePolicy extends BackgroundFileTransferablePolicy { +class FileTreeTransferablePolicy extends BackgroundFileTransferablePolicy { private final FileTree tree; @@ -22,6 +22,12 @@ class FileTreeTransferablePolicy extends BackgroundFileTransferablePolicy files) { + return true; + } + + @Override protected void clear() { tree.clear(); @@ -29,48 +35,58 @@ class FileTreeTransferablePolicy extends BackgroundFileTransferablePolicy chunks) { - DefaultTreeModel model = tree.getModel(); - DefaultMutableTreeNode root = (DefaultMutableTreeNode) model.getRoot(); + protected void process(List chunks) { + FolderNode root = tree.getRoot(); - for (DefaultMutableTreeNode node : chunks) { + for (AbstractTreeNode node : chunks) { root.add(node); } - model.reload(root); + tree.getModel().reload(); } @Override protected void load(List files) { - for (File file : files) { - DefaultMutableTreeNode node = getTree(file); - - // operation may be aborted via interrupt - if (Thread.currentThread().isInterrupted()) - return; - - publish(node); + try { + for (File file : files) { + AbstractTreeNode node = getTreeNode(file); + + // publish on EDT + publish(node); + } + } catch (InterruptedException e) { + // supposed to happen if background execution was aborted } } - private DefaultMutableTreeNode getTree(File file) { - DefaultMutableTreeNode node = new DefaultMutableTreeNode(file); + private AbstractTreeNode getTreeNode(File file) throws InterruptedException { + if (Thread.interrupted()) + throw new InterruptedException(); - if (file.isDirectory() && !Thread.currentThread().isInterrupted()) { - // run through folders first - for (File f : file.listFiles(FileBotUtil.FOLDERS_ONLY)) { - node.add(getTree(f)); + if (file.isDirectory()) { + File[] files = file.listFiles(); + + FolderNode node = new FolderNode(FileUtil.getFolderName(file), files.length); + + // add folders first + for (File f : files) { + if (f.isDirectory()) { + node.add(getTreeNode(f)); + } } - // then files - for (File f : file.listFiles(FileBotUtil.FILES_ONLY)) { - node.add(getTree(f)); + for (File f : files) { + if (f.isFile()) { + node.add(getTreeNode(f)); + } } + + return node; } - return node; + return new FileNode(file); } diff --git a/source/net/sourceforge/filebot/ui/panel/analyze/SplitTool.java b/source/net/sourceforge/filebot/ui/panel/analyze/SplitTool.java new file mode 100644 index 00000000..ca114353 --- /dev/null +++ b/source/net/sourceforge/filebot/ui/panel/analyze/SplitTool.java @@ -0,0 +1,131 @@ + +package net.sourceforge.filebot.ui.panel.analyze; + + +import java.awt.Color; +import java.io.File; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import javax.swing.BorderFactory; +import javax.swing.JLabel; +import javax.swing.JScrollPane; +import javax.swing.JSpinner; +import javax.swing.SpinnerNumberModel; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreeModel; + +import net.miginfocom.swing.MigLayout; +import net.sourceforge.filebot.ui.panel.analyze.FileTree.FolderNode; +import net.sourceforge.filebot.ui.transfer.DefaultTransferHandler; +import net.sourceforge.tuned.FileUtil; +import net.sourceforge.tuned.ui.GradientStyle; +import net.sourceforge.tuned.ui.LoadingOverlayPane; +import net.sourceforge.tuned.ui.notification.SeparatorBorder; + + +public class SplitTool extends Tool implements ChangeListener { + + private FileTree tree = new FileTree(); + + private SpinnerNumberModel spinnerModel = new SpinnerNumberModel(4480, 0, Integer.MAX_VALUE, 100); + + + public SplitTool() { + super("Split"); + + JScrollPane treeScrollPane = new JScrollPane(tree); + treeScrollPane.setBorder(BorderFactory.createEmptyBorder()); + + JSpinner spinner = new JSpinner(spinnerModel); + spinner.setEditor(new JSpinner.NumberEditor(spinner, "#")); + + LoadingOverlayPane loadingOverlayPane = new LoadingOverlayPane(treeScrollPane, this); + loadingOverlayPane.setBorder(new SeparatorBorder(2, new Color(0, 0, 0, 90), GradientStyle.TOP_TO_BOTTOM, SeparatorBorder.Position.BOTTOM)); + + setLayout(new MigLayout("insets 0, nogrid, fill", "align center")); + + add(loadingOverlayPane, "grow, wrap"); + + add(new JLabel("Split every")); + add(spinner, "wmax 80, gap top rel, gap bottom unrel"); + add(new JLabel("MB.")); + + tree.setTransferHandler(new DefaultTransferHandler(null, new FileTreeExportHandler())); + tree.setDragEnabled(true); + + spinnerModel.addChangeListener(this); + } + + + private long getSplitSize() { + return spinnerModel.getNumber().intValue() * FileUtil.MEGA; + } + + private FolderNode sourceModel = null; + + + public void stateChanged(ChangeEvent e) { + if (sourceModel != null) + setSourceModel(sourceModel); + } + + + @Override + protected TreeModel createModelInBackground(FolderNode sourceModel, Cancellable cancellable) { + this.sourceModel = sourceModel; + + FolderNode root = new FolderNode(); + int nextPart = 1; + + long splitSize = getSplitSize(); + + List currentPart = new ArrayList(50); + List remainder = new ArrayList(50); + long totalSize = 0; + + for (Iterator iterator = sourceModel.fileIterator(); iterator.hasNext() && !cancellable.isCancelled();) { + File file = iterator.next(); + + long fileSize = file.length(); + + if (fileSize > splitSize) { + remainder.add(file); + continue; + } + + if (totalSize + fileSize > splitSize) { + // part is full, add node and start with next one + root.add(createStatisticsNode(String.format("Part %d", nextPart++), currentPart)); + + // reset total size and file list + totalSize = 0; + currentPart.clear(); + } + + totalSize += fileSize; + currentPart.add(file); + } + + if (!currentPart.isEmpty()) { + // add last part + root.add(createStatisticsNode(String.format("Part %d", nextPart++), currentPart)); + } + + if (!remainder.isEmpty()) { + root.add(createStatisticsNode("Remainder", remainder)); + } + + return new DefaultTreeModel(root); + } + + + @Override + protected void setModel(TreeModel model) { + tree.setModel(model); + } + +} diff --git a/source/net/sourceforge/filebot/ui/panel/analyze/Tool.java b/source/net/sourceforge/filebot/ui/panel/analyze/Tool.java new file mode 100644 index 00000000..7dd2be03 --- /dev/null +++ b/source/net/sourceforge/filebot/ui/panel/analyze/Tool.java @@ -0,0 +1,114 @@ + +package net.sourceforge.filebot.ui.panel.analyze; + + +import java.io.File; +import java.util.List; +import java.util.concurrent.Semaphore; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.swing.JComponent; +import javax.swing.SwingWorker; + +import net.sourceforge.filebot.ui.panel.analyze.FileTree.FileNode; +import net.sourceforge.filebot.ui.panel.analyze.FileTree.FolderNode; +import net.sourceforge.tuned.FileUtil; +import net.sourceforge.tuned.ui.LoadingOverlayPane; + + +abstract class Tool extends JComponent { + + private UpdateModelTask updateTask = null; + private Semaphore updateSemaphore = new Semaphore(1); + + + public Tool(String name) { + setName(name); + } + + + public synchronized void setSourceModel(FolderNode sourceModel) { + if (updateTask != null) { + updateTask.cancel(false); + } + + updateTask = new UpdateModelTask(sourceModel); + + updateSemaphore.acquireUninterruptibly(); + setLoading(true); + updateTask.execute(); + } + + + private void setLoading(boolean loading) { + firePropertyChange(LoadingOverlayPane.LOADING_PROPERTY, !loading, loading); + } + + + protected abstract M createModelInBackground(FolderNode sourceModel, Cancellable cancellable); + + + protected abstract void setModel(M model); + + + private class UpdateModelTask extends SwingWorker implements Cancellable { + + private final FolderNode sourceModel; + + + public UpdateModelTask(FolderNode sourceModel) { + this.sourceModel = sourceModel; + } + + + @Override + protected M doInBackground() throws Exception { + return createModelInBackground(sourceModel, this); + } + + + @Override + protected void done() { + // update task will only be cancelled if a newer update task has been started + if (!isCancelled()) { + try { + setModel(get()); + } catch (Exception e) { + // should not happen + Logger.getLogger("global").log(Level.WARNING, e.toString()); + } + } + + setLoading(false); + updateSemaphore.release(); + } + } + + + protected static interface Cancellable { + + boolean isCancelled(); + } + + + protected FolderNode createStatisticsNode(String name, List files) { + FolderNode folder = new FolderNode(null, files.size()); + + long totalSize = 0; + + for (File file : files) { + folder.add(new FileNode(file)); + totalSize += file.length(); + } + + // format the number of files string (e.g. 1 file, 2 files, ...) + String numberOfFiles = String.format("%,d %s", files.size(), files.size() == 1 ? "file" : "files"); + + // set node text (e.g. txt (1 file, 42 Byte)) + folder.setTitle(String.format("%s (%s, %s)", name, numberOfFiles, FileUtil.formatSize(totalSize))); + + return folder; + } + +} diff --git a/source/net/sourceforge/filebot/ui/panel/analyze/TypeTool.java b/source/net/sourceforge/filebot/ui/panel/analyze/TypeTool.java new file mode 100644 index 00000000..461fd26e --- /dev/null +++ b/source/net/sourceforge/filebot/ui/panel/analyze/TypeTool.java @@ -0,0 +1,76 @@ + +package net.sourceforge.filebot.ui.panel.analyze; + + +import java.io.File; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.TreeMap; +import java.util.Map.Entry; + +import javax.swing.BorderFactory; +import javax.swing.JScrollPane; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreeModel; + +import net.miginfocom.swing.MigLayout; +import net.sourceforge.filebot.ui.panel.analyze.FileTree.FolderNode; +import net.sourceforge.filebot.ui.transfer.DefaultTransferHandler; +import net.sourceforge.tuned.FileUtil; +import net.sourceforge.tuned.ui.LoadingOverlayPane; + + +public class TypeTool extends Tool { + + private FileTree tree = new FileTree(); + + + public TypeTool() { + super("Types"); + + setLayout(new MigLayout("insets 0, fill")); + + JScrollPane sp = new JScrollPane(tree); + sp.setBorder(BorderFactory.createEmptyBorder()); + add(new LoadingOverlayPane(sp, this), "grow"); + + tree.setTransferHandler(new DefaultTransferHandler(null, new FileTreeExportHandler())); + tree.setDragEnabled(true); + } + + + @Override + protected TreeModel createModelInBackground(FolderNode sourceModel, Cancellable cancellable) { + TreeMap> map = new TreeMap>(); + + for (Iterator iterator = sourceModel.fileIterator(); iterator.hasNext() && !cancellable.isCancelled();) { + File file = iterator.next(); + String extension = FileUtil.getExtension(file); + + List files = map.get(extension); + + if (files == null) { + files = new ArrayList(50); + map.put(extension, files); + } + + files.add(file); + } + + FolderNode root = new FolderNode(); + + for (Entry> entry : map.entrySet()) { + root.add(createStatisticsNode(entry.getKey(), entry.getValue())); + } + + return new DefaultTreeModel(root); + } + + + @Override + protected void setModel(TreeModel model) { + tree.setModel(model); + } + +} diff --git a/source/net/sourceforge/filebot/ui/panel/list/FileListTransferablePolicy.java b/source/net/sourceforge/filebot/ui/panel/list/FileListTransferablePolicy.java index 005e5886..ae822417 100644 --- a/source/net/sourceforge/filebot/ui/panel/list/FileListTransferablePolicy.java +++ b/source/net/sourceforge/filebot/ui/panel/list/FileListTransferablePolicy.java @@ -26,6 +26,12 @@ class FileListTransferablePolicy extends FileTransferablePolicy { } + @Override + protected boolean accept(List files) { + return true; + } + + @Override protected void clear() { list.getModel().clear(); @@ -34,21 +40,22 @@ class FileListTransferablePolicy extends FileTransferablePolicy { @Override protected void load(List files) { - if (files.size() > 1) { - list.setTitle(FileUtil.getFolderName(files.get(0).getParentFile())); - } + // set title based on parent folder of first file + list.setTitle(FileUtil.getFolderName(files.get(0).getParentFile())); if (FileBotUtil.containsOnlyFolders(files)) { - loadFolderList(files); + loadFolders(files); } else if (FileBotUtil.containsOnlyTorrentFiles(files)) { - loadTorrentList(files); + loadTorrents(files); } else { - super.load(files); + for (File file : files) { + list.getModel().add(FileUtil.getFileName(file)); + } } } - private void loadFolderList(List folders) { + private void loadFolders(List folders) { if (folders.size() == 1) { // if only one folder was dropped, use its name as title list.setTitle(FileUtil.getFolderName(folders.get(0))); @@ -62,7 +69,7 @@ class FileListTransferablePolicy extends FileTransferablePolicy { } - private void loadTorrentList(List torrentFiles) { + private void loadTorrents(List torrentFiles) { try { List torrents = new ArrayList(torrentFiles.size()); @@ -81,17 +88,11 @@ class FileListTransferablePolicy extends FileTransferablePolicy { } } catch (IOException e) { // should not happen - Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, e.toString(), e); + Logger.getLogger("global").log(Level.SEVERE, e.toString(), e); } } - @Override - protected void load(File file) { - list.getModel().add(FileUtil.getFileName(file)); - } - - @Override public String getFileFilterDescription() { return "files, folders and torrents"; diff --git a/source/net/sourceforge/filebot/ui/panel/rename/FilesListTransferablePolicy.java b/source/net/sourceforge/filebot/ui/panel/rename/FilesListTransferablePolicy.java index c8346815..28f1ca70 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/FilesListTransferablePolicy.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/FilesListTransferablePolicy.java @@ -22,6 +22,12 @@ class FilesListTransferablePolicy extends FileTransferablePolicy { } + @Override + protected boolean accept(List files) { + return true; + } + + @Override protected void clear() { model.clear(); @@ -32,17 +38,18 @@ class FilesListTransferablePolicy extends FileTransferablePolicy { protected void load(List files) { if (FileBotUtil.containsOnlyFolders(files)) { for (File folder : files) { - super.load(Arrays.asList(folder.listFiles())); + loadFiles(Arrays.asList(folder.listFiles())); } } else { - super.load(files); + loadFiles(files); } } - @Override - protected void load(File file) { - model.add(new FileEntry(file)); + protected void loadFiles(List files) { + for (File file : files) { + model.add(new FileEntry(file)); + } } diff --git a/source/net/sourceforge/filebot/ui/panel/rename/NamesListTransferablePolicy.java b/source/net/sourceforge/filebot/ui/panel/rename/NamesListTransferablePolicy.java index 639b96ba..527dde7d 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/NamesListTransferablePolicy.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/NamesListTransferablePolicy.java @@ -105,7 +105,7 @@ class NamesListTransferablePolicy extends FilesListTransferablePolicy { submit(entries); } catch (IOException e) { - Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, e.toString(), e); + Logger.getLogger("global").log(Level.SEVERE, e.toString(), e); } } @@ -124,7 +124,7 @@ class NamesListTransferablePolicy extends FilesListTransferablePolicy { submit(entries); } catch (IOException e) { - Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, e.toString(), e); + Logger.getLogger("global").log(Level.SEVERE, e.toString(), e); } } diff --git a/source/net/sourceforge/filebot/ui/panel/sfv/SfvTransferablePolicy.java b/source/net/sourceforge/filebot/ui/panel/sfv/SfvTransferablePolicy.java index dbd5fabc..2f8d9a7b 100644 --- a/source/net/sourceforge/filebot/ui/panel/sfv/SfvTransferablePolicy.java +++ b/source/net/sourceforge/filebot/ui/panel/sfv/SfvTransferablePolicy.java @@ -29,6 +29,12 @@ class SfvTransferablePolicy extends BackgroundFileTransferablePolicy files) { + return true; + } + + @Override protected void clear() { checksumComputationService.reset(); @@ -49,7 +55,7 @@ class SfvTransferablePolicy extends BackgroundFileTransferablePolicy files) { - if (FileBotUtil.containsOnlySfvFiles(files)) { - // one or more sfv files - for (File file : files) { - loadSfvFile(file); - } - } else if ((files.size() == 1) && files.get(0).isDirectory()) { - // one single folder - File file = files.get(0); - - for (File f : file.listFiles()) { - load(f, file, ""); - } - } else { - // bunch of files - for (File f : files) { - load(f, f.getParentFile(), ""); + try { + if (FileBotUtil.containsOnlySfvFiles(files)) { + // one or more sfv files + for (File file : files) { + loadSfvFile(file); + } + } else if ((files.size() == 1) && files.get(0).isDirectory()) { + // one single folder + File file = files.get(0); + + for (File f : file.listFiles()) { + load(f, file, ""); + } + } else { + // bunch of files + for (File f : files) { + load(f, f.getParentFile(), ""); + } } + } catch (InterruptedException e) { + // supposed to happen if background execution was aborted } } - protected void load(File file, File column, String prefix) { - if (Thread.currentThread().isInterrupted()) - return; + protected void load(File file, File column, String prefix) throws InterruptedException { + if (Thread.interrupted()) + throw new InterruptedException(); if (file.isDirectory()) { // load all files in the file tree @@ -122,4 +132,5 @@ class SfvTransferablePolicy extends BackgroundFileTransferablePolicy extends FileTransferab * * @param chunks */ - protected synchronized final void publish(V... chunks) { + protected synchronized void publish(V... chunks) { if (worker != null) { worker.publishChunks(chunks); } } - private class BackgroundWorker extends SwingWorker { + private class BackgroundWorker extends SwingWorker { private final List files; @@ -87,9 +87,8 @@ public abstract class BackgroundFileTransferablePolicy extends FileTransferab @Override - protected Void doInBackground() { + protected Object doInBackground() { load(files); - return null; } @@ -114,13 +113,13 @@ public abstract class BackgroundFileTransferablePolicy extends FileTransferab @Override public void started(PropertyChangeEvent evt) { - propertyChangeSupport.firePropertyChange(LOADING_PROPERTY, null, true); + propertyChangeSupport.firePropertyChange(LOADING_PROPERTY, false, true); } @Override public void done(PropertyChangeEvent evt) { - propertyChangeSupport.firePropertyChange(LOADING_PROPERTY, null, false); + propertyChangeSupport.firePropertyChange(LOADING_PROPERTY, true, false); } } diff --git a/source/net/sourceforge/filebot/ui/transfer/FileTransferablePolicy.java b/source/net/sourceforge/filebot/ui/transfer/FileTransferablePolicy.java index 42ddcd35..cc617a7a 100644 --- a/source/net/sourceforge/filebot/ui/transfer/FileTransferablePolicy.java +++ b/source/net/sourceforge/filebot/ui/transfer/FileTransferablePolicy.java @@ -57,7 +57,7 @@ public abstract class FileTransferablePolicy extends TransferablePolicy { files.add(file); } catch (Exception e) { // URISyntaxException, IllegalArgumentException, FileNotFoundException - Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.WARNING, "Invalid file url: " + line); + Logger.getLogger("global").log(Level.WARNING, "Invalid file url: " + line); } } @@ -79,42 +79,21 @@ public abstract class FileTransferablePolicy extends TransferablePolicy { public void handleTransferable(Transferable tr, TransferAction action) { List files = getFilesFromTransferable(tr); - if (action != TransferAction.ADD) + if (action != TransferAction.ADD) { clear(); + } load(files); } - protected boolean accept(List files) { - for (File f : files) - if (!accept(f)) - return false; - - return true; - } + protected abstract boolean accept(List files); - protected void load(List files) { - for (File file : files) { - load(file); - } - } + protected abstract void load(List files); - protected boolean accept(File file) { - return file.isFile() || file.isDirectory(); - } - - - protected void clear() { - - } - - - protected void load(File file) { - - } + protected abstract void clear(); public String getFileFilterDescription() { diff --git a/source/net/sourceforge/tuned/FilterIterator.java b/source/net/sourceforge/tuned/FilterIterator.java new file mode 100644 index 00000000..6e68f2b0 --- /dev/null +++ b/source/net/sourceforge/tuned/FilterIterator.java @@ -0,0 +1,58 @@ + +package net.sourceforge.tuned; + + +import java.util.Iterator; + + +public abstract class FilterIterator implements Iterator { + + private final Iterator sourceIterator; + + + public FilterIterator(Iterable source) { + this(source.iterator()); + } + + + public FilterIterator(Iterator sourceIterator) { + this.sourceIterator = sourceIterator; + } + + + @Override + public boolean hasNext() { + return peekNext(false) != null; + } + + + @Override + public T next() { + try { + return peekNext(true); + } finally { + current = null; + } + } + + private T current = null; + + + private T peekNext(boolean forceNext) { + while (current == null && (forceNext || (sourceIterator.hasNext()))) { + current = filter(sourceIterator.next()); + } + + return current; + } + + + protected abstract T filter(S sourceValue); + + + @Override + public void remove() { + sourceIterator.remove(); + } + +} diff --git a/source/net/sourceforge/tuned/TreeIterator.java b/source/net/sourceforge/tuned/TreeIterator.java new file mode 100644 index 00000000..4c8a5424 --- /dev/null +++ b/source/net/sourceforge/tuned/TreeIterator.java @@ -0,0 +1,63 @@ + +package net.sourceforge.tuned; + + +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedList; + + +public abstract class TreeIterator implements Iterator { + + private final LinkedList> recursionStack = new LinkedList>(); + + + public TreeIterator(T... root) { + recursionStack.push(Arrays.asList(root).iterator()); + } + + + protected abstract Iterator children(T node); + + + @Override + public boolean hasNext() { + return currentIterator().hasNext(); + } + + + @Override + public T next() { + T node = currentIterator().next(); + + Iterator children = children(node); + if (children != null && children.hasNext()) { + // step into next recursion level + recursionStack.push(children); + } + + return node; + } + + + private Iterator currentIterator() { + Iterator iterator = recursionStack.peek(); + + if (iterator.hasNext() || recursionStack.size() <= 1) + return iterator; + + // step back one recursion level + recursionStack.pop(); + + return currentIterator(); + } + + + @Override + public void remove() { + // can't just use remove() on current iterator, because + // we may have stepped into the next recursion level + throw new UnsupportedOperationException(); + } + +} diff --git a/source/net/sourceforge/tuned/ui/LoadingOverlayPane.java b/source/net/sourceforge/tuned/ui/LoadingOverlayPane.java index e1e1d05c..73f2a697 100644 --- a/source/net/sourceforge/tuned/ui/LoadingOverlayPane.java +++ b/source/net/sourceforge/tuned/ui/LoadingOverlayPane.java @@ -2,20 +2,15 @@ package net.sourceforge.tuned.ui; -import java.awt.Dimension; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; -import javax.swing.BorderFactory; -import javax.swing.Icon; import javax.swing.JComponent; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.OverlayLayout; + +import net.miginfocom.swing.MigLayout; -public class LoadingOverlayPane extends JPanel { +public class LoadingOverlayPane extends JComponent { public static final String LOADING_PROPERTY = "loading"; @@ -26,37 +21,21 @@ public class LoadingOverlayPane extends JPanel { private int millisToOverlay = 500; - public LoadingOverlayPane(JComponent component, Icon animation) { - this(component, new JLabel(""), getView(component)); + public LoadingOverlayPane(JComponent component, JComponent propertyChangeSource) { + this(component, new ProgressIndicator(), propertyChangeSource); } - public LoadingOverlayPane(JComponent component, JComponent animation) { - this(component, animation, getView(component)); - } - - - public LoadingOverlayPane(JComponent component, JComponent animation, JComponent view) { - setLayout(new OverlayLayout(this)); + public LoadingOverlayPane(JComponent component, JComponent animationComponent, JComponent propertyChangeSource) { + setLayout(new MigLayout("fill, insets 0")); + this.animationComponent = animationComponent; - this.animationComponent = animation; + add(animationComponent, "pos visual.x2-pref-18px 8px"); + add(component, "grow"); - component.setAlignmentX(1.0f); - component.setAlignmentY(0.0f); + animationComponent.setVisible(false); - animation.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 20)); - - animation.setAlignmentX(1.0f); - animation.setAlignmentY(0.0f); - animationComponent.setPreferredSize(new Dimension(48, 48)); - animationComponent.setMaximumSize(animationComponent.getPreferredSize()); - - add(animation); - add(component); - - setOverlayVisible(true); - - view.addPropertyChangeListener(LOADING_PROPERTY, loadingListener); + propertyChangeSource.addPropertyChangeListener(LOADING_PROPERTY, loadingListener); } @@ -66,16 +45,6 @@ public class LoadingOverlayPane extends JPanel { } - private static JComponent getView(JComponent component) { - if (component instanceof JScrollPane) { - JScrollPane scrollPane = (JScrollPane) component; - return (JComponent) scrollPane.getViewport().getView(); - } - - return component; - } - - public void setOverlayVisible(boolean b) { overlayEnabled = b; diff --git a/source/net/sourceforge/tuned/ui/ProgressIndicator.java b/source/net/sourceforge/tuned/ui/ProgressIndicator.java index b5ef9ae2..0e8ef356 100644 --- a/source/net/sourceforge/tuned/ui/ProgressIndicator.java +++ b/source/net/sourceforge/tuned/ui/ProgressIndicator.java @@ -5,73 +5,64 @@ package net.sourceforge.tuned.ui; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Dimension; -import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.Stroke; -import java.awt.geom.Arc2D; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; import java.awt.geom.Ellipse2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.Calendar; -import javax.swing.BoundedRangeModel; -import javax.swing.JPanel; +import javax.swing.JComponent; +import javax.swing.Timer; -public class ProgressIndicator extends JPanel { +public class ProgressIndicator extends JComponent { - private BoundedRangeModel model = null; + private float radius = 4.0f; + private int shapeCount = 3; - private boolean indeterminate = false; + private float strokeWidth = 2f; + private Stroke stroke = new BasicStroke(strokeWidth, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND); - private float indeterminateRadius = 4.0f; - private int indeterminateShapeCount = 1; - - private float progressStrokeWidth = 4.5f; - private float remainingStrokeWidth = 2f; - - private Stroke progressStroke = new BasicStroke(progressStrokeWidth, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND); - private Stroke remainingStroke = new BasicStroke(remainingStrokeWidth, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND); - - private Color progressColor = Color.orange; - private Color remainingColor = new Color(0f, 0f, 0f, 0.25f); - - private Color textColor = new Color(0x5F5F5F); - - private boolean paintText = true; - private boolean paintBackground = false; + private Color progressShapeColor = Color.orange; + private Color backgroundShapeColor = new Color(0f, 0f, 0f, 0.25f); private final Rectangle2D frame = new Rectangle2D.Double(); - private final Arc2D arc = new Arc2D.Double(); private final Ellipse2D circle = new Ellipse2D.Double(); private final Dimension baseSize = new Dimension(32, 32); + private Timer updateTimer = new Timer(20, new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + repaint(); + } + });; + public ProgressIndicator() { - this(null); - } - + setPreferredSize(baseSize); + + addComponentListener(new ComponentAdapter() { + + @Override + public void componentHidden(ComponentEvent e) { + stopAnimation(); + } + - public ProgressIndicator(BoundedRangeModel model) { - this.model = model; - - indeterminate = (model == null); - - setFont(new Font(Font.DIALOG, Font.BOLD, 8)); - } - - - public double getProgress() { - if (model == null) - return 0; - - double total = model.getMaximum() - model.getMinimum(); - double current = model.getValue() - model.getMinimum(); - - return current / total; + @Override + public void componentShown(ComponentEvent e) { + startAnimation(); + } + }); } @@ -85,85 +76,38 @@ public class ProgressIndicator extends JPanel { g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - if (paintBackground) { - frame.setFrame(0, 0, baseSize.width, baseSize.height); - - g2d.setPaint(getBackground()); - circle.setFrame(frame); - g2d.fill(circle); - } + frame.setFrame(radius, radius, baseSize.width - radius * 2 - 1, baseSize.height - radius * 2 - 1); - double inset = Math.max(Math.max(remainingStrokeWidth, progressStrokeWidth), indeterminateRadius); - frame.setFrame(inset, inset, baseSize.width - inset * 2 - 1, baseSize.height - inset * 2 - 1); - - if (!indeterminate) { - paintProgress(g2d); - } else { - paintIndeterminate(g2d); - } + paintShapes(g2d); } - protected void paintProgress(Graphics2D g2d) { - - double progress = getProgress(); - - // remaining circle + protected void paintShapes(Graphics2D g2d) { circle.setFrame(frame); - g2d.setStroke(remainingStroke); - g2d.setPaint(remainingColor); - - g2d.draw(circle); - - // progress circle - arc.setArc(frame, 90, progress * 360 * -1, Arc2D.OPEN); - - g2d.setStroke(progressStroke); - g2d.setPaint(progressColor); - - g2d.draw(arc); - - if (paintText) { - // text - g2d.setFont(getFont()); - g2d.setPaint(textColor); - - String text = String.format("%d%%", (int) (100 * progress)); - Rectangle2D textBounds = g2d.getFontMetrics().getStringBounds(text, g2d); - - g2d.drawString(text, (float) (frame.getCenterX() - textBounds.getX() - (textBounds.getWidth() / 2f) + 0.5f), (float) (frame.getCenterY() - textBounds.getY() - (textBounds.getHeight() / 2))); - } - } - - - protected void paintIndeterminate(Graphics2D g2d) { - circle.setFrame(frame); - - g2d.setStroke(remainingStroke); - g2d.setPaint(remainingColor); + g2d.setStroke(stroke); + g2d.setPaint(backgroundShapeColor); g2d.draw(circle); Point2D center = new Point2D.Double(frame.getCenterX(), frame.getMinY()); - circle.setFrameFromCenter(center, new Point2D.Double(center.getX() + indeterminateRadius, center.getY() + indeterminateRadius)); + circle.setFrameFromCenter(center, new Point2D.Double(center.getX() + radius, center.getY() + radius)); - g2d.setStroke(progressStroke); - g2d.setPaint(progressColor); + g2d.setStroke(stroke); + g2d.setPaint(progressShapeColor); Calendar now = Calendar.getInstance(); double theta = getTheta(now.get(Calendar.MILLISECOND), now.getMaximum(Calendar.MILLISECOND)); g2d.rotate(theta, frame.getCenterX(), frame.getCenterY()); - theta = getTheta(1, indeterminateShapeCount); + theta = getTheta(1, shapeCount); - for (int i = 0; i < indeterminateShapeCount; i++) { + for (int i = 0; i < shapeCount; i++) { g2d.rotate(theta, frame.getCenterX(), frame.getCenterY()); g2d.fill(circle); } - } @@ -172,58 +116,18 @@ public class ProgressIndicator extends JPanel { } - public BoundedRangeModel getModel() { - return model; + public void startAnimation() { + updateTimer.restart(); } - public void setModel(BoundedRangeModel model) { - this.model = model; + public void stopAnimation() { + updateTimer.stop(); } - public boolean isIndeterminate() { - return indeterminate; - } - - - public void setIndeterminate(boolean indeterminate) { - this.indeterminate = indeterminate; - } - - - public void setIndeterminateRadius(float indeterminateRadius) { - this.indeterminateRadius = indeterminateRadius; - } - - - public void setIndeterminateShapeCount(int indeterminateShapeCount) { - this.indeterminateShapeCount = indeterminateShapeCount; - } - - - public void setProgressColor(Color progressColor) { - this.progressColor = progressColor; - } - - - public void setRemainingColor(Color remainingColor) { - this.remainingColor = remainingColor; - } - - - public void setTextColor(Color textColor) { - this.textColor = textColor; - } - - - public void setPaintBackground(boolean paintBackground) { - this.paintBackground = paintBackground; - } - - - public void setPaintText(boolean paintString) { - this.paintText = paintString; + public void setShapeCount(int indeterminateShapeCount) { + this.shapeCount = indeterminateShapeCount; } }