Mercurial > hg > ccmieditor
view java/src/uk/ac/qmul/eecs/ccmi/gui/CCmIPopupMenu.java @ 8:ea7885bd9bff tip
fixed bug : render solid line as dotted/dashed when moving the stylus from dotted/dashed to solid
author | ccmi-guest |
---|---|
date | Thu, 03 Jul 2014 16:12:20 +0100 |
parents | d66dd5880081 |
children |
line wrap: on
line source
/* 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()); }