mirror of https://github.com/weisJ/darklaf.git
weisj
4 years ago
11 changed files with 1025 additions and 40 deletions
@ -0,0 +1,74 @@
|
||||
/* |
||||
* MIT License |
||||
* |
||||
* Copyright (c) 2020 Jannis Weis |
||||
* |
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and |
||||
* associated documentation files (the "Software"), to deal in the Software without restriction, |
||||
* including without limitation the rights to use, copy, modify, merge, publish, distribute, |
||||
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is |
||||
* furnished to do so, subject to the following conditions: |
||||
* |
||||
* The above copyright notice and this permission notice shall be included in all copies or |
||||
* substantial portions of the Software. |
||||
* |
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT |
||||
* NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, |
||||
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||||
* |
||||
*/ |
||||
package com.github.weisj.darklaf.components.filetree; |
||||
|
||||
import java.io.File; |
||||
|
||||
import javax.swing.*; |
||||
import javax.swing.filechooser.FileSystemView; |
||||
import javax.swing.tree.TreeModel; |
||||
|
||||
public class FileTree extends JTree { |
||||
|
||||
public FileTree() { |
||||
this(null); |
||||
} |
||||
|
||||
public FileTree(final File rootFile) { |
||||
this(rootFile, false); |
||||
} |
||||
|
||||
public FileTree(final File rootFile, final boolean showHiddenFiles) { |
||||
FileSystemView fileSystemView = FileSystemView.getFileSystemView(); |
||||
setFileTreeModel(createModel(fileSystemView, rootFile, showHiddenFiles)); |
||||
setCellRenderer(new FileTreeCellRenderer(fileSystemView)); |
||||
setRootVisible(false); |
||||
} |
||||
|
||||
protected FileTreeModel createModel(final FileSystemView fsv, final File rootFile, final boolean showHiddenFiles) { |
||||
return new FileTreeModel(fsv, rootFile, showHiddenFiles); |
||||
} |
||||
|
||||
public boolean isShowHiddenFiles() { |
||||
return getModel().isShowHiddenFiles(); |
||||
} |
||||
|
||||
public void setShowHiddenFiles(final boolean showHiddenFiles) { |
||||
getModel().setShowHiddenFiles(showHiddenFiles); |
||||
} |
||||
|
||||
@Override |
||||
public FileTreeModel getModel() { |
||||
return (FileTreeModel) super.getModel(); |
||||
} |
||||
|
||||
public void setFileTreeModel(final FileTreeModel fileTreeModel) { |
||||
super.setModel(fileTreeModel); |
||||
} |
||||
|
||||
@Override |
||||
public void setModel(final TreeModel newModel) {} |
||||
|
||||
public void reload() { |
||||
getModel().reload(); |
||||
} |
||||
} |
@ -0,0 +1,49 @@
|
||||
/* |
||||
* MIT License |
||||
* |
||||
* Copyright (c) 2020 Jannis Weis |
||||
* |
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and |
||||
* associated documentation files (the "Software"), to deal in the Software without restriction, |
||||
* including without limitation the rights to use, copy, modify, merge, publish, distribute, |
||||
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is |
||||
* furnished to do so, subject to the following conditions: |
||||
* |
||||
* The above copyright notice and this permission notice shall be included in all copies or |
||||
* substantial portions of the Software. |
||||
* |
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT |
||||
* NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, |
||||
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||||
* |
||||
*/ |
||||
package com.github.weisj.darklaf.components.filetree; |
||||
|
||||
import java.awt.*; |
||||
import java.nio.file.Path; |
||||
|
||||
import javax.swing.*; |
||||
import javax.swing.filechooser.FileSystemView; |
||||
import javax.swing.tree.DefaultTreeCellRenderer; |
||||
|
||||
public class FileTreeCellRenderer extends DefaultTreeCellRenderer { |
||||
|
||||
private final FileSystemView fsv; |
||||
|
||||
public FileTreeCellRenderer(final FileSystemView fileSystemView) { |
||||
this.fsv = fileSystemView; |
||||
} |
||||
|
||||
@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(); |
||||
if (f != null) { |
||||
setIcon(fsv.getSystemIcon(f.toFile())); |
||||
setText(fsv.getSystemDisplayName(f.toFile())); |
||||
} |
||||
return this; |
||||
} |
||||
} |
@ -0,0 +1,89 @@
|
||||
/* |
||||
* MIT License |
||||
* |
||||
* Copyright (c) 2020 Jannis Weis |
||||
* |
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and |
||||
* associated documentation files (the "Software"), to deal in the Software without restriction, |
||||
* including without limitation the rights to use, copy, modify, merge, publish, distribute, |
||||
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is |
||||
* furnished to do so, subject to the following conditions: |
||||
* |
||||
* The above copyright notice and this permission notice shall be included in all copies or |
||||
* substantial portions of the Software. |
||||
* |
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT |
||||
* NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, |
||||
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||||
* |
||||
*/ |
||||
package com.github.weisj.darklaf.components.filetree; |
||||
|
||||
import java.io.File; |
||||
import java.nio.file.Path; |
||||
|
||||
import javax.swing.filechooser.FileSystemView; |
||||
import javax.swing.tree.DefaultTreeModel; |
||||
|
||||
public class FileTreeModel extends DefaultTreeModel { |
||||
|
||||
protected final FileSystemView fsv; |
||||
protected boolean showHiddenFiles; |
||||
|
||||
public FileTreeModel(final FileSystemView fileSystemView) { |
||||
this(fileSystemView, (Path) null, false); |
||||
} |
||||
|
||||
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 Path root, final boolean showHiddenFiles) { |
||||
super(null); |
||||
init(); |
||||
this.showHiddenFiles = showHiddenFiles; |
||||
this.fsv = fileSystemView; |
||||
this.root = createRoot(root); |
||||
} |
||||
|
||||
protected void init() {} |
||||
|
||||
protected FileTreeNode createRoot(final Path root) { |
||||
if (root == null) { |
||||
return new FileTreeNode.RootNode(this); |
||||
} else { |
||||
return createNode(null, root); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void reload() { |
||||
getRoot().reload(); |
||||
} |
||||
|
||||
@Override |
||||
public FileTreeNode getRoot() { |
||||
return (FileTreeNode) super.getRoot(); |
||||
} |
||||
|
||||
public void setShowHiddenFiles(final boolean showHiddenFiles) { |
||||
if (showHiddenFiles == this.showHiddenFiles) |
||||
return; |
||||
this.showHiddenFiles = showHiddenFiles; |
||||
reload(); |
||||
} |
||||
|
||||
public boolean isShowHiddenFiles() { |
||||
return showHiddenFiles; |
||||
} |
||||
|
||||
protected FileTreeNode createNode(final FileTreeNode parent, final Path file) { |
||||
return new FileTreeNode(parent, file, this); |
||||
} |
||||
|
||||
protected void register(final FileTreeNode node) {} |
||||
|
||||
protected void unregister(final FileTreeNode node) {} |
||||
} |
@ -0,0 +1,313 @@
|
||||
/* |
||||
* MIT License |
||||
* |
||||
* Copyright (c) 2020 Jannis Weis |
||||
* |
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and |
||||
* associated documentation files (the "Software"), to deal in the Software without restriction, |
||||
* including without limitation the rights to use, copy, modify, merge, publish, distribute, |
||||
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is |
||||
* furnished to do so, subject to the following conditions: |
||||
* |
||||
* The above copyright notice and this permission notice shall be included in all copies or |
||||
* substantial portions of the Software. |
||||
* |
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT |
||||
* NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, |
||||
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||||
* |
||||
*/ |
||||
package com.github.weisj.darklaf.components.filetree; |
||||
|
||||
import java.io.IOException; |
||||
import java.nio.file.*; |
||||
import java.util.*; |
||||
import java.util.concurrent.atomic.AtomicReference; |
||||
import java.util.function.Consumer; |
||||
import java.util.function.Function; |
||||
import java.util.stream.Stream; |
||||
|
||||
import javax.swing.*; |
||||
import javax.swing.tree.TreeNode; |
||||
|
||||
public class FileTreeNode implements TreeNode, Comparable<FileTreeNode> { |
||||
|
||||
protected final FileTreeNode parent; |
||||
protected final FileTreeModel model; |
||||
protected final Path file; |
||||
protected AtomicReference<List<FileTreeNode>> children; |
||||
protected WatchKey watchKey; |
||||
|
||||
public FileTreeNode(final FileTreeNode parent, final Path file, final FileTreeModel model) { |
||||
this.model = model; |
||||
this.file = file; |
||||
this.parent = parent; |
||||
this.children = new AtomicReference<>(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean equals(final Object o) { |
||||
if (this == o) |
||||
return true; |
||||
if (o == null || getClass() != o.getClass()) |
||||
return false; |
||||
FileTreeNode that = (FileTreeNode) o; |
||||
return Objects.equals(file, that.file); |
||||
} |
||||
|
||||
@Override |
||||
public int hashCode() { |
||||
return file != null ? file.hashCode() : 0; |
||||
} |
||||
|
||||
public Path getFile() { |
||||
return file; |
||||
} |
||||
|
||||
private int addSorted(final List<FileTreeNode> nodes, final FileTreeNode node) { |
||||
int index = Collections.binarySearch(nodes, node); |
||||
if (index < 0) |
||||
index = ~index; |
||||
nodes.add(index, node); |
||||
model.register(node); |
||||
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) { |
||||
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}); |
||||
} |
||||
|
||||
public void reload() { |
||||
reload(Integer.MAX_VALUE); |
||||
} |
||||
|
||||
protected void reload(final int depth) { |
||||
if (depth < 0) |
||||
return; |
||||
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); |
||||
} |
||||
} else { |
||||
if (isHidden(node.file)) { |
||||
remove(fileList, node); |
||||
} else if (!fileList.contains(node)) { |
||||
add(fileList, node); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
}, () -> { |
||||
if (depth > 0) |
||||
fileList.forEach(n -> n.reload(depth - 1)); |
||||
}); |
||||
} |
||||
|
||||
private List<FileTreeNode> getChildren() { |
||||
return children.updateAndGet(list -> { |
||||
if (list != null) { |
||||
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); |
||||
}); |
||||
return fileList; |
||||
}); |
||||
} |
||||
|
||||
private boolean isHidden(final Path p) { |
||||
try { |
||||
return Files.isHidden(p) || p.toFile().isHidden(); |
||||
} catch (IOException e) { |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
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 <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 <T> void doInBackground(final Consumer<Consumer<T>> task, final Consumer<List<T>> processor) { |
||||
doInBackground(task, processor, () -> { |
||||
}); |
||||
} |
||||
|
||||
private <T> void doInBackground(final Consumer<Consumer<T>> task, final Consumer<List<T>> processor, |
||||
final Runnable doneTask) { |
||||
SwingWorker<Void, T> worker = new SwingWorker<Void, T>() { |
||||
@Override |
||||
public Void doInBackground() { |
||||
task.accept(this::publish); |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
protected void process(final List<T> chunks) { |
||||
processor.accept(chunks); |
||||
} |
||||
|
||||
@Override |
||||
protected void done() { |
||||
doneTask.run(); |
||||
} |
||||
}; |
||||
worker.execute(); |
||||
} |
||||
|
||||
@Override |
||||
public TreeNode getChildAt(final int childIndex) { |
||||
return getChildren().get(childIndex); |
||||
} |
||||
|
||||
@Override |
||||
public int getChildCount() { |
||||
return getChildren().size(); |
||||
} |
||||
|
||||
@Override |
||||
public TreeNode getParent() { |
||||
return parent; |
||||
} |
||||
|
||||
@Override |
||||
public int getIndex(final TreeNode node) { |
||||
if (node == null) { |
||||
throw new IllegalArgumentException("argument is null"); |
||||
} |
||||
if (!isNodeChild(node)) { |
||||
return -1; |
||||
} |
||||
return getChildren().indexOf(node); |
||||
} |
||||
|
||||
@Override |
||||
public boolean getAllowsChildren() { |
||||
return Files.isDirectory(file); |
||||
} |
||||
|
||||
@Override |
||||
public boolean isLeaf() { |
||||
return getChildren().size() == 0; |
||||
} |
||||
|
||||
@Override |
||||
public Enumeration<? extends TreeNode> children() { |
||||
return Collections.enumeration(children.get()); |
||||
} |
||||
|
||||
public boolean isNodeChild(final TreeNode aNode) { |
||||
if (aNode == null) |
||||
return false; |
||||
return (aNode.getParent() == this); |
||||
} |
||||
|
||||
@Override |
||||
public int compareTo(final FileTreeNode o) { |
||||
if (o == null) |
||||
return -1; |
||||
boolean thisDir = Files.isDirectory(file); |
||||
boolean oDir = Files.isDirectory(o.file); |
||||
if (thisDir == oDir) { |
||||
return file.compareTo(o.file); |
||||
} else { |
||||
return thisDir ? -1 : 1; |
||||
} |
||||
} |
||||
|
||||
public static class RootNode extends FileTreeNode { |
||||
|
||||
public RootNode(final FileTreeModel model) { |
||||
super(null, null, model); |
||||
List<FileTreeNode> nodes = new ArrayList<>(); |
||||
FileSystems.getDefault().getRootDirectories().forEach(p -> { |
||||
FileTreeNode node = model.createNode(RootNode.this, p); |
||||
model.register(node); |
||||
nodes.add(node); |
||||
}); |
||||
children.set(nodes); |
||||
} |
||||
|
||||
@Override |
||||
protected void reload(final int depth) { |
||||
if (depth < 0) |
||||
return; |
||||
List<FileTreeNode> nodes = children.get(); |
||||
FileSystems.getDefault().getRootDirectories().forEach(p -> { |
||||
FileTreeNode node = model.createNode(this, p); |
||||
if (!nodes.contains(node)) { |
||||
model.register(node); |
||||
nodes.add(node); |
||||
} |
||||
}); |
||||
nodes.removeIf(n -> { |
||||
if (Files.notExists(n.file)) { |
||||
model.unregister(n); |
||||
return true; |
||||
} |
||||
return false; |
||||
}); |
||||
|
||||
if (depth > 0) |
||||
children.get().forEach(n -> n.reload(depth - 1)); |
||||
} |
||||
|
||||
@Override |
||||
public boolean isLeaf() { |
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
public boolean getAllowsChildren() { |
||||
return true; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,80 @@
|
||||
/* |
||||
* MIT License |
||||
* |
||||
* Copyright (c) 2020 Jannis Weis |
||||
* |
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and |
||||
* associated documentation files (the "Software"), to deal in the Software without restriction, |
||||
* including without limitation the rights to use, copy, modify, merge, publish, distribute, |
||||
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is |
||||
* furnished to do so, subject to the following conditions: |
||||
* |
||||
* The above copyright notice and this permission notice shall be included in all copies or |
||||
* substantial portions of the Software. |
||||
* |
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT |
||||
* NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, |
||||
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||||
* |
||||
*/ |
||||
package com.github.weisj.darklaf.components.filetree; |
||||
|
||||
import java.io.File; |
||||
|
||||
import javax.swing.*; |
||||
import javax.swing.filechooser.FileSystemView; |
||||
|
||||
public class WatchFileTree extends FileTree { |
||||
|
||||
public WatchFileTree() { |
||||
super(null); |
||||
} |
||||
|
||||
public WatchFileTree(final File rootFile) { |
||||
super(rootFile, false); |
||||
} |
||||
|
||||
public WatchFileTree(final File rootFile, final boolean showHiddenFiles) { |
||||
super(rootFile, showHiddenFiles); |
||||
} |
||||
|
||||
@Override |
||||
protected FileTreeModel createModel(final FileSystemView fsv, final File rootFile, final boolean showHiddenFiles) { |
||||
return new WatchFileTreeModel(fsv, rootFile, showHiddenFiles); |
||||
} |
||||
|
||||
@Override |
||||
public void setFileTreeModel(final FileTreeModel fileTreeModel) { |
||||
if (getModel() == fileTreeModel) |
||||
return; |
||||
if (getModel() instanceof WatchFileTreeModel) { |
||||
((WatchFileTreeModel) getModel()).stopWatching(); |
||||
} |
||||
if (isVisible() && fileTreeModel instanceof WatchFileTreeModel) { |
||||
((WatchFileTreeModel) fileTreeModel).startWatching(); |
||||
} |
||||
super.setFileTreeModel(fileTreeModel); |
||||
} |
||||
|
||||
@Override |
||||
public void addNotify() { |
||||
super.addNotify(); |
||||
SwingUtilities.invokeLater(() -> { |
||||
if (getModel() instanceof WatchFileTreeModel) { |
||||
((WatchFileTreeModel) getModel()).startWatching(); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
@Override |
||||
public void removeNotify() { |
||||
super.removeNotify(); |
||||
SwingUtilities.invokeLater(() -> { |
||||
if (getModel() instanceof WatchFileTreeModel) { |
||||
((WatchFileTreeModel) getModel()).stopWatching(); |
||||
} |
||||
}); |
||||
} |
||||
} |
@ -0,0 +1,187 @@
|
||||
/* |
||||
* MIT License |
||||
* |
||||
* Copyright (c) 2020 Jannis Weis |
||||
* |
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and |
||||
* associated documentation files (the "Software"), to deal in the Software without restriction, |
||||
* including without limitation the rights to use, copy, modify, merge, publish, distribute, |
||||
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is |
||||
* furnished to do so, subject to the following conditions: |
||||
* |
||||
* The above copyright notice and this permission notice shall be included in all copies or |
||||
* substantial portions of the Software. |
||||
* |
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT |
||||
* NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, |
||||
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||||
* |
||||
*/ |
||||
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.concurrent.ScheduledExecutorService; |
||||
import java.util.concurrent.ScheduledFuture; |
||||
import java.util.concurrent.ScheduledThreadPoolExecutor; |
||||
import java.util.concurrent.TimeUnit; |
||||
import java.util.concurrent.atomic.AtomicBoolean; |
||||
import java.util.logging.Logger; |
||||
|
||||
import javax.swing.filechooser.FileSystemView; |
||||
|
||||
import com.github.weisj.darklaf.util.LogUtil; |
||||
|
||||
public class WatchFileTreeModel extends FileTreeModel { |
||||
|
||||
private static final Logger LOGGER = LogUtil.getLogger(WatchFileTreeModel.class); |
||||
private static final ScheduledExecutorService scheduler = createScheduler(); |
||||
private WatchService watchService; |
||||
private Map<Watchable, FileTreeNode> nodeMap; |
||||
private Object lock; |
||||
|
||||
private final AtomicBoolean isScheduled = new AtomicBoolean(false); |
||||
private ScheduledFuture<?> watchTask; |
||||
|
||||
private static WatchService createWatchService() { |
||||
WatchService ws = null; |
||||
try { |
||||
ws = FileSystems.getDefault().newWatchService(); |
||||
} catch (IOException e) { |
||||
e.printStackTrace(); |
||||
} |
||||
return ws; |
||||
} |
||||
|
||||
private static ScheduledExecutorService createScheduler() { |
||||
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1, r -> { |
||||
final Thread thread = new Thread(r, "File Tree Watch Thread"); |
||||
thread.setDaemon(true); |
||||
thread.setPriority(Thread.MIN_PRIORITY); |
||||
return thread; |
||||
}); |
||||
executor.setContinueExistingPeriodicTasksAfterShutdownPolicy(false); |
||||
executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); |
||||
return executor; |
||||
} |
||||
|
||||
public WatchFileTreeModel(final FileSystemView fileSystemView) { |
||||
super(fileSystemView); |
||||
} |
||||
|
||||
public WatchFileTreeModel(final FileSystemView fileSystemView, final File root, final boolean showHiddenFiles) { |
||||
super(fileSystemView, root, showHiddenFiles); |
||||
} |
||||
|
||||
public WatchFileTreeModel(final FileSystemView fileSystemView, final Path root, final boolean showHiddenFiles) { |
||||
super(fileSystemView, root, showHiddenFiles); |
||||
} |
||||
|
||||
@Override |
||||
protected void init() { |
||||
lock = new Object(); |
||||
watchService = createWatchService(); |
||||
nodeMap = Collections.synchronizedMap(new HashMap<>()); |
||||
} |
||||
|
||||
private Object getLock() { |
||||
return lock; |
||||
} |
||||
|
||||
protected WatchService getWatchService() { |
||||
return watchService; |
||||
} |
||||
|
||||
protected Map<Watchable, FileTreeNode> getNodeMap() { |
||||
return nodeMap; |
||||
} |
||||
|
||||
public void startWatching() { |
||||
if (watchTask != null) |
||||
return; |
||||
isScheduled.set(true); |
||||
watchTask = scheduler.schedule(this::watch, 0, TimeUnit.SECONDS); |
||||
} |
||||
|
||||
public void stopWatching() { |
||||
if (watchTask != null) { |
||||
isScheduled.set(false); |
||||
watchTask.cancel(true); |
||||
watchTask = null; |
||||
} |
||||
} |
||||
|
||||
private void watch() { |
||||
while (isScheduled.get()) { |
||||
WatchKey key; |
||||
try { |
||||
key = watchService.take(); |
||||
} catch (InterruptedException x) { |
||||
x.printStackTrace(); |
||||
return; |
||||
} |
||||
|
||||
FileTreeNode parent = getNodeMap().get(key.watchable()); |
||||
if (parent != null) { |
||||
LOGGER.fine(() -> "Event for \"" + parent.file + "\""); |
||||
if (parent.parent != null) { |
||||
parent.parent.reload(1); |
||||
} else { |
||||
parent.reload(0); |
||||
} |
||||
} |
||||
|
||||
List<WatchEvent<?>> watchEventList = key.pollEvents(); |
||||
for (WatchEvent<?> event : watchEventList) { |
||||
WatchEvent.Kind<?> kind = event.kind(); |
||||
|
||||
Path path = (Path) event.context(); |
||||
if (kind == StandardWatchEventKinds.OVERFLOW) { |
||||
continue; |
||||
} |
||||
LOGGER.finer("Event Type " + kind.name()); |
||||
FileTreeNode node = getNodeMap().get(((Path) key.watchable()).resolve(path)); |
||||
if (node != null) { |
||||
LOGGER.finer(() -> "Affected node \"" + node.file + "\""); |
||||
node.reload(0); |
||||
} |
||||
} |
||||
|
||||
key.reset(); |
||||
} |
||||
} |
||||
|
||||
protected void register(final FileTreeNode node) { |
||||
synchronized (getLock()) { |
||||
WatchService ws = getWatchService(); |
||||
if (ws == null || !Files.isDirectory(node.file)) |
||||
return; |
||||
if (getNodeMap().containsKey(node.file)) |
||||
return; |
||||
try { |
||||
LOGGER.finer(() -> "Register watch service for \"" + node.file + "\""); |
||||
node.watchKey = node.file.register(ws, StandardWatchEventKinds.ENTRY_CREATE, |
||||
StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY); |
||||
getNodeMap().put(node.file, node); |
||||
} catch (IOException ignored) { |
||||
} |
||||
} |
||||
} |
||||
|
||||
protected void unregister(final FileTreeNode node) { |
||||
synchronized (getLock()) { |
||||
if (node.watchKey == null) |
||||
return; |
||||
LOGGER.finer(() -> "Unregister watch service for \"" + node.file + "\""); |
||||
getNodeMap().remove(node.file); |
||||
node.watchKey.cancel(); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,79 @@
|
||||
/* |
||||
* MIT License |
||||
* |
||||
* Copyright (c) 2020 Jannis Weis |
||||
* |
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and |
||||
* associated documentation files (the "Software"), to deal in the Software without restriction, |
||||
* including without limitation the rights to use, copy, modify, merge, publish, distribute, |
||||
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is |
||||
* furnished to do so, subject to the following conditions: |
||||
* |
||||
* The above copyright notice and this permission notice shall be included in all copies or |
||||
* substantial portions of the Software. |
||||
* |
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT |
||||
* NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, |
||||
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||||
* |
||||
*/ |
||||
package ui.tree; |
||||
|
||||
import java.awt.*; |
||||
|
||||
import javax.swing.*; |
||||
|
||||
import ui.ComponentDemo; |
||||
import ui.DemoPanel; |
||||
|
||||
import com.github.weisj.darklaf.components.OverlayScrollPane; |
||||
import com.github.weisj.darklaf.components.filetree.FileTree; |
||||
import com.github.weisj.darklaf.components.filetree.WatchFileTree; |
||||
import com.github.weisj.darklaf.ui.button.ButtonConstants; |
||||
import com.github.weisj.darklaf.ui.tree.DarkTreeUI; |
||||
|
||||
public class FileTreeDemo implements ComponentDemo { |
||||
|
||||
public static void main(final String[] args) { |
||||
ComponentDemo.showDemo(new FileTreeDemo()); |
||||
} |
||||
|
||||
@Override |
||||
public JComponent createComponent() { |
||||
FileTree tree = new WatchFileTree(null, false); |
||||
DemoPanel panel = new DemoPanel(new OverlayScrollPane(tree), new BorderLayout(), 0); |
||||
JPanel controlPanel = panel.addControls(); |
||||
controlPanel.add(new JCheckBox("Show hidden files") { |
||||
{ |
||||
setSelected(tree.isShowHiddenFiles()); |
||||
addActionListener(e -> tree.setShowHiddenFiles(isSelected())); |
||||
} |
||||
}); |
||||
controlPanel.add(new JButton("Reload") { |
||||
{ |
||||
putClientProperty(ButtonConstants.KEY_THIN, true); |
||||
addActionListener(e -> tree.reload()); |
||||
} |
||||
}); |
||||
controlPanel = panel.addControls(); |
||||
controlPanel.add(new JLabel(DarkTreeUI.KEY_LINE_STYLE + ":", JLabel.RIGHT)); |
||||
controlPanel.add(new JComboBox<String>() { |
||||
{ |
||||
addItem(DarkTreeUI.STYLE_LINE); |
||||
addItem(DarkTreeUI.STYLE_DASHED); |
||||
addItem(DarkTreeUI.STYLE_NONE); |
||||
setSelectedItem(DarkTreeUI.STYLE_LINE); |
||||
addItemListener(e -> tree.putClientProperty(DarkTreeUI.KEY_LINE_STYLE, e.getItem())); |
||||
} |
||||
}); |
||||
return panel; |
||||
} |
||||
|
||||
@Override |
||||
public String getTitle() { |
||||
return "File Tree Demo"; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,125 @@
|
||||
/* |
||||
* MIT License |
||||
* |
||||
* Copyright (c) 2020 Jannis Weis |
||||
* |
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and |
||||
* associated documentation files (the "Software"), to deal in the Software without restriction, |
||||
* including without limitation the rights to use, copy, modify, merge, publish, distribute, |
||||
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is |
||||
* furnished to do so, subject to the following conditions: |
||||
* |
||||
* The above copyright notice and this permission notice shall be included in all copies or |
||||
* substantial portions of the Software. |
||||
* |
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT |
||||
* NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, |
||||
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
||||
* |
||||
*/ |
||||
package com.github.weisj.darklaf.util; |
||||
|
||||
import java.util.function.Consumer; |
||||
import java.util.function.Function; |
||||
import java.util.function.Predicate; |
||||
import java.util.function.Supplier; |
||||
|
||||
public class Lambdas { |
||||
|
||||
private Lambdas() {} |
||||
|
||||
public static <T, K, E extends Throwable> Function<T, K> orDefault(final CheckedFunction<T, K, E> wrappee, |
||||
final K fallback) { |
||||
return t -> { |
||||
try { |
||||
return wrappee.apply(t); |
||||
} catch (final Throwable e) { |
||||
return fallback; |
||||
} |
||||
}; |
||||
} |
||||
|
||||
public static <T, E extends Throwable> Supplier<T> orDefault(final CheckedSupplier<T, E> wrappee, |
||||
final T fallback) { |
||||
return () -> { |
||||
try { |
||||
return wrappee.get(); |
||||
} catch (final Throwable e) { |
||||
return fallback; |
||||
} |
||||
}; |
||||
} |
||||
|
||||
public static <T, E extends Throwable> Predicate<T> orDefault(final CheckedPredicate<T, E> wrappee, |
||||
final boolean fallback) { |
||||
return t -> { |
||||
try { |
||||
return wrappee.test(t); |
||||
} catch (final Throwable e) { |
||||
return fallback; |
||||
} |
||||
}; |
||||
} |
||||
|
||||
public static <T, K, E extends Throwable> Function<T, K> wrap(final CheckedFunction<T, K, E> wrappee) { |
||||
return t -> { |
||||
try { |
||||
return wrappee.apply(t); |
||||
} catch (final Throwable e) { |
||||
throw new RuntimeException(e); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
public static <T, E extends Throwable> Consumer<T> wrap(final CheckedConsumer<T, E> wrappee) { |
||||
return t -> { |
||||
try { |
||||
wrappee.accept(t); |
||||
} catch (final Throwable e) { |
||||
throw new RuntimeException(e); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
public static <T, E extends Throwable> Supplier<T> wrap(final CheckedSupplier<T, E> wrappee) { |
||||
return () -> { |
||||
try { |
||||
return wrappee.get(); |
||||
} catch (final Throwable e) { |
||||
throw new RuntimeException(e); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
public static <T, E extends Throwable> Predicate<T> wrap(final CheckedPredicate<T, E> wrappee) { |
||||
return t -> { |
||||
try { |
||||
return wrappee.test(t); |
||||
} catch (final Throwable e) { |
||||
throw new RuntimeException(e); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
public interface CheckedFunction<T, K, E extends Throwable> { |
||||
|
||||
K apply(final T value) throws E; |
||||
} |
||||
|
||||
public interface CheckedConsumer<T, E extends Throwable> { |
||||
|
||||
void accept(final T value) throws E; |
||||
} |
||||
|
||||
public interface CheckedSupplier<T, E extends Throwable> { |
||||
|
||||
T get() throws E; |
||||
} |
||||
|
||||
public interface CheckedPredicate<T, E extends Throwable> { |
||||
|
||||
boolean test(final T value) throws E; |
||||
} |
||||
} |
Loading…
Reference in new issue