/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package org.apache.airavata.xbaya.ui.widgets.component;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragSource;
import java.awt.dnd.DragSourceAdapter;
import java.awt.dnd.DragSourceListener;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.ToolTipManager;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import org.apache.airavata.common.utils.SwingUtil;
import org.apache.airavata.workflow.model.component.Component;
import org.apache.airavata.workflow.model.component.ComponentException;
import org.apache.airavata.workflow.model.component.ComponentOperationReference;
import org.apache.airavata.workflow.model.component.ComponentReference;
import org.apache.airavata.workflow.model.component.ComponentRegistry;
import org.apache.airavata.workflow.model.component.ComponentRegistryException;
import org.apache.airavata.workflow.model.component.ws.WSComponent;
import org.apache.airavata.workflow.model.exceptions.WorkflowRuntimeException;
import org.apache.airavata.xbaya.XBayaEngine;
import org.apache.airavata.xbaya.component.registry.ComponentController;
import org.apache.airavata.xbaya.ui.utils.ErrorMessages;
import org.apache.airavata.xbaya.ui.widgets.XBayaComponent;
import org.apache.airavata.xbaya.ui.widgets.component.ComponentSelectorEvent.ComponentSelectorEventType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The ComponentTreeViewer class shows the selectedComponent tree.
*
*/
public class ComponentSelector implements XBayaComponent {
/**
* The title.
*/
public static final String TITLE = "Component List";
private static final Logger logger = LoggerFactory.getLogger(ComponentSelector.class);
private XBayaEngine engine;
private JTree tree;
private ComponentTreeModel treeModel;
private ComponentReference selectedComponentReference;
private Component selectedComponent;
private List<ComponentSelectorListener> listeners;
private DragSourceListener dragSourceListener;
private JPopupMenu popup;
/**
* @param engine
*/
public ComponentSelector(XBayaEngine engine) {
this.engine = engine;
this.listeners = new LinkedList<ComponentSelectorListener>();
initGUI();
}
/**
* @return the Pane
*/
public JTree getSwingComponent() {
return this.tree;
}
/**
* Adds a new selectedComponent registry to the end of the tree.
*
* @param componentTree
*/
public void addComponentTree(ComponentTreeNode componentTree) {
addComponentTree(-1, componentTree);
}
public void removeComponentTree(final ComponentTreeNode componentTree) {
ComponentSelector.this.treeModel.removeNodeFromParent(componentTree);
// SwingUtilities.invokeLater(new Runnable() {
// public void run() {
// ComponentSelector.this.treeModel.removeNodeFromParent(componentTree);
// }
//
// });
}
public synchronized void removeComponentRegistry(final String componentRegistryName) {
ComponentTreeNode root = ComponentSelector.this.treeModel.getRoot();
ComponentTreeNode[] treeNodes = root.getChildren().toArray(new ComponentTreeNode[]{});
for(ComponentTreeNode treeNode:treeNodes){
if (treeNode.getComponentRegistry().getName().equals(componentRegistryName)){
root.remove(treeNode);
}
}
treeModel.reload();
}
/**
* Adds a new selectedComponent registry to the specified location.
*
* @param index
* The index to ineart a new selectedComponent registry
* @param componentTree
*/
public void addComponentTree(final int index, final ComponentTreeNode componentTree) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
ComponentTreeNode root = ComponentSelector.this.treeModel.getRoot();
if (index < 0) {
// Have to go through DefaultTreeModol to dynamically
// add a node
ComponentSelector.this.treeModel.addNodeInto(componentTree, root);
} else {
// Have to go through DefaultTreeModol to dynamically
// add a node
ComponentSelector.this.treeModel.insertNodeInto(componentTree, root, index);
}
makeVisible(componentTree);
}
});
}
/**
* Removes a registry currently selected.
*/
public void removeSelectedRegistry() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
TreePath selectionPath = ComponentSelector.this.tree.getSelectionPath();
ComponentTreeNode selectedNode = (ComponentTreeNode) selectionPath.getLastPathComponent();
if (selectedNode.getLevel() == 1) {
ComponentSelector.this.treeModel.removeNodeFromParent(selectedNode);
}
}
});
}
/**
* @throws ComponentRegistryException
*/
public void updateSelectedRegistry() throws ComponentRegistryException {
final TreePath[] selectionPathHolder = new TreePath[1];
try {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
selectionPathHolder[0] = ComponentSelector.this.tree.getSelectionPath();
}
});
} catch (InterruptedException e) {
// Should not happen.
throw new WorkflowRuntimeException(e);
} catch (InvocationTargetException e) {
// Should not happen.
throw new WorkflowRuntimeException(e);
}
TreePath selectionPath = selectionPathHolder[0];
if (selectionPath == null) {
// TODO this case should be handled in the menu before comming here.
return;
}
if (selectionPath.getPathCount() >= 2) {
final ComponentTreeNode selectedNode = (ComponentTreeNode) selectionPath.getPath()[1];
reloadComponentRegistryNode(selectedNode);
}
}
private void reloadComponentRegistryNode(
final ComponentTreeNode selectedNode)
throws ComponentRegistryException {
ComponentRegistry registry = selectedNode.getComponentRegistry();
final ComponentTreeNode componentTree = ComponentController.getComponentTree(registry);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
ComponentTreeNode root = ComponentSelector.this.treeModel.getRoot();
int index = root.getIndex(selectedNode);
ComponentSelector.this.treeModel.removeNodeFromParent(selectedNode);
ComponentSelector.this.treeModel.insertNodeInto(componentTree, root, index);
}
});
}
/**
* Updates all the registry entries.
*
* @throws ComponentRegistryException
*/
public void update() throws ComponentRegistryException {
final List<ComponentRegistry> registries = new ArrayList<ComponentRegistry>();
if (SwingUtilities.isEventDispatchThread()) {
getRegistries(registries);
} else {
try {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
getRegistries(registries);
}
});
} catch (InterruptedException e) {
// Should not happen.
throw new WorkflowRuntimeException(e);
} catch (InvocationTargetException e) {
// Should not happen.
throw new WorkflowRuntimeException(e);
}
}
final List<ComponentTreeNode> newSubTrees = new ArrayList<ComponentTreeNode>();
for (ComponentRegistry registry : registries) {
ComponentTreeNode componentTree = ComponentController.getComponentTree(registry);
newSubTrees.add(componentTree);
}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
ComponentTreeNode root = ComponentSelector.this.treeModel.getRoot();
ComponentSelector.this.treeModel.removeChildren(root);
logger.debug("Removed all");
for (ComponentTreeNode subTree : newSubTrees) {
ComponentSelector.this.treeModel.addNodeInto(subTree, root);
}
makeVisible((ComponentTreeNode) root.getFirstChild());
}
});
}
/**
* Returns the selectedComponent.
*
* @return The selectedComponent
*/
public Component getSelectedComponent() {
return this.selectedComponent;
}
/**
* @param listener
*/
public synchronized void addComponentSelectorListener(ComponentSelectorListener listener) {
this.listeners.add(listener);
}
/**
* @param listener
*/
public synchronized void removeComponentSelectorListener(ComponentSelectorListener listener) {
this.listeners.remove(listener);
}
private void dragGestureRecognized(DragGestureEvent event) {
if (this.selectedComponentReference != null) {
event.startDrag(DragSource.DefaultCopyDrop,
new ComponentSourceTransferable(this.selectedComponentReference), this.dragSourceListener);
}
}
private List<ComponentRegistry> getRegistries(List<ComponentRegistry> registries) {
ComponentTreeNode root = this.treeModel.getRoot();
for (ComponentTreeNode componentTree : root.getChildren()) {
registries.add(componentTree.getComponentRegistry());
}
return registries;
}
private void makeVisible(ComponentTreeNode node) {
// Make sure the user can see the new node, but don't scroll to
// right.
TreePath treePath = new TreePath(node.getPath());
Rectangle bounds = ComponentSelector.this.tree.getPathBounds(treePath);
if (bounds != null) {
// Prevent right scroll.
bounds.x = 0;
bounds.width = 0;
this.tree.scrollRectToVisible(bounds);
} else {
// null during the initialization.
this.tree.scrollPathToVisible(treePath);
}
}
/**
* This method is called when a component is selected. It reads the component information from the server and set
* the selectedComponent.
*
* @param treePath
* The path of the selected selectedComponent.
*/
private void select(TreePath treePath) {
final ComponentTreeNode selectedNode = (ComponentTreeNode) treePath.getLastPathComponent();
final ComponentReference componentReference = selectedNode.getComponentReference();
selectComponent(null);
this.selectedComponentReference = null;
if (componentReference != null) {
this.selectedComponentReference = componentReference;
new Thread() {
@Override
public void run() {
try {
// get all components and check the number of
// components. If there are multiple, expand the tree.
final List<? extends Component> components = componentReference.getComponents();
if (components.size() == 1) {
selectComponent(components.get(0));
} else {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
expandTreeLeaf(selectedNode, components);
}
});
}
} catch (ComponentException e) {
selectComponent(null);
ComponentSelector.this.engine.getGUI().getErrorWindow().error(ErrorMessages.COMPONENT_FORMAT_ERROR, e);
} catch (ComponentRegistryException e) {
selectComponent(null);
ComponentSelector.this.engine.getGUI().getErrorWindow().error(ErrorMessages.COMPONENT_LOAD_ERROR, e);
} catch (RuntimeException e) {
selectComponent(null);
ComponentSelector.this.engine.getGUI().getErrorWindow().error(ErrorMessages.COMPONENT_LOAD_ERROR, e);
} catch (Exception e) {
selectComponent(null);
ComponentSelector.this.engine.getGUI().getErrorWindow().error(ErrorMessages.COMPONENT_LOAD_ERROR, e);
}
}
}.start();
}
}
private void expandTreeLeaf(ComponentTreeNode selectedNode, List<? extends Component> components) {
ComponentReference componentReference = selectedNode.getComponentReference();
ComponentTreeNode newNode = new ComponentTreeNode(componentReference.getName());
ComponentTreeNode parent = (ComponentTreeNode) selectedNode.getParent();
int index = this.treeModel.getIndexOfChild(parent, selectedNode);
this.treeModel.removeNodeFromParent(selectedNode);
this.treeModel.insertNodeInto(newNode, parent, index);
for (Component component : components) {
WSComponent wsComponent = (WSComponent) component;
String operationName = wsComponent.getOperationName();
ComponentOperationReference reference = new ComponentOperationReference(operationName, wsComponent);
ComponentTreeNode child = new ComponentTreeNode(reference);
this.treeModel.addNodeInto(child, newNode);
}
// expand
TreeNode[] path = newNode.getPath();
this.tree.expandPath(new TreePath(path));
}
private void selectComponent(Component component) {
this.selectedComponent = component;
notifyListeners(new ComponentSelectorEvent(ComponentSelectorEventType.COMPONENT_SELECTED, this, component));
}
private void showPopupIfNecessary(MouseEvent event) {
Point point = event.getPoint();
TreePath path = this.tree.getClosestPathForLocation(point.x, point.y);
this.tree.setSelectionPath(path);
if (path.getPathCount() >= 2) {
this.popup.show(event.getComponent(), point.x, point.y);
}
}
private void notifyListeners(ComponentSelectorEvent event) {
for (ComponentSelectorListener listener : this.listeners) {
listener.componentSelectorChanged(event);
}
}
private void initGUI() {
this.treeModel = new ComponentTreeModel(new ComponentTreeNode("Components"));
this.tree = new JTree(this.treeModel);
// Add a tool tip.
ToolTipManager.sharedInstance().registerComponent(this.tree);
DefaultTreeCellRenderer renderer = new DefaultTreeCellRenderer() {
@Override
public java.awt.Component getTreeCellRendererComponent(JTree t, Object value, boolean sel,
boolean expanded, boolean leaf, int row, boolean focus) {
super.getTreeCellRendererComponent(t, value, sel, expanded, leaf, row, focus);
ComponentTreeNode node = (ComponentTreeNode) value;
if (node.getComponentReference() == null) {
setToolTipText(null);
} else {
setToolTipText("Drag a component to the composer to add");
}
return this;
}
};
// Change icons
try {
renderer.setOpenIcon(SwingUtil.createImageIcon("opened.gif"));
renderer.setClosedIcon(SwingUtil.createImageIcon("closed.gif"));
renderer.setLeafIcon(SwingUtil.createImageIcon("leaf.gif"));
} catch (RuntimeException e) {
logger.warn("Failed to load image icons. " + "It will use the default icons instead.", e);
}
this.tree.setCellRenderer(renderer);
this.tree.setShowsRootHandles(true);
this.tree.setEditable(false);
this.tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
this.tree.addTreeSelectionListener(new TreeSelectionListener() {
public void valueChanged(TreeSelectionEvent event) {
// Doesn't do anything if deselected, which happens during the
// update.
if (event.isAddedPath()) {
TreePath path = event.getPath();
select(path);
}
}
});
// Drag and dtop
DragGestureListener dragGestureListener = new DragGestureListener() {
public void dragGestureRecognized(DragGestureEvent event) {
ComponentSelector.this.dragGestureRecognized(event);
}
};
DragSource dragSource = DragSource.getDefaultDragSource();
dragSource.createDefaultDragGestureRecognizer(this.tree, DnDConstants.ACTION_COPY_OR_MOVE, dragGestureListener);
this.dragSourceListener = new DragSourceAdapter() {
// Overwrite some methods when needed.
};
// Popup
this.tree.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent event) {
if (event.isPopupTrigger()) {
showPopupIfNecessary(event);
}
}
});
createNodePopupMenu();
}
private void createNodePopupMenu() {
this.popup = new JPopupMenu();
JMenuItem refreshItem = new JMenuItem("Refresh Registry");
refreshItem.addActionListener(new AbstractAction() {
public void actionPerformed(ActionEvent event) {
new Thread() {
@Override
public void run() {
try {
updateSelectedRegistry();
} catch (ComponentRegistryException e) {
ComponentSelector.this.engine.getGUI().getErrorWindow().error(
ErrorMessages.COMPONENT_LIST_LOAD_ERROR, e);
} catch (RuntimeException e) {
ComponentSelector.this.engine.getGUI().getErrorWindow().error(
ErrorMessages.COMPONENT_LIST_LOAD_ERROR, e);
} catch (Error e) {
ComponentSelector.this.engine.getGUI().getErrorWindow().error(ErrorMessages.UNEXPECTED_ERROR, e);
}
}
}.start();
}
});
this.popup.add(refreshItem);
}
public void refresh() {
this.getSwingComponent().repaint();
this.tree.repaint();
this.treeModel.reload();
}
}