fiore@3: /* fiore@3: CCmI Editor - A Collaborative Cross-Modal Diagram Editing Tool fiore@3: fiore@3: Copyright (C) 2011 Queen Mary University of London (http://ccmi.eecs.qmul.ac.uk/) fiore@3: fiore@3: This program is free software: you can redistribute it and/or modify fiore@3: it under the terms of the GNU General Public License as published by fiore@3: the Free Software Foundation, either version 3 of the License, or fiore@3: (at your option) any later version. fiore@3: fiore@3: This program is distributed in the hope that it will be useful, fiore@3: but WITHOUT ANY WARRANTY; without even the implied warranty of fiore@3: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the fiore@3: GNU General Public License for more details. fiore@3: fiore@3: You should have received a copy of the GNU General Public License fiore@3: along with this program. If not, see . fiore@3: */ fiore@3: package uk.ac.qmul.eecs.ccmi.gui; fiore@3: fiore@3: import java.awt.Component; fiore@3: import java.awt.event.ActionEvent; fiore@3: import java.awt.event.ActionListener; fiore@3: import java.text.MessageFormat; fiore@3: import java.util.ArrayList; fiore@3: import java.util.Collections; fiore@3: import java.util.Comparator; fiore@3: import java.util.Iterator; fiore@3: import java.util.List; fiore@3: import java.util.ResourceBundle; fiore@3: import java.util.Set; fiore@3: fiore@3: import javax.swing.JMenuItem; fiore@3: import javax.swing.JOptionPane; fiore@3: import javax.swing.JPopupMenu; fiore@3: fiore@3: import uk.ac.qmul.eecs.ccmi.diagrammodel.DiagramElement; fiore@3: import uk.ac.qmul.eecs.ccmi.diagrammodel.NodeProperties; fiore@3: import uk.ac.qmul.eecs.ccmi.network.AwarenessMessage; fiore@3: import uk.ac.qmul.eecs.ccmi.network.Command; fiore@3: import uk.ac.qmul.eecs.ccmi.network.DiagramEventActionSource; fiore@3: import uk.ac.qmul.eecs.ccmi.utils.InteractionLog; fiore@3: fiore@3: /** fiore@3: * This class provides the two menus to handle nodes and edges on the visual graph. This class fiore@3: * provides an abstract implementation common to both the node and edge menus. The specific fiore@3: * implementations are internal static classes, inheriting from this class. fiore@3: * fiore@3: */ fiore@3: @SuppressWarnings("serial") fiore@3: public abstract class CCmIPopupMenu extends JPopupMenu { fiore@3: /** fiore@3: * This constructor is called by subclasses constructor. fiore@3: * fiore@3: * @param reference the element this menu refers to (it popped up by right-clicking on it) fiore@3: * @param parentComponent the component where the menu is going to be displayed fiore@3: * @param modelUpdater the model updater to make changed to {@code reference} fiore@3: * @param selectedElements other elements eventually selected on the graph, which are going fiore@3: * to undergo the same changes as {@code reference}, being selected together with it. fiore@3: */ fiore@3: protected CCmIPopupMenu(DiagramElement reference, fiore@3: Component parentComponent, DiagramModelUpdater modelUpdater, fiore@3: Set selectedElements) { fiore@3: super(); fiore@3: this.modelUpdater = modelUpdater; fiore@3: this.parentComponent = parentComponent; fiore@3: this.reference = reference; fiore@3: this.selectedElements = selectedElements; fiore@3: } fiore@3: fiore@3: /** fiore@3: * Returns the the element this menu refers to. fiore@3: * @return the element this menu refers to. fiore@3: */ fiore@3: public DiagramElement getElement(){ fiore@3: return reference; fiore@3: } fiore@3: fiore@3: /** fiore@3: * Add the a menu item to this menu. A menu item, once clicked on, will prompt the user for a new name fiore@3: * for the referee element and will execute the update through the modelUpdater passed as argument fiore@3: * to the constructor. fiore@3: */ fiore@3: protected void addNameMenuItem() { fiore@3: /* add set name menu item */ fiore@3: JMenuItem setNameMenuItem = new JMenuItem( fiore@3: resources.getString("menu.set_name")); fiore@3: setNameMenuItem.addActionListener(new ActionListener() { fiore@3: @Override fiore@3: public void actionPerformed(ActionEvent e) { fiore@3: String type = (reference instanceof Node) ? "node" : "edge"; fiore@3: if (!modelUpdater.getLock(reference, Lock.NAME, fiore@3: new DiagramEventActionSource(DiagramEventSource.GRPH, fiore@3: Command.Name.SET_NODE_NAME, reference.getId(),reference.getName()))) { fiore@3: iLog("Could not get the lock on " + type + " for renaming", fiore@3: DiagramElement.toLogString(reference)); fiore@3: JOptionPane.showMessageDialog(parentComponent, fiore@3: MessageFormat.format(resources fiore@3: .getString("dialog.lock_failure.name"), fiore@3: type)); fiore@3: return; fiore@3: } fiore@3: iLog("open rename " + type + " dialog", fiore@3: DiagramElement.toLogString(reference)); fiore@3: String name = JOptionPane.showInputDialog(parentComponent, fiore@3: MessageFormat.format( fiore@3: resources.getString("dialog.input.name"), fiore@3: reference.getName()), reference.getName()); fiore@3: if (name == null) fiore@3: iLog("cancel rename " + type + " dialog", fiore@3: DiagramElement.toLogString(reference)); fiore@3: else fiore@3: /* node has been locked at selection time */ fiore@3: modelUpdater.setName(reference, name.trim(), fiore@3: DiagramEventSource.GRPH); fiore@3: modelUpdater.yieldLock(reference, Lock.NAME, fiore@3: new DiagramEventActionSource(DiagramEventSource.GRPH, fiore@3: Command.Name.SET_NODE_NAME, reference.getId(),reference.getName())); fiore@3: } fiore@3: fiore@3: }); fiore@3: add(setNameMenuItem); fiore@3: } fiore@3: fiore@3: /** fiore@3: * Add the a delete item to this menu. A menu item, once clicked on, will prompt the user for a confirmation fiore@3: * for the deletion of referee element and will execute the update through the modelUpdater passed as argument fiore@3: * to the constructor. fiore@3: */ fiore@3: protected void addDeleteMenuItem() { fiore@3: JMenuItem deleteMenuItem = new JMenuItem(resources.getString("menu.delete")); fiore@3: deleteMenuItem.addActionListener(new ActionListener() { fiore@3: @Override fiore@3: public void actionPerformed(ActionEvent evt) { fiore@3: /* create a new Set to maintain iterator consistency as elementTakenOut will change selectedItems */ fiore@3: List workList = new ArrayList(selectedElements); fiore@3: /* right click on an element with no selection involved */ fiore@3: if(workList.isEmpty()){ fiore@3: workList.add(reference); fiore@3: /* right click on an element with other elements selected, thus we ignore the * fiore@3: * currently selected elements and try to delete only the right clicked one. */ fiore@3: }else if(!workList.contains(reference)){ fiore@3: workList.clear(); fiore@3: workList.add(reference); fiore@3: }else{ fiore@3: /* If the right clicked element selected together with other elements, try to * fiore@3: * delete them all. First delete all edges and then all nodes to keep consistency. * fiore@3: * We are deleting a bunch of objects and if a node is deleted before an edge * fiore@3: * attached to it, then an exception will be triggered at the moment of edge * fiore@3: * deletion because the edge will be already deleted as a result of the node deletion */ fiore@3: Collections.sort(workList, new Comparator(){ fiore@3: @Override fiore@3: public int compare(DiagramElement e1,DiagramElement e2) { fiore@3: boolean e1isEdge = e1 instanceof Edge; fiore@3: boolean e2isEdge = e2 instanceof Edge; fiore@3: if(e1isEdge && !e2isEdge){ fiore@3: return -1; fiore@3: } fiore@3: if(!e1isEdge && e2isEdge){ fiore@3: return 1; fiore@3: } fiore@3: return 0; fiore@3: } fiore@3: }); fiore@3: } fiore@3: fiore@3: ListalreadyLockedElements = new ArrayList(workList.size()); fiore@3: /* check which, of the selected elements, can be deleted and which ones are currently held by * fiore@3: * other clients. If an element is locked it's removed from the list and put into a separated set */ fiore@3: for(Iterator itr=workList.iterator(); itr.hasNext();){ fiore@3: DiagramElement selected = itr.next(); fiore@3: boolean isNode = selected instanceof Node; fiore@3: if(!modelUpdater.getLock(selected, fiore@3: Lock.DELETE, fiore@3: new DiagramEventActionSource( fiore@3: DiagramEventSource.GRPH, fiore@3: isNode ? Command.Name.REMOVE_NODE : Command.Name.REMOVE_EDGE, fiore@3: selected.getId(),selected.getName()))){ fiore@3: itr.remove(); fiore@3: alreadyLockedElements.add(selected); fiore@3: } fiore@3: } fiore@3: fiore@3: /* all the elements are locked by other clients */ fiore@3: if(workList.isEmpty()){ fiore@3: iLog("Could not get lock on any selected element for deletion",""); fiore@3: JOptionPane.showMessageDialog( fiore@3: JOptionPane.getFrameForComponent(parentComponent), fiore@3: alreadyLockedElements.size() == 1 ? // singular vs plural fiore@3: resources.getString("dialog.lock_failure.delete") : fiore@3: resources.getString("dialog.lock_failure.deletes")); fiore@3: return; fiore@3: } fiore@3: fiore@3: String warning = ""; fiore@3: if(!alreadyLockedElements.isEmpty()){ fiore@3: StringBuilder builder = new StringBuilder(resources.getString("dialog.lock_failure.deletes_warning")); fiore@3: for(DiagramElement alreadyLocked : alreadyLockedElements) fiore@3: builder.append(alreadyLocked.getName()).append(' '); fiore@3: warning = builder.append('\n').toString(); fiore@3: iLog("Could not get lock on some selected element for deletion",warning); fiore@3: } fiore@3: fiore@3: iLog("open delete dialog",warning); fiore@3: int answer = JOptionPane.showConfirmDialog( fiore@3: JOptionPane.getFrameForComponent(parentComponent), fiore@3: warning+resources.getString("dialog.confirm.deletions"), fiore@3: resources.getString("dialog.confirm.title"), fiore@3: SpeechOptionPane.YES_NO_OPTION); fiore@3: if(answer == JOptionPane.YES_OPTION){ fiore@3: /* the user chose to delete the elements, proceed (locks * fiore@3: * will be automatically removed upon deletion by the server ) */ fiore@3: for(DiagramElement selected : workList){ fiore@3: modelUpdater.takeOutFromCollection(selected,DiagramEventSource.GRPH); fiore@3: modelUpdater.sendAwarenessMessage( fiore@3: AwarenessMessage.Name.STOP_A, fiore@3: new DiagramEventActionSource(DiagramEventSource.TREE, fiore@3: (selected instanceof Node) ? Command.Name.REMOVE_NODE : Command.Name.REMOVE_EDGE, fiore@3: selected.getId(),selected.getName()) fiore@3: ); fiore@3: } fiore@3: }else{ fiore@3: /* the user chose not to delete the elements, release the acquired locks */ fiore@3: for(DiagramElement selected : workList){ fiore@3: /* if it's a node all its attached edges were locked as well */ fiore@3: /*if(selected instanceof Node){ DONE IN THE SERVER fiore@3: Node n = (Node)selected; fiore@3: for(int i=0; i selectedElements) { fiore@3: super(node, parentComponent, modelUpdater, selectedElements); fiore@3: addNameMenuItem(); fiore@3: addPropertyMenuItem(); fiore@3: addDeleteMenuItem(); fiore@3: } fiore@3: fiore@3: private void addPropertyMenuItem() { fiore@3: final Node nodeRef = (Node) reference; fiore@3: /* if the node has no properties defined, then don't add the menu item */ fiore@3: if(nodeRef.getProperties().isNull()) fiore@3: return; fiore@3: /* add set property menu item*/ fiore@3: JMenuItem setPropertiesMenuItem = new JMenuItem(resources.getString("menu.set_properties")); fiore@3: setPropertiesMenuItem.addActionListener(new ActionListener(){ fiore@3: @Override fiore@3: public void actionPerformed(ActionEvent e) { fiore@3: if(!modelUpdater.getLock(nodeRef, Lock.PROPERTIES, new DiagramEventActionSource(DiagramEventSource.GRPH,Command.Name.SET_PROPERTIES,nodeRef.getId(),reference.getName()))){ fiore@3: iLog("Could not get the lock on node for properties",DiagramElement.toLogString(nodeRef)); fiore@3: JOptionPane.showMessageDialog(parentComponent, resources.getString("dialog.lock_failure.properties")); fiore@3: return; fiore@3: } fiore@3: iLog("open edit properties dialog",DiagramElement.toLogString(nodeRef)); fiore@3: NodeProperties properties = PropertyEditorDialog.showDialog(JOptionPane.getFrameForComponent(parentComponent),nodeRef.getPropertiesCopy()); fiore@3: if(properties == null){ // user clicked on cancel fiore@3: iLog("cancel edit properties dialog",DiagramElement.toLogString(nodeRef)); fiore@3: modelUpdater.yieldLock(nodeRef, Lock.PROPERTIES, new DiagramEventActionSource(DiagramEventSource.GRPH,Command.Name.SET_PROPERTIES,nodeRef.getId(),reference.getName())); fiore@3: return; fiore@3: } fiore@3: if(!properties.isNull()) fiore@3: modelUpdater.setProperties(nodeRef,properties,DiagramEventSource.GRPH); fiore@3: modelUpdater.yieldLock(nodeRef, Lock.PROPERTIES,new DiagramEventActionSource(DiagramEventSource.GRPH,Command.Name.SET_PROPERTIES,nodeRef.getId(),reference.getName())); fiore@3: } fiore@3: }); fiore@3: add(setPropertiesMenuItem); fiore@3: } fiore@3: } fiore@3: fiore@3: /** fiore@3: * A popup menu to perform changes (e.g. delete, rename etc.) to a edge from the visual graph. fiore@3: */ fiore@3: public static class EdgePopupMenu extends CCmIPopupMenu { fiore@3: /** fiore@3: * Constructs an {@code EdgePopupMenu} to perform changes to an edge from the visual diagram. fiore@3: * This constructor is normally called when the user clicks in the neighbourhood of a node fiore@3: * connected to this edge. the menu will then include items to change an end label or fiore@3: * an arrow head. fiore@3: * @param edge the edge this menu refers to. fiore@3: * @param node one attached node some menu item will refer to. fiore@3: * @param parentComponent the component where the menu is going to be displayed. fiore@3: * @param modelUpdater the model updater used to make changed to {@code edge}. fiore@3: * @param selectedElements other elements eventually selected on the graph, which are going fiore@3: * to undergo the same changes as {@code edge}, being selected together with it. fiore@3: */ fiore@3: public EdgePopupMenu( Edge edge, Node node, Component parentComponent, DiagramModelUpdater modelUpdater, fiore@3: Set selectedElements){ fiore@3: super(edge,parentComponent,modelUpdater,selectedElements); fiore@3: addNameMenuItem(); fiore@3: if(node != null){ fiore@3: nodeRef = node; fiore@3: Object[] arrowHeads = new Object[edge.getAvailableEndDescriptions().length + 1]; fiore@3: for(int i=0;i selectedElements){ fiore@3: this(edge,null,parentComponent,modelUpdater,selectedElements); fiore@3: } fiore@3: fiore@3: private void addEndMenuItems(final Object[] arrowHeads){ fiore@3: final Edge edgeRef = (Edge)reference; fiore@3: /* Label menu item */ fiore@3: JMenuItem setLabelMenuItem = new JMenuItem(resources.getString("menu.set_label")); fiore@3: setLabelMenuItem.addActionListener(new ActionListener(){ fiore@3: @Override fiore@3: public void actionPerformed(ActionEvent evt) { fiore@3: if(!modelUpdater.getLock(edgeRef, Lock.EDGE_END, new DiagramEventActionSource(DiagramEventSource.GRPH,Command.Name.SET_ENDLABEL,edgeRef.getId(),reference.getName()))){ fiore@3: iLog("Could not get lock on edge for end label",DiagramElement.toLogString(edgeRef)+" end node:"+DiagramElement.toLogString(nodeRef)); fiore@3: JOptionPane.showMessageDialog(parentComponent, resources.getString("dialog.lock_failure.end")); fiore@3: return; fiore@3: } fiore@3: iLog("open edge label dialog",DiagramElement.toLogString(edgeRef)+" end node:"+DiagramElement.toLogString(nodeRef)); fiore@3: String text = JOptionPane.showInputDialog(parentComponent,resources.getString("dialog.input.label")); fiore@3: if(text != null) fiore@3: modelUpdater.setEndLabel(edgeRef,nodeRef,text,DiagramEventSource.GRPH); fiore@3: else fiore@3: iLog("cancel edge label dialog",DiagramElement.toLogString(edgeRef)+" end node:"+DiagramElement.toLogString(nodeRef)); fiore@3: modelUpdater.yieldLock(edgeRef, Lock.EDGE_END, new DiagramEventActionSource(DiagramEventSource.GRPH,Command.Name.SET_ENDLABEL,edgeRef.getId(),reference.getName())); fiore@3: } fiore@3: }); fiore@3: add(setLabelMenuItem); fiore@3: fiore@3: if(arrowHeads.length > 1){ fiore@3: /* arrow head menu item */ fiore@3: JMenuItem selectArrowHeadMenuItem = new JMenuItem(resources.getString("menu.choose_arrow_head")); fiore@3: selectArrowHeadMenuItem.addActionListener(new ActionListener(){ fiore@3: @Override fiore@3: public void actionPerformed(ActionEvent e) { fiore@3: if(!modelUpdater.getLock(edgeRef, Lock.EDGE_END, new DiagramEventActionSource(DiagramEventSource.GRPH,Command.Name.SET_ENDDESCRIPTION,edgeRef.getId(),reference.getName()))){ fiore@3: iLog("Could not get lock on edge for arrow head",DiagramElement.toLogString(edgeRef)+" end node:"+DiagramElement.toLogString(nodeRef)); fiore@3: JOptionPane.showMessageDialog(parentComponent, resources.getString("dialog.lock_failure.end")); fiore@3: return; fiore@3: } fiore@3: iLog("open select arrow head dialog",DiagramElement.toLogString(edgeRef)+" end node:"+DiagramElement.toLogString(nodeRef)); fiore@3: String arrowHead = (String)JOptionPane.showInputDialog( fiore@3: parentComponent, fiore@3: resources.getString("dialog.input.arrow"), fiore@3: resources.getString("dialog.input.arrow.title"), fiore@3: JOptionPane.PLAIN_MESSAGE, fiore@3: null, fiore@3: arrowHeads, fiore@3: arrowHeads); fiore@3: if(arrowHead == null){ fiore@3: iLog("cancel select arrow head dialog",DiagramElement.toLogString(edgeRef)+" end node:"+DiagramElement.toLogString(nodeRef)); fiore@3: modelUpdater.yieldLock(edgeRef, Lock.EDGE_END, new DiagramEventActionSource(DiagramEventSource.GRPH,Command.Name.SET_ENDDESCRIPTION,edgeRef.getId(),reference.getName())); fiore@3: return; fiore@3: } fiore@3: for(int i=0; i selectedElements; fiore@3: private static ResourceBundle resources = ResourceBundle.getBundle(CCmIPopupMenu.class.getName()); fiore@3: }