/*
* SoapUI, Copyright (C) 2004-2016 SmartBear Software
*
* Licensed under the EUPL, Version 1.1 or - as soon as they will be approved by the European Commission - subsequent
* versions of the EUPL (the "Licence");
* You may not use this work except in compliance with the Licence.
* You may obtain a copy of the Licence at:
*
* http://ec.europa.eu/idabc/eupl
*
* Unless required by applicable law or agreed to in writing, software distributed under the Licence is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the Licence for the specific language governing permissions and limitations
* under the Licence.
*/
package com.eviware.soapui.ui;
import com.eviware.soapui.model.ModelItem;
import com.eviware.soapui.model.project.Project;
import com.eviware.soapui.model.tree.SoapUITreeModel;
import com.eviware.soapui.model.tree.SoapUITreeNode;
import com.eviware.soapui.model.tree.SoapUITreeNodeRenderer;
import com.eviware.soapui.model.tree.nodes.ProjectTreeNode;
import com.eviware.soapui.model.workspace.Workspace;
import com.eviware.soapui.support.UISupport;
import com.eviware.soapui.support.action.swing.ActionList;
import com.eviware.soapui.support.action.swing.ActionListBuilder;
import com.eviware.soapui.support.action.swing.ActionSupport;
import com.eviware.soapui.support.components.JXToolBar;
import com.eviware.soapui.support.swing.MenuBuilderHelper;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JToggleButton;
import javax.swing.JTree;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.ToolTipManager;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.dnd.Autoscroll;
import java.awt.event.ActionEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* The SoapUI navigator tree
*
* @author Ole.Matzura
*/
public class Navigator extends JPanel {
public static final String NAVIGATOR = "navigator";
private Workspace workspace;
private JTree mainTree;
private SoapUITreeModel treeModel;
private Set<NavigatorListener> listeners = new HashSet<NavigatorListener>();
public Navigator(Workspace workspace) {
super(new BorderLayout());
setName(NAVIGATOR);
this.workspace = workspace;
buildUI();
}
private void buildUI() {
treeModel = new SoapUITreeModel(workspace);
mainTree = new NavigatorTree(treeModel);
mainTree.setRootVisible(true);
mainTree.setExpandsSelectedPaths(true);
mainTree.setScrollsOnExpand(true);
mainTree.setToggleClickCount(0);
mainTree.addMouseListener(new TreeMouseListener());
mainTree.addTreeSelectionListener(new InternalTreeSelectionListener());
mainTree.setCellRenderer(new SoapUITreeNodeRenderer());
mainTree.setBorder(null);
mainTree.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
mainTree.addKeyListener(new TreeKeyListener());
JScrollPane sp = new JScrollPane(mainTree);
sp.setBorder(BorderFactory.createEmptyBorder());
add(sp, BorderLayout.CENTER);
add(buildToolbar(), BorderLayout.NORTH);
setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
}
private Component buildToolbar() {
JXToolBar toolbar = UISupport.createSmallToolbar();
JToggleButton toggleButton = new JToggleButton(new TogglePropertiesAction());
toggleButton.setToolTipText("Toggles displaying of Test Properties in tree");
toggleButton.setSize(10, 12);
toolbar.addFixed(toggleButton);
toolbar.addGlue();
return toolbar;
}
private static class NavigatorTree extends JTree implements Autoscroll {
public NavigatorTree(SoapUITreeModel treeModel) {
super(treeModel);
}
private static final int AUTOSCROLL_MARGIN = 12;
public void autoscroll(Point pt) {
// Figure out which row we�re on.
int nRow = getRowForLocation(pt.x, pt.y);
// If we are not on a row then ignore this autoscroll request
if (nRow < 0) {
return;
}
Rectangle raOuter = getBounds();
// Now decide if the row is at the top of the screen or at the
// bottom. We do this to make the previous row (or the next
// row) visible as appropriate. If we�re at the absolute top or
// bottom, just return the first or last row respectively.
nRow = (pt.y + raOuter.y <= AUTOSCROLL_MARGIN) // Is row at top of
// screen?
? (nRow <= 0 ? 0 : nRow - 1) // Yes, scroll up one row
: (nRow < getRowCount() - 1 ? nRow + 1 : nRow); // No,
// scroll
// down one
// row
scrollRowToVisible(nRow);
}
// Calculate the insets for the *JTREE*, not the viewport
// the tree is in. This makes it a bit messy.
public Insets getAutoscrollInsets() {
Rectangle raOuter = getBounds();
Rectangle raInner = getParent().getBounds();
return new Insets(raInner.y - raOuter.y + AUTOSCROLL_MARGIN, raInner.x - raOuter.x + AUTOSCROLL_MARGIN,
raOuter.height - raInner.height - raInner.y + raOuter.y + AUTOSCROLL_MARGIN, raOuter.width
- raInner.width - raInner.x + raOuter.x + AUTOSCROLL_MARGIN);
}
}
public Project getCurrentProject() {
TreePath path = mainTree.getSelectionPath();
if (path == null) {
return null;
}
Object node = path.getLastPathComponent();
while (node != null && !(node instanceof ProjectTreeNode)) {
path = path.getParentPath();
node = (path == null ? null : path.getLastPathComponent());
}
if (node == null) {
return null;
}
return ((ProjectTreeNode) node).getProject();
}
public void addNavigatorListener(NavigatorListener listener) {
listeners.add(listener);
}
public void removeNavigatorListener(NavigatorListener listener) {
listeners.remove(listener);
}
public void selectModelItem(ModelItem modelItem) {
TreePath path = treeModel.getPath(modelItem);
mainTree.setSelectionPath(path);
mainTree.expandPath(path);
mainTree.scrollPathToVisible(path);
}
public TreePath getTreePath(ModelItem modelItem) {
return treeModel.getPath(modelItem);
}
public JTree getMainTree() {
return mainTree;
}
public ModelItem getSelectedItem() {
TreePath path = mainTree.getSelectionPath();
if (path == null) {
return null;
}
return ((SoapUITreeNode) path.getLastPathComponent()).getModelItem();
}
private final class TreeKeyListener extends KeyAdapter {
public void keyPressed(KeyEvent e) {
TreePath selectionPath = mainTree.getSelectionPath();
if (selectionPath == null || mainTree.getSelectionCount() == 0) {
return;
}
if (mainTree.getSelectionCount() == 1) {
SoapUITreeNode lastPathComponent = (SoapUITreeNode) selectionPath.getLastPathComponent();
ActionList actions = lastPathComponent.getActions();
if (actions != null) {
actions.dispatchKeyEvent(e);
}
if (!e.isConsumed()) {
KeyStroke ks = KeyStroke.getKeyStrokeForEvent(e);
if (ks.equals(UISupport.getKeyStroke("alt C"))) {
mainTree.collapsePath(selectionPath);
e.consume();
} else if (ks.equals(UISupport.getKeyStroke("alt E"))) {
mainTree.collapsePath(selectionPath);
int row = mainTree.getSelectionRows()[0];
TreePath nextPath = mainTree.getPathForRow(row + 1);
TreePath path = mainTree.getPathForRow(row);
while (path != null && !path.equals(nextPath)) {
mainTree.expandRow(row);
path = mainTree.getPathForRow(++row);
}
e.consume();
}
}
} else {
TreePath[] selectionPaths = mainTree.getSelectionPaths();
List<ModelItem> targets = new ArrayList<ModelItem>();
for (TreePath treePath : selectionPaths) {
SoapUITreeNode node = (SoapUITreeNode) treePath.getLastPathComponent();
targets.add(node.getModelItem());
}
if (targets.size() > 0) {
ActionList actions = ActionListBuilder
.buildMultiActions(targets.toArray(new ModelItem[targets.size()]));
if (actions.getActionCount() > 0) {
actions.dispatchKeyEvent(e);
}
}
}
}
}
public class InternalTreeSelectionListener implements TreeSelectionListener {
public void valueChanged(TreeSelectionEvent e) {
Object obj = e.getPath().getLastPathComponent();
if (obj instanceof SoapUITreeNode) {
SoapUITreeNode treeNode = (SoapUITreeNode) obj;
MenuBuilderHelper.buildTreeNodeMenu(treeNode);
if (!listeners.isEmpty()) {
TreePath newPath = e.getNewLeadSelectionPath();
NavigatorListener[] array = listeners.toArray(new NavigatorListener[listeners.size()]);
for (NavigatorListener listener : array) {
listener.nodeSelected(newPath == null ? null : treeNode);
}
}
}
}
}
public class TreeMouseListener extends MouseAdapter {
private final class CollapseRowAction extends AbstractAction {
private final int row;
public CollapseRowAction(int row) {
this.row = row;
}
public void actionPerformed(ActionEvent e) {
collapseAll(mainTree.getPathForRow(row));
mainTree.collapseRow(row);
}
private void collapseAll(TreePath tp) {
if (tp == null) {
return;
}
Object node = tp.getLastPathComponent();
TreeModel model = mainTree.getModel();
if (!model.isLeaf(node)) {
mainTree.collapsePath(tp);
for (int i = 0; i < model.getChildCount(node); i++) {
// for (int i = node.childCount()-4;i>=0;i--){
collapseAll(tp.pathByAddingChild(model.getChild(node, i)));
}
mainTree.collapsePath(tp);
}
}
}
private final class ExpandRowAction extends AbstractAction {
private final int row;
public ExpandRowAction(int row) {
this.row = row;
}
public void actionPerformed(ActionEvent e) {
mainTree.expandRow(row);
expandAll(mainTree.getPathForRow(row));
}
private void expandAll(TreePath tp) {
if (tp == null) {
return;
}
Object node = tp.getLastPathComponent();
TreeModel model = mainTree.getModel();
if (!model.isLeaf(node)) {
mainTree.expandPath(tp);
for (int i = 0; i < model.getChildCount(node); i++) {
expandAll(tp.pathByAddingChild(model.getChild(node, i)));
}
}
}
}
private ActionList actions;
public void mouseClicked(MouseEvent e) {
if (e.isPopupTrigger()) {
showPopup(e);
} else if (e.getClickCount() < 2) {
return;
}
if (mainTree.getSelectionCount() == 1) {
int row = mainTree.getRowForLocation(e.getX(), e.getY());
TreePath path = mainTree.getSelectionPath();
if (path == null && row == -1) {
return;
}
if (path == null || mainTree.getRowForPath(path) != row) {
mainTree.setSelectionRow(row);
}
SoapUITreeNode node = (SoapUITreeNode) path.getLastPathComponent();
actions = node.getActions();
if (actions != null) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
if (actions != null) {
actions.performDefaultAction(new ActionEvent(mainTree, 0, null));
actions = null;
}
}
});
}
}
}
public void mousePressed(MouseEvent e) {
if (e.isPopupTrigger()) {
showPopup(e);
}
}
public void mouseReleased(MouseEvent e) {
if (e.isPopupTrigger()) {
showPopup(e);
}
}
private void showToolTipLessPopupMenu(JPopupMenu pm, int x, int y) {
pm.addPopupMenuListener(new PopupMenuListener() {
@Override
public void popupMenuCanceled(PopupMenuEvent e) {
ToolTipManager.sharedInstance().setEnabled(true);
}
@Override
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
ToolTipManager.sharedInstance().setEnabled(true);
}
@Override
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
ToolTipManager.sharedInstance().setEnabled(false);
}
});
pm.show(mainTree, x, y);
}
private void showPopup(MouseEvent e) {
if (mainTree.getSelectionCount() < 2) {
TreePath path = mainTree.getPathForLocation((int) e.getPoint().getX(), (int) e.getPoint().getY());
if (path == null) {
int row = (int) e.getPoint().getY() / mainTree.getRowHeight();
if (row != -1) {
JPopupMenu collapsePopup = new JPopupMenu();
collapsePopup.add("Collapse").addActionListener(new CollapseRowAction(row));
collapsePopup.add("Expand").addActionListener(new ExpandRowAction(row));
showToolTipLessPopupMenu(collapsePopup, e.getX(), e.getY());
}
return;
}
SoapUITreeNode node = (SoapUITreeNode) path.getLastPathComponent();
JPopupMenu popupMenu = node.getPopup();
if (popupMenu == null) {
return;
}
mainTree.setSelectionPath(path);
showToolTipLessPopupMenu(popupMenu, e.getX(), e.getY());
} else {
TreePath[] selectionPaths = mainTree.getSelectionPaths();
List<ModelItem> targets = new ArrayList<ModelItem>();
for (TreePath treePath : selectionPaths) {
SoapUITreeNode node = (SoapUITreeNode) treePath.getLastPathComponent();
targets.add(node.getModelItem());
}
if (targets.size() > 0) {
ActionList actions = ActionListBuilder
.buildMultiActions(targets.toArray(new ModelItem[targets.size()]));
if (actions.getActionCount() > 0) {
JPopupMenu popup = new JPopupMenu();
ActionSupport.addActions(actions, popup);
showToolTipLessPopupMenu(popup, e.getX(), e.getY());
}
}
}
}
}
public boolean isVisible(TreePath path) {
return mainTree.isVisible(path);
}
public boolean isExpanded(TreePath path) {
return mainTree.isExpanded(path);
}
private class TogglePropertiesAction extends AbstractAction {
public TogglePropertiesAction() {
putValue(SMALL_ICON, UISupport.createImageIcon("/properties_step.png"));
}
public void actionPerformed(ActionEvent e) {
Enumeration<TreePath> expandedDescendants = mainTree.getExpandedDescendants(getTreePath(workspace));
TreePath selectionPath = mainTree.getSelectionPath();
treeModel.setShowProperties(!treeModel.isShowProperties());
while (expandedDescendants != null && expandedDescendants.hasMoreElements()) {
mainTree.expandPath(expandedDescendants.nextElement());
}
if (selectionPath != null) {
mainTree.setSelectionPath(selectionPath);
}
}
}
}