Java tutorial
/******************************************************************************* * Copyright (c) 2010 Ricardo Juan Palma Durn. * * This source file 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, version 3 of * the License. The text of the GNU Lesser General Public License * is included with this application in the file LICENSE.TXT. * * This source file 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. ******************************************************************************/ package jbt.tools.bteditor.editor; import java.io.File; import java.io.IOException; import java.util.Collection; import java.util.Hashtable; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Vector; import jbt.tools.bteditor.ApplicationIcons; import jbt.tools.bteditor.NodesLoader; import jbt.tools.bteditor.BTXMLManager; import jbt.tools.bteditor.actions.SaveBTAction; import jbt.tools.bteditor.actions.SaveBTAsAction; import jbt.tools.bteditor.event.ITreeModifierListener; import jbt.tools.bteditor.event.TreeModifiedEvent; import jbt.tools.bteditor.model.BT; import jbt.tools.bteditor.model.BTNode; import jbt.tools.bteditor.model.ConceptualBTNode; import jbt.tools.bteditor.model.BTNode.Identifier; import jbt.tools.bteditor.model.BTNode.Parameter; import jbt.tools.bteditor.model.ConceptualBTNode.NodeInternalType; import jbt.tools.bteditor.model.ConceptualBTNode.ParameterType; import jbt.tools.bteditor.util.IconsPaths; import jbt.tools.bteditor.util.OverlayImageIcon; import jbt.tools.bteditor.util.Pair; import jbt.tools.bteditor.util.StandardDialogs; import jbt.tools.bteditor.util.Utilities; import jbt.tools.bteditor.viewers.BTNodeIndentifierTransfer; import jbt.tools.bteditor.viewers.ConceptualBTNodeTransfer; import jbt.tools.bteditor.views.NodeInfo; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IAction; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.viewers.DecoratingLabelProvider; import org.eclipse.jface.viewers.DoubleClickEvent; import org.eclipse.jface.viewers.IDoubleClickListener; import org.eclipse.jface.viewers.ILabelDecorator; import org.eclipse.jface.viewers.ILabelProvider; import org.eclipse.jface.viewers.ILabelProviderListener; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredContentProvider; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerSorter; import org.eclipse.swt.SWT; import org.eclipse.swt.dnd.DND; import org.eclipse.swt.dnd.DragSourceEvent; import org.eclipse.swt.dnd.DragSourceListener; import org.eclipse.swt.dnd.DropTargetEvent; import org.eclipse.swt.dnd.DropTargetListener; import org.eclipse.swt.dnd.Transfer; import org.eclipse.swt.events.KeyAdapter; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.MenuDetectEvent; import org.eclipse.swt.events.MenuDetectListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Group; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Text; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeItem; import org.eclipse.ui.IActionBars; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorSite; import org.eclipse.ui.IPartListener; import org.eclipse.ui.IPartService; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.PartInitException; import org.eclipse.ui.actions.ActionFactory; import org.eclipse.ui.part.EditorPart; /** * Editor that is used for editing behavior trees ({@link BT}). This editor just * shows the tree in a tree-like way, letting the user modify a behaviour tree. * <p> * This editor can be used to insert nodes into the tree, delete them, and edit * their parameters. Nodes are inserted via a drag and drop mechanism. * <p> * For insertion purposes, this class is compatible with the * {@link ConceptualBTNodeTransfer} class, so a new empty node is inserted as a * child of the target of a drop operation when the underlying data is * compatible with {@link ConceptualBTNodeTransfer}. * <p> * Also, nodes of the tree can be moved around by using the drag and drop * mechanism. This class is compatible with the * {@link BTNodeIndentifierTransfer} class, so a dragged node is inserted as a * sibling of the target of the drop operation. * <p> * A context menu lets the user delete nodes (nodes can be deleted also by using * the delete and back space keys) and edit nodes' guards. Copying and pasting * nodes from the BTEditor into another editor (or the same one) is supported * through the context menu and global actions (Ctrl+C and Ctrl+V). However, it * should be noted that dragging from a BTEditor and dropping onto another * editor is not still supported. * <p> * For those nodes that have parameters, their value can be set by double * clicking on them. * <p> * This editor implements the {@link ITreeModifierListener} in order to change * the dirty status of the editor when the underlying tree is modified. * <p> * An important feature of the BTEditor is that it can be used to also edit the * guard of a node of another tree. BTEditors are opened with * {@link BTEditorInput} objects. BTEditorInput can specify where the tree that * the BTEditor edits comes from, and one of the possible options is to tell the * BTEditorInput that the tree comes from a node's guard. In such case, the * BTEditor has pretty much the same functionality as a normal BTEditor, with * small differences. * <p> * For instance, the way it responds to the <i>save</i> action is different. * When the BTEditor that contains a guard is saved, the guard is not saved into * a file, but stored into the node whose guard is being edited. Also, the root * node of a guard does not have a name, unlike that of normal tree. * <p> * A BTEditor's behaviour tree can therefore have guards being edited at any * time. In such case, if the editor is closed, all the editors editing guards * of the tree will be dissociated from the tree, and as a result, they will not * be considered guards any more (just a normal behaviour tree). * * @author Ricardo Juan Palma Durn * */ public class BTEditor extends EditorPart implements ITreeModifierListener { public static final String ID = "jbt.tools.bteditor.editor.BTEditor"; /** The viewer that is being used to display the tree. */ private TreeViewer viewer; /** The tree that the BTEditor is managing. */ private BT tree; /** Flag that indicates whether the tree is dirty or not. */ private boolean dirty; /** * Red color for cells containing erroneous items. */ private Color ERROR_COLOR = Utilities.getDisplay().getSystemColor(SWT.COLOR_RED); /** * List of all the editors that are currently editing the guards of this * tree's nodes. BTEditors are indexed by the BTNode whose guard is being * edited. */ private Map<BTNode, BTEditor> openGuardEditors; /** * In case this BTEditor is editing a guard, this variable stores the * behaviour tree that contains the node whose guard is being edited ( * {@link #guardNode}). */ private BT guardTree; /** * In case this BTEditor is editing a guard, this variable stores the BTNode * whose guard is being edited. */ private BTNode guardNode; /** * * @see org.eclipse.ui.part.EditorPart#doSave(org.eclipse.core.runtime.IProgressMonitor) */ public void doSave(IProgressMonitor monitor) { /* First, check if the tree has no structural errors. */ if (!checkTree()) { StandardDialogs.errorDialog("Tree not saved", "Errors were detected while validating the tree"); monitor.setCanceled(true); return; } /* * The save the tree. */ try { if (this.isFromFile()) { /* If the tree comes from a file, save the tree into a file. */ new SaveBTAction(this.tree, ((BTEditorInput) getEditorInput()).getTreeName()).run(); this.dirty = false; firePropertyChange(EditorPart.PROP_DIRTY); } else if (this.isFromGuard()) { /* * If the tree comes from a guard, then set the tree as a guard * of the "this.guardNode". Note that we set a clone of the * guard, not the original one. By doing so, the guard of the * original node will not be modified even if this editor's tree * is modified. */ BTNode guard = this.tree.getRoot().getChildren().get(0); if (guard != null) { this.guardNode.setGuard(guard.clone()); this.guardTree.fireTreeChanged(this); BTEditorInput editorInput = (BTEditorInput) this.getEditorInput(); Utilities.getBTEditor( Long.parseLong(editorInput.getTreeName().split(File.pathSeparator)[0])).viewer .refresh(); } this.dirty = false; firePropertyChange(EditorPart.PROP_DIRTY); } else { /* Otherwise, do a save as. */ doSaveAs(); } } catch (Exception e) { StandardDialogs.exceptionDialog("Tree not saved", "Errors were detected while saving the tree", e); monitor.setCanceled(true); } } /** * * @see org.eclipse.ui.part.EditorPart#doSaveAs() */ public void doSaveAs() { /* First, check if the tree has no structural errors. */ if (!checkTree()) { StandardDialogs.errorDialog("Tree not saved", "Errors were detected while validating the tree"); return; } SaveBTAsAction action = new SaveBTAsAction(this.tree, this.getEditorInput().getName()); try { action.run(); if (action.getSelectedFile() != null) { BTEditorInput editorInput = (BTEditorInput) getEditorInput(); editorInput.setTreeName(action.getSelectedFile()); this.dirty = false; setIsFromFile(true); /* * If the tree comes from a guard, it must be dissociated from * its original tree. From then on, this BTEditor will be * managed as a normal BTEditor. */ if (isFromGuard()) { dissociateFromParentTree(); } setPartName(editorInput.getName()); firePropertyChange(EditorPart.PROP_DIRTY); firePropertyChange(PROP_TITLE); } } catch (Exception e) { StandardDialogs.exceptionDialog("Error saving the tree", "There was an error when saving the tree", e); } } /** * * @see org.eclipse.ui.part.EditorPart#init(org.eclipse.ui.IEditorSite, * org.eclipse.ui.IEditorInput) */ public void init(IEditorSite site, IEditorInput input) throws PartInitException { if (!(input instanceof BTEditorInput)) { throw new PartInitException("Illegal IEditorInput. Must be " + BTEditorInput.class.getCanonicalName()); } setSite(site); setInputWithNotify(input); BTEditorInput editorInput = (BTEditorInput) input; setPartName(input.getName()); this.openGuardEditors = new Hashtable<BTNode, BTEditor>(); /* Set the IPartListener that will handle close events. */ IPartService partService = (IPartService) this.getSite().getService(IPartService.class); partService.addPartListener(new BTEditorPartListener()); if (editorInput.isFromFile()) { /* If the tree comes from a file, load the file. */ try { this.tree = BTXMLManager.load(editorInput.getTreeName()); this.tree.addTreeModifiedListener(this); this.dirty = false; } catch (IOException e) { throw new PartInitException("There were errors while loading the tree: " + e.getMessage(), e); } } else if (editorInput.isFromGuard()) { /* * If the tree comes from a guard, we have to construct a new tree * whose root is the guard. */ this.tree = new BT(); this.tree.addTreeModifiedListener(this); BTEditor activeEditor = Utilities.getActiveBTEditor(); this.guardTree = activeEditor.getBT(); String[] pieces = editorInput.getTreeName().split(File.pathSeparator); this.guardNode = this.guardTree.findNode(new Identifier(pieces[1])); /* * Important: the root node (type ROOT) of the guard's tree is not a * normal ROOT, since it has no name. Therefore, we clone the * original ROOT type and remove its ability to provide a name. */ ConceptualBTNode conceptualNoNameRoot = NodesLoader.getNode(NodeInternalType.ROOT.toString(), null) .clone(); conceptualNoNameRoot.setHasName(false); BTNode noNameRoot = this.tree.createNode(conceptualNoNameRoot); BTNode guard = this.guardNode.getGuard(); if (guard != null) { /* If the node had a guard, then the editor is not dirty. */ BTNode clonedGuard = guard.clone(); clonedGuard.setParent(noNameRoot); noNameRoot.addChild(clonedGuard); this.dirty = false; } else { /* Otherwise, the editor is dirty. */ this.dirty = true; } this.tree.setRoot(noNameRoot); this.setTitleImage(ApplicationIcons.getIcon(IconsPaths.GUARD)); } else { /* Otherwise, create a new empty BT. */ this.tree = new BT(); this.tree.addTreeModifiedListener(this); tree.setRoot(tree.createNode(NodesLoader.getNode(NodeInternalType.ROOT.toString(), null))); this.dirty = true; } } /** * * @see org.eclipse.ui.part.EditorPart#isDirty() */ public boolean isDirty() { return this.dirty; } /** * Returns true; */ public boolean isSaveAsAllowed() { return true; } /** * * @see org.eclipse.ui.part.WorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite) */ public void createPartControl(Composite parent) { initializeViewer(parent, SWT.NONE); IActionBars bars = this.getEditorSite().getActionBars(); if (bars.getGlobalActionHandler(ActionFactory.COPY.getId()) == null) { bars.setGlobalActionHandler(ActionFactory.COPY.getId(), new BTEditorCopyNode()); bars.updateActionBars(); } if (bars.getGlobalActionHandler(ActionFactory.PASTE.getId()) == null) { bars.setGlobalActionHandler(ActionFactory.PASTE.getId(), new BTEditorPasteNode()); bars.updateActionBars(); } /* Expands all the nodes of the tree. */ this.expandTree(true); /* * Selection listener that updates the NodeInfo view in order to show * the currently selected node. */ this.viewer.addSelectionChangedListener(new ISelectionChangedListener() { public void selectionChanged(SelectionChangedEvent event) { NodeInfo nodeInfoView = (NodeInfo) Utilities.getView(NodeInfo.class); if (nodeInfoView != null) { List<BTNode> selectedElements = getSelectedElements(); if (selectedElements.size() == 1) { nodeInfoView.setNode(selectedElements.get(0)); } } } }); } /** * * @see org.eclipse.ui.part.WorkbenchPart#setFocus() */ public void setFocus() { /* * Update the NodeInfo view so that it displays the information of the * currently selected node. */ NodeInfo nodeInfoView = (NodeInfo) Utilities.getView(NodeInfo.class); if (nodeInfoView != null) { List<BTNode> selectedElements = getSelectedElements(); if (selectedElements.size() == 1) { nodeInfoView.setNode(selectedElements.get(0)); } else { nodeInfoView.setNode(null); } } } private void initializeViewer(Composite parent, int style) { /* Initializes the viewer. */ this.viewer = new TreeViewer(parent, style); this.viewer.setContentProvider(new BTContentProvider()); BTLabelProvider btLabelProvider = new BTLabelProvider(); DecoratingLabelProvider decoratingLabelProvider = new DecoratingLabelProvider(btLabelProvider, btLabelProvider); this.viewer.setLabelProvider(decoratingLabelProvider); this.viewer.setInput(this.tree); final Tree treeWidget = (Tree) this.viewer.getControl(); /* Key listener for deleting nodes. The root node cannot be deleted. */ treeWidget.addKeyListener(new KeyAdapter() { public void keyPressed(KeyEvent e) { if (e.keyCode == SWT.DEL || e.keyCode == SWT.BS) { List<BTNode> selectedNodes = getSelectedElements(); if (selectedNodes.size() != 0) { new DeleteNode(selectedNodes).run(); } } } }); /* Adding drag support. */ this.viewer.addDragSupport(DND.DROP_MOVE, new Transfer[] { BTNodeIndentifierTransfer.getInstance() }, new BTEditorDragSourceListener()); /* Adding drop support. */ this.viewer.addDropSupport(DND.DROP_MOVE, new Transfer[] { ConceptualBTNodeTransfer.getInstance(), BTNodeIndentifierTransfer.getInstance() }, new BTEditorDropTargetListener()); /* Menu listener that creates the context menu. */ treeWidget.addMenuDetectListener(new MenuDetectListener() { public void menuDetected(MenuDetectEvent e) { List<BTNode> selectedNodes = getSelectedElements(); if (selectedNodes.size() != 0) { MenuManager menuManager = new MenuManager(); menuManager.add(new DeleteNode(selectedNodes)); menuManager.add(new ExpandNodes(selectedNodes)); menuManager.add(new CollapseNodes(selectedNodes)); if (selectedNodes.size() == 1) { BTNode selectedNode = selectedNodes.get(0); if (!selectedNode.getConceptualNode().getType().equals(NodeInternalType.ROOT.toString())) { menuManager.add(new EditGuard(selectedNode)); } menuManager.add(new CopyNode()); PasteNode pasteAction = new PasteNode(); menuManager.add(pasteAction); } treeWidget.setMenu(menuManager.createContextMenu(treeWidget)); } } }); /* * Listener that shows a panel for editing the parameters of the node. */ this.viewer.addDoubleClickListener(new IDoubleClickListener() { public void doubleClick(DoubleClickEvent event) { IStructuredSelection selection = (IStructuredSelection) event.getSelection(); if (!selection.isEmpty()) { BTNode selectedNode = (BTNode) selection.getFirstElement(); if (selectedNode.getConceptualNode().getParameters().size() != 0 || (selectedNode.getConceptualNode().getType() == NodeInternalType.ROOT.toString() && !isFromGuard())) { new NodeParametersDialog(Utilities.getShell(), selectedNode).open(); } } } }); } /** * Expands or collapses all the nodes of the tree. If <code>expand</code> is * true, the tree is expanded. If false, it is collapsed. */ public void expandTree(boolean expand) { if (expand) { this.viewer.expandAll(); } else { this.viewer.collapseAll(); } this.clearErrors(); } /** * Returns a list of the selected elements, or an empty list if none is * selected. */ public List<BTNode> getSelectedElements() { return ((IStructuredSelection) this.viewer.getSelection()).toList(); } /** * * @see jbt.tools.bteditor.event.ITreeModifierListener#treeModified(jbt.tools.bteditor.event.TreeModifiedEvent) */ public void treeModified(TreeModifiedEvent event) { this.dirty = true; firePropertyChange(EditorPart.PROP_DIRTY); } /** * Checks the validity of the tree. It highlights the nodes that are * incorrect. * * @return true if the tree is correct, and false otherwise. */ public boolean checkTree() { this.clearErrors(); List<BTNode> incorrectNodes = this.tree.checkTree(); if (incorrectNodes.size() != 0) { for (BTNode n : incorrectNodes) { this.setErrorColor(n); } return false; } return true; } /** * Returns the BT that is being edited by this BTEditor. * * @return the BT that is being edited by this BTEditor. */ public BT getBT() { return this.tree; } /** * Selects a node of the BT. If the node does not exist, nothing happens. * * @param node * the node to select. */ public void selectNode(BTNode node) { this.viewer.setSelection(new StructuredSelection(node), true); } /** * Selects a node of the BT. If the node does not exist, nothing happens. * * @param nodeID * the identifier of the node to select. */ public void selectNode(Identifier nodeID) { BTNode node = this.tree.findNode(nodeID); if (node != null) { this.viewer.setSelection(new StructuredSelection(node), true); } } /** * Returns true if the BT that this BTEditor is editing comes from a file, * and false otherwise. * * @return true if the BT that this BTEditor is editing comes from a file, * and false otherwise. */ private boolean isFromFile() { return ((BTEditorInput) getEditorInput()).isFromFile(); } /** * Sets if the BT that this BTEditor is editing comes from a file. * * @param isFromFile * true if the BT comes from a file, and false otherwise. */ private void setIsFromFile(boolean isFromFile) { ((BTEditorInput) getEditorInput()).setIsFromFile(isFromFile); } /** * Returns true if the BT that this BTEditor is editing comes from a guard, * and false otherwise. * * @return true if the BT that this BTEditor is editing comes from a guard, * and false otherwise. */ private boolean isFromGuard() { return ((BTEditorInput) getEditorInput()).isFromGuard(); } /** * Sets if the BT that this BTEditor is editing comes from a guard. * * @param isFromGuard * true if the BT comes from a guard, and false otherwise. */ private void setIsFromGuard(boolean isFromGuard) { ((BTEditorInput) getEditorInput()).setIsFromGuard(isFromGuard); } /** * Highlights with an error color the node <code>node</code>. */ private void setErrorColor(BTNode node) { TreeItem item = findNode(this.viewer.getTree().getTopItem(), node); if (item != null) { item.setBackground(ERROR_COLOR); } } /** * Returns the TreeItem associated to <code>node</code>. The search starts * from the node by <code>item</code>, so all the nodes whose root is * <code>item</code> are searched for. Returns null if no such element is * found. */ private TreeItem findNode(TreeItem item, BTNode node) { if (node == item.getData()) { return item; } else { for (TreeItem child : item.getItems()) { TreeItem found = findNode(child, node); if (found != null) { return found; } } return null; } } /** * Clears the errors that may be being showed in the tree of the editor. */ public void clearErrors() { this.tree.clearErrors(); TreeItem rootItem = viewer.getTree().getTopItem(); /* * This check is done because when the TreeViewer is created in the * BTEditor, somehow the rootItem is null despite the fact that there is * an actual root element in the tree. I do not know why this happened. * Actually, after so many changes, I do not know if this still happens * :S */ if (rootItem != null) { internalClearErrorColors(rootItem); } } /** * Clears the error color from all the nodes of the tree whose root element * is <code>item</code>. */ private void internalClearErrorColors(TreeItem item) { item.setBackground(null); for (TreeItem child : item.getItems()) { internalClearErrorColors(child); } } /** * Label provider of the underlying TreeViewer. * * @author Ricardo Juan Palma Durn * */ private static class BTLabelProvider implements ILabelProvider, ILabelDecorator { private List<Image> disposableImages = new LinkedList<Image>(); public void addListener(ILabelProviderListener listener) { } public void dispose() { for (Image i : this.disposableImages) { i.dispose(); } } public boolean isLabelProperty(Object element, String property) { return false; } public void removeListener(ILabelProviderListener listener) { } public Image getImage(Object element) { BTNode node = (BTNode) element; return ApplicationIcons.getIcon(node.getConceptualNode().getIcon()); } public String getText(Object element) { BTNode node = (BTNode) element; if (node.getConceptualNode().getType().equals(NodeInternalType.SUBTREE_LOOKUP.toString())) { for (Parameter p : node.getParameters()) { if (p.getName().equals("subtreeName")) { return node.getConceptualNode().getReadableType() + " - " + p.getValue(); } } } return node.getConceptualNode().getReadableType(); } /** * If the BTNode has a guard, it is decorated. Otherwise, it is left * unchanged. * * @see org.eclipse.jface.viewers.ILabelDecorator#decorateImage(org.eclipse.swt.graphics.Image, * java.lang.Object) */ public Image decorateImage(Image image, Object element) { /* Overlay decorator image over base image. */ BTNode node = (BTNode) element; if (node.getGuard() != null) { Image result; OverlayImageIcon overlayIcon = new OverlayImageIcon(image, ApplicationIcons.getIcon(IconsPaths.GUARD)); result = overlayIcon.getImage(); this.disposableImages.add(result); return result; } else { return null; } } public String decorateText(String text, Object element) { return null; } } /** * Content provider for the underlying TreeViewer. Its input is a {@link BT} * . * * @author Ricardo Juan Palma Durn * */ private static class BTContentProvider implements ITreeContentProvider { public Object[] getChildren(Object parentElement) { BTNode node = (BTNode) parentElement; return node.getChildren().toArray(); } public Object getParent(Object element) { BTNode node = (BTNode) element; return node.getParent(); } public boolean hasChildren(Object element) { BTNode node = (BTNode) element; return node.getNumChildren() > 0; } public Object[] getElements(Object inputElement) { BTNode root = ((BT) inputElement).getRoot(); if (root != null) { return new Object[] { root }; } else { return new Object[] {}; } } public void dispose() { } public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { } } /** * Copies the currently selected BTNode of the currently active BTEditor * (<b>not necessarily this one</b>) into the * {@link BTEditorCopyAndPasteManager} for future paste operations. If the * root node is selected, what is copied is its child (if it has one). If no * node is selected, nothing is copied. If several nodes are currently * selected, nothing is copied. If there is no active BTEditor, nothing is * copied either. * * @author Ricardo Juan Palma Durn * */ private class BTEditorCopyNode extends Action implements IAction { private BTNode selectedNode; public BTEditorCopyNode() { this.setText("Copy"); } public void run() { BTEditor activeEditor = Utilities.getActiveBTEditor(); if (activeEditor != null) { List<BTNode> selectedElements = activeEditor.getSelectedElements(); if (selectedElements.size() == 1) { this.selectedNode = selectedElements.get(0); if (this.selectedNode.getConceptualNode().getType().equals(NodeInternalType.ROOT.toString())) { if (this.selectedNode.getChildren().size() != 0) { this.selectedNode = this.selectedNode.getChildren().get(0); } else { this.selectedNode = null; } } } if (this.selectedNode != null) { BTEditorCopyAndPasteManager.getInstance().copy(this.selectedNode); } } } } /** * Paste into the currently selected node of the currently active BTEditor * (<b>not necessarily this one</b>) whatever BTNode is currently copied in * the {@link BTEditorCopyAndPasteManager}. The node is pasted as a child of * the currently selected node. If no node is currently copied in the * BTEditorCopyAndPasteManager, nothing is done. If the currently selected * node cannot hold any more children, nothing is done. Also, if there is no * active BTEditor, nothing is done. * * @author Ricardo Juan Palma Durn * */ private class BTEditorPasteNode extends Action implements IAction { private BTNode selectedNode; public BTEditorPasteNode() { this.setText("Paste"); } public void run() { if (!BTEditorCopyAndPasteManager.getInstance().hasCopy()) { return; } BTEditor activeEditor = Utilities.getActiveBTEditor(); if (activeEditor != null) { List<BTNode> selectedElements = activeEditor.getSelectedElements(); if (selectedElements.size() == 1) { this.selectedNode = selectedElements.get(0); if (this.selectedNode.getConceptualNode().getNumChildren() != -1 && this.selectedNode .getConceptualNode().getNumChildren() <= this.selectedNode.getNumChildren()) { this.selectedNode = null; } } if (this.selectedNode != null) { BTNode pastedNode = BTEditorCopyAndPasteManager.getInstance().paste(); activeEditor.tree.recomputeIDs(pastedNode); if (pastedNode.getBT() != activeEditor.tree) { activeEditor.tree.reassignUnderlyingBT(pastedNode); } this.selectedNode.addChild(pastedNode); pastedNode.setParent(this.selectedNode); activeEditor.tree.updateNodeCounter(); activeEditor.viewer.refresh(this.selectedNode); activeEditor.treeChanged(this); } } } } /** * Copies the currently selected BTNode into the * {@link BTEditorCopyAndPasteManager} for future paste operations. If no * node is selected, nothing is copied. If the root node is selected, what * is copied is its child (if it has one). If several nodes are currently * selected, nothing is copied either. * * @author Ricardo Juan Palma Durn * */ private class CopyNode extends Action implements IAction { private BTNode selectedNode; public CopyNode() { this.setText("Copy"); } public void run() { List<BTNode> selectedElements = getSelectedElements(); if (selectedElements.size() == 1) { this.selectedNode = selectedElements.get(0); if (this.selectedNode.getConceptualNode().getType().equals(NodeInternalType.ROOT.toString())) { if (this.selectedNode.getChildren().size() != 0) { this.selectedNode = this.selectedNode.getChildren().get(0); } else { this.selectedNode = null; } } } if (this.selectedNode != null) { BTEditorCopyAndPasteManager.getInstance().copy(this.selectedNode); } } } /** * Paste into the currently selected node whatever BTNode is currently * copied in the {@link BTEditorCopyAndPasteManager}. The node is pasted as * a child of the currently selected node. If no node is currently copied in * the BTEditorCopyAndPasteManager, nothing is done. Also, if the currently * selected node cannot hold any more children, nothing is done. * * @author Ricardo Juan Palma Durn * */ private class PasteNode extends Action implements IAction { private BTNode selectedNode; public PasteNode() { this.setText("Paste"); } public void run() { if (!BTEditorCopyAndPasteManager.getInstance().hasCopy()) { return; } List<BTNode> selectedElements = getSelectedElements(); if (selectedElements.size() == 1) { this.selectedNode = selectedElements.get(0); if (this.selectedNode.getConceptualNode().getNumChildren() != -1 && this.selectedNode .getConceptualNode().getNumChildren() <= this.selectedNode.getNumChildren()) { this.selectedNode = null; } } if (this.selectedNode != null) { BTNode pastedNode = BTEditorCopyAndPasteManager.getInstance().paste(); if (pastedNode.getBT() != tree) { tree.reassignUnderlyingBT(pastedNode); } tree.recomputeIDs(pastedNode); this.selectedNode.addChild(pastedNode); pastedNode.setParent(this.selectedNode); tree.updateNodeCounter(); viewer.refresh(this.selectedNode); treeChanged(this); } } } /** * Action for deleting a list of nodes from the tree. The root node is not * deleted. * * @author Ricardo Juan Palma Durn * */ private class DeleteNode extends Action { private List<BTNode> selectedNodes; public DeleteNode(List<BTNode> selectedNodes) { this.setText("Delete node"); this.selectedNodes = selectedNodes; } public void run() { boolean nodeDeleted = false; for (BTNode node : this.selectedNodes) { if (!node.getConceptualNode().getType().equals(NodeInternalType.ROOT.toString())) { if (openGuardEditors.containsKey(node)) { boolean delete = StandardDialogs.confirmationDialog("Node with open guard", "Node " + node.getID().toString() + " has an open guard. If this node is removed, the guard will be dissociated from the tree. Are you sure you want to delete the node?"); if (delete) { BTNode parent = node.getParent(); parent.removeChild(node); viewer.refresh(parent); nodeDeleted = true; BTEditor guardEditor = openGuardEditors.get(node); BTEditorInput editorInput = (BTEditorInput) guardEditor.getEditorInput(); editorInput.setTreeName(editorInput.getName()); guardEditor.dissociateFromParentTree(); guardEditor.dirty = true; guardEditor.firePropertyChange(PROP_TITLE); guardEditor.firePropertyChange(PROP_DIRTY); openGuardEditors.remove(node); } } else { BTNode parent = node.getParent(); parent.removeChild(node); viewer.refresh(parent); nodeDeleted = true; } } } if (nodeDeleted) { treeChanged(viewer); } } } /** * Action that expands nodes of the tree. * * @author Ricardo Juan Palma Durn * */ private class ExpandNodes extends Action { private List<BTNode> nodesToExpand; public ExpandNodes(List<BTNode> nodesToExpand) { this.setText("Expand node"); this.nodesToExpand = nodesToExpand; } public void run() { for (BTNode node : this.nodesToExpand) { viewer.expandToLevel(node, TreeViewer.ALL_LEVELS); } } } private class EditGuard extends Action { private BTNode node; public EditGuard(BTNode node) { this.setText("Edit Guard"); this.node = node; } public void run() { new GuardEditionDialog(Utilities.getShell(), this.node).open(); } } /** * Action that expands nodes of the tree. * * @author Ricardo Juan Palma Durn * */ private class CollapseNodes extends Action { private List<BTNode> nodesToCollapse; public CollapseNodes(List<BTNode> nodesToCollapse) { this.setText("Collapse node"); this.nodesToCollapse = nodesToCollapse; } public void run() { for (BTNode node : this.nodesToCollapse) { viewer.collapseToLevel(node, TreeViewer.ALL_LEVELS); } } } /** * Dialog that lets the user: * <ul> * <li>Add a guard to a node. * <li>Modify the guard's parameters. * <li>Remove the guard from a node. * </ul> * * @author Ricardo Juan Palma Durn * */ private class GuardEditionDialog extends Dialog { private BTNode node; private TreeViewer guardsViewer; private Button editGuardButton; private Button addSimpleGuardButton; private Button removeGuardButton; private Button addComplexGuardButton; public GuardEditionDialog(Shell parentShell, BTNode node) { super(parentShell); this.node = node; this.setBlockOnOpen(false); this.setShellStyle(SWT.RESIZE | SWT.CLOSE | SWT.APPLICATION_MODAL | SWT.MAX); } /* * (non-Javadoc) * * @see * org.eclipse.jface.dialogs.TitleAreaDialog#createContents(org.eclipse * .swt.widgets.Composite) */ protected Control createContents(Composite parent) { Control contents = super.createContents(parent); return contents; } /** * * @see org.eclipse.jface.dialogs.Dialog#createDialogArea(org.eclipse.swt.widgets.Composite) */ protected Control createDialogArea(Composite parent) { Composite composite = (Composite) super.createDialogArea(parent);// new composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); composite.setLayout(new GridLayout(2, false)); /* * We force the default size of the list displaying the guard to be * two rows, and to have at least a minimum width. */ Composite guardsViewerComposite = new Composite(composite, SWT.NONE) { public Point computeSize(int wHint, int hHint, boolean changed) { return new Point(wHint < 200 ? 200 : wHint, guardsViewer.getTree().getItemHeight() * 2); } }; guardsViewerComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); guardsViewerComposite.setLayout(new FillLayout()); this.guardsViewer = new TreeViewer(guardsViewerComposite); this.guardsViewer.setContentProvider(new BTContentProvider()); this.guardsViewer.setLabelProvider(new BTLabelProvider()); /* Double click listener for editing the guards parameters. */ this.guardsViewer.addDoubleClickListener(new IDoubleClickListener() { public void doubleClick(DoubleClickEvent event) { if (node.getGuard() != null) { if (node.getGuard().getChildren().size() != 0) { try { openGuardEditor(node); close(); } catch (PartInitException ex) { StandardDialogs.exceptionDialog("Error opening the guard", "There was an unexpected error when opening the guard", ex); } } else if (node.getGuard().getConceptualNode().getParameters().size() != 0) { new NodeParametersDialog(getShell(), node.getGuard()).open(); } } } }); if (this.node.getGuard() != null) { BT input = new BT(this.node.getGuard()); this.guardsViewer.setInput(input); } Composite buttonsComposite = new Composite(composite, SWT.NONE); buttonsComposite.setLayout(new GridLayout(1, true)); buttonsComposite.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false)); this.addSimpleGuardButton = createAddSimpleGuardButton(buttonsComposite); this.addComplexGuardButton = createAddComplexGuardButton(buttonsComposite); this.editGuardButton = createEditGuardButton(buttonsComposite); this.removeGuardButton = createRemoveGuardButton(buttonsComposite); return composite; } private Button createAddSimpleGuardButton(Composite parent) { Button addSimpleGuardButton = new Button(parent, SWT.PUSH); addSimpleGuardButton.setText("Add simple guard"); addSimpleGuardButton.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); final GuardEditionDialog guardEditionDialog = this; addSimpleGuardButton.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent e) { new GuardInsertionDialog(Utilities.getShell(), node, guardEditionDialog).open(); } }); return addSimpleGuardButton; } private Button createAddComplexGuardButton(Composite parent) { Button addGuardButton = new Button(parent, SWT.PUSH); addGuardButton.setText("Add complex guard"); addGuardButton.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); addGuardButton.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent e) { try { openGuardEditor(node); close(); } catch (PartInitException ex) { StandardDialogs.exceptionDialog("Error opening the guard", "There was an unexpected error when opening the guard", ex); } } }); return addGuardButton; } private Button createEditGuardButton(Composite parent) { Button editGuardButton = new Button(parent, SWT.PUSH); editGuardButton.setText("Edit guard"); editGuardButton.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); editGuardButton.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent e) { if (node.getGuard() != null) { if (node.getGuard().getChildren().size() != 0) { try { openGuardEditor(node); close(); } catch (PartInitException ex) { StandardDialogs.exceptionDialog("Error when creating new tree", "There was an unexpected error when creating the tree", ex); } } else if (node.getGuard().getConceptualNode().getParameters().size() != 0) { new NodeParametersDialog(getShell(), node.getGuard()).open(); } } } }); return editGuardButton; } private Button createRemoveGuardButton(Composite parent) { final Button removeGuardButton = new Button(parent, SWT.PUSH); removeGuardButton.setText("Remove guard"); removeGuardButton.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); removeGuardButton.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent e) { if (node.getGuard() != null) { node.setGuard(null); guardsViewer.setInput(null); /* Dissociate the editor that is editing the guard. */ if (openGuardEditors.containsKey(node)) { BTEditor guardEditor = openGuardEditors.get(node); BTEditorInput editorInput = (BTEditorInput) guardEditor.getEditorInput(); editorInput.setTreeName(editorInput.getName()); guardEditor.dissociateFromParentTree(); guardEditor.dirty = true; guardEditor.firePropertyChange(PROP_TITLE); guardEditor.firePropertyChange(PROP_DIRTY); } treeChanged(removeGuardButton); } } }); return removeGuardButton; } /* * (non-Javadoc) * * @see org.eclipse.jface.window.Window#configureShell(org.eclipse.swt. * widgets .Shell) */ protected void configureShell(Shell shell) { super.configureShell(shell); shell.setText("Guard Editor"); } /* * (non-Javadoc) * * @see org.eclipse.jface.dialogs.Dialog#createButtonsForButtonBar(org. * eclipse .swt.widgets.Composite) */ protected void createButtonsForButtonBar(Composite parent) { createButton(parent, IDialogConstants.CLOSE_ID, IDialogConstants.CLOSE_LABEL, false); } /* * (non-Javadoc) * * @see org.eclipse.jface.dialogs.Dialog#buttonPressed(int) */ protected void buttonPressed(int buttonId) { this.setReturnCode(buttonId); if (buttonId == IDialogConstants.CLOSE_ID) { viewer.update(this.node, null); close(); } } public void setGuard() { if (this.node.getGuard() != null) { BT input = new BT(this.node.getGuard()); this.guardsViewer.setInput(input); } } } /** * Dialog that shows all the loaded leaf nodes and lets the user select one * to be inserted as the guard of a node. * * @author Ricardo Juan Palma Durn * */ private class GuardInsertionDialog extends Dialog { private BTNode node; private TableViewer leafNodesViewer; private GuardEditionDialog guardEditionDialog; public GuardInsertionDialog(Shell parentShell, BTNode node, GuardEditionDialog guardEditionDialog) { super(parentShell); this.node = node; this.setBlockOnOpen(false); this.guardEditionDialog = guardEditionDialog; this.setShellStyle(SWT.RESIZE | SWT.CLOSE | SWT.APPLICATION_MODAL); } /* * (non-Javadoc) * * @see * org.eclipse.jface.dialogs.TitleAreaDialog#createContents(org.eclipse * .swt.widgets.Composite) */ protected Control createContents(Composite parent) { Control contents = super.createContents(parent); return contents; } /** * * @see org.eclipse.jface.dialogs.Dialog#createDialogArea(org.eclipse.swt.widgets.Composite) */ protected Control createDialogArea(Composite parent) { Composite composite = (Composite) super.createDialogArea(parent); /* * We force the default size of the list displaying the list of leaf * nodes guard to be seven rows. */ Composite leafNodesViewerComposite = new Composite(composite, SWT.NONE) { public Point computeSize(int wHint, int hHint, boolean changed) { return new Point(leafNodesViewer.getTable().computeSize(SWT.DEFAULT, SWT.DEFAULT).x, leafNodesViewer.getTable().getItemHeight() * 7); } }; leafNodesViewerComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); leafNodesViewerComposite.setLayout(new FillLayout()); this.leafNodesViewer = new TableViewer(leafNodesViewerComposite, SWT.BORDER | SWT.SINGLE); this.leafNodesViewer.setContentProvider(new ConceptualBTNodeListContentProvider()); this.leafNodesViewer.setLabelProvider(new ConceptualBTNodeListLabelProvider()); /* * Build a list with all the nodes that can be used as guards, which * are leaf nodes. */ List<ConceptualBTNode> standardNodes = NodesLoader.getStandardNodes(); List<ConceptualBTNode> nonStandardNodes = NodesLoader.getNonStandardNodes(); List<ConceptualBTNode> leafNodes = new Vector<ConceptualBTNode>(); for (ConceptualBTNode node : standardNodes) { if (node.getNumChildren() == 0) { leafNodes.add(node); } } for (ConceptualBTNode node : nonStandardNodes) { if (node.getNumChildren() == 0) { leafNodes.add(node); } } /* This list is the input to the viewer. */ this.leafNodesViewer.setInput(leafNodes); this.leafNodesViewer.getTable().select(0); /* * Set a sorter for the elements. First standard nodes, then * conditions, and finally actions. */ this.leafNodesViewer.setSorter(new ViewerSorter() { public int compare(Viewer viewer, Object e1, Object e2) { ConceptualBTNode node1 = (ConceptualBTNode) e1; ConceptualBTNode node2 = (ConceptualBTNode) e2; String node1Type = node1.getType(); String node2Type = node2.getType(); if ((node1Type.equals(NodeInternalType.ACTION.toString()) || node1Type.equals(NodeInternalType.CONDITION.toString())) && node2Type.equals(node1Type)) { return node1.getReadableType().compareToIgnoreCase(node2.getReadableType()); } if (node1Type.equals(NodeInternalType.ACTION.toString()) && node2Type.equals(NodeInternalType.CONDITION.toString())) { return 1; } if (node1Type.equals(NodeInternalType.CONDITION.toString()) && node2Type.equals(NodeInternalType.ACTION.toString())) { return -1; } if (!node1Type.equals(NodeInternalType.ACTION.toString()) && !node1Type.equals(NodeInternalType.CONDITION.toString())) { if (!node2Type.equals(NodeInternalType.ACTION.toString()) && !node2Type.equals(NodeInternalType.CONDITION.toString())) { return node1.getReadableType().compareToIgnoreCase(node2.getReadableType()); } else { return -1; } } return node1.getReadableType().compareToIgnoreCase(node2.getReadableType()); } }); /* Add double click listener. */ this.leafNodesViewer.addDoubleClickListener(new IDoubleClickListener() { public void doubleClick(DoubleClickEvent event) { setGuardFromSelection(); treeChanged(this); close(); } }); return composite; } /** * Content provider for a list of {@link ConceptualBTNode} objects. * * Its input is a List<ConceptualBTNode>. * * @author Ricardo Juan Palma Durn * */ private class ConceptualBTNodeListContentProvider implements IStructuredContentProvider { public Object[] getElements(Object inputElement) { return ((List<ConceptualBTNode>) inputElement).toArray(); } public void dispose() { } public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { } } /** * Label provider for objects of type ConceptualBTNode. * * @author Ricardo Juan Palma Durn * */ private class ConceptualBTNodeListLabelProvider implements ILabelProvider { public Image getImage(Object element) { ConceptualBTNode node = (ConceptualBTNode) element; return ApplicationIcons.getIcon(node.getIcon()); } public String getText(Object element) { ConceptualBTNode node = (ConceptualBTNode) element; return node.getReadableType(); } public void addListener(ILabelProviderListener listener) { } public void dispose() { } public boolean isLabelProperty(Object element, String property) { return false; } public void removeListener(ILabelProviderListener listener) { } } /* * (non-Javadoc) * * @see org.eclipse.jface.window.Window#configureShell(org.eclipse.swt. * widgets .Shell) */ protected void configureShell(Shell shell) { super.configureShell(shell); shell.setText("Guard Editor"); } /* * (non-Javadoc) * * @see org.eclipse.jface.dialogs.Dialog#createButtonsForButtonBar(org. * eclipse .swt.widgets.Composite) */ protected void createButtonsForButtonBar(Composite parent) { createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, false); createButton(parent, IDialogConstants.CLOSE_ID, IDialogConstants.CLOSE_LABEL, false); } /* * (non-Javadoc) * * @see org.eclipse.jface.dialogs.Dialog#buttonPressed(int) */ protected void buttonPressed(int buttonId) { this.setReturnCode(buttonId); if (buttonId == IDialogConstants.CLOSE_ID) { close(); } else { setGuardFromSelection(); treeChanged(this); close(); } } private void setGuardFromSelection() { ConceptualBTNode selectedConceptualBTNode = (ConceptualBTNode) ((IStructuredSelection) this.leafNodesViewer .getSelection()).toList().get(0); this.node.setGuard(tree.createNode(selectedConceptualBTNode)); this.guardEditionDialog.setGuard(); } } /** * Dialog that is used to enter parameter values. * * @author Ricardo Juan Palma Durn * */ private class NodeParametersDialog extends Dialog { private BTNode node; private NameEditorComposite nameEditor; private ParametersEditorComposite nodeEditor; /** * Constructor. <code>node</code> must be a node with parameters. */ public NodeParametersDialog(Shell shell, BTNode node) { super(shell); this.setBlockOnOpen(false); this.node = node; this.setShellStyle(SWT.RESIZE | SWT.CLOSE | SWT.APPLICATION_MODAL); } /* * (non-Javadoc) * * @see * org.eclipse.jface.dialogs.TitleAreaDialog#createContents(org.eclipse * .swt.widgets.Composite) */ protected Control createContents(Composite parent) { Control contents = super.createContents(parent); return contents; } /** * * @see org.eclipse.jface.dialogs.Dialog#createDialogArea(org.eclipse.swt.widgets.Composite) */ protected Control createDialogArea(Composite parent) { Composite composite = (Composite) super.createDialogArea(parent); if (this.node.getConceptualNode().getType().equals(NodeInternalType.ROOT.toString())) { this.nameEditor = new NameEditorComposite(composite, SWT.NONE, this.node.getName()); this.nameEditor.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); } if (this.node.getConceptualNode().getParameters().size() != 0) { Group parametersGroup = new Group(composite, SWT.NONE); parametersGroup.setText("Parameters"); parametersGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); parametersGroup.setLayout(new GridLayout(1, false)); this.nodeEditor = new ParametersEditorComposite(parametersGroup, SWT.NONE, this.node); this.nodeEditor.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); } return composite; } /* * (non-Javadoc) * * @see org.eclipse.jface.window.Window#configureShell(org.eclipse.swt. * widgets .Shell) */ protected void configureShell(Shell shell) { super.configureShell(shell); shell.setText("Node Editor"); } /* * (non-Javadoc) * * @see org.eclipse.jface.dialogs.Dialog#createButtonsForButtonBar(org. * eclipse .swt.widgets.Composite) */ protected void createButtonsForButtonBar(Composite parent) { createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true); createButton(parent, IDialogConstants.CLOSE_ID, IDialogConstants.CLOSE_LABEL, false); } /* * (non-Javadoc) * * @see org.eclipse.jface.dialogs.Dialog#buttonPressed(int) */ protected void buttonPressed(int buttonId) { this.setReturnCode(buttonId); if (buttonId == IDialogConstants.CLOSE_ID) { close(); } else { if (this.nodeEditor != null) { try { List<Parameter> params = this.nodeEditor.getParameters(); this.node.clearParameters(); for (Parameter p : params) { this.node.addParameter(p); } } catch (Exception e) { MessageDialog.openError(this.getShell(), "Error validating parameters", e.getMessage()); return; } } if (this.nameEditor != null) { try { this.node.setName(this.nameEditor.getNodeName()); } catch (Exception e) { MessageDialog.openError(this.getShell(), "Error validating name", e.getMessage()); return; } } treeChanged(viewer); if (node.getConceptualNode().getType().equals(NodeInternalType.SUBTREE_LOOKUP.toString())) { viewer.update(node, null); } close(); } } } /** * Composite for editing the name of the root tree. * * @author Ricardo Juan Palma Durn * */ private class NameEditorComposite extends Composite { private Label nameLabel; private Text nameValue; /** * <code>initialName</code> is the name that is displayed when the * Composite is created. It may be null, in which case no name is * displayed. */ public NameEditorComposite(Composite parent, int style, String initialName) { super(parent, style); this.setLayout(new GridLayout(2, false)); this.nameLabel = new Label(this, SWT.NONE); this.nameLabel.setText("Name"); this.nameLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false)); this.nameValue = new Text(this, SWT.BORDER); this.nameValue.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); this.nameValue.setText(initialName == null ? "" : initialName); } /** * Returns the name, or throws an exception if it is not a valid name. */ public String getNodeName() throws RuntimeException { if (this.nameValue.getText().equals("")) { throw new RuntimeException("Invalid name"); } return this.nameValue.getText(); } } /** * Composite for editing the parameters of a node. It internally stores many * {@link ParameterComposite}, one for every parameter of the node. * * @author Ricardo Juan Palma Durn * */ private class ParametersEditorComposite extends Composite { private BTNode node; private List<ParameterComposite> parameterComposites; public ParametersEditorComposite(Composite parent, int style, BTNode node) { super(parent, style); this.node = node; this.setLayout(new GridLayout(1, false)); this.parameterComposites = new Vector<ParameterComposite>(); List<jbt.tools.bteditor.model.ConceptualBTNode.Parameter> parameters = this.node.getConceptualNode() .getParameters(); for (int i = 0; i < parameters.size(); i++) { String currentParameterValue = this.node.getParameters().size() > 0 ? this.node.getParameters().get(i).getValue() : null; boolean currentFromContext = this.node.getParameters().size() > 0 ? this.node.getParameters().get(i).getFromContext() : false; ParameterComposite pc = new ParameterComposite(this, SWT.NONE, parameters.get(i), currentParameterValue, currentFromContext); this.parameterComposites.add(pc); pc.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); } } public List<Parameter> getParameters() throws RuntimeException { List<Parameter> result = new Vector<Parameter>(); for (ParameterComposite pc : this.parameterComposites) { result.add(pc.getParameter()); } return result; } } /** * Composite for editing an individual parameter. If the parameter is * contextable, the value of the context location where to find the * parameter may be specified. * * @author Ricardo Juan Palma Durn * */ private class ParameterComposite extends Composite { private jbt.tools.bteditor.model.ConceptualBTNode.Parameter parameter; private Label nameLabel; private Text valueText; private Button fromContextButton; private static final int DEFAULT_TEXT_FIELD_WIDTH = 100; /** * <code>initialValue</code> may be null. */ public ParameterComposite(Composite parent, int style, jbt.tools.bteditor.model.ConceptualBTNode.Parameter parameter, String initialValue, boolean initialFromContext) { super(parent, style); this.setLayout(new GridLayout(3, false)); this.parameter = parameter; String tooltip = getTooltip(parameter); this.nameLabel = new Label(this, SWT.NONE); this.nameLabel .setText(this.parameter.getName() + " (" + this.parameter.getType().getReadableType() + ")"); this.nameLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false)); this.nameLabel.setToolTipText(tooltip); this.valueText = new Text(this, SWT.BORDER); this.valueText.setText(initialValue == null ? "" : initialValue); GridData data = new GridData(SWT.FILL, SWT.CENTER, true, false); data.widthHint = DEFAULT_TEXT_FIELD_WIDTH; this.valueText.setLayoutData(data); this.valueText.setToolTipText(tooltip); if (this.parameter.getContextable()) { this.fromContextButton = new Button(this, SWT.CHECK); this.fromContextButton.setText("From context"); this.fromContextButton.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false)); this.fromContextButton.setSelection(initialFromContext); this.fromContextButton.setToolTipText( "If activated, the specified value represents the place, in the context, where the value of the variable will be retrieved from"); if (this.parameter.getType() == ParameterType.OBJECT) { this.fromContextButton.setEnabled(false); this.fromContextButton.setSelection(true); } } } /** * Returns the parameter shown by the Composite, and throws an exception * in case there is some error in the parameter. */ public Parameter getParameter() throws RuntimeException { /* * If the parameter must come from the context, then any non-empty * string value is allowed. */ if (this.parameter.getContextable() && this.fromContextButton.getSelection()) { if (this.valueText.getText().equals("")) { throw new RuntimeException("Invalid parameter value for parameter " + this.parameter.getName() + ". Must be a non-empty string."); } } else { if (!BTNode.checkParameter(parameter, this.valueText.getText(), tree)) { throw new RuntimeException("Invalid parameter value (" + this.valueText.getText() + ") for parameter " + this.parameter.getName()); } } Parameter result = new Parameter(); result.setName(this.parameter.getName()); result.setValue(this.valueText.getText()); if (this.parameter.getContextable()) { result.setFromContext(this.fromContextButton.getSelection()); } return result; } /** * Returns an appropriate descriptive tooltip for a * {@link jbt.tools.bteditor.model.ConceptualBTNode.Parameter}. */ private String getTooltip(jbt.tools.bteditor.model.ConceptualBTNode.Parameter parameterDefinition) { ParameterType parameterType = parameterDefinition.getType(); if (parameterType == ParameterType.BOOLEAN) { return "Boolean value. Can be either \"true\" or \"false\""; } if (parameterType == ParameterType.COORDINATE) { return "Coordinate value. Must be a sequence of real numbers separated by blank spaces"; } if (parameterType == ParameterType.DIRECTION) { return "Direction value. Can be any integer value"; } if (parameterType == ParameterType.INTEGER) { return "Integer value"; } if (parameterType == ParameterType.NODE_ID) { String validTypes = ""; if (parameterDefinition.getNodeClasses().size() != 0) { for (String type : parameterDefinition.getNodeClasses()) { validTypes += NodesLoader.getNode(type, null).getReadableType() + ", "; } validTypes = validTypes.substring(0, validTypes.length() - 2); } return "Node identifier. Must be the identifier of a node of the tree" + (validTypes.equals("") ? "" : ", with one of the following types: " + validTypes); } if (parameterType == ParameterType.OBJECT) { return "Object. Always retrieved from the context"; } if (parameterType == ParameterType.PARALLEL_POLICY) { return "Parallel task policy. Can be either \"sequence\" or \"selector\""; } if (parameterType == ParameterType.REAL) { return "Real value"; } if (parameterType == ParameterType.STATUS_CODE) { return "Task termination status code. Can be either \"success\" or \"failure\""; } if (parameterType == ParameterType.STRING) { return "String value. Any non-empty string"; } if (parameterType == ParameterType.LIST_OF_VARIABLES) { return "List of variables, surrounded each one by \"\", and separated by blank spaces"; } return ""; } } /** * Drag listener used by the BTEditor. This drag listener transfers the * selected element {@link BTNode}, and is compatible with the * {@link BTNodeIndentifierTransfer} type. * * @author Ricardo Juan Palma Durn * */ private class BTEditorDragSourceListener implements DragSourceListener { public void dragStart(DragSourceEvent event) { List<BTNode> selection = ((IStructuredSelection) viewer.getSelection()).toList(); if (selection.size() == 1) { if (!selection.get(0).getConceptualNode().getType().equals(NodeInternalType.ROOT.toString())) { event.doit = true; return; } } event.doit = false; } public void dragSetData(DragSourceEvent event) { if (BTNodeIndentifierTransfer.getInstance().isSupportedType(event.dataType)) { event.data = ((IStructuredSelection) viewer.getSelection()).getFirstElement(); } } public void dragFinished(DragSourceEvent event) { } } /** * Drop target listener used by the BTEditor. This drop target listener is * compatible with two transfer types, {@link ConceptualBTNodeTransfer} and * {@link BTNodeIndentifierTransfer}. * <p> * When the data being transfered is compatible with * {@link ConceptualBTNodeTransfer}, a new empty BTNode is created as a * child or as a sibling of the target of the drop operation (depending on * the specific position of the cursor at the moment the operation is * performed). * <p> * When the data being transfered is compatible with * {@link BTNodeIndentifierTransfer}, that node is removed from its current * position and inserted as a sibling or as a child of the target of the * drop operation (depending on the specific position of the cursor at the * moment the operation is performed). * <p> * <b>It should be noted that dragging from a BTEditor and dropping onto * another editor is not supported.</b> * * @author Ricardo Juan Palma Durn * */ private class BTEditorDropTargetListener implements DropTargetListener { public void dragEnter(DropTargetEvent event) { for (int i = 0; i < event.dataTypes.length; i++) { if (BTNodeIndentifierTransfer.getInstance().isSupportedType(event.dataTypes[i])) { event.currentDataType = event.dataTypes[i]; event.detail = DND.DROP_MOVE; break; } if (ConceptualBTNodeTransfer.getInstance().isSupportedType(event.dataTypes[i])) { event.currentDataType = event.dataTypes[i]; event.detail = DND.DROP_MOVE; break; } } } public void dragLeave(DropTargetEvent event) { event.detail = DND.DROP_MOVE; } public void dragOperationChanged(DropTargetEvent event) { event.detail = DND.DROP_MOVE; } public void dragOver(DropTargetEvent event) { if (ConceptualBTNodeTransfer.getInstance().isSupportedType(event.currentDataType)) { if (event.item == null) { event.detail = DND.DROP_NONE; return; } BTNode node = (BTNode) event.item.getData(); if (node.getConceptualNode().getType().equals(NodeInternalType.ROOT.toString())) { /* * The root node is always allowed as a target for inserting * as a child. */ event.feedback = DND.FEEDBACK_SELECT | DND.FEEDBACK_SCROLL | DND.FEEDBACK_EXPAND; } else { /* * If the target is not the root, then the source node can * be inserted as a child or as a sibling of the target. */ if (!closeToBottom((TreeItem) event.item)) { /* * Cannot move if the limit number of children is * exceeded. */ if (node.getConceptualNode().getNumChildren() != -1) { if (node.getConceptualNode().getNumChildren() <= node.getNumChildren()) { event.detail = DND.DROP_NONE; event.feedback = DND.FEEDBACK_EXPAND; return; } } event.feedback = DND.FEEDBACK_SELECT | DND.FEEDBACK_SCROLL | DND.FEEDBACK_EXPAND; } else { /* * Cannot move if the limit number of children is * exceeded. */ BTNode parent = node.getParent(); if (parent.getConceptualNode().getNumChildren() != -1) { if (parent.getConceptualNode().getNumChildren() <= parent.getNumChildren()) { event.detail = DND.DROP_NONE; event.feedback = DND.FEEDBACK_EXPAND; return; } } event.feedback = DND.FEEDBACK_INSERT_AFTER | DND.FEEDBACK_SCROLL | DND.FEEDBACK_EXPAND; } } event.detail = DND.DROP_MOVE; } else if (BTNodeIndentifierTransfer.getInstance().isSupportedType(event.currentDataType)) { if (event.item == null) { event.detail = DND.DROP_NONE; return; } BTNode node = (BTNode) event.item.getData(); /* Cannot move as a sibling of the root. */ if (node.getConceptualNode().getType().equals(NodeInternalType.ROOT.toString())) { event.detail = DND.DROP_NONE; event.feedback = DND.FEEDBACK_EXPAND; } else { BTNode parent = node.getParent(); if (!closeToBottom((TreeItem) event.item)) { /* * Cannot move if the limit number of children is * exceeded. */ if (node.getConceptualNode().getNumChildren() != -1) { if (node.getConceptualNode().getNumChildren() <= node.getNumChildren()) { event.detail = DND.DROP_NONE; event.feedback = DND.FEEDBACK_EXPAND; return; } } event.feedback = DND.FEEDBACK_SELECT | DND.FEEDBACK_SCROLL | DND.FEEDBACK_EXPAND; } else { /* * Cannot move if the limit number of children is * exceeded. */ if (parent.getConceptualNode().getNumChildren() != -1) { if (parent.getConceptualNode().getNumChildren() <= parent.getNumChildren()) { event.detail = DND.DROP_NONE; event.feedback = DND.FEEDBACK_EXPAND; return; } } event.feedback = DND.FEEDBACK_INSERT_AFTER | DND.FEEDBACK_SCROLL | DND.FEEDBACK_EXPAND; } event.detail = DND.DROP_MOVE; } } } public void drop(DropTargetEvent event) { if (ConceptualBTNodeTransfer.getInstance().isSupportedType(event.currentDataType)) { Pair<String, String> nodeIdentifier = (Pair<String, String>) event.data; BTNode selectedNode = (BTNode) event.item.getData(); ConceptualBTNode newConceptualNode = NodesLoader.getNode(nodeIdentifier.getFirst(), nodeIdentifier.getSecond()); /* * This is a bit messy. * * If the target node ("selectedNode") is the root: * * 1) If the root has no child, then the dropped node is * inserted as the child of the root. * * 2) If it has one child (the root can only have one child), * the dropped node is inserted as its new only child, and the * old one is inserted as a child of the new one. If the dropped * node does not support children, then the drop operation is * canceled. * * If the target node is not the root, then proceed as usual: the * dropped node is inserted as a child or as a sibling of the * target node. */ if (selectedNode.getConceptualNode().getType().equals(NodeInternalType.ROOT.toString())) { if (selectedNode.getNumChildren() != 0) { if (newConceptualNode.getNumChildren() == 0) { return; } else { BTNode newNode = tree.createNode( NodesLoader.getNode(nodeIdentifier.getFirst(), nodeIdentifier.getSecond())); BTNode oldRoot = selectedNode.getChildren().get(0); selectedNode.removeChild(oldRoot); newNode.setParent(selectedNode); selectedNode.addChild(newNode); oldRoot.setParent(newNode); newNode.addChild(oldRoot); viewer.refresh(selectedNode); viewer.expandToLevel(selectedNode, 1); treeChanged(viewer); clearErrors(); return; } } } BTNode newNode = tree .createNode(NodesLoader.getNode(nodeIdentifier.getFirst(), nodeIdentifier.getSecond())); /* * Insert the source node as a child or as sibling of the target * depending on the current relative position of the cursor to * the target. */ if (!closeToBottom((TreeItem) event.item)) { newNode.setParent(selectedNode); selectedNode.addChild(newNode); viewer.expandToLevel(selectedNode, 1); viewer.refresh(selectedNode); } else { selectedNode.addAsSibling(newNode); viewer.refresh(selectedNode.getParent()); } treeChanged(viewer); clearErrors(); } if (BTNodeIndentifierTransfer.getInstance().isSupportedType(event.currentDataType)) { BTNode target = (BTNode) event.item.getData(); Identifier id = (Identifier) event.data; BTNode source = tree.findNode(id); if (source.hasAsDescendant(target)) { event.detail = DND.DROP_NONE; return; } /* * Insert the source node as a child or as sibling of the target * depending on the current relative position of the cursor to * the target. */ if (!closeToBottom((TreeItem) event.item)) { target.addChild(source); source.getParent().removeChild(source); source.setParent(target); } else { target.addAsSibling(source); } viewer.refresh(); viewer.expandToLevel(target, 1); treeChanged(viewer); clearErrors(); } } public void dropAccept(DropTargetEvent event) { } /** * Given a TreeItem of the tree, this function analyses whether the * current cursor position is close to its bottom or not. This is used * to tell whether the dropped element is inserted as a child or as a * sibling of the target node. * * @return true if the cursor position is close to the bottom of * <code>treeItem</code>. */ private boolean closeToBottom(TreeItem treeItem) { Display display = treeItem.getDisplay(); Point cursorLocation = treeItem.getDisplay().getCursorLocation(); Rectangle itemLocation = treeItem.getBounds(); int height = viewer.getTree().getItemHeight(); int margin = ((int) height * 0.20) <= 1 ? 1 : (int) (height * 0.20); /* * If the margin is null, return true, since insertion as sibling * prevails over insertion as child. */ if (margin == 0) { return true; } int limitHeight = itemLocation.y + height - 1 - margin; Point mappedCoords = display.map(null, viewer.getTree(), cursorLocation); if (mappedCoords.y >= limitHeight) { return true; } else { return false; } } } /** * Method that is called when the tree has experimented a change. This will * notify all the listeners of the underlying tree by firing a * {@link TreeModifiedEvent}. */ private void treeChanged(Object source) { this.tree.fireTreeChanged(source); } /** * Opens a BTEditor for editing the guard of a BTNode. */ private void openGuardEditor(final BTNode node) throws PartInitException { IWorkbenchPage activePage = Utilities.getMainWindowActivePage(); BTEditorInput editorInput = new BTEditorInput( ((BTEditorInput) getEditorInput()).getEditorID() + File.pathSeparator + node.getID().toString(), false, true); BTEditor openEditor = (BTEditor) activePage.openEditor(editorInput, BTEditor.ID); if (!openGuardEditors.containsKey(node)) { openGuardEditors.put(node, openEditor); } } /** * The IPartListener that BTEditors use to manage close events. When a * BTEditor is closed, two things happen: * * <ul> * <li>If this is an BTEditor that contains nodes whose guards are being * edited in other editors, then those editors are dissociated from its * corresponding tree, so that they are no longer editing a guard, but a * normal behaviour tree. * <li>If this is a BTEditor that is editing a guard, then it removes itself * from the list of BTEditors {@link BTEditor#openGuardEditors}. * </ul> * * @author Ricardo Juan Palma Durn * */ private class BTEditorPartListener implements IPartListener { public void partActivated(IWorkbenchPart part) { } public void partBroughtToTop(IWorkbenchPart part) { } public void partClosed(IWorkbenchPart part) { /* * Must be kept in mind that this IPartListener is shared among all * the editors, so trying to acces "this" will just not work. The * BTEditor that is closed is the input argument "part". */ if (part instanceof BTEditor) { /* * First dissociate all the editors that are editing nodes' * guards of this tree. */ BTEditor editorToClose = (BTEditor) part; Collection<BTEditor> guardEditors = editorToClose.openGuardEditors.values(); for (BTEditor editor : guardEditors) { BTEditorInput editorInput = (BTEditorInput) editor.getEditorInput(); editorInput.setTreeName(editorInput.getName()); editor.dissociateFromParentTree(); editor.dirty = true; editor.firePropertyChange(PROP_TITLE); editor.firePropertyChange(PROP_DIRTY); } // if (guardEditors.size() != 0) { // StandardDialogs // .informationDialog( // "Guards still open", // "The closed behaviour tree contained some open guards. They have been dissociated from the tree, but they are kept open."); // } guardEditors.clear(); /* * Then, if this editor is editing a guard, remove itself from * the "openGuardEditors" list of the editor that contains the * tree that has the node whose guard is being edited by this * editor. */ if (editorToClose.isFromGuard()) { BTEditorInput editorInput = (BTEditorInput) editorToClose.getEditorInput(); String[] pieces = editorInput.getTreeName().split(File.pathSeparator); long parentEditorID = Long.parseLong(pieces[0]); BTEditor parentEditor = Utilities.getBTEditor(parentEditorID); if (parentEditor != null) { parentEditor.openGuardEditors.remove(editorToClose.guardNode); } } } } public void partDeactivated(IWorkbenchPart part) { } public void partOpened(IWorkbenchPart part) { } } /** * This is used for BTEditors that are editing guards. It dissociates the * BTEditor from the tree that contains the node whose guard is being * edited. By doing so, this editor will be considered not to come from a guard * (<code>setIsFromGuard(false)</code>), so it will change its title image to * that of a normal BT. Also, {@link BTEditor#guardNode} and * {@link BTEditor#guardTree} are set to null. Finally, the root node of the * tree (that of type ROOT) is modified so that it is a normal ROOT node * (that is, it can have a name). */ private void dissociateFromParentTree() { if (isFromGuard()) { this.setIsFromGuard(false); this.setTitleImage(ApplicationIcons.getIcon(IconsPaths.BT)); BTNode newRoot = this.tree.createNode(NodesLoader.getNode(NodeInternalType.ROOT.toString(), null)); if (this.tree.getRoot().getChildren().size() != 0) { newRoot.addChild(this.tree.getRoot().getChildren().get(0)); } this.tree.setRoot(newRoot); this.guardNode = null; this.guardTree = null; this.viewer.refresh(); this.viewer.expandAll(); } } }