* heavy refactoring of the analyze panel

* added TreeIterator and FilterIterator
* refactored file transferable policies
* refactored loading overlay
This commit is contained in:
Reinhard Pointner 2008-12-27 11:35:53 +00:00
parent 1cf51ae179
commit 2fc8bb7195
20 changed files with 1126 additions and 652 deletions

View File

@ -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<File> convertToList() {
TreeNode node = (TreeNode) getModel().getRoot();
return convertToList(node);
}
public List<File> convertToList(TreeNode node) {
ArrayList<File> list = new ArrayList<File>();
convertToListImpl((DefaultMutableTreeNode) node, list);
return list;
}
private void convertToListImpl(DefaultMutableTreeNode node, List<File> 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);
}
}
}
}

View File

@ -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<File> files = (List<File>) 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());
}
}
};

View File

@ -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<TreeNode> changedNodes = new ArrayList<TreeNode>(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<TreeNode> dirtyNodes = new HashSet<TreeNode>();
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<File> selectionFiles = null;
if (selectionPaths != null) {
selectionFiles = new LinkedHashSet<File>(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<File> selectionParentFolders = new LinkedHashSet<File>(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<File> files) {
super(text);
putValue("files", files);
}
@SuppressWarnings("unchecked")
public void actionPerformed(ActionEvent event) {
try {
for (File file : (Collection<File>) 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<List<File>, Void> {
public static class AbstractTreeNode implements TreeNode {
private TreeNode parent;
@Override
protected List<File> 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<File> 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<? 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 List<AbstractTreeNode> 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<AbstractTreeNode>(initialCapacity);
}
public void setTitle(String title) {
this.title = title;
}
@Override
public String toString() {
return title;
}
public List<AbstractTreeNode> 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<? extends TreeNode> 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<AbstractTreeNode> treeIterator() {
return new TreeIterator<AbstractTreeNode>(this) {
@Override
protected Iterator<AbstractTreeNode> children(AbstractTreeNode 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<AbstractTreeNode, File>(treeIterator()) {
@Override
protected File filter(AbstractTreeNode node) {
if (node instanceof FileNode)
return ((FileNode) node).getFile();
// filter out non-file nodes
return null;
}
};
}
}
}

View File

@ -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;
}
}

View File

@ -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<File> files = new LinkedHashSet<File>();
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<File> 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;
}
}

View File

@ -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();
}
}
};

View File

@ -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<DefaultMutableTreeNode> {
class FileTreeTransferablePolicy extends BackgroundFileTransferablePolicy<AbstractTreeNode> {
private final FileTree tree;
@ -22,6 +22,12 @@ class FileTreeTransferablePolicy extends BackgroundFileTransferablePolicy<Defaul
}
@Override
protected boolean accept(List<File> files) {
return true;
}
@Override
protected void clear() {
tree.clear();
@ -29,48 +35,58 @@ class FileTreeTransferablePolicy extends BackgroundFileTransferablePolicy<Defaul
@Override
protected void process(List<DefaultMutableTreeNode> chunks) {
DefaultTreeModel model = tree.getModel();
DefaultMutableTreeNode root = (DefaultMutableTreeNode) model.getRoot();
protected void process(List<AbstractTreeNode> 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<File> 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);
}

View File

@ -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<TreeModel> 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<File> currentPart = new ArrayList<File>(50);
List<File> remainder = new ArrayList<File>(50);
long totalSize = 0;
for (Iterator<File> 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);
}
}

View File

@ -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<M> 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<M, Void> 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<File> 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;
}
}

View File

@ -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<TreeModel> {
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<String, List<File>> map = new TreeMap<String, List<File>>();
for (Iterator<File> iterator = sourceModel.fileIterator(); iterator.hasNext() && !cancellable.isCancelled();) {
File file = iterator.next();
String extension = FileUtil.getExtension(file);
List<File> files = map.get(extension);
if (files == null) {
files = new ArrayList<File>(50);
map.put(extension, files);
}
files.add(file);
}
FolderNode root = new FolderNode();
for (Entry<String, List<File>> entry : map.entrySet()) {
root.add(createStatisticsNode(entry.getKey(), entry.getValue()));
}
return new DefaultTreeModel(root);
}
@Override
protected void setModel(TreeModel model) {
tree.setModel(model);
}
}

View File

