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