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}