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