@ -26,6 +26,12 @@ class FileListTransferablePolicy extends FileTransferablePolicy {
}
@Override
protected boolean accept(List<File> files) {
return true;
}
@Override
protected void clear() {
list.getModel().clear();
@ -34,21 +40,22 @@ class FileListTransferablePolicy extends FileTransferablePolicy {
@Override
protected void load(List<File> 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<File> folders) {
private void loadFolders(List<File> 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<File> torrentFiles) {
private void loadTorrents(List<File> torrentFiles) {
try {
List<Torrent> torrents = new ArrayList<Torrent>(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";

View File

@ -22,6 +22,12 @@ class FilesListTransferablePolicy extends FileTransferablePolicy {
}
@Override
protected boolean accept(List<File> files) {
return true;
}
@Override
protected void clear() {
model.clear();
@ -32,17 +38,18 @@ class FilesListTransferablePolicy extends FileTransferablePolicy {
protected void load(List<File> 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<File> files) {
for (File file : files) {
model.add(new FileEntry(file));
}
}

View File

@ -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);
}
}

View File

@ -29,6 +29,12 @@ class SfvTransferablePolicy extends BackgroundFileTransferablePolicy<ChecksumTab
}
@Override
protected boolean accept(List<File> files) {
return true;
}
@Override
protected void clear() {
checksumComputationService.reset();
@ -49,7 +55,7 @@ class SfvTransferablePolicy extends BackgroundFileTransferablePolicy<ChecksumTab
String line = null;
Pattern pattern = Pattern.compile("(.*)\\s+(\\p{XDigit}{8})");
while (((line = in.readLine()) != null) && !Thread.currentThread().isInterrupted()) {
while (((line = in.readLine()) != null) && !Thread.interrupted()) {
if (line.startsWith(";"))
continue;
@ -74,7 +80,7 @@ class SfvTransferablePolicy extends BackgroundFileTransferablePolicy<ChecksumTab
in.close();
} 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);
}
}
@ -87,30 +93,34 @@ class SfvTransferablePolicy extends BackgroundFileTransferablePolicy<ChecksumTab
@Override
protected void load(List<File> 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<ChecksumTab
publish(new ChecksumTableModel.ChecksumCell(prefix + file.getName(), checksumComputationService.schedule(file, column), column));
}
}
}

View File

@ -69,14 +69,14 @@ public abstract class BackgroundFileTransferablePolicy<V> 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<Void, V> {
private class BackgroundWorker extends SwingWorker<Object, V> {
private final List<File> files;
@ -87,9 +87,8 @@ public abstract class BackgroundFileTransferablePolicy<V> extends FileTransferab
@Override
protected Void doInBackground() {
protected Object doInBackground() {
load(files);
return null;
}
@ -114,13 +113,13 @@ public abstract class BackgroundFileTransferablePolicy<V> 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);
}
}

View File

@ -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<File> files = getFilesFromTransferable(tr);
if (action != TransferAction.ADD)
if (action != TransferAction.ADD) {
clear();
}
load(files);
}
protected boolean accept(List<File> files) {
for (File f : files)
if (!accept(f))
return false;
return true;
}
protected abstract boolean accept(List<File> files);
protected void load(List<File> files) {
for (File file : files) {
load(file);
}
}
protected abstract void load(List<File> 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() {

View File

@ -0,0 +1,58 @@
package net.sourceforge.tuned;
import java.util.Iterator;
public abstract class FilterIterator<S, T> implements Iterator<T> {
private final Iterator<S> sourceIterator;
public FilterIterator(Iterable<S> source) {
this(source.iterator());
}
public FilterIterator(Iterator<S> 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();
}
}

View File

@ -0,0 +1,63 @@
package net.sourceforge.tuned;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
public abstract class TreeIterator<T> implements Iterator<T> {
private final LinkedList<Iterator<T>> recursionStack = new LinkedList<Iterator<T>>();
public TreeIterator(T... root) {
recursionStack.push(Arrays.asList(root).iterator());
}
protected abstract Iterator<T> children(T node);
@Override
public boolean hasNext() {
return currentIterator().hasNext();
}
@Override
public T next() {
T node = currentIterator().next();
Iterator<T> children = children(node);
if (children != null && children.hasNext()) {
// step into next recursion level
recursionStack.push(children);
}
return node;
}
private Iterator<T> currentIterator() {
Iterator<T> 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();
}
}

View File

@ -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;

View File

@ -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;
}
}