Browse Source

Move more work from edt into background thread when updating directories.

pull/214/head
weisj 4 years ago
parent
commit
564b0ebe79
  1. 15
      core/src/main/java/com/github/weisj/darklaf/components/RotatableIconAnimator.java
  2. 37
      core/src/main/java/com/github/weisj/darklaf/components/filetree/FileTree.java
  3. 3
      core/src/main/java/com/github/weisj/darklaf/components/filetree/FileTreeCellRenderer.java
  4. 21
      core/src/main/java/com/github/weisj/darklaf/components/filetree/FileTreeModel.java
  5. 133
      core/src/main/java/com/github/weisj/darklaf/components/filetree/FileTreeNode.java
  6. 15
      core/src/main/java/com/github/weisj/darklaf/components/filetree/WatchFileTree.java
  7. 13
      core/src/main/java/com/github/weisj/darklaf/components/filetree/WatchFileTreeModel.java
  8. 2
      core/src/test/java/ui/tree/FileTreeDemo.java

15
core/src/main/java/com/github/weisj/darklaf/components/RotatableIconAnimator.java

@ -27,6 +27,7 @@ import javax.swing.*;
import com.github.weisj.darklaf.icons.RotatableIcon; import com.github.weisj.darklaf.icons.RotatableIcon;
import com.github.weisj.darklaf.util.Alignment; import com.github.weisj.darklaf.util.Alignment;
import com.github.weisj.darklaf.util.DarkUIUtil;
public class RotatableIconAnimator extends Timer { public class RotatableIconAnimator extends Timer {
@ -43,8 +44,6 @@ public class RotatableIconAnimator extends Timer {
super(100, null); super(100, null);
if (icon == null) if (icon == null)
throw new IllegalArgumentException("Icon is null"); throw new IllegalArgumentException("Icon is null");
if (parent == null)
throw new IllegalArgumentException("Component is null");
addActionListener(this::onAction); addActionListener(this::onAction);
setRepeats(true); setRepeats(true);
this.icon = icon; this.icon = icon;
@ -53,16 +52,26 @@ public class RotatableIconAnimator extends Timer {
} }
public void resume() { public void resume() {
if (!isRunning())
start(); start();
} }
public void onAction(final ActionEvent e) { public void onAction(final ActionEvent e) {
icon.setRotation(Math.PI * 2 * (((double) frame) / frameCount)); icon.setRotation(Math.PI * 2 * (((double) frame) / frameCount));
parent.repaint(); repaint();
frame = (frame + 1) % frameCount; frame = (frame + 1) % frameCount;
} }
protected void repaint() {
DarkUIUtil.repaint(getParent());
}
public JComponent getParent() {
return parent;
}
public void suspend() { public void suspend() {
if (isRunning())
stop(); stop();
} }
} }

37
core/src/main/java/com/github/weisj/darklaf/components/filetree/FileTree.java

