/*******************************************************************************
* Copyright (c) 2016 Weasis Team and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Nicolas Roduit - initial API and implementation
*******************************************************************************/
package org.weasis.base.explorer;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.ExpandVetoException;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import org.osgi.framework.FrameworkUtil;
import org.osgi.service.prefs.Preferences;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.weasis.base.explorer.list.IDiskFileList;
import org.weasis.base.explorer.list.impl.JIThumbnailListPane;
import org.weasis.core.api.explorer.DataExplorerView;
import org.weasis.core.api.explorer.model.DataExplorerModel;
import org.weasis.core.api.service.BundlePreferences;
import org.weasis.core.ui.docking.PluginTool;
import org.weasis.core.ui.docking.UIManager;
import org.weasis.core.ui.util.DefaultAction;
import org.weasis.core.ui.util.TitleMenuItem;
import bibliothek.gui.dock.common.CLocation;
import bibliothek.gui.dock.common.mode.ExtendedMode;
@SuppressWarnings("serial")
public class DefaultExplorer extends PluginTool implements DataExplorerView {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultExplorer.class);
private static final JIExplorerContext treeContext = new JIExplorerContext();
public static final String BUTTON_NAME = "Explorer"; //$NON-NLS-1$
public static final String NAME = Messages.getString("DefaultExplorer.name"); //$NON-NLS-1$
public static final String P_LAST_DIR = "last.dir"; //$NON-NLS-1$
private static final String PREFERENCE_NODE = "view"; //$NON-NLS-1$
protected FileTreeModel model;
protected TreePath clickedPath;
protected final JIThumbnailListPane<?> jilist;
protected boolean changed;
private final JTree tree;
private JPanel jRootPanel = new JPanel();
public DefaultExplorer(final FileTreeModel model) {
super(BUTTON_NAME, NAME, POSITION.WEST, ExtendedMode.NORMALIZED, PluginTool.Type.EXPLORER, 10);
if (model == null) {
throw new IllegalArgumentException();
}
setDockableWidth(400);
this.tree = new JTree(model);
this.jilist = new JIThumbnailListPane<>();
this.changed = false;
this.model = model;
tree.putClientProperty("JTree.lineStyle", "Angled"); //$NON-NLS-1$ //$NON-NLS-2$
final TreeRenderer renderer = new TreeRenderer();
tree.setCellRenderer(renderer);
tree.addTreeSelectionListener(new JITreeDiskSelectionAdapter());
tree.addTreeExpansionListener(new JITreeDiskExpansionAdapter());
tree.addTreeWillExpandListener(new JITreeDiskWillExpandAdapter());
tree.addMouseListener(new PopupTrigger());
tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
tree.setAlignmentX((float) 0.5);
tree.setShowsRootHandles(false);
tree.setDragEnabled(false);
JScrollPane treePane = new JScrollPane(tree);
JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, treePane, jilist);
splitPane.setOneTouchExpandable(true);
splitPane.setDividerLocation(200);
// Provide minimum sizes for the two components in the split pane
Dimension minimumSize = new Dimension(150, 150);
treePane.setMinimumSize(minimumSize);
treePane.setMinimumSize(minimumSize);
jRootPanel.setLayout(new BorderLayout());
jRootPanel.add(splitPane, BorderLayout.CENTER);
}
@Override
public Component getToolComponent() {
return jRootPanel;
}
protected void iniLastPath() {
Path prefDir = null;
Preferences prefs =
BundlePreferences.getDefaultPreferences(FrameworkUtil.getBundle(this.getClass()).getBundleContext());
if (prefs != null) {
Preferences p = prefs.node(PREFERENCE_NODE);
try {
prefDir = Paths.get(p.get(P_LAST_DIR, System.getProperty("user.home"))); //$NON-NLS-1$
} catch (InvalidPathException e) {
LOGGER.error("Get last dir path", e);
}
}
if (prefDir == null) {
prefDir = Paths.get(System.getProperty("user.home")); //$NON-NLS-1$
}
if (Files.isReadable(prefDir) && Files.isDirectory(prefDir)) {
final TreeNode selectedTreeNode = findNode(prefDir);
if (selectedTreeNode != null) {
expandPaths(prefDir);
}
}
}
protected void saveLastPath() {
Path dir = getCurrentDir();
if (dir != null) {
Preferences prefs =
BundlePreferences.getDefaultPreferences(FrameworkUtil.getBundle(this.getClass()).getBundleContext());
if (prefs != null && Files.isReadable(dir)) {
Preferences p = prefs.node(PREFERENCE_NODE);
BundlePreferences.putStringPreferences(p, P_LAST_DIR, dir.toString());
}
}
}
public static JIExplorerContext getTreeContext() {
return treeContext;
}
@Override
public String toString() {
return NAME;
}
public DefaultMutableTreeNode getTreeNode(final TreePath path) {
return (TreeNode) (path.getLastPathComponent());
}
public TreeNode getSelectedNode() {
final TreePath path = tree.getSelectionPath();
if (path != null) {
return (TreeNode) path.getLastPathComponent();
} else {
return null;
}
}
public Path getCurrentDir() {
final TreePath path = tree.getSelectionPath();
if (path != null) {
return ((TreeNode) path.getLastPathComponent()).getNodePath();
}
return null;
}
public IDiskFileList getJIList() {
return this.jilist;
}
public void updateSelectionPath(Path path) {
final TreePath treePath = tree.getSelectionPath();
final TreeNode node = (TreeNode) treePath.getLastPathComponent();
if (node != null) {
node.setNodePath(path);
getTreeModel().nodeStructureChanged(node);
}
}
public void refresh() {
final TreePath path = tree.getSelectionPath();
final TreeNode node = (TreeNode) path.getLastPathComponent();
node.refresh();
getTreeModel().reload(node);
tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
setBackground(Color.white);
setAlignmentX((float) 0.5);
tree.setShowsRootHandles(false);
}
public final TreeNode findNode(Path dir) {
TreeNode parentNode = (TreeNode) tree.getModel().getRoot();
if (!parentNode.isExplored()) {
parentNode.explore();
}
final Path parentFile = parentNode.getNodePath();
if (parentFile.equals(Paths.get(JIUtility.ROOT_FOLDER))) {
final int count = tree.getModel().getChildCount(parentNode);
for (int i = 0; i < count; i++) {
final Object oneChild = tree.getModel().getChild(parentNode, i);
final Path onePath = ((TreeNode) oneChild).getNodePath();
if (dir.startsWith(onePath)) {
parentNode = (TreeNode) oneChild;
break;
}
}
} else if (!dir.startsWith(parentFile)) {
return null;
}
final Iterator<Path> iter = dir.iterator();
while (iter.hasNext()) {
if (!parentNode.isExplored()) {
parentNode.explore();
}
iter.next();
final int count = tree.getModel().getChildCount(parentNode);
for (int i = 0; i < count; i++) {
final Object oneChild = tree.getModel().getChild(parentNode, i);
final Path onePath = ((TreeNode) oneChild).getNodePath();
if (dir.startsWith(onePath)) {
if (dir.equals(onePath)) {
return (TreeNode) oneChild;
}
parentNode = (TreeNode) oneChild;
break;
}
}
}
return null;
}
public DefaultTreeModel getTreeModel() {
return this.model;
}
public void setTreeModel(final FileTreeModel model) {
this.model = model;
}
final class PopupTrigger extends MouseAdapter {
@Override
public void mousePressed(final MouseEvent evt) {
showPopup(evt);
}
@Override
public void mouseReleased(final MouseEvent evt) {
showPopup(evt);
}
private void showPopup(final MouseEvent evt) {
// Context menu
if (SwingUtilities.isRightMouseButton(evt)) {
JPopupMenu popupMenu = DefaultExplorer.this.buidContexMenu(evt);
if (popupMenu != null) {
popupMenu.show(evt.getComponent(), evt.getX(), evt.getY());
}
}
}
}
private void jTreeDiskValueChanged(final TreeSelectionEvent e) {
final TreeNode selectedTreeNode = (TreeNode) getTreeNode(e.getPath());
// Refresh
if (selectedTreeNode != null) {
final ArrayList<TreeNode> vec = new ArrayList<>(1);
vec.add(selectedTreeNode);
getTreeContext().setSelectedDirNodes(vec, selectedTreeNode);
final Path selectedDir = ((TreeNode) tree.getSelectionPath().getLastPathComponent()).getNodePath();
if (selectedDir != null) {
this.jilist.loadDirectory(selectedDir);
}
}
}
public JPopupMenu buidContexMenu(final MouseEvent e) {
try {
JPopupMenu popupMenu = new JPopupMenu();
TitleMenuItem itemTitle =
new TitleMenuItem(Messages.getString("DefaultExplorer.sel_path"), popupMenu.getInsets()); //$NON-NLS-1$
popupMenu.add(itemTitle);
popupMenu.addSeparator();
final int x = e.getX();
final int y = e.getY();
final TreePath path = tree.getPathForLocation(x, y);
if (path == null) {
return null;
}
JMenuItem menuItem =
new JMenuItem(new DefaultAction(tree.isExpanded(path) ? Messages.getString("DefaultExplorer.collapse") //$NON-NLS-1$
: Messages.getString("DefaultExplorer.expand"), event -> { //$NON-NLS-1$
if (DefaultExplorer.this.clickedPath == null) {
return;
}
if (tree.isExpanded(DefaultExplorer.this.clickedPath)) {
tree.collapsePath(DefaultExplorer.this.clickedPath);
} else {
tree.expandPath(DefaultExplorer.this.clickedPath);
}
}));
popupMenu.add(menuItem);
menuItem = new JMenuItem(new DefaultAction(Messages.getString("DefaultExplorer.refresh"), event -> { //$NON-NLS-1$
repaint();
refresh();
}));
popupMenu.add(menuItem);
popupMenu.addSeparator();
boolean importAction = false;
JMenu scan = new JMenu(Messages.getString("DefaultExplorer.import_to")); //$NON-NLS-1$
JMenu scansub = new JMenu(Messages.getString("DefaultExplorer.import_sub")); //$NON-NLS-1$
synchronized (UIManager.EXPLORER_PLUGINS) {
for (final DataExplorerView dataExplorerView : UIManager.EXPLORER_PLUGINS) {
if (dataExplorerView != DefaultExplorer.this) {
importAction = true;
JMenuItem item = new JMenuItem(new DefaultAction(dataExplorerView.getUIName(), event -> {
final Path selectedDir =
((TreeNode) DefaultExplorer.this.tree.getSelectionPath().getLastPathComponent())
.getNodePath();
if (selectedDir != null) {
dataExplorerView.importFiles(selectedDir.toFile().listFiles(), false);
}
}));
scan.add(item);
item = new JMenuItem(new DefaultAction(dataExplorerView.getUIName(), event -> {
final Path selectedDir =
((TreeNode) DefaultExplorer.this.tree.getSelectionPath().getLastPathComponent())
.getNodePath();
if (selectedDir != null) {
dataExplorerView.importFiles(selectedDir.toFile().listFiles(), true);
}
}));
scansub.add(item);
}
}
}
if (importAction) {
popupMenu.add(scan);
popupMenu.add(scansub);
}
tree.setSelectionPath(path);
tree.scrollPathToVisible(path);
DefaultExplorer.this.clickedPath = path;
return popupMenu;
} catch (final Exception exp) {
} finally {
e.consume();
}
return null;
}
/**
* Expands the tree to the given path.
*/
public final void expandPaths(Path selectedDir) {
final TreeNode node = findNode(selectedDir);
if (node == null) {
return;
}
final ArrayList<TreeNode> list = new ArrayList<>(1);
list.add(node);
getTreeContext().setSelectedDirNodes(list, 0);
final TreePath newPath = new TreePath(node.getPath());
if (!tree.isExpanded(newPath)) {
tree.expandPath(newPath);
}
tree.setSelectionPath(newPath);
tree.scrollPathToVisible(newPath);
}
final class JITreeDiskWillExpandAdapter implements javax.swing.event.TreeWillExpandListener {
@Override
public void treeWillExpand(final TreeExpansionEvent e) throws ExpandVetoException {
final TreePath path = e.getPath();
final TreeNode selectedNode = (TreeNode) path.getLastPathComponent();
if (!selectedNode.isExplored()) {
selectedNode.explore();
}
}
@Override
public void treeWillCollapse(final TreeExpansionEvent e) {
// Do nothing
}
}
final class JITreeDiskExpansionAdapter implements javax.swing.event.TreeExpansionListener {
@Override
public void treeExpanded(final TreeExpansionEvent e) {
final Thread runner = new Thread() {
@Override
public void run() {
SwingUtilities.invokeLater(() -> Optional.ofNullable(DefaultExplorer.this.tree.getSelectionPath())
.ifPresent(DefaultExplorer.this.tree::setSelectionPath));
}
};
runner.start();
}
@Override
public void treeCollapsed(final TreeExpansionEvent e) {
// Do nothing
}
}
final class JITreeDiskSelectionAdapter implements javax.swing.event.TreeSelectionListener {
@Override
public void valueChanged(final TreeSelectionEvent e) {
final Thread runner = new Thread() {
@Override
public void run() {
SwingUtilities.invokeLater(() -> jTreeDiskValueChanged(e));
}
};
runner.start();
}
}
@Override
public void dispose() {
super.closeDockable();
}
@Override
public DataExplorerModel getDataExplorerModel() {
return model;
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
// Do nothing
}
@Override
public String getDescription() {
return NAME;
}
@Override
public Icon getIcon() {
return null;
}
@Override
public String getUIName() {
return NAME;
}
@Override
protected void changeToolWindowAnchor(CLocation clocation) {
}
@Override
public List<Action> getOpenExportDialogAction() {
return Collections.emptyList();
}
@Override
public List<Action> getOpenImportDialogAction() {
return Collections.emptyList();
}
@Override
public void importFiles(File[] files, boolean recursive) {
// Do no import external files
}
@Override
public boolean canImportFiles() {
return false;
}
}