LavaBlast Software Blog

Help your franchise business get to the next level.
AddThis Feed Button

Multi-level contextual menus in Eclipse/GEF

clock June 22, 2009 10:46 by author JKealey

 

Scenario: I want my ContextMenuProvider to have multiple levels

Imagine you already have a ContextMenuProvider setup in your GEF editor but you would like to have multiple levels of actions, grouping elements together. This is one of the elements we recently had to accomplish in jUCMNav and since the we couldn’t easily find sample code on Google, we thought it would be nice to share this code with you.

 multilevel_contextmenu

Step 1) Code for the sub menu container

   1:  package seg.jUCMNav.actions;
   2:   
   3:  import org.eclipse.jface.action.Action;
   4:  import org.eclipse.jface.action.IAction;
   5:  import org.eclipse.jface.action.IMenuCreator;
   6:  import org.eclipse.jface.resource.ImageDescriptor;
   7:  import org.eclipse.swt.SWT;
   8:  import org.eclipse.swt.events.SelectionEvent;
   9:  import org.eclipse.swt.events.SelectionListener;
  10:  import org.eclipse.swt.widgets.Control;
  11:  import org.eclipse.swt.widgets.Menu;
  12:  import org.eclipse.swt.widgets.MenuItem;
  13:   
  14:  /**
  15:   * This action contains other actions and helps create another level of
  16:   * contextual menus.
  17:   * 
  18:   * @author jkealey
  19:   * 
  20:   */
  21:  public class SubmenuAction extends Action implements SelectionListener
  22:  {
  23:      // / Who to inform when this action is fired (meaning display the submenu)
  24:      private SelectionListener actionInstance;
  25:   
  26:      // the list of actions that are contained within this action
  27:      private IAction[] actions;
  28:   
  29:      // should we hide the disabled ones (if not, they will appear as grayed out)
  30:      private boolean hideDisabled;
  31:   
  32:      /***
  33:       * Create a submenu.
  34:       * 
  35:       * @param subactions
  36:       *            the actions that are contained within
  37:       * @param text
  38:       *            the container's textual label
  39:       * @param toolTip
  40:       *            the container's tooltip
  41:       * @param descriptor
  42:       *            the container's image descriptor
  43:       * @param hideDisabledActions
  44:       *            should we hide the disabled ones (if not, they will appear as
  45:       *            grayed out)
  46:       */
  47:      public SubmenuAction(IAction[] subactions, String text, String toolTip, ImageDescriptor descriptor, boolean hideDisabledActions)
  48:      {
  49:          // indicate that this is a secondary fly-out menu.
  50:          super("", IAction.AS_DROP_DOWN_MENU);
  51:   
  52:          this.actionInstance = this;
  53:          this.actions = subactions;
  54:          this.hideDisabled = hideDisabledActions;
  55:   
  56:          setText(text);
  57:          setToolTipText(toolTip);
  58:          setImageDescriptor(descriptor);
  59:   
  60:          // the secondayr menu logic
  61:          setMenuCreator(new IMenuCreator()
  62:          {
  63:              public Menu getMenu(Control parent)
  64:              {
  65:                  // this would be used outside of a menu. not useful for us.
  66:                  return null;
  67:              }
  68:   
  69:              public Menu getMenu(Menu parent)
  70:              {
  71:                  // create a submenu
  72:                  Menu menu = new Menu(parent);
  73:                  // fill it with our actions
  74:                  for (int i = 0; i < actions.length; i++)
  75:                  {
  76:                      // skip the disabled ones if necessary (or null actions)
  77:                      if (actions[i] == null || !actions[i].isEnabled() && hideDisabled)
  78:                          continue;
  79:   
  80:                      // create the submenu item
  81:                      MenuItem item = new MenuItem(menu, SWT.NONE);
  82:   
  83:                      // memorize the index
  84:                      item.setData(new Integer(i));
  85:   
  86:                      // identify it
  87:                      item.setText(actions[i].getText());
  88:   
  89:                      // create its image
  90:                      if (actions[i].getImageDescriptor() != null)
  91:                          item.setImage(actions[i].getImageDescriptor().createImage());
  92:   
  93:                      // inform us when something is selected.
  94:                      item.addSelectionListener(actionInstance);
  95:                  }
  96:                  return menu;
  97:              }
  98:   
  99:              public void dispose()
 100:              {
 101:              }
 102:          });
 103:   
 104:      }
 105:   
 106:      /**
 107:       * Returns how many items are enabled in the flyout. Useful to hide the
 108:       * submenu when none are enabled.
 109:       * 
 110:       * @return the number of currently enabled menu items.
 111:       */
 112:      public int getActiveOperationCount()
 113:      {
 114:          int operationCount = 0;
 115:          for (int i = 0; i < actions.length; i++)
 116:              operationCount += actions[i] != null && actions[i].isEnabled() ? 1 : 0;
 117:   
 118:          return operationCount;
 119:      }
 120:   
 121:      /**
 122:       * Runs the default action
 123:       */
 124:      public void run()
 125:      {
 126:          actions[0].run();
 127:      }
 128:   
 129:      /**
 130:       * Runs the default action
 131:       */
 132:      public void widgetDefaultSelected(SelectionEvent e)
 133:      {
 134:          actions[0].run();
 135:      }
 136:   
 137:      /**
 138:       * Called when an item in the drop-down menu is selected. Runs the
 139:       * associated run() method
 140:       */
 141:      public void widgetSelected(SelectionEvent e)
 142:      {
 143:          // get the index from the data and run that action.
 144:          actions[((Integer) (((MenuItem) (e.getSource())).getData())).intValue()].run();
 145:      }
 146:  }

