/* * 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); } } } }