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: }