Step 2) Setup your GEF ContextMenuProvider

   1:  public class UrnContextMenuProvider extends ContextMenuProvider {
   2:   
   3:      private ActionRegistry actionRegistry;
   4:      /**
   5:       * @param viewer
   6:       * @param registry
   7:       *            has to be passed in case we don't want to use the action registry used in the viewer. [is this bad coding?]
   8:       */
   9:      public UrnContextMenuProvider(EditPartViewer viewer, ActionRegistry registry) {
  10:          super(viewer);
  11:          setActionRegistry(registry);
  12:      }
  13:   
  14:      /**
  15:       * 
  16:       * @return the action registry used by the context menu provider.
  17:       */
  18:      private ActionRegistry getActionRegistry() {
  19:          return actionRegistry;
  20:      }
  21:   
  22:      /**
  23:       * 
  24:       * @param registry
  25:       *            the action registry used by the context menu provider.
  26:       */
  27:      private void setActionRegistry(ActionRegistry registry) {
  28:          actionRegistry = registry;
  29:      }
  30:   
  31:   
  32:      /**
  33:       * Looks up a set of actions in the action registry. If they are enabled, adds them to the correct groups.
  34:       */
  35:      public void buildContextMenu(IMenuManager manager) {
  36:          GEFActionConstants.addStandardActionGroups(manager);
  37:   
  38:      // regular action
  39:      IAction action = getActionRegistry().getAction(AddLabelAction.ADDLABEL);
  40:          if (action.isEnabled())
  41:              manager.appendToGroup(GEFActionConstants.GROUP_REST, action);
  42:   
  43:      // compound action
  44:          IAction[] actions = new IAction[13];
  45:          actions[0] = getActionRegistry().getAction(AddOrForkAction.ADDORFORK);
  46:          actions[1] = getActionRegistry().getAction(AddAndForkAction.ADDANDFORK);
  47:          actions[2] = getActionRegistry().getAction(AddOrJoinAction.ADDORJOIN);
  48:          actions[3] = getActionRegistry().getAction(AddAndJoinAction.ADDANDJOIN);
  49:   
  50:          SubmenuAction submenu = new SubmenuAction(actions, "Path Operations", "Path operations", actions[0].getImageDescriptor(), true); 
  51:          if (submenu.getActiveOperationCount()>0)
  52:              manager.appendToGroup(GEFActionConstants.GROUP_REST, submenu);
  53:   
  54:      // ... add other actions ... 
  55:     }
  56:  }

That’s all there is to it!



Eclipse Contextual Help in WizardDialog

clock June 22, 2009 10:08 by author JKealey

Although LavaBlast mainly produces .NET-based solutions, we’re currently working on usability enhancements to our favourite requirements engineering tool, jUCMNav. jUCMNav is Java-based open source project (an Eclipse plug-in) for graphical software requirements modelling built using the Graphical Editing Framework (GEF) and the Eclipse Modeling Framework (EMF).  Personally, I love the development cycle (with automatic incremental compilation) in Java/Eclipse. Writing plug-ins for Eclipse is tons of fun and there is an active community contributing to the Eclipse project (and its subprojects). However, I often find myself wanting to do something simple and having to fiddle around with 12 source code excerpts to make it work as a whole for various reasons.

  • You know how to do something for general Eclipse plug-ins but not where to hook it up to your GEF editor.
  • You know how to do something in SWT/Draw2D but not where to hook it up to your GEF editor.
  • You get burned by random conventions that aren’t clearly defined
  • Some file you would think would be included is ignored by your build script.
    In any case, I’ll post a few technical details on some of the issues we had to solve, hoping it will help someone out in the future!

Scenario: You are using a WizardDialog in your application, but the ? button does nothing.

Or: How do you set up contextual help on a WizardDialog in Eclipse?

Step 1) Create a help_contexts.xml file.

<?xml version="1.0" encoding="UTF-8"?>
<?NLS TYPE="org.eclipse.help.contexts"?>
<contexts>
   <context  id="help_general" >
        <description>test</description>
        <topic label="test" href="http://domain.com/help.html"/>
   </context>
</contexts>

 

  • The name of the XML file is not important.
  • Important: DO NOT include your plug-in name in the context id (here: “help_general”)
  • Important: DO NOT include any periods in the context id (here: “help_general”, not “help.general”)
  • You may reference local help files – they don’t need to be external.

     

    Step 2) Reference the contexts file from your plugin.xml

    <extension point="org.eclipse.help.contexts">
             <contexts file="help_contexts.xml">
             </contexts>
    </extension>
  • The contexts element has an optional plugin-id parameter. Leave this empty unless you want to contribute help contexts to another plug-in.

Step 3) Ensure help_contexts.xml is packaged with your application

  • Edit your build.properties file to ensure it includes help_contexts.xml (bin.includes = …, help_contexts.xml, …)
  • Note the Bundle-SymbolicName in your Manifest.MF (also visible in your plugin.xml editor). If none found, note Bundle-Name.  Example: com.domain.myplugin

Step 4) Set the context id in the WizardPage

public class MyWizardPage extends WizardPage
    public void createControl(Composite parent) {
        PlatformUI.getWorkbench.getHelpSystem.setHelp(parent, "com.domain.myplugin.help_general");
    }
}
  • You must add this in each WizardPage, not the WizardDialog.
    Hope this helped!


Month List

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2017

Sign in