001/* 002 * Copyright (c) 2000-2010 The Regents of the University of California. 003 * All rights reserved. 004 * 005 * '$Author: crawl $' 006 * '$Date: 2015-08-24 22:44:14 +0000 (Mon, 24 Aug 2015) $' 007 * '$Revision: 33630 $' 008 * 009 * Permission is hereby granted, without written agreement and without 010 * license or royalty fees, to use, copy, modify, and distribute this 011 * software and its documentation for any purpose, provided that the above 012 * copyright notice and the following two paragraphs appear in all copies 013 * of this software. 014 * 015 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 016 * FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 017 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 018 * THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 019 * SUCH DAMAGE. 020 * 021 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 022 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 023 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 024 * PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 025 * CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 026 * ENHANCEMENTS, OR MODIFICATIONS. 027 * 028 */ 029 030package org.kepler.gui; 031 032import java.awt.Component; 033import java.util.HashMap; 034import java.util.Iterator; 035import java.util.LinkedHashMap; 036import java.util.LinkedList; 037import java.util.List; 038import java.util.Map; 039 040import javax.swing.Action; 041import javax.swing.JMenuItem; 042 043import org.apache.commons.logging.Log; 044import org.apache.commons.logging.LogFactory; 045import org.kepler.configuration.ConfigurationManager; 046import org.kepler.configuration.ConfigurationNamespace; 047import org.kepler.configuration.ConfigurationProperty; 048import org.kepler.configuration.ConfigurationUtilities; 049 050import diva.canvas.CanvasLayer; 051import diva.canvas.Figure; 052import diva.graph.GraphController; 053import diva.graph.GraphPane; 054import diva.gui.toolbox.JContextMenu; 055import diva.gui.toolbox.MenuFactory; 056import ptolemy.actor.Director; 057import ptolemy.actor.gui.TableauFrame; 058import ptolemy.kernel.ComponentEntity; 059import ptolemy.kernel.Port; 060import ptolemy.kernel.Relation; 061import ptolemy.kernel.util.Attribute; 062import ptolemy.kernel.util.IllegalActionException; 063import ptolemy.kernel.util.NameDuplicationException; 064import ptolemy.kernel.util.NamedObj; 065import ptolemy.vergil.basic.BasicGraphController; 066import ptolemy.vergil.basic.BasicGraphFrame; 067import ptolemy.vergil.basic.ContextMenuFactoryCreator; 068import ptolemy.vergil.toolbox.MenuItemFactory; 069import ptolemy.vergil.toolbox.PtolemyMenuFactory; 070 071////////////////////////////////////////////////////////////////////////// 072//// KeplerContextMenuFactory 073 074/** 075 * A factory that creates popup context menus for Kepler actors, directors, etc. 076 * 077 * @author Matthew Brooke 078 * @version $Id: KeplerContextMenuFactory.java 12101 2006-02-28 00:50:34Z brooke$ 079 * @since Ptolemy II 1.0 080 * @Pt.ProposedRating Red 081 * @Pt.AcceptedRating Red 082 */ 083public class KeplerContextMenuFactory extends PtolemyMenuFactory implements 084 MenuFactory { 085 086 /** 087 * Create a new menu factory that contains no menu item factories. 088 * 089 * @param controller 090 * GraphController 091 */ 092 public KeplerContextMenuFactory(GraphController controller) { 093 super(controller); 094 this.controller = controller; 095 096 ConfigurationProperty prop = ConfigurationManager 097 .getInstance() 098 .getProperty(ConfigurationManager.getModule("gui"), 099 new ConfigurationNamespace(CONTEXT_MENU_MAPPINGS_NAME)); 100 _configurationMap = ConfigurationUtilities.getPairsMap(prop, false); 101 102 // load the enable types 103 ConfigurationProperty enableProperty = prop.getProperty(CONTEXT_MENU_ENABLE_TYPE); 104 if(enableProperty == null) { 105 _enableMap = new HashMap<String,List<Class<?>>>(); 106 } else { 107 Map<String,List<String>> map = ConfigurationUtilities.getMultiValuePairsMap(enableProperty); 108 _enableMap = _convertMapOfClassNamesToClasses(map); 109 } 110 111 // load the disable types 112 ConfigurationProperty disableProperty = prop.getProperty(CONTEXT_MENU_DISABLE_TYPE); 113 if(disableProperty == null) { 114 _disableMap = new HashMap<String,List<Class<?>>>(); 115 } else { 116 Map<String,List<String>> map = ConfigurationUtilities.getMultiValuePairsMap(disableProperty); 117 _disableMap = _convertMapOfClassNamesToClasses(map); 118 } 119 120 } 121 122 // ///////////////////////////////////////////////////////////////// 123 // // public methods //// 124 125 /** 126 * Create an instance of the menu associated with this factory. 127 * 128 * @param figure 129 * The figure for which to create a context menu. 130 * @return JContextMenu 131 */ 132 @Override 133 public JContextMenu create(Figure figure) { 134 135 /** 136 * @todo - FIXME - wanted to do this only once, then cache the menu - 137 * however, the menu actions in PTII "stick" at the value of the 138 * actor first clicked on, unless we redo this each time - MB 139 */ 140 141 NamedObj object = _getObjectFromFigure(figure); 142 if (object == null) { 143 return null; 144 } 145 146 menuItemHolder = new JContextMenu(object, object.getFullName()); 147 148 Component parent = getParent(figure); 149 menuItemHolder.setInvoker(parent); 150 151 // 1) Get all PTII menu items and put them in a Map for easier 152 // access later... 153 Map<String, Action> origMenuItemsMap = getOriginalMenuItemsMap(object, false); 154 155 // 2) Now we have all the PTII menu items, get the 156 // Kepler-specific menu mappings from the preferences file, 157 // then go thru the Kepler menu mappings and 158 // populate the new popup menu with Kepler menus, 159 // creating any new menu items that don't exist yet 160 161 // this is a Map that will be used to keep track of 162 // what we have added to the menus, and in what order 163 menu = createKeplerContextMenu(origMenuItemsMap, object, 164 getTableauFrame(figure)); 165 166 if (menu == null) { 167 log.error("Problem creating Kepler context menus - using PTII defaults"); 168 return super.create(figure); 169 } 170 return menu; 171 } 172 173 // ///////////////////////////////////////////////////////////////// 174 // // private methods //// 175 176 private JContextMenu createKeplerContextMenu(Map<String, Action> ptiiMenuActionsMap, 177 NamedObj target, TableauFrame tFrameInstance) { 178 179 if (ptiiMenuActionsMap == null) { 180 return null; 181 } 182 183 final LinkedHashMap<String, JMenuItem> keplerCtxtMenuMap = new LinkedHashMap<String, JMenuItem>(); 184 final JContextMenu contextMenu = new JContextMenu(target, target.getFullName()); 185 186 if(isDebugging) { 187 log.debug("***************\nKEPLER CONTEXT MENUS:\n***************\n"); 188 } 189 190 try { 191 for(Map.Entry<String, String> entry : _configurationMap.entrySet()) { 192 String nextKey = entry.getKey(); 193 String nextVal = entry.getValue(); 194 195 // System.out.println("key: " + nextKey + " val: " + nextVal + 196 // " menuBaseName: " + _menuBaseName); 197 198 if (nextKey == null || !nextKey.startsWith(_menuBaseName)) { 199 continue; 200 } 201 if (isDebugging) { 202 log.debug("nextKey: " + nextKey); 203 } 204 205 if (isDebugging) { 206 log.debug("nextVal: " + nextVal); 207 } 208 209 if (nextVal == null || nextVal.trim().length() < 1) { 210 if (isDebugging) { 211 log.warn("no menu mapping found for key: " + nextKey); 212 } 213 // System.out.println("no menu mapping found for key: " + 214 // nextKey); 215 continue; 216 } 217 218 final Class<?> targetClass = target.getClass(); 219 220 // see if this menu item name has any enableTypes 221 final List<Class<?>> enableTypes = _enableMap.get(nextKey); 222 if (enableTypes != null) { 223 boolean found = false; 224 for(Class<?> enableType : enableTypes) { 225 if(enableType.isAssignableFrom(targetClass)) { 226 // the enableType matches the target, so the menu item will be displayed 227 found = true; 228 break; 229 } 230 } 231 232 if(!found) { 233 if(isDebugging) { 234 log.debug("Not displaying " + nextKey + " for target " + target + 235 " since does not match enableType"); 236 } 237 continue; 238 } 239 } 240 241 // see if this menu item name has any disableTypes 242 final List<Class<?>> disableTypes = _disableMap.get(nextKey); 243 if(disableTypes != null) { 244 boolean found = false; 245 for(Class<?> disableType : disableTypes) { 246 if(disableType.isAssignableFrom(targetClass)) { 247 // the disableType matches the target, so the menu item will not be displayed. 248 if(isDebugging) { 249 log.debug("Not displaying " + nextKey + " for target " + target + 250 " since matches disableType " + disableType); 251 } 252 found = true; 253 break; 254 } 255 } 256 257 if(found) { 258 continue; 259 } 260 } 261 262 Action action = null; 263 264 if (nextKey.indexOf(MenuMapper.MENU_SEPARATOR_KEY) < 0) { 265 266 action = MenuMapper.getActionFor(nextVal, 267 ptiiMenuActionsMap, tFrameInstance); 268 if (action == null) { 269 if (isDebugging) { 270 log.warn("null action for value " + nextVal); 271 } 272 // System.out.println("WARNING: null action for context menu item: " 273 // + nextVal); 274 continue; 275 } 276 } 277 // get rid of prefix - like "ACTOR->", "DIRECTOR->" etc 278 if (nextKey.startsWith(_menuBaseName)) { 279 nextKey = nextKey.substring(menuPathPrefixLength); 280 } 281 282 // System.out.println("adding menu for key: " + nextKey + 283 // " action: " + action ); 284 MenuMapper.addMenuFor(nextKey, action, contextMenu, 285 keplerCtxtMenuMap); 286 } 287 288 } catch (Exception ex) { 289 if (isDebugging) { 290 log.warn("Exception opening menu mappings: " + ex 291 + "\nDefaulting to PTII menus"); 292 if (isDebugging) { 293 ex.printStackTrace(); 294 } 295 return null; 296 } 297 } 298 299 if(isDebugging) { 300 log.debug("***************\nEND KEPLER CONTEXT MENUS:\n***************\n"); 301 } 302 303 return contextMenu; 304 } 305 306 /** 307 * get Map of name/value pairs containing menu paths of original PTII 308 * context- menu items, and their correspondign Action objects 309 * 310 * @param object 311 * NamedObj 312 * @param isWorkflow 313 * boolean - @todo - FIXME - this is a gnarly hack because a 314 * workflow is actually a TypedCompositeActor, so if we just rely 315 * in the "instanceof" checks like we do for other context menus, 316 * this code will assume the workflow is actually an actor, and 317 * will display the actor context menu instead of the workflow 318 * one 319 * @return Map 320 */ 321 protected Map<String, Action> getOriginalMenuItemsMap(NamedObj object, boolean isWorkflow) { 322 323 Map<String, Action> retMap = new HashMap<String, Action>(); 324 if (isWorkflow) { 325 _menuBaseName = WORKFLOW_BASE_NAME; 326 } else if (object instanceof Director) { 327 _menuBaseName = DIRECTOR_BASE_NAME; 328 } else if (object instanceof Attribute) { 329 _menuBaseName = ATTRIB_BASE_NAME; 330 } else if (object instanceof ComponentEntity) { 331 _menuBaseName = ACTOR_BASE_NAME; 332 } else if (object instanceof Port) { 333 _menuBaseName = PORT_BASE_NAME; 334 } else if (object instanceof Relation) { 335 _menuBaseName = LINK_BASE_NAME; 336 } else { // catch-all 337 _menuBaseName = "UNKNOWN"; 338 if (isDebugging) { 339 log.error("KeplerContextMenuFactory was asked to handle a NamedObj " 340 + "type that was not recognized: " 341 + object.getClassName()); 342 } 343 } 344 menuPathPrefixLength = _menuBaseName.length() 345 + MenuMapper.MENU_PATH_DELIMITER.length(); 346 347 Iterator<?> i = menuItemFactoryList().iterator(); 348 349 while (i.hasNext()) { 350 MenuItemFactory factory = (MenuItemFactory) i.next(); 351 JMenuItem menuItem = factory.create(menuItemHolder, object); 352 353 if(menuItem != null) { 354 StringBuffer pathBuff = new StringBuffer(_menuBaseName); 355 // System.out.println("ptii context menu item found: "+ 356 // menuItem.getText()); 357 if (isDebugging) { 358 log.debug("Found PTII context-menu item: " 359 + menuItem.getText()); 360 } 361 MenuMapper.storePTIIMenuItems(menuItem, pathBuff, 362 MenuMapper.MENU_PATH_DELIMITER, retMap); 363 } 364 } 365 return retMap; 366 } 367 368 protected Component getParent(Figure figure) { 369 370 if (figure != null) { 371 CanvasLayer layer = figure.getLayer(); 372 GraphPane pane = (GraphPane) layer.getCanvasPane(); 373 return pane.getCanvas(); 374 } else { 375 BasicGraphFrame bgf = null; 376 try { 377 bgf = ((BasicGraphController) controller).getFrame(); 378 } catch (Exception ex) { 379 bgf = null; 380 } 381 return (Component) bgf; 382 } 383 } 384 385 private TableauFrame getTableauFrame(Figure figure) { 386 387 Component parent = getParent(figure); 388 389 if (parent != null) { 390 //System.out.println(parent); 391 while (parent.getParent() != null) { 392 parent = parent.getParent(); 393 } 394 if (parent instanceof TableauFrame) { 395 log.debug("TABLEAUFRAME FOUND"); 396 return (TableauFrame) parent; 397 } 398 } 399 log.warn("getTableauFrame() returning NULL!!"); 400 return null; 401 } 402 403 /** Convert the class names to Class objects in the map values. */ 404 private Map<String,List<Class<?>>> _convertMapOfClassNamesToClasses(Map<String,List<String>> map) { 405 406 Map<String,List<Class<?>>> retval = new HashMap<String,List<Class<?>>>(); 407 for(Map.Entry<String,List<String>> entry : map.entrySet()) { 408 List<Class<?>> classes = new LinkedList<Class<?>>(); 409 for(String className : entry.getValue()) { 410 try { 411 Class<?> clazz = Class.forName(className); 412 classes.add(clazz); 413 } catch (ClassNotFoundException e) { 414 log.warn("Context menu class not in classpath: " + className); 415 } 416 } 417 retval.put(entry.getKey(), classes); 418 } 419 420 return retval; 421 } 422 423 // ///////////////////////////////////////////////////////////////// 424 // // private members //// 425 426 /** The name of the configuration file containing the context 427 * menu mappings for Kepler. 428 */ 429 private static final String CONTEXT_MENU_MAPPINGS_NAME = "uiContextMenuMappings"; 430 431 private static final String CONTEXT_MENU_ENABLE_TYPE = "enableType"; 432 private static final String CONTEXT_MENU_DISABLE_TYPE = "disableType"; 433 434 private static final Log log = LogFactory 435 .getLog(KeplerContextMenuFactory.class.getName()); 436 437 private static final boolean isDebugging = log.isDebugEnabled(); 438 439 // the popup menu associated with this particular instance. 440 private JContextMenu menu = null; 441 442 // a dummy popup menu to hold all the previously-added menu items associated 443 // with this particular instance, so we can rearrange them to suit our needs 444 private JContextMenu menuItemHolder = null; 445 446 protected String _menuBaseName = null; 447 448 // the length of the first section of the menu path, which identifies the 449 // type of context menu - eg the prefix part of "ACTOR->Look Inside" would 450 // be 451 // "ACTOR->", and the menuPathPrefixLength for this would be 7 452 private int menuPathPrefixLength; 453 private GraphController controller; 454 455 private final static String DIRECTOR_BASE_NAME = "DIRECTOR"; 456 private final static String ACTOR_BASE_NAME = "ACTOR"; 457 private final static String ATTRIB_BASE_NAME = "ATTRIBUTE"; 458 private final static String PORT_BASE_NAME = "PORT"; 459 private final static String LINK_BASE_NAME = "LINK"; 460 private final static String WORKFLOW_BASE_NAME = "WORKFLOW"; 461 462 463 /** A map of context menu item to Action names. */ 464 private Map<String,String> _configurationMap; 465 466 /** A map of context menu item names to class names. An item 467 * on this list only appears in the context menu if the target 468 * is an instance or subclass of one of the value class names. 469 */ 470 private Map<String,List<Class<?>>> _enableMap; 471 472 /** A map of context menu item names to class names. An item 473 * on this list does not appear in the context menu if the target 474 * is an instance or subclass of one of the value class names. 475 */ 476 private Map<String,List<Class<?>>> _disableMap; 477 478 // ///////////////////////////////////////////////////////////////// 479 // // inner classes //// 480 481 /** 482 * A factory that creates the KeplerContextMenuFactory - used by the config 483 * 484 * @author Matthew Brooke 485 */ 486 public static class Factory extends ContextMenuFactoryCreator { 487 488 /** 489 * Create an factory with the given name and container. 490 * 491 * @param container 492 * The container. 493 * @param name 494 * The name of the entity. 495 * @exception IllegalActionException 496 * If the container is incompatible with this attribute. 497 * @exception NameDuplicationException 498 * If the name coincides with an attribute already in the 499 * container. 500 */ 501 public Factory(NamedObj container, String name) 502 throws IllegalActionException, NameDuplicationException { 503 super(container, name); 504 } 505 506 @Override 507 public MenuFactory createContextMenuFactory(GraphController controller) { 508 return new KeplerContextMenuFactory(controller); 509 } 510 } 511 512}