@ -22,30 +22,38 @@
package com.github.weisj.darklaf.components.filetree; package com.github.weisj.darklaf.components.filetree;
import java.io.File; import java.io.File;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import javax.swing.*; import javax.swing.*;
import javax.swing.filechooser.FileSystemView; import javax.swing.filechooser.FileSystemView;
import javax.swing.tree.TreeModel; import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
public class FileTree extends JTree { public class FileTree extends JTree {
public FileTree() { public FileTree() {
this(null); this((File[]) null);
} }
public FileTree(final File rootFile) { public FileTree(final File... rootFile) {
this(rootFile, false); this(false, rootFile);
} }
public FileTree(final File rootFile, final boolean showHiddenFiles) { public FileTree(final boolean showHiddenFiles, final File... rootFiles) {
FileSystemView fileSystemView = FileSystemView.getFileSystemView(); FileSystemView fileSystemView = FileSystemView.getFileSystemView();
setFileTreeModel(createModel(fileSystemView, rootFile, showHiddenFiles)); setFileTreeModel(createModel(fileSystemView, showHiddenFiles, rootFiles));
setCellRenderer(new FileTreeCellRenderer(fileSystemView)); setCellRenderer(new FileTreeCellRenderer(fileSystemView));
setRootVisible(false); setRootVisible(false);
} }
protected FileTreeModel createModel(final FileSystemView fsv, final File rootFile, final boolean showHiddenFiles) { protected FileTreeModel createModel(final FileSystemView fsv, final boolean showHiddenFiles,
return new FileTreeModel(fsv, rootFile, showHiddenFiles); final File... rootFiles) {
return new FileTreeModel(fsv, showHiddenFiles, rootFiles);
} }
public boolean isShowHiddenFiles() { public boolean isShowHiddenFiles() {
@ -71,4 +79,19 @@ public class FileTree extends JTree {
public void reload() { public void reload() {
getModel().reload(); getModel().reload();
} }
public Path getSelectedFile() {
TreePath path = getSelectionPath();
if (path == null)
return null;
return ((FileTreeNode) path.getLastPathComponent()).getFile();
}
public List<Path> getSelectedFiles() {
TreePath[] paths = getSelectionPaths();
if (paths == null)
return Collections.emptyList();
return Arrays.stream(getSelectionPaths()).map(TreePath::getLastPathComponent).map(FileTreeNode.class::cast)
.map(FileTreeNode::getFile).collect(Collectors.toList());
}
} }

3
core/src/main/java/com/github/weisj/darklaf/components/filetree/FileTreeCellRenderer.java

@ -39,7 +39,8 @@ public class FileTreeCellRenderer extends DefaultTreeCellRenderer {
@Override @Override
public Component getTreeCellRendererComponent(final JTree tree, final Object value, final boolean selected, public Component getTreeCellRendererComponent(final JTree tree, final Object value, final boolean selected,
final boolean expanded, final boolean leaf, final int row, final boolean hasFocus) { final boolean expanded, final boolean leaf, final int row, final boolean hasFocus) {
Path f = ((FileTreeNode) value).getFile(); FileTreeNode node = ((FileTreeNode) value);
Path f = node.getFile();
if (f != null) { if (f != null) {
setIcon(fsv.getSystemIcon(f.toFile())); setIcon(fsv.getSystemIcon(f.toFile()));
setText(fsv.getSystemDisplayName(f.toFile())); setText(fsv.getSystemDisplayName(f.toFile()));

21
core/src/main/java/com/github/weisj/darklaf/components/filetree/FileTreeModel.java

@ -23,6 +23,8 @@ package com.github.weisj.darklaf.components.filetree;
import java.io.File; import java.io.File;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
import javax.swing.filechooser.FileSystemView; import javax.swing.filechooser.FileSystemView;
import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.DefaultTreeModel;
@ -33,28 +35,29 @@ public class FileTreeModel extends DefaultTreeModel {
protected boolean showHiddenFiles; protected boolean showHiddenFiles;
public FileTreeModel(final FileSystemView fileSystemView) { public FileTreeModel(final FileSystemView fileSystemView) {
this(fileSystemView, (Path) null, false); this(fileSystemView, false, (Path[]) null);
} }
public FileTreeModel(final FileSystemView fileSystemView, final File root, final boolean showHiddenFiles) { public FileTreeModel(final FileSystemView fileSystemView, final boolean showHiddenFiles, final File... roots) {
this(fileSystemView, root != null ? root.toPath() : null, showHiddenFiles); this(fileSystemView, showHiddenFiles,
roots != null ? Arrays.stream(roots).map(File::toPath).toArray(Path[]::new) : null);
} }
public FileTreeModel(final FileSystemView fileSystemView, final Path root, final boolean showHiddenFiles) { public FileTreeModel(final FileSystemView fileSystemView, final boolean showHiddenFiles, final Path... roots) {
super(null); super(null);
init(); init();
this.showHiddenFiles = showHiddenFiles; this.showHiddenFiles = showHiddenFiles;
this.fsv = fileSystemView; this.fsv = fileSystemView;
this.root = createRoot(root); this.root = createRoot(roots);
} }
protected void init() {} protected void init() {}
protected FileTreeNode createRoot(final Path root) { protected FileTreeNode createRoot(final Path... roots) {
if (root == null) { if (roots == null || roots.length != 1) {
return new FileTreeNode.RootNode(this); return new FileTreeNode.RootNode(this, roots == null ? Collections.emptyList() : Arrays.asList(roots));
} else { } else {
return createNode(null, root); return createNode(null, roots[0]);
} }
} }

133
core/src/main/java/com/github/weisj/darklaf/components/filetree/FileTreeNode.java

@ -24,9 +24,9 @@ package com.github.weisj.darklaf.components.filetree;
import java.io.IOException; import java.io.IOException;
import java.nio.file.*; import java.nio.file.*;
import java.util.*; import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Stream; import java.util.stream.Stream;
import javax.swing.*; import javax.swing.*;
@ -37,6 +37,7 @@ public class FileTreeNode implements TreeNode, Comparable<FileTreeNode> {
protected final FileTreeNode parent; protected final FileTreeNode parent;
protected final FileTreeModel model; protected final FileTreeModel model;
protected final Path file; protected final Path file;
protected AtomicInteger taskCount = new AtomicInteger();
protected AtomicReference<List<FileTreeNode>> children; protected AtomicReference<List<FileTreeNode>> children;
protected WatchKey watchKey; protected WatchKey watchKey;
@ -75,18 +76,13 @@ public class FileTreeNode implements TreeNode, Comparable<FileTreeNode> {
return index; return index;
} }
private void add(final List<FileTreeNode> nodes, final FileTreeNode node) { private int remove(final List<FileTreeNode> nodes, final FileTreeNode node) {
int index = addSorted(nodes, node);
model.nodesWereInserted(FileTreeNode.this, new int[] {index});
}
private void remove(final List<FileTreeNode> nodes, final FileTreeNode node) {
int index = nodes.indexOf(node); int index = nodes.indexOf(node);
if (index < 0) if (index >= 0) {
return;
nodes.remove(node); nodes.remove(node);
model.unregister(node); model.unregister(node);
model.nodesWereRemoved(FileTreeNode.this, new int[] {index}, new Object[] {node}); }
return index;
} }
public void reload() { public void reload() {
@ -99,30 +95,45 @@ public class FileTreeNode implements TreeNode, Comparable<FileTreeNode> {
if (children.get() == null) if (children.get() == null)
return; return;
List<FileTreeNode> fileList = children.get(); List<FileTreeNode> fileList = children.get();
doInBackground((Consumer<FileTreeNode> proc) -> { this.<ReloadOp>doInBackground(pub -> {
traverseChildren(this::toNodes).accept(proc); taskCount.getAndIncrement();
fileList.stream().filter(n -> Files.notExists(n.file)).forEach(proc); this.traverseChildren(s -> {
}, chunks -> { Stream.concat(s.map(this::toNode), fileList.stream()).map(n -> {
for (FileTreeNode node : chunks) { if (Files.notExists(n.file)) {
if (Files.notExists(node.file)) { return ReloadOp.remove(n, remove(fileList, n));
remove(fileList, node);
} else { } else {
if (model.showHiddenFiles) { if (model.showHiddenFiles) {
if (!fileList.contains(node)) { if (!fileList.contains(n)) {
add(fileList, node); return ReloadOp.add(n, addSorted(fileList, n));
} }
} else { } else {
if (isHidden(node.file)) { if (isHidden(n.file)) {
remove(fileList, node); return ReloadOp.remove(n, remove(fileList, n));
} else if (!fileList.contains(node)) { } else if (!fileList.contains(n)) {
add(fileList, node); return ReloadOp.add(n, addSorted(fileList, n));
} }
} }
} }
return null;
}).forEach(pub);
});
}, chunk -> {
for (ReloadOp op : chunk) {
if (op != null) {
switch (op.type) {
case ADD:
model.nodesWereInserted(FileTreeNode.this, new int[] {op.index});
break;
case REMOVE:
model.nodesWereRemoved(FileTreeNode.this, new int[] {op.index}, new Object[] {op.node});
break;
}
}
} }
}, () -> { }, () -> {
if (depth > 0) if (depth > 0)
fileList.forEach(n -> n.reload(depth - 1)); fileList.forEach(n -> n.reload(depth - 1));
taskCount.getAndDecrement();
}); });
} }
@ -132,17 +143,13 @@ public class FileTreeNode implements TreeNode, Comparable<FileTreeNode> {
return list; return list;
} }
List<FileTreeNode> fileList = Collections.synchronizedList(new ArrayList<>()); List<FileTreeNode> fileList = Collections.synchronizedList(new ArrayList<>());
doInBackground( this.<Integer>doInBackground(pub -> {
traverseChildren(asNodes(stream -> stream.filter(p -> model.showHiddenFiles || !isHidden(p)))), traverseChildren(s -> {
chunks -> { s.filter(p -> model.showHiddenFiles || !isHidden(p)).map(this::toNode)
Collections.sort(fileList); .map(n -> addSorted(fileList, n)).forEach(pub);
int[] indices = new int[chunks.size()]; });
int i = 0; }, chunks -> {
for (FileTreeNode node : chunks) { model.nodesWereInserted(FileTreeNode.this, chunks.stream().mapToInt(Integer::intValue).toArray());
indices[i] = addSorted(fileList, node);
i++;
}
model.nodesWereInserted(FileTreeNode.this, indices);
}); });
return fileList; return fileList;
}); });
@ -156,24 +163,17 @@ public class FileTreeNode implements TreeNode, Comparable<FileTreeNode> {
} }
} }
private Function<Stream<Path>, Stream<FileTreeNode>> asNodes( private FileTreeNode toNode(final Path path) {
final Function<Stream<Path>, Stream<Path>> transformer) { return model.createNode(FileTreeNode.this, path);
return stream -> toNodes(transformer.apply(stream));
}
private Stream<FileTreeNode> toNodes(final Stream<Path> stream) {
return stream.map(p -> model.createNode(FileTreeNode.this, p));
} }
private <T> Consumer<Consumer<T>> traverseChildren(final Function<Stream<Path>, Stream<T>> transformer) { private void traverseChildren(final Consumer<Stream<Path>> consumer) {
return publish -> {
if (Files.isDirectory(file)) { if (Files.isDirectory(file)) {
try (Stream<Path> files = Files.walk(file, 1, FileVisitOption.FOLLOW_LINKS)) { try (Stream<Path> files = Files.walk(file, 1, FileVisitOption.FOLLOW_LINKS)) {
transformer.apply(files.filter(p -> !file.equals(p)).filter(Files::isReadable)).forEach(publish); consumer.accept(files.filter(p -> !file.equals(p)).filter(Files::isReadable));
} catch (IOException ignored) { } catch (IOException ignored) {
} }
} }
};
} }
private <T> void doInBackground(final Consumer<Consumer<T>> task, final Consumer<List<T>> processor) { private <T> void doInBackground(final Consumer<Consumer<T>> task, final Consumer<List<T>> processor) {
@ -263,12 +263,19 @@ public class FileTreeNode implements TreeNode, Comparable<FileTreeNode> {
} }
} }
public boolean isBusy() {
return taskCount.get() > 0;
}
public static class RootNode extends FileTreeNode { public static class RootNode extends FileTreeNode {
public RootNode(final FileTreeModel model) { private final List<Path> roots;
public RootNode(final FileTreeModel model, final List<Path> roots) {
super(null, null, model); super(null, null, model);
this.roots = roots;
List<FileTreeNode> nodes = new ArrayList<>(); List<FileTreeNode> nodes = new ArrayList<>();
FileSystems.getDefault().getRootDirectories().forEach(p -> { createInitialDirectories().forEach(p -> {
FileTreeNode node = model.createNode(RootNode.this, p); FileTreeNode node = model.createNode(RootNode.this, p);
model.register(node); model.register(node);
nodes.add(node); nodes.add(node);
@ -276,12 +283,16 @@ public class FileTreeNode implements TreeNode, Comparable<FileTreeNode> {
children.set(nodes); children.set(nodes);
} }
protected Iterable<Path> createInitialDirectories() {
return roots.size() > 0 ? roots : FileSystems.getDefault().getRootDirectories();
}
@Override @Override
protected void reload(final int depth) { protected void reload(final int depth) {
if (depth < 0) if (depth < 0)
return; return;
List<FileTreeNode> nodes = children.get(); List<FileTreeNode> nodes = children.get();
FileSystems.getDefault().getRootDirectories().forEach(p -> { createInitialDirectories().forEach(p -> {
FileTreeNode node = model.createNode(this, p); FileTreeNode node = model.createNode(this, p);
if (!nodes.contains(node)) { if (!nodes.contains(node)) {
model.register(node); model.register(node);
@ -310,4 +321,30 @@ public class FileTreeNode implements TreeNode, Comparable<FileTreeNode> {
return true; return true;
} }
} }
private static class ReloadOp {
private final Type type;
private final FileTreeNode node;
private final int index;
private ReloadOp(final Type type, final FileTreeNode node, final int index) {
this.type = type;
this.node = node;
this.index = index;
}
private static ReloadOp add(final FileTreeNode n, final int index) {
return new ReloadOp(Type.ADD, n, index);
}
private static ReloadOp remove(final FileTreeNode n, final int index) {
return new ReloadOp(Type.REMOVE, n, index);
}
private enum Type {
ADD, REMOVE
}
}
} }

15
core/src/main/java/com/github/weisj/darklaf/components/filetree/WatchFileTree.java

@ -29,20 +29,21 @@ import javax.swing.filechooser.FileSystemView;
public class WatchFileTree extends FileTree { public class WatchFileTree extends FileTree {
public WatchFileTree() { public WatchFileTree() {
super(null); super();
} }
public WatchFileTree(final File rootFile) { public WatchFileTree(final File... rootFiles) {
super(rootFile, false); super(rootFiles);
} }
public WatchFileTree(final File rootFile, final boolean showHiddenFiles) { public WatchFileTree(final boolean showHiddenFiles, final File... rootFiles) {
super(rootFile, showHiddenFiles); super(showHiddenFiles, rootFiles);
} }
@Override @Override
protected FileTreeModel createModel(final FileSystemView fsv, final File rootFile, final boolean showHiddenFiles) { protected FileTreeModel createModel(final FileSystemView fsv, final boolean showHiddenFiles,
return new WatchFileTreeModel(fsv, rootFile, showHiddenFiles); final File... rootFiles) {
return new WatchFileTreeModel(fsv, showHiddenFiles, rootFiles);
} }
@Override @Override

13
core/src/main/java/com/github/weisj/darklaf/components/filetree/WatchFileTreeModel.java

@ -24,10 +24,7 @@ package com.github.weisj.darklaf.components.filetree;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.*; import java.nio.file.*;
import java.util.Collections; import java.util.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ScheduledThreadPoolExecutor;
@ -76,12 +73,12 @@ public class WatchFileTreeModel extends FileTreeModel {
super(fileSystemView); super(fileSystemView);
} }
public WatchFileTreeModel(final FileSystemView fileSystemView, final File root, final boolean showHiddenFiles) { public WatchFileTreeModel(final FileSystemView fileSystemView, final boolean showHiddenFiles, final File... roots) {
super(fileSystemView, root, showHiddenFiles); super(fileSystemView, showHiddenFiles, roots);
} }
public WatchFileTreeModel(final FileSystemView fileSystemView, final Path root, final boolean showHiddenFiles) { public WatchFileTreeModel(final FileSystemView fileSystemView, final boolean showHiddenFiles, final Path... roots) {
super(fileSystemView, root, showHiddenFiles); super(fileSystemView, showHiddenFiles, roots);
} }
@Override @Override

2
core/src/test/java/ui/tree/FileTreeDemo.java

@ -42,7 +42,7 @@ public class FileTreeDemo implements ComponentDemo {
@Override @Override
public JComponent createComponent() { public JComponent createComponent() {
FileTree tree = new WatchFileTree(null, false); FileTree tree = new WatchFileTree(false);
DemoPanel panel = new DemoPanel(new OverlayScrollPane(tree), new BorderLayout(), 0); DemoPanel panel = new DemoPanel(new OverlayScrollPane(tree), new BorderLayout(), 0);
JPanel controlPanel = panel.addControls(); JPanel controlPanel = panel.addControls();
controlPanel.add(new JCheckBox("Show hidden files") { controlPanel.add(new JCheckBox("Show hidden files") {

Loading…
Cancel
Save