001/* 002 * Copyright (c) 1998-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.awt.event.ActionEvent; 034import java.awt.event.ActionListener; 035import java.lang.reflect.Constructor; 036import java.util.Collections; 037import java.util.HashSet; 038import java.util.Iterator; 039import java.util.LinkedHashMap; 040import java.util.List; 041import java.util.Map; 042import java.util.Set; 043 044import javax.swing.AbstractAction; 045import javax.swing.Action; 046import javax.swing.JCheckBoxMenuItem; 047import javax.swing.JComponent; 048import javax.swing.JMenu; 049import javax.swing.JMenuBar; 050import javax.swing.JMenuItem; 051 052import org.apache.commons.logging.Log; 053import org.apache.commons.logging.LogFactory; 054import org.kepler.build.modules.ModuleTree; 055import org.kepler.configuration.ConfigurationManager; 056import org.kepler.configuration.ConfigurationNamespace; 057import org.kepler.configuration.ConfigurationProperty; 058 059import diva.gui.GUIUtilities; 060import diva.gui.toolbox.JContextMenu; 061import ptolemy.actor.gui.TableauFrame; 062import ptolemy.gui.MemoryCleaner; 063 064/** 065 * MenuMapper 066 * 067 * A menu creation Runnable that creates and adds a new JMenuBar that provides a 068 * 'view' (in a loose sense) of the ptii menus. It enumerates all the existing 069 * ptii menu items at runtime, and re-uses them, rearranged (and optionally 070 * renamed) - all set via mappings in a localizable resourcebundle properties 071 * (text) file. 072 * 073 * If/when new menu items get added to ptii, they are immediately available for 074 * use here, just by adding the relevant text mapping to the properties file. 075 * 076 * 077 * @author Matthew Brooke 078 * @version $Id: MenuMapper.java 33630 2015-08-24 22:44:14Z crawl $ 079 * @since 080 * @Pt.ProposedRating 081 * @Pt.AcceptedRating 082 */ 083public class MenuMapper { 084 085 // ///////////////////////////////////////////////////////////////// 086 // // public variables //// 087 088 public final static String MENUITEM_TYPE = "MENUITEM_TYPE"; 089 090 public final static String CHECKBOX_MENUITEM_TYPE = "CHECKBOX_MENUITEM_TYPE"; 091 092 public final static String NEW_JMENUITEM_KEY = "NEW_JMENUITEM"; 093 094 // ///////////////////////////////////////////////////////////////// 095 // // public methods //// 096 097 /** 098 * Debugging method 099 */ 100 public void printDebugInfo() { 101 System.out.println("_ptiiMenuActionsMap: " + _ptiiMenuActionsMap.size()); 102 for (String s : _ptiiMenuActionsMap.keySet()) { 103 Action a = _ptiiMenuActionsMap.get(s); 104 System.out.println(s + " : " + a.toString()); 105 } 106 } 107 108 /** 109 * constructor 110 * 111 * @param ptiiMenubar 112 * JMenuBar the existing ptii menubar containing the original 113 * menus 114 * @param tableauFrameInstance 115 * TableauFrame the frame from which the ptii menu bar will be 116 * hidden, and to which the new menu bar will be added 117 */ 118 public MenuMapper(final TableauFrame tableauFrameInstance) { 119 this._tableauFrameInstance = tableauFrameInstance; 120 _mappers.add(this); 121 init(); 122 } 123 124 public void init() { 125 126 if (_tableauFrameInstance == null) { 127 log.error("MenuMapper cannot proceed, due to one or more NULL values:" 128 + "\nptiiMenubar = " 129 + _ptiiMenubar 130 + "\ntableauFrameInstance = " 131 + _tableauFrameInstance 132 + "\ndefaulting to PTII menus"); 133 134 return; 135 } 136 137 // First, we need to make sure PTII has finished constructing its menus. 138 // Those menus are created and assembled in a thread that is started in 139 // the 140 // pack() method of ptolemy.gui.Top. As that thread finishes, it adds 141 // the 142 // new ptii JMenuBar to the frame - so we test for that here, and don't 143 // proceed until the frame has the new JMenuBar added 144 // For safety, so we don't have an infinite loop, we also set a safety 145 // counter: 146 // maxWaitMS is the longest the app waits for the ptii 147 // menus to be added, before it continues anyway. 148 final int maxWaitMS = 500; 149 // sleepTimeMS is the amount of time it sleeps per loop: 150 final int sleepTimeMS = 5; 151 // //// 152 final int maxLoops = maxWaitMS / sleepTimeMS; 153 int safetyCounter = 0; 154 155 while (safetyCounter++ < maxLoops 156 && !_tableauFrameInstance.isMenuPopulated()) { 157 158 if (isDebugging) { 159 log.debug("Waiting for PTII menus to be created... " 160 + safetyCounter); 161 } 162 try { 163 Thread.sleep(sleepTimeMS); 164 } catch (Exception e) { 165 // ignore 166 } 167 } 168 169 JMenuBar _keplerMenubar = null; 170 if(_ptiiMenubar == null) { 171 _ptiiMenubar = _tableauFrameInstance.getJMenuBar(); 172 } 173 174 if (_ptiiMenubar != null) { 175 176 // gets here if a PTII menubar has been added to frame... 177 178 // 1) Now PTII has finished constructing its menus, get all 179 // menu items and put them in a Map for easier access later... 180 _ptiiMenuActionsMap = getPTIIMenuActionsMap(); 181 Map<String, Action> ptiiMenuActionsMap = _ptiiMenuActionsMap; 182 183 // 2) Now we have all the PTII menu items, get the 184 // Kepler-specific menu mappings from the preferences file, 185 // then go thru the Kepler menu mappings and 186 // populate the new menubar with Kepler menus, 187 // creating any new menu items that don't exist yet 188 189 // this is a Map that will be used to keep track of 190 // what we have added to the menus, and in what order 191 _keplerMenubar = createKeplerMenuBar(ptiiMenuActionsMap); 192 193 if (_keplerMenubar != null) { 194 195 // First, look to see if any menus are empty. If 196 // they are, remove the top-level menu from the menubar... 197 // ** NOTE - do these by counting *down* to zero, otherwise the 198 // menus' 199 // indices change dynamically as we remove menus, causing 200 // errors! 201 for (int m = _keplerMenubar.getMenuCount() - 1; m >= 0; m--) { 202 JMenu nextMenu = _keplerMenubar.getMenu(m); 203 if (nextMenu.getMenuComponentCount() < 1) { 204 if (isDebugging) { 205 log.debug("deleting empty menu: " 206 + nextMenu.getText()); 207 } 208 _keplerMenubar.remove(nextMenu); 209 } 210 } 211 // hide the ptii menubar 212 _tableauFrameInstance.hideMenuBar(); 213 214 // add the new menu bar 215 _tableauFrameInstance.setJMenuBar(_keplerMenubar); 216 } else { 217 log.error("Problem creating Kepler menus - defaulting to PTII menus"); 218 } 219 220 } else { 221 // gets here if a PTII menubar has *NOT* been added to frame... 222 // Therefore, this frame doesn't have a menubar by default, 223 // so we probably shouldn't add one, for now, at least 224 225 // hide the ptii menubar (may not be necessary) 226 _tableauFrameInstance.hideMenuBar(); 227 228 // add the new menu bar (may not be necessary) 229 _tableauFrameInstance.setJMenuBar(null); 230 } 231 } 232 233 public static Action getActionFor(String key, 234 Map<String, Action> menuActionsMap, 235 TableauFrame tableauFrameInstance) { 236 if (isDebugging) { 237 log.debug("getActionFor(" + key + "," + menuActionsMap.toString() 238 + ")"); 239 } 240 241 Action action = null; 242 243 // it's a mapping... 244 // NOTE that all keys in ptiiMenuActionsMap are 245 // uppercase, to make the ptii value entries in the 246 // menu mappings props file case-insensitive 247 String uKey = key.toUpperCase(); 248 249 if (key.indexOf(MENU_PATH_DELIMITER) > -1) { 250 Object val = menuActionsMap.get(uKey); 251 if (val instanceof Action) { 252 action = (Action) val; 253 } 254 } else { 255 // it's a class or a separator 256 257 if (uKey.equals(MENU_SEPARATOR_KEY.toUpperCase())) { 258 259 // it's a separator 260 return null; 261 262 } else { 263 264 // it's a class - try to instantiate... 265 Object actionObj = null; 266 boolean actionInitialized = true; 267 try { 268 // create the class 269 String classSearchName = key.trim(); 270 ClassLoader thisClassLoader = MenuMapper.class.getClassLoader(); 271 Class<?> classDefinition = thisClassLoader.loadClass(classSearchName); 272 273 // add the arg types 274 Class<?>[] args = new Class[] { TableauFrame.class }; 275 276 // create a constructor 277 Constructor<?> constructor; 278 try { 279 constructor = classDefinition.getConstructor(args); 280 } catch (java.lang.NoSuchMethodException nsme) { 281 args = new Class[] { ptolemy.vergil.basic.BasicGraphFrame.class }; 282 constructor = classDefinition.getConstructor(args); 283 } 284 285 // set the args 286 Object[] argImp = new Object[] { tableauFrameInstance }; 287 288 // create the object 289 //if (isDebugging) log.debug("BEFORE"); 290 actionObj = constructor.newInstance(argImp); 291 //if (isDebugging) log.debug("AFTER"); 292 293 // if the action object is an InitializableAction, try to 294 // initialize it. 295 if(actionObj instanceof InitializableAction) { 296 actionInitialized = ((InitializableAction)actionObj).initialize(menuActionsMap); 297 } 298 299 } catch (Exception e) { 300 log.warn("Exception trying to create an Action for classname: <" 301 + key + ">:\n" + e.getCause() + " (" + e + ")"); 302 actionObj = null; 303 } 304 305 if (actionObj == null) { 306 // System.out.println("2Error creating action for class: " + 307 // key); 308 if (isDebugging) { 309 log.error("Problem trying to create an Action for classname: <" 310 + key 311 + ">\nPossible reasons:\n" 312 + "1) Should be a fully-qualified classname, including " 313 + "the package - Check carefully for mistakes.\n" 314 + "2) class must implement javax.swing.Action, and must " 315 + "have a constructor of the form: \n" 316 + " MyConstructor(ptolemy.actor.gui.TableauFrame)\n" 317 + "Returning NULL Action for classname: " + key); 318 } 319 return null; 320 // see if the initialization failed. if so, set the actionObj 321 // to null so this action is not put on the menu bar. 322 } else if(!actionInitialized) { 323 actionObj = null; 324 } 325 action = (Action) actionObj; 326 } 327 } 328 return action; 329 } 330 331 /** 332 * Recurse through all the submenu heirarchy beneath the passed JMenu 333 * parameter, and for each "leaf node" (ie a menu item that is not a 334 * container for other menu items), add the Action and its menu path to the 335 * passed Map 336 * 337 * @param nextMenuItem 338 * the JMenu to recurse into 339 * @param menuPathBuff 340 * a delimited String representation of the hierarchical "path" 341 * to this menu item. This will be used as the key in the 342 * actionsMap. For example, the "Graph Editor" menu item beneath 343 * the "New" item on the "File" menu would have a menuPath of 344 * File->New->Graph Editor. Delimeter is "->" (no quotes), and 345 * spaces are allowed within menu text strings, but not around 346 * the delimiters; i.e: New->Graph Editor is OK, but File ->New 347 * is not. 348 * @param MENU_PATH_DELIMITER 349 * String 350 * @param actionsMap 351 * the Map containing key => value pairs of the form: menuPath 352 * (as described above) => Action (the javax.swing.Action 353 * assigned to this menu item) 354 */ 355 public static void storePTIIMenuItems(JMenuItem nextMenuItem, 356 StringBuffer menuPathBuff, final String MENU_PATH_DELIMITER, 357 Map<String, Action> actionsMap) { 358 359 menuPathBuff.append(MENU_PATH_DELIMITER); 360 if (nextMenuItem != null && nextMenuItem.getText() != null) { 361 String str = nextMenuItem.getText(); 362 // do not make the recent files menu item upper case since 363 // it contains paths in the file system. 364 if (menuPathBuff.toString().startsWith("FILE->RECENT FILES->")) { 365 menuPathBuff = new StringBuffer("File->Recent Files->"); 366 } else { 367 str = str.toUpperCase(); 368 } 369 menuPathBuff.append(str); 370 } 371 372 if(isDebugging) { 373 log.debug(menuPathBuff.toString()); 374 } 375 // System.out.println(menuPathBuff.toString()); 376 377 if (nextMenuItem instanceof JMenu) { 378 storePTIITopLevelMenus((JMenu) nextMenuItem, 379 menuPathBuff.toString(), MENU_PATH_DELIMITER, actionsMap); 380 } else { 381 Action nextAction = nextMenuItem.getAction(); 382 // if there is no Action, look for an ActionListener 383 // System.out.println("Processing menu " + nextMenuItem.getText()); 384 if (nextAction == null) { 385 final ActionListener[] actionListeners = nextMenuItem 386 .getActionListeners(); 387 // System.out.println("No Action for " + nextMenuItem.getText() 388 // + "; found " + actionListeners.length 389 // + " ActionListeners"); 390 if (actionListeners.length > 0) { 391 if (isDebugging) { 392 log.debug(actionListeners[0].getClass().getName()); 393 } 394 // ASSUMPTION: there is only one ActionListener 395 nextAction = new AbstractAction() { 396 public void actionPerformed(ActionEvent a) { 397 actionListeners[0].actionPerformed(a); 398 } 399 }; 400 // add all these values - @see diva.gui.GUIUtilities 401 nextAction.putValue(Action.NAME, nextMenuItem.getText()); 402 // System.out.println("storing ptII action for menu " + 403 // nextMenuItem.getText()); 404 nextAction.putValue(GUIUtilities.LARGE_ICON, 405 nextMenuItem.getIcon()); 406 nextAction.putValue(GUIUtilities.MNEMONIC_KEY, new Integer( 407 nextMenuItem.getMnemonic())); 408 nextAction.putValue("tooltip", 409 nextMenuItem.getToolTipText()); 410 nextAction.putValue(GUIUtilities.ACCELERATOR_KEY, 411 nextMenuItem.getAccelerator()); 412 nextAction.putValue("menuItem", nextMenuItem); 413 } else { 414 if (isDebugging) { 415 log.warn("No Action or ActionListener found for " 416 + nextMenuItem.getText()); 417 } 418 } 419 } 420 if (!actionsMap.containsValue(nextAction)) { 421 actionsMap.put(menuPathBuff.toString(), nextAction); 422 if (isDebugging) { 423 log.debug(menuPathBuff.toString() + " :: ACTION: " 424 + nextAction); 425 } 426 } 427 } 428 } 429 430 // ///////////////////////////////////////////////////////////////// 431 // // private methods //// 432 433 private JMenuBar createKeplerMenuBar(Map<String, Action> ptiiMenuActionsMap) { 434 435 final LinkedHashMap<String, JMenuItem> keplerMenuMap = new LinkedHashMap<String, JMenuItem>(); 436 final JMenuBar _keplerMenubar = new JMenuBar(); 437 438 if(isDebugging) { 439 log.debug("***************\nKEPLER MENUS:\n***************\n"); 440 } 441 442 Iterator<ConfigurationProperty> it = null; 443 try { 444 445 ConfigurationProperty prop = ConfigurationManager.getInstance() 446 .getProperty(ConfigurationManager.getModule("gui"), 447 new ConfigurationNamespace("uiMenuMappings")); 448 449 if (prop == null) { 450 log.warn("Could not load uiMenuMappings configuration file."); 451 return null; 452 } 453 454 List<ConfigurationProperty> reposList = prop.getProperties("name", 455 true); 456 it = reposList.iterator(); 457 458 if (it == null) { 459 log.warn("Menu mappings do not contain any valid assignments"); 460 return null; 461 } 462 463 separatorJustAdded = false; 464 465 while (it.hasNext()) { 466 467 ConfigurationProperty cp = it.next(); 468 String nextKey = cp.getValue(); 469 String nextVal = cp.getParent().getProperty("value").getValue(); 470 ConfigurationProperty moduleProperty = cp.getParent() 471 .getProperty("module"); 472 if (moduleProperty != null) { 473 String nextModule = moduleProperty.getValue(); 474 ModuleTree mt = ModuleTree.instance(); 475 if (!mt.contains(nextModule)) { 476 if(isDebugging) { 477 log.debug("Skipping this entry (" + nextKey 478 + ") because '" + nextModule 479 + "' is not active"); 480 } 481 continue; 482 } else if(isDebugging) { 483 log.debug("Processing this entry (" + nextKey 484 + ") explicitly because '" + nextModule 485 + "' is active"); 486 } 487 } else if(isDebugging) { 488 log.debug(nextKey + " has no module associated with it"); 489 } 490 // String nextKey = (String) (it.next()); 491 492 if (isDebugging) { 493 log.debug("nextKey: " + nextKey); 494 } 495 if (nextKey == null || nextKey.trim().length() < 1) { 496 continue; 497 } 498 499 if (isDebugging) { 500 log.debug("nextVal: " + nextVal); 501 } 502 503 if (nextVal == null || nextVal.trim().length() < 1) { 504 log.warn("no menu mapping found for key: " + nextKey); 505 continue; 506 } 507 508 Action action = null; 509 510 if (nextKey.indexOf(MENU_SEPARATOR_KEY) < 0) { 511 512 // if it's the recent files menu item, add all 513 // the existing entries. 514 // NOTE: we can't put the entries in the menu mappings 515 // configuration file since they are dynamic. 516 if (nextVal.equals("File->Recent Files")) { 517 for (Map.Entry<String, Action> entry : ptiiMenuActionsMap 518 .entrySet()) { 519 if (entry.getKey().startsWith( 520 "File->Recent Files->")) { 521 addMenuFor(entry.getKey(), entry.getValue(), 522 _keplerMenubar, keplerMenuMap); 523 //System.out.println("adding recent entry: " + entry.getKey()); 524 } 525 } 526 continue; 527 } 528 529 action = getActionFor(nextVal, ptiiMenuActionsMap, 530 _tableauFrameInstance); 531 532 if (action == null) { 533 if (isDebugging) { 534 log.warn("null action for value " + nextVal); 535 } 536 continue; 537 } 538 // action exists, and it's not a separator 539 // menuItemCount++; 540 } 541 542 addMenuFor(nextKey, action, _keplerMenubar, keplerMenuMap); 543 // prevTopLevel 544 // = _keplerMenubar.getMenu(_keplerMenubar.getMenuCount() - 545 // 1).getText(); 546 } 547 548 } catch (Exception ex) { 549 if (isDebugging) { 550 log.warn("Exception creating Kepler menus: " + ex 551 + "\nDefaulting to PTII menus"); 552 if (isDebugging) { 553 ex.printStackTrace(); 554 } 555 return _keplerMenubar; 556 } 557 } 558 559 separatorJustAdded = false; 560 561 if(isDebugging) { 562 log.debug("***************\nEND KEPLER MENUS:\n***************\n"); 563 } 564 return _keplerMenubar; 565 } 566 567 public Map<String, Action> getPTIIMenuActionsMap() { 568 569 if (_ptiiMenubar == null) { 570 _ptiiMenubar = _tableauFrameInstance.getJMenuBar(); 571 } 572 573 if (_ptiiMenuActionsMap == null || _reloadPtolemyMenus) { 574 if(isDebugging) { 575 log.debug("**************\nEXISTING PTII MENUS:\n**************\n"); 576 } 577 578 // NOTE: use a LinkedHashMap to preserve the order of the 579 // recently opened files list 580 _ptiiMenuActionsMap = new LinkedHashMap<String, Action>(); 581 582 for (int m = 0; m < _ptiiMenubar.getMenuCount(); m++) { 583 JMenu nextMenu = _ptiiMenubar.getMenu(m); 584 585 storePTIITopLevelMenus(nextMenu, nextMenu.getText() 586 .toUpperCase(), MENU_PATH_DELIMITER, 587 _ptiiMenuActionsMap); 588 } 589 _reloadPtolemyMenus = false; 590 if(isDebugging) { 591 log.debug("\n**************\nEND PTII MENUS:\n*****************\n"); 592 } 593 } 594 return _ptiiMenuActionsMap; 595 } 596 597 private static void storePTIITopLevelMenus(JMenu nextMenu, String menuPath, 598 final String MENU_PATH_DELIMITER, 599 Map<String, Action> ptiiMenuActionsMap) { 600 601 int totMenuItems = nextMenu.getMenuComponentCount(); 602 603 for (int n = 0; n < totMenuItems; n++) { 604 Component nextComponent = nextMenu.getMenuComponent(n); 605 if (nextComponent instanceof JMenuItem) { 606 storePTIIMenuItems((JMenuItem) nextComponent, new StringBuffer( 607 menuPath), MENU_PATH_DELIMITER, ptiiMenuActionsMap); 608 } 609 // (if it's not an instanceof JMenuItem, it must 610 // be a separator, and can therefore be ignored) 611 } 612 } 613 614 public static JMenuItem addMenuFor(String key, Action action, 615 JComponent topLvlContainer, Map<String, JMenuItem> keplerMenuMap) { 616 617 if (topLvlContainer == null) { 618 if(isDebugging) { 619 log.debug("NULL container received (eg JMenuBar) - returning NULL"); 620 } 621 return null; 622 } 623 if (key == null) { 624 if(isDebugging) { 625 log.debug("NULL key received"); 626 } 627 return null; 628 } 629 key = key.trim(); 630 631 if (key.length() < 1) { 632 if(isDebugging) { 633 log.debug("BLANK key received"); 634 } 635 return null; 636 } 637 if (action == null && key.indexOf(MENU_SEPARATOR_KEY) < 0) { 638 if (isDebugging) { 639 log.debug("NULL action received, but was not a separator: " 640 + key); 641 } 642 return null; 643 } 644 645 if (keplerMenuMap.containsKey(key)) { 646 if (isDebugging) { 647 log.debug("Menu already added; skipping: " + key); 648 } 649 return null; 650 } 651 652 // split delimited parts and ensure menus all exist 653 String[] menuLevel = key.split(MENU_PATH_DELIMITER); 654 655 int totLevels = menuLevel.length; 656 657 // create a menu for each "menuLevel" if it doesn't already exist 658 final StringBuffer nextLevelBuff = new StringBuffer(); 659 String prevLevelStr = null; 660 JMenuItem leafMenuItem = null; 661 662 for (int levelIdx = 0; levelIdx < totLevels; levelIdx++) { 663 664 // save previous value 665 prevLevelStr = nextLevelBuff.toString(); 666 667 String nextLevelStr = menuLevel[levelIdx]; 668 // get the index of the first MNEMONIC_SYMBOL 669 int mnemonicIdx = nextLevelStr.indexOf(MNEMONIC_SYMBOL); 670 char mnemonicChar = 0; 671 672 // if an MNEMONIC_SYMBOL exists, remove all underscores. Then, idx 673 // of 674 // first underscore becomes idx of letter it used to precede - this 675 // is the mnemonic letter 676 if (mnemonicIdx > -1) { 677 nextLevelStr = nextLevelStr.replaceAll(MNEMONIC_SYMBOL, ""); 678 mnemonicChar = nextLevelStr.charAt(mnemonicIdx); 679 } 680 if (levelIdx != 0) { 681 nextLevelBuff.append(MENU_PATH_DELIMITER); 682 } 683 nextLevelBuff.append(nextLevelStr); 684 685 // don't add multiple separators together... 686 if (nextLevelStr.indexOf(MENU_SEPARATOR_KEY) > -1) { 687 if (separatorJustAdded == false) { 688 689 // Check if we're at the top level, since this makes sense 690 // only for 691 // context menu - we can't add a separator to a JMenuBar 692 if (levelIdx == 0) { 693 if (topLvlContainer instanceof JContextMenu) { 694 ((JContextMenu) topLvlContainer).addSeparator(); 695 } 696 } else { 697 JMenu parent = (JMenu) keplerMenuMap.get(prevLevelStr); 698 699 if (parent != null) { 700 if (parent.getMenuComponentCount() < 1) { 701 if(isDebugging) { 702 log.debug("------ NOT adding separator to parent " 703 + parent.getText() 704 + ", since it does not contain any menu items"); 705 } 706 } else { 707 if(isDebugging) { 708 log.debug("------ adding separator to parent " 709 + parent.getText()); 710 } 711 // add separator to parent 712 parent.addSeparator(); 713 separatorJustAdded = true; 714 } 715 } 716 } 717 } 718 } else if (!keplerMenuMap.containsKey(nextLevelBuff.toString())) { 719 // If menu has not already been created, we need 720 // to create it and then add it to the parent level... 721 722 JMenuItem menuItem = null; 723 724 // if we're at a "leaf node" - need to create a JMenuItem 725 if (levelIdx == totLevels - 1) { 726 727 // save old display name to use as actionCommand on 728 // menuitem, 729 // since some parts of PTII still 730 // use "if (actionCommand.equals("SaveAs")) {..." etc 731 String oldDisplayName = (String) action 732 .getValue(Action.NAME); 733 734 // action.putValue(Action.NAME, nextLevelStr); 735 736 if (mnemonicChar > 0) { 737 action.putValue(GUIUtilities.MNEMONIC_KEY, new Integer( 738 mnemonicChar)); 739 } 740 741 // Now we look to see if it's a checkbox 742 // menu item, or just a regular one 743 String menuItemType = (String) (action 744 .getValue(MENUITEM_TYPE)); 745 746 if (menuItemType != null 747 && menuItemType == CHECKBOX_MENUITEM_TYPE) { 748 menuItem = new JCheckBoxMenuItem(action); 749 } else { 750 menuItem = new JMenuItem(action); 751 } 752 753 // -------------------------------------------------------------- 754 /** @todo - setting menu names - TEMPORARY FIX - FIXME */ 755 // Currently, if we use the "proper" way of setting menu 756 // names - 757 // ie by using action.putValue(Action.NAME, "somename");, 758 // then 759 // the name appears on the port buttons on the toolbar, 760 // making 761 // them huge. As a temporary stop-gap, I am just setting the 762 // new 763 // display name using setText() instead of 764 // action.putValue(.., 765 // but this needs to be fixed elsewhere - we want to be able 766 // to 767 // use action.putValue(Action.NAME (ie uncomment the line 768 // above 769 // that reads: 770 // action.putValue(Action.NAME, nextLevelStr); 771 // and delete the line below that reads: 772 // menuItem.setText(nextLevelStr); 773 // otherwise this may bite us in future... 774 menuItem.setText(nextLevelStr); 775 // -------------------------------------------------------------- 776 777 // set old display name as actionCommand on 778 // menuitem, for ptii backward-compatibility 779 menuItem.setActionCommand(oldDisplayName); 780 781 // add JMenuItem to the Action, so it can be accessed by 782 // Action code 783 action.putValue(NEW_JMENUITEM_KEY, menuItem); 784 leafMenuItem = menuItem; 785 } else { 786 // if we're *not* at a "leaf node" - need to create a JMenu 787 menuItem = new JMenu(nextLevelStr); 788 if (mnemonicChar > 0) { 789 menuItem.setMnemonic(mnemonicChar); 790 } 791 } 792 // level 0 is a special case, since the container (JMenuBar or 793 // JContextMenu) is not a JMenu or a JMenuItem, so we can't 794 // use the same code to add child to parent... 795 if (levelIdx == 0) { 796 if (topLvlContainer instanceof JMenuBar) { 797 // this handles JMenuBar menus 798 ((JMenuBar) topLvlContainer).add(menuItem); 799 } else if (topLvlContainer instanceof JContextMenu) { 800 // this handles popup context menus 801 ((JContextMenu) topLvlContainer).add(menuItem); 802 } 803 // add to Map 804 keplerMenuMap.put(nextLevelBuff.toString(), menuItem); 805 separatorJustAdded = false; 806 } else { 807 JMenu parent = (JMenu) keplerMenuMap.get(prevLevelStr); 808 if (parent != null) { 809 // add to parent 810 parent.add(menuItem); 811 // add to Map 812 keplerMenuMap.put(nextLevelBuff.toString(), menuItem); 813 separatorJustAdded = false; 814 } else { 815 if (isDebugging) { 816 log.debug("Parent menu is NULL" + prevLevelStr); 817 } 818 } 819 } 820 } 821 } 822 return leafMenuItem; 823 } 824 825 public void clear() { 826 /*int removed =*/ MemoryCleaner.removeActionListeners(_ptiiMenubar); 827 //System.out.println("MenuMapper menubar action listeners removed: " + removed); 828 _ptiiMenuActionsMap.clear(); 829 _tableauFrameInstance = null; 830 _mappers.remove(this); 831 } 832 833 /** Remap all open menubars. */ 834 public static void reloadAllMenubars() { 835 for(MenuMapper mapper : _mappers) { 836 mapper.reloadPtolemyMenus(); 837 } 838 } 839 840 /** Remap the menu. */ 841 public void reloadPtolemyMenus() { 842 _reloadPtolemyMenus = true; 843 init(); 844 _tableauFrameInstance.validate(); 845 } 846 847 // ///////////////////////////////////////////////////////////////// 848 // // private variables //// 849 850 private static final Log log = LogFactory 851 .getLog(MenuMapper.class.getName()); 852 853 private static final boolean isDebugging = log.isDebugEnabled(); 854 855 private JMenuBar _ptiiMenubar; 856 857 private TableauFrame _tableauFrameInstance; 858 859 private Map<String, Action> _ptiiMenuActionsMap; 860 861 private static boolean separatorJustAdded = false; 862 863 // NOTE - MUST NOT contain the char defined in MNEMONIC_SYMBOL, 864 // or the String defined as MENU_PATH_DELIMITER 865 public static final String MENU_PATH_DELIMITER = "->"; 866 867 // NOTE - MUST NOT contain the String defined in MNEMONIC_SYMBOL, 868 // or the String defined as MENU_PATH_DELIMITER 869 public static final String MENU_SEPARATOR_KEY = "MENU_SEPARATOR"; 870 871 // NOTE - MUST NOT match the String defined as MENU_PATH_DELIMITER, 872 // or the String defined as MENU_SEPARATOR_KEY 873 public static final String MNEMONIC_SYMBOL = "~"; 874 875 /** A collection of all MenuMapper objects. */ 876 private static Set<MenuMapper> _mappers = Collections.synchronizedSet(new HashSet<MenuMapper>()); 877 878 /** If true, reload the Ptolemy menu. */ 879 private boolean _reloadPtolemyMenus = false; 880}