Browse Source

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

pull/214/head
weisj 4 years ago
parent
commit
564b0ebe79
  1. 19
      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. 153
      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

19
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.util.Alignment;
import com.github.weisj.darklaf.util.DarkUIUtil;
public class RotatableIconAnimator extends Timer {
@ -43,8 +44,6 @@ public class RotatableIconAnimator extends Timer {
super(100, null);
if (icon == null)
throw new IllegalArgumentException("Icon is null");
if (parent == null)
throw new IllegalArgumentException("Component is null");
addActionListener(this::onAction);
setRepeats(true);
this.icon = icon;
@ -53,16 +52,26 @@ public class RotatableIconAnimator extends Timer {
}
public void resume() {
start();
if (!isRunning())
start();
}
public void onAction(final ActionEvent e) {
icon.setRotation(Math.PI * 2 * (((double) frame) / frameCount));
parent.repaint();
repaint();
frame = (frame + 1) % frameCount;
}
protected void repaint() {
DarkUIUtil.repaint(getParent());
}
public JComponent getParent() {
return parent;
}
public void suspend() {
stop();
if (isRunning())
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;
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.filechooser.FileSystemView;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
public class FileTree extends JTree {
public FileTree() {
this(null);
this((File[]) null);
}
public FileTree(final File rootFile) {
this(rootFile, false);
public FileTree(final File... rootFile) {
this(false, rootFile);
}
public FileTree(final File rootFile, final boolean showHiddenFiles) {
public FileTree(final boolean showHiddenFiles, final File... rootFiles) {
FileSystemView fileSystemView = FileSystemView.getFileSystemView();
setFileTreeModel(createModel(fileSystemView, rootFile, showHiddenFiles));
setFileTreeModel(createModel(fileSystemView, showHiddenFiles, rootFiles));
setCellRenderer(new FileTreeCellRenderer(fileSystemView));
setRootVisible(false);
}
protected FileTreeModel createModel(final FileSystemView fsv, final File rootFile, final boolean showHiddenFiles) {
return new FileTreeModel(fsv, rootFile, showHiddenFiles);
protected FileTreeModel createModel(final FileSystemView fsv, final boolean showHiddenFiles,
final File... rootFiles) {
return new FileTreeModel(fsv, showHiddenFiles, rootFiles);
}
public boolean isShowHiddenFiles() {
@ -71,4 +79,19 @@ public class FileTree extends JTree {
public void 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
public Component getTreeCellRendererComponent(final JTree tree, final Object value, final boolean selected,
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) {
setIcon(fsv.getSystemIcon(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.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
import javax.swing.filechooser.FileSystemView;
import javax.swing.tree.DefaultTreeModel;
@ -33,28 +35,29 @@ public class FileTreeModel extends DefaultTreeModel {
protected boolean showHiddenFiles;
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) {
this(fileSystemView, root != null ? root.toPath() : null, showHiddenFiles);
public FileTreeModel(final FileSystemView fileSystemView, final boolean showHiddenFiles, final File... roots) {
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);
init();
this.showHiddenFiles = showHiddenFiles;
this.fsv = fileSystemView;
this.root = createRoot(root);
this.root = createRoot(roots);
}
protected void init() {}
protected FileTreeNode createRoot(final Path root) {
if (root == null) {
return new FileTreeNode.RootNode(this);
protected FileTreeNode createRoot(final Path... roots) {
if (roots == null || roots.length != 1) {
return new FileTreeNode.RootNode(this, roots == null ? Collections.emptyList() : Arrays.asList(roots));
} else {
return createNode(null, root);
return createNode(null, roots[0]);
}
}

153
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.nio.file.*;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Stream;
import javax.swing.*;
@ -37,6 +37,7 @@ public class FileTreeNode implements TreeNode, Comparable<FileTreeNode> {
protected final FileTreeNode parent;
protected final FileTreeModel model;
protected final Path file;
protected AtomicInteger taskCount = new AtomicInteger();
protected AtomicReference<List<FileTreeNode>> children;
protected WatchKey watchKey;
@ -75,18 +76,13 @@ public class FileTreeNode implements TreeNode, Comparable<FileTreeNode> {
return index;
}
private void add(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) {
private int remove(final List<FileTreeNode> nodes, final FileTreeNode node) {
int index = nodes.indexOf(node);
if (index < 0)
return;
nodes.remove(node);
model.unregister(node);
model.nodesWereRemoved(FileTreeNode.this, new int[] {index}, new Object[] {node});
if (index >= 0) {
nodes.remove(node);
model.unregister(node);
}
return index;
}
public void reload() {
@ -99,30 +95,45 @@ public class FileTreeNode implements TreeNode, Comparable<FileTreeNode> {
if (children.get() == null)
return;
List<FileTreeNode> fileList = children.get();
doInBackground((Consumer<FileTreeNode> proc) -> {
traverseChildren(this::toNodes).accept(proc);
fileList.stream().filter(n -> Files.notExists(n.file)).forEach(proc);
}, chunks -> {
for (FileTreeNode node : chunks) {
if (Files.notExists(node.file)) {
remove(fileList, node);
} else {
if (model.showHiddenFiles) {
if (!fileList.contains(node)) {
add(fileList, node);
}
this.<ReloadOp>doInBackground(pub -> {
taskCount.getAndIncrement();
this.traverseChildren(s -> {
Stream.concat(s.map(this::toNode), fileList.stream()).map(n -> {
if (Files.notExists(n.file)) {
return ReloadOp.remove(n, remove(fileList, n));
} else {
if (isHidden(node.file)) {
remove(fileList, node);
} else if (!fileList.contains(node)) {
add(fileList, node);
if (model.showHiddenFiles) {
if (!fileList.contains(n)) {
return ReloadOp.add(n, addSorted(fileList, n));
}
} else {
if (isHidden(n.file)) {
return ReloadOp.remove(n, remove(fileList, n));
} else if (!fileList.contains(n)) {
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)
fileList.forEach(n -> n.reload(depth - 1));
taskCount.getAndDecrement();
});
}
@ -132,18 +143,14 @@ public class FileTreeNode implements TreeNode, Comparable<FileTreeNode> {
return list;
}
List<FileTreeNode> fileList = Collections.synchronizedList(new ArrayList<>());
doInBackground(
traverseChildren(asNodes(stream -> stream.filter(p -> model.showHiddenFiles || !isHidden(p)))),
chunks -> {
Collections.sort(fileList);
int[] indices = new int[chunks.size()];
int i = 0;
for (FileTreeNode node : chunks) {
indices[i] = addSorted(fileList, node);
i++;
}
model.nodesWereInserted(FileTreeNode.this, indices);
});
this.<Integer>doInBackground(pub -> {
traverseChildren(s -> {
s.filter(p -> model.showHiddenFiles || !isHidden(p)).map(this::toNode)
.map(n -> addSorted(fileList, n)).forEach(pub);
});
}, chunks -> {
model.nodesWereInserted(FileTreeNode.this, chunks.stream().mapToInt(Integer::intValue).toArray());
});
return fileList;
});
}
@ -156,24 +163,17 @@ public class FileTreeNode implements TreeNode, Comparable<FileTreeNode> {
}
}
private Function<Stream<Path>, Stream<FileTreeNode>> asNodes(
final Function<Stream<Path>, Stream<Path>> transformer) {
return stream -> toNodes(transformer.apply(stream));
}
private Stream<FileTreeNode> toNodes(final Stream<Path> stream) {
return stream.map(p -> model.createNode(FileTreeNode.this, p));
private FileTreeNode toNode(final Path path) {
return model.createNode(FileTreeNode.this, path);
}
private <T> Consumer<Consumer<T>> traverseChildren(final Function<Stream<Path>, Stream<T>> transformer) {
return publish -> {
if (Files.isDirectory(file)) {
try (Stream<Path> files = Files.walk(file, 1, FileVisitOption.FOLLOW_LINKS)) {
transformer.apply(files.filter(p -> !file.equals(p)).filter(Files::isReadable)).forEach(publish);
} catch (IOException ignored) {
}
private void traverseChildren(final Consumer<Stream<Path>> consumer) {
if (Files.isDirectory(file)) {
try (Stream<Path> files = Files.walk(file, 1, FileVisitOption.FOLLOW_LINKS)) {
consumer.accept(files.filter(p -> !file.equals(p)).filter(Files::isReadable));
} catch (IOException ignored) {
}
};
}
}
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 RootNode(final FileTreeModel model) {
private final List<Path> roots;
public RootNode(final FileTreeModel model, final List<Path> roots) {
super(null, null, model);
this.roots = roots;
List<FileTreeNode> nodes = new ArrayList<>();
FileSystems.getDefault().getRootDirectories().forEach(p -> {
createInitialDirectories().forEach(p -> {
FileTreeNode node = model.createNode(RootNode.this, p);
model.register(node);
nodes.add(node);
@ -276,12 +283,16 @@ public class FileTreeNode implements TreeNode, Comparable<FileTreeNode> {
children.set(nodes);
}
protected Iterable<Path> createInitialDirectories() {
return roots.size() > 0 ? roots : FileSystems.getDefault().getRootDirectories();
}
@Override
protected void reload(final int depth) {
if (depth < 0)
return;
List<FileTreeNode> nodes = children.get();
FileSystems.getDefault().getRootDirectories().forEach(p -> {
createInitialDirectories().forEach(p -> {
FileTreeNode node = model.createNode(this, p);
if (!nodes.contains(node)) {
model.register(node);
@ -310,4 +321,30 @@ public class FileTreeNode implements TreeNode, Comparable<FileTreeNode> {
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 WatchFileTree() {
super(null);
super();
}
public WatchFileTree(final File rootFile) {
super(rootFile, false);
public WatchFileTree(final File... rootFiles) {
super(rootFiles);
}
public WatchFileTree(final File rootFile, final boolean showHiddenFiles) {
super(rootFile, showHiddenFiles);
public WatchFileTree(final boolean showHiddenFiles, final File... rootFiles) {
super(showHiddenFiles, rootFiles);
}
@Override
protected FileTreeModel createModel(final FileSystemView fsv, final File rootFile, final boolean showHiddenFiles) {
return new WatchFileTreeModel(fsv, rootFile, showHiddenFiles);
protected FileTreeModel createModel(final FileSystemView fsv, final boolean showHiddenFiles,
final File... rootFiles) {
return new WatchFileTreeModel(fsv, showHiddenFiles, rootFiles);
}
@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.IOException;
import java.nio.file.*;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
@ -76,12 +73,12 @@ public class WatchFileTreeModel extends FileTreeModel {
super(fileSystemView);
}
public WatchFileTreeModel(final FileSystemView fileSystemView, final File root, final boolean showHiddenFiles) {
super(fileSystemView, root, showHiddenFiles);
public WatchFileTreeModel(final FileSystemView fileSystemView, final boolean showHiddenFiles, final File... roots) {
super(fileSystemView, showHiddenFiles, roots);
}
public WatchFileTreeModel(final FileSystemView fileSystemView, final Path root, final boolean showHiddenFiles) {
super(fileSystemView, root, showHiddenFiles);
public WatchFileTreeModel(final FileSystemView fileSystemView, final boolean showHiddenFiles, final Path... roots) {
super(fileSystemView, showHiddenFiles, roots);
}
@Override

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

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

Loading…
Cancel
Save