/*
* Copyright (C) 2014 by Array Systems Computing Inc. http://www.array.ca
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at your option)
* any later version.
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, see http://www.gnu.org/licenses/
*/
package org.esa.s1tbx.dat.toolviews.Projects;
import org.esa.snap.rcp.util.Dialogs;
import org.esa.snap.ui.PopupMenuFactory;
import org.esa.snap.ui.PopupMenuHandler;
import org.esa.snap.ui.UIUtils;
import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import java.awt.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.util.Enumeration;
/**
* A tree-view component for Projects
*/
class ProjectTree extends JTree implements PopupMenuFactory, ActionListener {
private Object menuContext;
private DefaultMutableTreeNode selectedNode;
private TreePath selectedPath;
private final Project project = Project.instance();
/**
* Constructs a new single selection <code>ProductTree</code>.
*/
public ProjectTree() {
this(false);
}
/**
* Constructs a new <code>ProductTree</code> with the given selection mode.
*
* @param multipleSelect whether or not the tree is multiple selection capable
*/
public ProjectTree(final boolean multipleSelect) {
getSelectionModel().setSelectionMode(multipleSelect
? TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION
: TreeSelectionModel.SINGLE_TREE_SELECTION);
addMouseListener(new PTMouseListener());
setCellRenderer(new PTCellRenderer());
setRootVisible(false);
setShowsRootHandles(true);
setToggleClickCount(2);
setExpandsSelectedPaths(true);
setScrollsOnExpand(true);
setAutoscrolls(true);
setDragEnabled(true);
setDropMode(DropMode.ON);
setTransferHandler(new TreeTransferHandler());
putClientProperty("JTree.lineStyle", "Angled");
ToolTipManager.sharedInstance().registerComponent(this);
final PopupMenuHandler popupMenuHandler = new PopupMenuHandler(this);
addMouseListener(popupMenuHandler);
addKeyListener(popupMenuHandler);
}
public JPopupMenu createPopupMenu(final Component component) {
return null;
}
public JPopupMenu createPopupMenu(MouseEvent event) {
selectedPath = getPathForLocation(event.getX(), event.getY());
if (selectedPath != null) {
setSelectionPath(selectedPath);
selectedNode = (DefaultMutableTreeNode) getLastSelectedPathComponent();
if (selectedNode != null) {
Object context = selectedNode.getUserObject();
if (context != null) {
return createPopup(context);
}
}
}
return null;
}
JPopupMenu createPopup(final Object context) {
final JPopupMenu popup = new JPopupMenu();
menuContext = context;
if (context instanceof ProjectSubFolder) {
final ProjectSubFolder folder = (ProjectSubFolder) context;
if (isExpanded(selectedPath))
createMenuItem(popup, "Collapse");
else
createMenuItem(popup, "Expand");
if (selectedNode.isRoot()) {
createMenuItem(popup, "Expand All");
addSeparator(popup);
createMenuItem(popup, "New Project...");
createMenuItem(popup, "Load Project...");
createMenuItem(popup, "Save Project");
createMenuItem(popup, "Save Project As...");
createMenuItem(popup, "Close Project");
createMenuItem(popup, "Refresh Project");
} else {
addSeparator(popup);
createMenuItem(popup, "Create Folder");
final JMenuItem menuItemRename = createMenuItem(popup, "Rename Folder");
final JMenuItem menuItemRemove = createMenuItem(popup, "Remove Folder");
if (!folder.canBeRemoved()) {
menuItemRename.setEnabled(false);
menuItemRemove.setEnabled(false);
}
if (!folder.isPhysical()) {
createMenuItem(popup, "Clear");
}
}
if (folder.getFolderType() == ProjectSubFolder.FolderType.PRODUCTSET) {
addSeparator(popup);
createMenuItem(popup, "New ProductSet...");
} else if (folder.getFolderType() == ProjectSubFolder.FolderType.GRAPH) {
addSeparator(popup);
createMenuItem(popup, "New Graph...");
}
} else if (context instanceof ProjectFile) {
createMenuItem(popup, "Open");
final DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) selectedNode.getParent();
final ProjectSubFolder folder = (ProjectSubFolder) parentNode.getUserObject();
if (folder.getFolderType() == ProjectSubFolder.FolderType.PRODUCT) {
createMenuItem(popup, "Open Subset");
}
if (!folder.isPhysical()) {
createMenuItem(popup, "Import as DIMAP");
createMenuItem(popup, "Import Subset as DIMAP");
}
createMenuItem(popup, "Remove");
}
return popup;
}
private JMenuItem createMenuItem(final JPopupMenu popup, final String text) {
final JMenuItem menuItem;
menuItem = new JMenuItem(text);
menuItem.addActionListener(this);
popup.add(menuItem);
return menuItem;
}
private static void addSeparator(JPopupMenu popup) {
if (popup.getComponentCount() > 0) {
popup.addSeparator();
}
}
/**
* Invoked when an action occurs.
*/
public void actionPerformed(ActionEvent e) {
final DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) selectedNode.getParent();
final String actionCmd = e.getActionCommand();
switch (actionCmd) {
case "Create Folder": {
final ProjectSubFolder subFolder = (ProjectSubFolder) menuContext;
project.createNewFolder(subFolder);
break;
}
case "Remove Folder":
if (parentNode != null) {
final Object context = parentNode.getUserObject();
if (context != null) {
final ProjectSubFolder parentFolder = (ProjectSubFolder) context;
final ProjectSubFolder subFolder = (ProjectSubFolder) menuContext;
project.deleteFolder(parentFolder, subFolder);
}
}
break;
case "Rename Folder":
if (parentNode != null) {
final Object context = parentNode.getUserObject();
if (context != null) {
final ProjectSubFolder subFolder = (ProjectSubFolder) menuContext;
project.renameFolder(subFolder);
}
}
break;
case "Remove":
if (parentNode != null) {
final ProjectSubFolder parentFolder = (ProjectSubFolder) parentNode.getUserObject();
if (parentFolder != null) {
final ProjectFile file = (ProjectFile) menuContext;
final Dialogs.Answer status = Dialogs.requestDecision("Delete", "Are you sure you want to delete "
+ file.getFile().toString(), true, null);
if (status == Dialogs.Answer.YES) {
project.removeFile(parentFolder, file.getFile());
}
}
}
break;
case "Open": {
final ProjectSubFolder parentFolder = (ProjectSubFolder) parentNode.getUserObject();
final ProjectFile file = (ProjectFile) menuContext;
Project.openFile(parentFolder, file.getFile());
break;
}
case "Open Subset": {
final ProjectSubFolder parentFolder = (ProjectSubFolder) parentNode.getUserObject();
final ProjectFile file = (ProjectFile) menuContext;
Project.openSubset(parentFolder, file.getFile());
break;
}
case "Import as DIMAP": {
final ProjectFile file = (ProjectFile) menuContext;
project.ImportFileList(new File[]{file.getFile()});
break;
}
case "Import Subset as DIMAP": {
final ProjectSubFolder parentFolder = (ProjectSubFolder) parentNode.getUserObject();
final ProjectFile file = (ProjectFile) menuContext;
project.importSubset(parentFolder, file.getFile());
break;
}
case "Clear": {
final ProjectSubFolder subFolder = (ProjectSubFolder) menuContext;
project.clearFolder(subFolder);
break;
}
case "Expand All":
expandAll();
break;
case "Expand":
expandPath(selectedPath);
break;
case "Collapse":
collapsePath(selectedPath);
break;
case "New Project...":
project.CreateNewProject();
break;
case "Load Project...":
project.LoadProject();
break;
case "Save Project":
project.SaveProject();
break;
case "Save Project As...":
project.SaveProjectAs();
break;
case "Close Project":
project.CloseProject();
break;
case "Refresh Project":
project.refreshProjectTree();
project.notifyEvent(true);
break;
case "New ProductSet...": {
final ProjectSubFolder subFolder = (ProjectSubFolder) menuContext;
project.createNewProductSet(subFolder);
break;
}
case "New Graph...": {
final ProjectSubFolder subFolder = (ProjectSubFolder) menuContext;
Project.createNewGraph(subFolder);
break;
}
}
}
void expandAll() {
final TreeNode root = (TreeNode) getModel().getRoot();
expandAll(this, new TreePath(root), true);
}
/**
* If expand is true, expands all nodes in the tree.
* Otherwise, collapses all nodes in the tree.
*
* @param tree the tree
* @param parent the parent path
* @param expand or collapse
*/
private static void expandAll(JTree tree, TreePath parent, boolean expand) {
// Traverse children
final TreeNode node = (TreeNode) parent.getLastPathComponent();
if (node.getChildCount() >= 0) {
for (Enumeration e = node.children(); e.hasMoreElements(); ) {
final TreeNode n = (TreeNode) e.nextElement();
final TreePath path = parent.pathByAddingChild(n);
expandAll(tree, path, expand);
}
}
// Expansion or collapse must be done bottom-up
if (expand) {
tree.expandPath(parent);
} else {
tree.collapsePath(parent);
}
}
public void populateTree(DefaultMutableTreeNode treeNode) {
setModel(new DefaultTreeModel(treeNode));
expandAll();
}
/**
* Selects the specified object in this tree's model. If the given object has no representation in the tree, the
* current selection will not be changed.
*
* @param toSelect the object whose representation in the tree will be selected.
*/
public void select(Object toSelect) {
final TreePath path = findTreePathFor(toSelect);
if (path != null) {
setSelectionPath(path);
}
}
private class PTMouseListener extends MouseAdapter {
@Override
public void mousePressed(MouseEvent event) {
int selRow = getRowForLocation(event.getX(), event.getY());
if (selRow >= 0) {
int clickCount = event.getClickCount();
if (clickCount > 1) {
final TreePath selPath = getPathForLocation(event.getX(), event.getY());
if (selPath != null) {
final DefaultMutableTreeNode node = (DefaultMutableTreeNode) selPath.getLastPathComponent();
final DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) node.getParent();
final Object o = node.getUserObject();
if (o instanceof ProjectFile) {
final ProjectFile file = (ProjectFile) o;
final ProjectSubFolder parentFolder = (ProjectSubFolder) parentNode.getUserObject();
Project.openFile(parentFolder, file.getFile());
}
}
}
}
}
}
private TreePath findTreePathFor(final Object o) {
final DefaultMutableTreeNode rootNode = (DefaultMutableTreeNode) getModel().getRoot();
final Enumeration enumeration = rootNode.depthFirstEnumeration();
while (enumeration.hasMoreElements()) {
final DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode) enumeration.nextElement();
if (treeNode.getUserObject() == o) {
return new TreePath(treeNode.getPath());
}
}
return null;
}
private static class PTCellRenderer extends DefaultTreeCellRenderer {
private final ImageIcon productIcon;
private final ImageIcon groupOpenIcon;
private final ImageIcon projectIcon;
public PTCellRenderer() {
productIcon = UIUtils.loadImageIcon("icons/RsProduct16.gif");
groupOpenIcon = UIUtils.loadImageIcon("icons/RsGroupOpen16.gif");
projectIcon = UIUtils.loadImageIcon("icons/RsGroupClosed16.gif");
}
@Override
public Component getTreeCellRendererComponent(JTree tree,
Object value,
boolean sel,
boolean expanded,
boolean leaf,
int row,
boolean hasFocus) {
super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
final DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode) value;
value = treeNode.getUserObject();
if (value instanceof ProjectFile) {
final ProjectFile file = (ProjectFile) value;
this.setText(file.getDisplayName());
this.setIcon(productIcon);
this.setToolTipText(leaf ? file.getToolTipText() : null);
} else if (value instanceof ProjectSubFolder) {
final ProjectSubFolder subFolder = (ProjectSubFolder) value;
this.setText(subFolder.getName());
this.setIcon(projectIcon);
this.setToolTipText(null);
}
return this;
}
}
private static class TreeTransferHandler extends TransferHandler {
public boolean canImport(TransferHandler.TransferSupport info) {
return info.isDataFlavorSupported(DataFlavor.stringFlavor);
}
public int getSourceActions(JComponent c) {
return TransferHandler.COPY;
}
/**
* Bundle up the selected items in a single list for export.
* Each line is separated by a newline.
*/
protected Transferable createTransferable(JComponent c) {
final JTree tree = (JTree) c;
final TreePath path = tree.getSelectionPath();
final DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
final DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) node.getParent();
final Object context = node.getUserObject();
if (context != null) {
if (context instanceof ProjectFile) {
final ProjectSubFolder parentFolder = (ProjectSubFolder) parentNode.getUserObject();
final ProjectFile file = (ProjectFile) context;
if (parentFolder.getFolderType() == ProjectSubFolder.FolderType.PRODUCTSET) {
return new StringSelection(ProductSet.GetListAsString(file.getFile()));
} else {
return new StringSelection(file.getFile().getAbsolutePath());
}
} else if (context instanceof ProjectSubFolder) {
final ProjectSubFolder parentFolder = (ProjectSubFolder) context;
return new StringSelection(parentFolder.getPath().getAbsolutePath());
}
}
return null;
}
/**
* Perform the actual import. This only supports drag and drop.
*/
public boolean importData(TransferHandler.TransferSupport info) {
if (!info.isDrop()) {
return false;
}
final JTree tree = (JTree) info.getComponent();
final DefaultTreeModel treeModel = (DefaultTreeModel) tree.getModel();
// Get the string that is being dropped.
final Transferable t = info.getTransferable();
String data;
try {
data = (String) t.getTransferData(DataFlavor.stringFlavor);
} catch (Exception e) {
return false;
}
// Wherever there is a newline in the incoming data,
// break it into a separate item in the list.
final String[] values = data.split("\n");
// Perform the actual import.
for (String value : values) {
final TreePath dropPath = tree.getDropLocation().getPath();
final DefaultMutableTreeNode node = (DefaultMutableTreeNode) dropPath.getLastPathComponent();
final DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) node.getParent();
final Object o = node.getUserObject();
if (o instanceof ProjectFile) {
final ProjectFile projFile = (ProjectFile) o;
final ProjectSubFolder projSubFolder = (ProjectSubFolder) parentNode.getUserObject();
if (projSubFolder.getFolderType() == ProjectSubFolder.FolderType.PRODUCTSET) {
ProductSet.AddProduct(projFile.getFile(), new File(value));
}
}
}
return true;
}
}
}