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