Archive

Posts Tagged ‘org.eclipse.ui.menus’

Writing an Eclipse Plug-in (Part 24): Common Navigator: Configuring the submenus (Presentation…again)

August 29, 2010 9 comments

When we last left our erstwhile travelers (that would be all of you) they were surfing the quantum wave on their way to a future refactoring of their past to allow them to simultaneously feel proud of their work and embarrassed that they were following some guy who is making it all up as he goes along causing them to need refactoring in the first place.

Hey. Refactoring happens. All the time. Just ask evolution (only don’t mention the alligators).

What I will post about is adding behavior to our menu items. After having added 4 (count ’em) new menu items it is time to make them actually do something. What should they do? Well, they are going to add elements into their respective XML files (and respectively) while looking like they are adding items into their respective category nodes in the navigator.
Read more…

Writing an Eclipse Plug-in (Part 22): Common Navigator: Adding submenus (Presentation)

May 30, 2010 2 comments

[If anyone cares: I have upgraded to Eclipse 3.6 RC3]

Happy Memorial Day weekend, everyone (at least those in the United States)!

For those who have accepted that life is meaningless, short and painful: chow down at the grill! Eat, drink and be merry for tomorrow you die!

For those who believe that life is meaningful, long and joyous: don’t overdo your carbs, remember that hot dogs have artificial colors and mystery meat, and grilling your food causes the formation of cancer causing agents due to carbonization. In other words, don’t eat, drink or be too merry because the odds are you won’t be dying tomorrow (or maybe you will).

But, hey! Enjoy the weekend!

Well, with that sense of merriment out of the way it is time to go back to the real reason we are here: finishing up the popup menu.
Read more…

Writing an Eclipse Plug-in (Part 20): Return of the Popup Menu (For an Empty Navigator)

April 4, 2010 4 comments

[This is a long post. It also feels like a bit of a mess. Whoda thought that creating a popup menu when there are no resources available in a navigator could be so non-trivial?]

So what’s the problem (or as they say in marketing speak: what is the challenge)?

The challenge (or as they say in real life: the pain) is quite easy to describe: when I right click in the custom navigator the popup menu appears. When I create a custom project and right-click on it the popup menu does not appear.

That behavior has to stop or I am turning this blog around right now (I’m not kidding! I’ll turn around right now!).

Alright. I lied. We actually have two problems:

  1. Remove the undefined menu items from the popup when nothing is available or selected
  2. Enable a specific set of menus when a Custom Project has been created

What this means is we have to decide when menu items appear/disappear or are enabled/disabled based on items being selected/unselected.

Sounds like a lot of combinations. Sounds like a job for a UML State diagram which I actually like when I am writing a real application. The issue here is that I am still kinda just messing with this and the state diagram makes me become too serious (I find that even the squirrels start to complain).

So let’s list the menu items we know so far:

  • New Custom Project
  • New Schema File
  • New Stored Procedure File
  • Open Project
  • Close Project
  • Copy
  • Paste
  • Delete
  • Import
  • Export
  • Refresh
  • Properties

Looks like a lot. Let’s think about this: copy, paste and delete don’t mean what they usually do, except for projects. I expect them to only copy/paste/delete the nodes they represent not entire files. Let’s leave them for last so let’s just remove them.

Open and Close project sounds too cool to be true. They will come after we do copy, paste and delete (and while we’re at it, how about working sets? Nah.).

Import and Export are also unknown. Since importing/exporting anything but Custom Projects seems rather odd, and we don’t know what it means to import or export Custom Projects, they will have to go too.

That leaves us with:

  • New Custom Project
  • New Schema File
  • New Stored Procedure File
  • Refresh
  • Properties

Much more managable. Also, we can expand what it means to be a schema and stored procedure in the Custom Navigator: the schema’s child nodes have New behavior as does the Stored Procedure node. So the list really looks like this:

  • New Custom Project
  • New Schema Table
  • New Schema View
  • New Schema Filter
  • New Stored Procedure
  • Refresh
  • Properties

When nothing is selected the following are enabled:

  • New Custom Project
  • Refresh

We can’t go around randomly creating Tables, Views and Filters just because, now can we?

When a Custom Project, or any custom resource, is selected the following are enabled:

  • New Custom Project
  • New Schema Table
  • New Schema View
  • New Schema Filter
  • New Stored Procedure
  • Refresh
  • Properties

As Richard Dreyfus once yelled into the open air: What does it mean?

What the above means is that the easiest way to control the popup menu for this custom navigator is to make one (or in this case two) rather than rely on the default popup and reconfigure it as we go. I tried desperately to avoid it, but in order to remove the default presentation of New, Import and Export it is just plain ol’ easier to make a new popup menu. Dems the breaks.

Tasks for this post and the next:

  1. Create a popup menu when the Custom navigator is empty
  2. Create a popup menu when a resource is selected in the Custom navigator

How (are we doing it?)

Time to back track. Remove the following:

  1. Remove all three commonWizard entries found under org.eclipse.ui.navigator.navigatorContent. That removes the menu entries under the popup menu New.
  2. Remove navigatorplugin –> plugin.xml –> org.eclipse.ui.menus
  3. Remove org.eclipse.ui.navigator.viewer –> customnavigator.navigator (viewerActionBinding) entry.

That was the easy stuff. The more involved steps for this post are:

  1. Define the popup menu and its insertionPoints
  2. Define the actionProvider
  3. Define the actionExtension
  4. Implement the action provider code (if not implementing the command framework)

Sounds pretty straightforward doesn’t it?

Let’s see how well we do.

  1. Define the popup menu and its insertionPoints
    1. Create a viewer entry in org.eclipse.ui.viewer
      • org.eclipse.ui.navigator.viewer –> New –> viewer
        • viewerId: customnavigator.navigator
    2. Create a popupMenu entry under the viewer
      • customnavigator.navigator (viewer) –> New –> popupMenu
        • id: customnavigator.navigator#PopupMenu
    3. Add two insertion points under the popupMenu
      • customnavigator.navigator#PopupMenu (popupMenu) –> New –> insertionPoint
        • name: group.new
      • customnavigator.navigator#PopupMenu (popupMenu) –> New –> insertionPoint
        • name: group.build
  2. Define the actionProviders
    1. org.eclipse.ui.navigator.navigatorContent –> New –> actionProvider
      • class: customnavigator.popup.actionprovider.CustomNewActionProvider
      • id: customnavigator.popup.actionprovider.CustomNewAction
      • customnavigator.popup.actionprovider.CustomNewActionProvider (actionProvider) –> enablement –> New –> or
      • or –> New –> adapt
        • type: org.eclipse.core.resources.IResource
      • or –> New –> adapt
        • type: java.util.Collection
      • java.util.Collection –> New –> count
        • value: 0
    2. org.eclipse.ui.navigator.navigatorContent –> New –> actionProvider
      • class: customnavigator.popup.actionprovider.CustomRefreshActionProvider
      • id: customnavigator.popup.actionprovider.CustomRefreshAction
      • customnavigator.popup.actionprovider.CustomRefreshActionProvider (actionProvider) –> enablement –> New –> or
      • or –> New –> adapt
        • type: org.eclipse.core.resources.IResource
      • or –> New –> adapt
        • type: java.util.Collection
      • java.util.Collection –> New –> count
        • value: 0
  3. Define the actionExtensions
    • org.eclipse.ui.navigator.viewer –> New –> viewerActionBinding
      • viewerId: customnavigator.navigator
    • customnavigator.navigator (viewerActionBinding) –> New –> includes
      • includes –> New –> actionExtension
      • pattern: customnavigator.popup.actionprovider.CustomNewAction
    • org.eclipse.ui.navigator.viewer –> customnavigator.navigator (viewerActionBinding) –> (includes) –> New –> actionExtension and change pattern to:
      • pattern: customnavigator.popup.actionprovider.CustomRefreshAction
  4. Implement the action provider code in the customnavigator plug-in
    customnavigator.popup.actionprovider.CustomNewActionProvider

    /**
     * Coder beware: this code is not warranted to do anything.
     * Some or all of this code is taken from the Eclipse code base.
     *
     * Copyright Mar 28, 2010 Carlos Valcarcel
     */
    package customnavigator.popup.actionprovider;
    
    import org.eclipse.jface.action.IMenuManager;
    import org.eclipse.jface.action.MenuManager;
    import org.eclipse.jface.action.Separator;
    import org.eclipse.ui.IWorkbenchWindow;
    import org.eclipse.ui.PlatformUI;
    import org.eclipse.ui.actions.ActionFactory;
    import org.eclipse.ui.navigator.CommonActionProvider;
    import org.eclipse.ui.navigator.ICommonActionExtensionSite;
    import org.eclipse.ui.navigator.ICommonMenuConstants;
    import org.eclipse.ui.navigator.ICommonViewerWorkbenchSite;
    import org.eclipse.ui.navigator.WizardActionGroup;
    
    public class CustomNewActionProvider extends CommonActionProvider {
    
        private static final String NEW_MENU_NAME = "common.new.menu";//$NON-NLS-1$
    
        private ActionFactory.IWorkbenchAction showDlgAction;
    
        private WizardActionGroup newWizardActionGroup;
    
        private boolean contribute = false;
    
        @Override
        public void init(ICommonActionExtensionSite anExtensionSite) {
    
            if (anExtensionSite.getViewSite() instanceof ICommonViewerWorkbenchSite) {
                IWorkbenchWindow window = ((ICommonViewerWorkbenchSite) anExtensionSite.getViewSite()).getWorkbenchWindow();
                showDlgAction = ActionFactory.NEW.create(window);
    
                newWizardActionGroup = new WizardActionGroup(window, PlatformUI.getWorkbench().getNewWizardRegistry(), WizardActionGroup.TYPE_NEW, anExtensionSite.getContentService());
    
                contribute = true;
            }
        }
    
        @Override
        public void fillContextMenu(IMenuManager menu) {
            IMenuManager submenu = new MenuManager(
                    "New",
                    NEW_MENU_NAME);
            if(!contribute) {
                return;
            }
    
            // fill the menu from the commonWizard contributions
            newWizardActionGroup.setContext(getContext());
            newWizardActionGroup.fillContextMenu(submenu);
    
            submenu.add(new Separator(ICommonMenuConstants.GROUP_ADDITIONS));
    
            // Add other ..
            submenu.add(new Separator());
            submenu.add(showDlgAction);
    
            // append the submenu after the GROUP_NEW group.
            menu.insertAfter(ICommonMenuConstants.GROUP_NEW, submenu);
        }
    
        @Override
        public void dispose() {
            if (showDlgAction!=null) {
                showDlgAction.dispose();
                showDlgAction = null;
            }
            super.dispose();
        }
    }
    

    customnavigator.popup.actionprovider.CustomRefreshActionProvider

    /**
     * Coder beware: this code is not warranted to do anything.
     * Some or all of this code is taken from the Eclipse code base.
     *
     * Copyright Apr 4, 2010 Carlos Valcarcel
     */
    package customnavigator.popup.actionprovider;
    
    import java.lang.reflect.InvocationTargetException;
    import java.util.Iterator;
    
    import org.eclipse.core.resources.IProject;
    import org.eclipse.core.resources.WorkspaceJob;
    import org.eclipse.core.runtime.CoreException;
    import org.eclipse.core.runtime.IAdaptable;
    import org.eclipse.core.runtime.IProgressMonitor;
    import org.eclipse.core.runtime.IStatus;
    import org.eclipse.core.runtime.Status;
    import org.eclipse.core.runtime.jobs.ISchedulingRule;
    import org.eclipse.jface.action.IMenuManager;
    import org.eclipse.jface.resource.ImageDescriptor;
    import org.eclipse.jface.viewers.IStructuredSelection;
    import org.eclipse.jface.viewers.StructuredViewer;
    import org.eclipse.jface.window.IShellProvider;
    import org.eclipse.osgi.util.NLS;
    import org.eclipse.swt.widgets.Shell;
    import org.eclipse.ui.IActionBars;
    import org.eclipse.ui.IWorkbenchCommandConstants;
    import org.eclipse.ui.actions.ActionFactory;
    import org.eclipse.ui.actions.RefreshAction;
    import org.eclipse.ui.actions.WorkspaceModifyOperation;
    import org.eclipse.ui.navigator.CommonActionProvider;
    import org.eclipse.ui.navigator.ICommonActionExtensionSite;
    import org.eclipse.ui.navigator.ICommonMenuConstants;
    
    import customnavigator.Activator;
    
    /**
     * The bulk of this code is taken from
     * org.eclipse.ui.internal.navigator.resources.actions.ResourceMgmtActionProvider
     * which is provided with Eclipse in case you want to look up the original.
     *
     * @author carlos
     */
    public class CustomRefreshActionProvider extends CommonActionProvider {
    
        private RefreshAction refreshAction;
    
        private Shell         shell;
    
        /*
         * (non-Javadoc)
         * @see
         * org.eclipse.ui.navigator.CommonActionProvider#init(org.eclipse.ui.navigator.ICommonActionExtensionSite)
         */
        @Override
        public void init(ICommonActionExtensionSite aSite) {
            super.init(aSite);
            shell = aSite.getViewSite().getShell();
            makeActions();
        }
    
        @Override
        public void fillActionBars(IActionBars actionBars) {
            actionBars.setGlobalActionHandler(ActionFactory.REFRESH.getId(), refreshAction);
            updateActionBars();
        }
    
        /**
         * Adds the refresh resource actions to the context menu.
         *
         * @param menu
         * context menu to add actions to
         */
        @SuppressWarnings("rawtypes")
        @Override
        public void fillContextMenu(IMenuManager menu) {
            IStructuredSelection selection = (IStructuredSelection) getContext().getSelection();
            boolean hasClosedProjects = false;
            Iterator resources = selection.iterator();
    
            while (resources.hasNext() && (!hasClosedProjects)) {
                Object next = resources.next();
                IProject project = null;
    
                if (next instanceof IProject) {
                    project = (IProject) next;
                } else if (next instanceof IAdaptable) {
                    project = (IProject) ((IAdaptable) next).getAdapter(IProject.class);
                }
    
                if (project == null) {
                    continue;
                }
    
                if (!project.isOpen()) {
                    hasClosedProjects = true;
                }
            }
    
            if (!hasClosedProjects) {
                refreshAction.selectionChanged(selection);
                menu.appendToGroup(ICommonMenuConstants.GROUP_BUILD, refreshAction);
            }
        }
    
        protected void makeActions() {
            IShellProvider sp = new IShellProvider() {
                @SuppressWarnings("synthetic-access")
                @Override
                public Shell getShell() {
                    return shell;
                }
            };
    
            refreshAction = new RefreshAction(sp) {
                @Override
                public void run() {
                    final IStatus[] errorStatus = new IStatus[1];
                    errorStatus[0] = Status.OK_STATUS;
                    final WorkspaceModifyOperation op = (WorkspaceModifyOperation) createOperation(errorStatus);
                    WorkspaceJob job = new WorkspaceJob("refresh") { //$NON-NLS-1$
    
                        @SuppressWarnings("synthetic-access")
                        @Override
                        public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException {
                            try {
                                op.run(monitor);
                                if (shell != null && !shell.isDisposed()) {
                                    shell.getDisplay().asyncExec(new Runnable() {
                                        @Override
                                        public void run() {
                                            StructuredViewer viewer = getActionSite().getStructuredViewer();
                                            if (viewer != null && viewer.getControl() != null && !viewer.getControl().isDisposed()) {
                                                viewer.refresh();
                                            }
                                        }
                                    });
                                }
                            } catch (InvocationTargetException e) {
                                String msg = NLS.bind("Exception in {0}. run: {1}", getClass().getName(), e.getTargetException());
                                throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, IStatus.ERROR, msg, e
                                        .getTargetException()));
                            } catch (InterruptedException e) {
                                return Status.CANCEL_STATUS;
                            }
                            return errorStatus[0];
                        }
    
                    };
                    ISchedulingRule rule = op.getRule();
                    if (rule != null) {
                        job.setRule(rule);
                    }
                    job.setUser(true);
                    job.schedule();
                }
            };
            refreshAction.setDisabledImageDescriptor(getImageDescriptor("icons/refresh_nav_disabled.gif"));//$NON-NLS-1$
            refreshAction.setImageDescriptor(getImageDescriptor("icons/refresh_nav_enabled.gif"));//$NON-NLS-1$
            refreshAction.setActionDefinitionId(IWorkbenchCommandConstants.FILE_REFRESH);
        }
    
        /**
         * Returns the image descriptor with the given relative path.
         */
        protected ImageDescriptor getImageDescriptor(String relativePath) {
            return Activator.getIDEImageDescriptor(relativePath);
    
        }
    
        @Override
        public void updateActionBars() {
            IStructuredSelection selection = (IStructuredSelection) getContext().getSelection();
            refreshAction.selectionChanged(selection);
        }
    
    }
    

    Add the following to customnavigator.Activator:

    public class Activator extends AbstractUIPlugin {
    ...
        public static ImageDescriptor getIDEImageDescriptor(String imagePath) {
            return AbstractUIPlugin.imageDescriptorFromPlugin(Activator.PLUGIN_ID, imagePath);
        }
    }
    

    Add the following icons to the customnavigator icons folder:
    refresh_nav_enabled.gif:

    refresh_nav_disabled.gif:

Don’t forget:

  • Fix the warnings in MANIFEST.MF and plugin.xml.
  • Open the Externalize Strings Wizard and move the two strings to messages.properties in the folder with the action provider code.

Why (did we do it that way?)

To do today’s tasks it is necessary to clean the deck. That means removing all the wonderous things that took advantage of all the default GUI hooks and basically putting them back with new hooks. Think of it like spring cleaning…without spring or the cleaning.

Since we have already decided to have only the menu entries we really need we have to remove the commonWizard and org.eclipse.ui.menus entries for now. Don’t worry, we’ll put them back. It will be easier and cleaner to add them in sequence rather than removing some pieces, moving things around and hoping they work eventually.

  1. Remove all three commonWizard entries found under org.eclipse.ui.navigator.navigatorContent. That removes the menu entries under the popup menu New.
  2. Remove navigatorplugin –> plugin.xml –> org.eclipse.ui.menus

    As you should already know from the last post, the menuContribution entry found under org.eclipse.ui.menus let’s you directly add new menu items to an existing popup by giving Eclipse the path to the menu being affected. We’ll use this again later. Some things are too good to give up for long.

  3. Remove org.eclipse.ui.navigator.viewer –> customnavigator.navigator (viewerActionBinding) entry.The value of actionExtension –> pattern refers to the ids of the actionProvider classes that execute the default behavior when a popup menu item is selected. For example, the value we just removed, org.eclipse.ui.navigator.resources.*, refers to the actionProvider ids found in the org.eclipse.ui.navigator.resources plug-in. Remember how the default popup menu displays New, Import, Export and Refresh? Well, if you open org.eclipse.ui.navigator.resources –> plugin.xml you will find an actionProvider entry for the following classes (there are others, but additional actionProvider do not concern me):
    • org.eclipse.ui.internal.navigator.resources.actions.NewActionProvider
    • org.eclipse.ui.internal.navigator.resources.actions.PortingActionProvider
    • org.eclipse.ui.internal.navigator.resources.actions.ResourceMgmtActionProvider

    The ids for the above classes are:

    • org.eclipse.ui.navigator.resources.NewActions
    • org.eclipse.ui.navigator.resources.PortingActions
    • org.eclipse.ui.navigator.resources.ResourceMgmtActions

    Notice that the above ids fit the pattern org.eclipse.ui.navigator.resources.*. Eclipse doesn’t care about the class name; it cares about the id. Yes, there are other actionProviders, but they have enablement criteria that keeps them from being displayed when nothing is selected.

  4. Define the new popup menu and the insertionPoints.Time to make the donuts.

    Let’s create the popup and add one menu item. In order to do that we have to

    1. Create a viewer entry in org.eclipse.ui.viewer
    2. Create a popupMenu entry under the viewer
    3. Add an insertion point under the popupMenu
    4. Create a viewerActionBinding and actionExtension entry in org.eclipse.ui.navigator.viewer

    Do the following in the customnavigator plugin.xml Extensions tab:

    1. Create a viewer entry in org.eclipse.ui.viewer
      • org.eclipse.ui.navigator.viewer –> New –> viewer
        • viewerId: customnavigator.navigator
    2. Create a popupMenu entry under the viewer
      • customnavigator.navigator (viewer) –> New –> popupMenu
        • id: customnavigator.navigator#PopupMenu
    3. Add an insertion point under the popupMenu
      • customnavigator.navigator#PopupMenu (popupMenu) –> New –> insertionPoint
        • name: group.new
    4. Create a viewerActionBinding and actionExtension entry in org.eclipse.ui.navigator.viewer
      • org.eclipse.ui.navigator.viewer –> New –> viewerActionBinding
        • viewerId: customnavigator.navigator
      • customnavigator.navigator (viewerActionBinding) –> New –> includes
        • includes –> New –> actionExtension
        • pattern: org.eclipse.ui.navigator.resources.NewActions

    Quick note: The name group.new comes from ICommonMenuConstants found in org.eclipse.ui.navigator. Whenever possible I recommend adhering to existing naming conventions just to make things easier to find.

    Just for yucks we are using an existing action provider: org.eclipse.ui.internal.navigator.resources.actions.NewActionProvider whose id is org.eclipse.ui.navigator.resources.NewActions. What is interesting about the NewActionProvider is that it creates a new menu insertion point in the popup which allows menu items to be added as submenus. What is bad about NewActionProvider is that it does it programmatically.

    NewActionProvider.java

    public class NewActionProvider extends CommonActionProvider {
    ...
    	public void fillContextMenu(IMenuManager menu) {
    		IMenuManager submenu = new MenuManager(
    				WorkbenchNavigatorMessages.NewActionProvider_NewMenu_label,
    				NEW_MENU_NAME);
    		if(!contribute) {
    			return;
    		}
    ...
    		// THIS IS A NEW INSERTION POINT! WHODA THUNK IT?
    		menu.insertAfter(ICommonMenuConstants.GROUP_NEW, submenu);
    	}
    ...
    }
    

    That’s right, we cannot declare an insertion point for submenus in plugin.xml; the insertion point for a submenu has to be declared programmatically. Yes, code will have to be written, but we are going to steal copy most of it anyway.

  5. Start the runtime workbench and check that the popup menu appears when there is nothing displayed in the navigator. Exit the runtime workbench when you are done. Let’s create our own version of this code.
  6. Implement a version of CustomNewActionProvider to create an insertion point for the New Wizards.
    1. org.eclipse.ui.navigator.navigatorContent –> New –> actionProvider
      • class: customnavigator.popup.actionprovider.CustomNewActionProvider
      • id: customnavigator.popup.actionprovider.CustomNewAction
        • customnavigator.popup.actionprovider.CustomNewActionProvider (actionProvider) –> enablement –> New –> or
        • or –> New –> adapt
          • type: org.eclipse.core.resources.IResource
        • or –> New –> adapt
          • type: java.util.Collection
        • java.util.Collection –> New –> count
          • value: 0

    Return to customnavigator.popup.actionprovider.CustomNewActionProvider (actionProvider). Click the class link. Create the class. Add the following code:

    /**
     * Coder beware: this code is not warranted to do anything.
     * Some or all of this code is taken from the Eclipse code base.
     *
     * Copyright Mar 28, 2010 Carlos Valcarcel
     */
    package customnavigator.popup.actionprovider;
    
    import org.eclipse.jface.action.IMenuManager;
    import org.eclipse.jface.action.MenuManager;
    import org.eclipse.jface.action.Separator;
    import org.eclipse.ui.IWorkbenchWindow;
    import org.eclipse.ui.PlatformUI;
    import org.eclipse.ui.actions.ActionFactory;
    import org.eclipse.ui.navigator.CommonActionProvider;
    import org.eclipse.ui.navigator.ICommonActionExtensionSite;
    import org.eclipse.ui.navigator.ICommonMenuConstants;
    import org.eclipse.ui.navigator.ICommonViewerWorkbenchSite;
    import org.eclipse.ui.navigator.WizardActionGroup;
    
    public class CustomNewActionProvider extends CommonActionProvider {
    
        private static final String NEW_MENU_NAME = "common.new.menu";//$NON-NLS-1$
    
        private ActionFactory.IWorkbenchAction showDlgAction;
    
        private WizardActionGroup newWizardActionGroup;
    
        private boolean contribute = false;
    
        @Override
        public void init(ICommonActionExtensionSite anExtensionSite) {
    
            if (anExtensionSite.getViewSite() instanceof ICommonViewerWorkbenchSite) {
                IWorkbenchWindow window = ((ICommonViewerWorkbenchSite) anExtensionSite.getViewSite()).getWorkbenchWindow();
                showDlgAction = ActionFactory.NEW.create(window);
    
                newWizardActionGroup = new WizardActionGroup(window, PlatformUI.getWorkbench().getNewWizardRegistry(), WizardActionGroup.TYPE_NEW, anExtensionSite.getContentService());
    
                contribute = true;
            }
        }
    
        @Override
        public void fillContextMenu(IMenuManager menu) {
            IMenuManager submenu = new MenuManager(
                    "New",
                    NEW_MENU_NAME);
            if(!contribute) {
                return;
            }
    
            // fill the menu from the commonWizard contributions
            newWizardActionGroup.setContext(getContext());
            newWizardActionGroup.fillContextMenu(submenu);
    
            submenu.add(new Separator(ICommonMenuConstants.GROUP_ADDITIONS));
    
            // Add other ..
            submenu.add(new Separator());
            submenu.add(showDlgAction);
    
            // append the submenu after the GROUP_NEW group.
            menu.insertAfter(ICommonMenuConstants.GROUP_NEW, submenu);
        }
    
        @Override
        public void dispose() {
            if (showDlgAction!=null) {
                showDlgAction.dispose();
                showDlgAction = null;
            }
            super.dispose();
        }
    }
    

    Now associate (bind) the actionProvider to the popup through the viewerActionBinding:

    • Go to org.eclipse.ui.navigator.viewer –> customnavigator.navigator (viewerActionBinding) –> (includes) –> org.eclipse.ui.navigator.resources.NewActions and change pattern to:
      • pattern: customnavigator.popup.actionprovider.CustomNewAction

    Notice the use of the id, not the class name.

  7. Start the runtime workbench and check that the New popup menu appears and that it has one submenu, Other, which both appears and is enabled when there is nothing displayed in the navigator. Exit the runtime workbench when you are done.
  8. Add Custom Project to the New menu
    Let’s put back one of the pieces we removed earlier:

    • org.eclipse.ui.navigator.navigatorContent –> New –> commonWizard
      • type: new
      • wizardId: customplugin.wizard.new.custom
  9. Start the runtime workbench and check that the popup menu appears when there is nothing displayed in the navigator. Exit the runtime workbench when you are done.
  10. Add the Refresh menu
    Let’s add the Refresh menu without the functionality…just to maintain purity of thought. Besides, I confuse easily. We’ll add the code in a few steps.First, add a new insertionPoint for the Refresh menu:

    • customnavigator.navigator#PopupMenu (popupMenu) –> New –> insertionPoint
      • name: group.build
      • separator: true

    The name group.build comes from ICommonMenuConstants found in org.eclipse.ui.navigator.

    Let’s define the actionProvider for the Refresh menu:

    1. org.eclipse.ui.navigator.navigatorContent –> New –> actionProvider
      • class: customnavigator.popup.actionprovider.CustomRefreshActionProvider
      • id: customnavigator.popup.actionprovider.CustomRefreshAction
    2. customnavigator.popup.actionprovider.CustomRefreshActionProvider –> enablement –> New –> or
    3. or –> New –> adapt
      • type: org.eclipse.core.resources.IResource
    4. or –> New –> adapt
      • type: java.util.Collection
    5. java.util.Collection –> New –> count
      • value: 0

    Next, let’s steal copy, the existing code from ResourceMgmtActionProvider to create CustomRefreshActionProvider.

    Return to customnavigator.popup.actionprovider.CustomRefreshActionProvider (actionProvider). Click the class link, create the class and insert this code:

    /**
     * Coder beware: this code is not warranted to do anything.
     * Some or all of this code is taken from the Eclipse code base.
     *
     * Copyright Apr 4, 2010 Carlos Valcarcel
     */
    package customnavigator.popup.actionprovider;
    
    import java.lang.reflect.InvocationTargetException;
    import java.util.Iterator;
    
    import org.eclipse.core.resources.IProject;
    import org.eclipse.core.resources.WorkspaceJob;
    import org.eclipse.core.runtime.CoreException;
    import org.eclipse.core.runtime.IAdaptable;
    import org.eclipse.core.runtime.IProgressMonitor;
    import org.eclipse.core.runtime.IStatus;
    import org.eclipse.core.runtime.Status;
    import org.eclipse.core.runtime.jobs.ISchedulingRule;
    import org.eclipse.jface.action.IMenuManager;
    import org.eclipse.jface.resource.ImageDescriptor;
    import org.eclipse.jface.viewers.IStructuredSelection;
    import org.eclipse.jface.viewers.StructuredViewer;
    import org.eclipse.jface.window.IShellProvider;
    import org.eclipse.osgi.util.NLS;
    import org.eclipse.swt.widgets.Shell;
    import org.eclipse.ui.IActionBars;
    import org.eclipse.ui.IWorkbenchCommandConstants;
    import org.eclipse.ui.actions.ActionFactory;
    import org.eclipse.ui.actions.RefreshAction;
    import org.eclipse.ui.actions.WorkspaceModifyOperation;
    import org.eclipse.ui.navigator.CommonActionProvider;
    import org.eclipse.ui.navigator.ICommonActionExtensionSite;
    import org.eclipse.ui.navigator.ICommonMenuConstants;
    
    import customnavigator.Activator;
    
    /**
     * The bulk of this code is taken from
     * org.eclipse.ui.internal.navigator.resources.actions.ResourceMgmtActionProvider
     * which is provided with Eclipse in case you want to look up the original.
     *
     * @author carlos
     */
    public class CustomRefreshActionProvider extends CommonActionProvider {
    
        private RefreshAction refreshAction;
    
        private Shell         shell;
    
        /*
         * (non-Javadoc)
         * @see
         * org.eclipse.ui.navigator.CommonActionProvider#init(org.eclipse.ui.navigator.ICommonActionExtensionSite)
         */
        @Override
        public void init(ICommonActionExtensionSite aSite) {
            super.init(aSite);
            shell = aSite.getViewSite().getShell();
            makeActions();
        }
    
        @Override
        public void fillActionBars(IActionBars actionBars) {
            actionBars.setGlobalActionHandler(ActionFactory.REFRESH.getId(), refreshAction);
            updateActionBars();
        }
    
        /**
         * Adds the refresh resource actions to the context menu.
         *
         * @param menu
         * context menu to add actions to
         */
        @SuppressWarnings("rawtypes")
        @Override
        public void fillContextMenu(IMenuManager menu) {
            IStructuredSelection selection = (IStructuredSelection) getContext().getSelection();
            boolean hasClosedProjects = false;
            Iterator resources = selection.iterator();
    
            while (resources.hasNext() && (!hasClosedProjects)) {
                Object next = resources.next();
                IProject project = null;
    
                if (next instanceof IProject) {
                    project = (IProject) next;
                } else if (next instanceof IAdaptable) {
                    project = (IProject) ((IAdaptable) next).getAdapter(IProject.class);
                }
    
                if (project == null) {
                    continue;
                }
    
                if (!project.isOpen()) {
                    hasClosedProjects = true;
                }
            }
    
            if (!hasClosedProjects) {
                refreshAction.selectionChanged(selection);
                menu.appendToGroup(ICommonMenuConstants.GROUP_BUILD, refreshAction);
            }
        }
    
        protected void makeActions() {
            IShellProvider sp = new IShellProvider() {
                @SuppressWarnings("synthetic-access")
                @Override
                public Shell getShell() {
                    return shell;
                }
            };
    
            refreshAction = new RefreshAction(sp) {
                @Override
                public void run() {
                    final IStatus[] errorStatus = new IStatus[1];
                    errorStatus[0] = Status.OK_STATUS;
                    final WorkspaceModifyOperation op = (WorkspaceModifyOperation) createOperation(errorStatus);
                    WorkspaceJob job = new WorkspaceJob("refresh") { //$NON-NLS-1$
    
                        @SuppressWarnings("synthetic-access")
                        @Override
                        public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException {
                            try {
                                op.run(monitor);
                                if (shell != null && !shell.isDisposed()) {
                                    shell.getDisplay().asyncExec(new Runnable() {
                                        @Override
                                        public void run() {
                                            StructuredViewer viewer = getActionSite().getStructuredViewer();
                                            if (viewer != null && viewer.getControl() != null && !viewer.getControl().isDisposed()) {
                                                viewer.refresh();
                                            }
                                        }
                                    });
                                }
                            } catch (InvocationTargetException e) {
                                String msg = NLS.bind("Exception in {0}. run: {1}", getClass().getName(), e.getTargetException());
                                throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, IStatus.ERROR, msg, e
                                        .getTargetException()));
                            } catch (InterruptedException e) {
                                return Status.CANCEL_STATUS;
                            }
                            return errorStatus[0];
                        }
    
                    };
                    ISchedulingRule rule = op.getRule();
                    if (rule != null) {
                        job.setRule(rule);
                    }
                    job.setUser(true);
                    job.schedule();
                }
            };
            refreshAction.setDisabledImageDescriptor(getImageDescriptor("icons/refresh_nav_disabled.gif"));//$NON-NLS-1$
            refreshAction.setImageDescriptor(getImageDescriptor("icons/refresh_nav_enabled.gif"));//$NON-NLS-1$
            refreshAction.setActionDefinitionId(IWorkbenchCommandConstants.FILE_REFRESH);
        }
    
        /**
         * Returns the image descriptor with the given relative path.
         */
        protected ImageDescriptor getImageDescriptor(String relativePath) {
            return Activator.getIDEImageDescriptor(relativePath);
    
        }
    
        @Override
        public void updateActionBars() {
            IStructuredSelection selection = (IStructuredSelection) getContext().getSelection();
            refreshAction.selectionChanged(selection);
        }
    
    }
    

    I removed any code that did not contribute to the goal: make RefreshAction work. I changed comments as well.

    Yes, there is a compile error in CustomRefreshActionProvider.getImageDescriptor(). To fix that we have to add the following method to customnavigator.Activator:

    public class Activator extends AbstractUIPlugin {
    ...
        public static ImageDescriptor getIDEImageDescriptor(String imagePath) {
            return AbstractUIPlugin.imageDescriptorFromPlugin(Activator.PLUGIN_ID, imagePath);
        }
    }
    

    Compile error fixed.

    In order for the CustomRefreshActionProvider to be called we have to add an entry under org.eclipse.ui.navigator.viewer –> customnavigator.navigator (viewerActionBinding):

    • org.eclipse.ui.navigator.viewer –> customnavigator.navigator (viewerActionBinding) –> (includes) –> New –> actionExtension and change pattern to:
      • pattern: customnavigator.popup.actionprovider.CustomRefreshAction

    Again, notice the use of the id, not the class name. Could I simple have one entry for all of these action providers by putting in a pattern of customnavigator.popup.actionprovider.*? Of course, but where’s the fun in that (in other words, use that kind of pattern once you understand why you are using it. Until then, create individual entries)?

  11. Start the runtime workbench and check that the expected popup menus appears when there is nothing displayed in the navigator. Exit the runtime workbench.

    For those of you wondering when we added support for F5: the key binding is added by RefreshAction.
  12. Add Refresh icons
    method makeActions() is looking for an enabled and a disabled image for Refresh. Add the following images to your customnavigator icon folder:

    refresh_nav_enabled.gif:

    refresh_nav_disabled.gif:

  13. Start the runtime workbench and check that the expected popup menus appears when there is nothing displayed in the navigator. Exit the runtime workbench.

What Just Happened?

So we took a few steps back and a few steps forward.

  • We removed the plugin.xml entries that reconfigured the default popup.
  • We created a new popup menu definition with insertion points.
  • We declared and implemented two action providers: New and Refresh.
  • We declared two action extensions that referred to the action providers.
  • We declared a commonWizard entry to add the New Custom Project Wizard to the New popup menu.

Not bad for a post that I just couldn’t find the time for. For some reason it felt like a lot to do to create a new default menu. Adding the other items will be much simpler. I hope.

In other news: some of you may have noticed that in past posts I occasionally mentioned Deployment files as a feature. I am easily confused. The only things we are going to do are Custom Projects, Schemas and Stored Procedures. Any references to anything else are red herrings, blind alleys, and otherwise dead ends. Avoid them unless you are intent on finding a place to sleep.

References

Building a Common Navigator based viewer, Part III: Configuring Menus

Code

Activator.java

package customnavigator;

import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.graphics.Image;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.osgi.framework.BundleContext;

/**
 * The activator class controls the plug-in life cycle
 */
public class Activator extends AbstractUIPlugin {

	// The plug-in ID
	public static final String PLUGIN_ID = "customnavigator"; //$NON-NLS-1$

	// The shared instance
	private static Activator plugin;
	
	/**
	 * The constructor
	 */
	public Activator() {
	    // empty for now
	}

	/*
	 * (non-Javadoc)
	 * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext)
	 */
	@Override
    public void start(BundleContext context) throws Exception {
		super.start(context);
		plugin = this;
	}

	/*
	 * (non-Javadoc)
	 * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext)
	 */
	@Override
    public void stop(BundleContext context) throws Exception {
		plugin = null;
		super.stop(context);
	}

	/**
	 * Returns the shared instance
	 *
	 * @return the shared instance
	 */
	public static Activator getDefault() {
		return plugin;
	}

    public static Image getImage(String imagePath) {
        ImageDescriptor imageDescriptor = AbstractUIPlugin.imageDescriptorFromPlugin(Activator.PLUGIN_ID, imagePath);
        Image image = imageDescriptor.createImage();

        return image;
    }

    public static ImageDescriptor getIDEImageDescriptor(String imagePath) {
        return AbstractUIPlugin.imageDescriptorFromPlugin(Activator.PLUGIN_ID, imagePath);
    }
}

CustomNewActionProvider.java

/**
 * Coder beware: this code is not warranted to do anything. 
 * Some or all of this code is taken from the Eclipse code base.
 *
 * Copyright Mar 28, 2010 Carlos Valcarcel
 */
package customnavigator.popup.actionprovider;

import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.navigator.CommonActionProvider;
import org.eclipse.ui.navigator.ICommonActionExtensionSite;
import org.eclipse.ui.navigator.ICommonMenuConstants;
import org.eclipse.ui.navigator.ICommonViewerWorkbenchSite;
import org.eclipse.ui.navigator.WizardActionGroup;

/**
 * Provides the new (artifact creation) menu options for a context menu.
 * 
 * <p>
 * The added submenu has the following structure
 * </p>
 * 
 * <ul>
 * <li>a set of context sensitive wizard shortcuts (as defined by
 * <b>org.eclipse.ui.navigator.commonWizard</b>), </li>
 * <li>another separator, </li>
 * <li>a generic "Other" new wizard shortcut action</li>
 * </ul>
 * 
 * @since 3.2
 * 
 */
public class CustomNewActionProvider extends CommonActionProvider {

    private static final String NEW_MENU_NAME = "common.new.menu";//$NON-NLS-1$

    private ActionFactory.IWorkbenchAction showDlgAction;

    private WizardActionGroup newWizardActionGroup;

    private boolean contribute = false;

    @Override
    public void init(ICommonActionExtensionSite anExtensionSite) {

        if (anExtensionSite.getViewSite() instanceof ICommonViewerWorkbenchSite) {
            IWorkbenchWindow window = ((ICommonViewerWorkbenchSite) anExtensionSite.getViewSite()).getWorkbenchWindow();
            showDlgAction = ActionFactory.NEW.create(window);

            newWizardActionGroup = new WizardActionGroup(window, PlatformUI.getWorkbench().getNewWizardRegistry(), WizardActionGroup.TYPE_NEW, anExtensionSite.getContentService());

            contribute = true;
        }
    }

    /**
     * Adds a submenu to the given menu with the name "group.new" see
     * {@link ICommonMenuConstants#GROUP_NEW}). The submenu contains the following structure:
     * 
     * <ul>
     * <li>a set of context sensitive wizard shortcuts (as defined by
     * <b>org.eclipse.ui.navigator.commonWizard</b>), </li>
     * <li>another separator, </li>
     * <li>a generic "Other" new wizard shortcut action</li>
     * </ul>
     */
    @Override
    public void fillContextMenu(IMenuManager menu) {
        IMenuManager submenu = new MenuManager(
                Messages.CustomNewActionProvider_popupNewLabel,
                NEW_MENU_NAME);
        if(!contribute) {
            return;
        }

        // fill the menu from the commonWizard contributions
        newWizardActionGroup.setContext(getContext());
        newWizardActionGroup.fillContextMenu(submenu);

        submenu.add(new Separator(ICommonMenuConstants.GROUP_ADDITIONS));

        // Add other ..
        submenu.add(new Separator());
        submenu.add(showDlgAction);

        // append the submenu after the GROUP_NEW group.
        menu.insertAfter(ICommonMenuConstants.GROUP_NEW, submenu);
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.actions.ActionGroup#dispose()
     */
    @Override
    public void dispose() {
        if (showDlgAction!=null) {
            showDlgAction.dispose();
            showDlgAction = null;
        }
        super.dispose();
    }
}

CustomRefreshActionProvider.java

/**
 * Coder beware: this code is not warranted to do anything. 
 * Some or all of this code is taken from the Eclipse code base.
 * 
 * Copyright Apr 4, 2010 Carlos Valcarcel
 */
package customnavigator.popup.actionprovider;

import java.lang.reflect.InvocationTargetException;
import java.util.Iterator;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.WorkspaceJob;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.jface.window.IShellProvider;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IWorkbenchCommandConstants;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.actions.RefreshAction;
import org.eclipse.ui.actions.WorkspaceModifyOperation;
import org.eclipse.ui.navigator.CommonActionProvider;
import org.eclipse.ui.navigator.ICommonActionExtensionSite;
import org.eclipse.ui.navigator.ICommonMenuConstants;

import customnavigator.Activator;

/**
 * The bulk of this code is taken from
 * org.eclipse.ui.internal.navigator.resources.actions.ResourceMgmtActionProvider
 * which is provided with Eclipse in case you want to look up the original.
 * 
 * @author carlos
 */
public class CustomRefreshActionProvider extends CommonActionProvider {

    private RefreshAction refreshAction;

    private Shell         shell;

    /*
     * (non-Javadoc)
     * @see
     * org.eclipse.ui.navigator.CommonActionProvider#init(org.eclipse.ui.navigator.ICommonActionExtensionSite)
     */
    @Override
    public void init(ICommonActionExtensionSite aSite) {
        super.init(aSite);
        shell = aSite.getViewSite().getShell();
        makeActions();
    }

    @Override
    public void fillActionBars(IActionBars actionBars) {
        actionBars.setGlobalActionHandler(ActionFactory.REFRESH.getId(), refreshAction);
        updateActionBars();
    }

    /**
     * Adds the refresh resource actions to the context menu.
     * 
     * @param menu
     * context menu to add actions to
     */
    @SuppressWarnings("rawtypes")
    @Override
    public void fillContextMenu(IMenuManager menu) {
        IStructuredSelection selection = (IStructuredSelection) getContext().getSelection();
        boolean hasClosedProjects = false;
        Iterator resources = selection.iterator();

        while (resources.hasNext() && (!hasClosedProjects)) {
            Object next = resources.next();
            IProject project = null;

            if (next instanceof IProject) {
                project = (IProject) next;
            } else if (next instanceof IAdaptable) {
                project = (IProject) ((IAdaptable) next).getAdapter(IProject.class);
            }

            if (project == null) {
                continue;
            }

            if (!project.isOpen()) {
                hasClosedProjects = true;
            }
        }
        
        if (!hasClosedProjects) {
            refreshAction.selectionChanged(selection);
            menu.appendToGroup(ICommonMenuConstants.GROUP_BUILD, refreshAction);
        }
    }

    protected void makeActions() {
        IShellProvider sp = new IShellProvider() {
            @SuppressWarnings("synthetic-access")
            @Override
            public Shell getShell() {
                return shell;
            }
        };

        refreshAction = new RefreshAction(sp) {
            @Override
            public void run() {
                final IStatus[] errorStatus = new IStatus[1];
                errorStatus[0] = Status.OK_STATUS;
                final WorkspaceModifyOperation op = (WorkspaceModifyOperation) createOperation(errorStatus);
                WorkspaceJob job = new WorkspaceJob("refresh") { //$NON-NLS-1$

                    @SuppressWarnings("synthetic-access")
                    @Override
                    public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException {
                        try {
                            op.run(monitor);
                            if (shell != null && !shell.isDisposed()) {
                                shell.getDisplay().asyncExec(new Runnable() {
                                    @Override
                                    public void run() {
                                        StructuredViewer viewer = getActionSite().getStructuredViewer();
                                        if (viewer != null && viewer.getControl() != null && !viewer.getControl().isDisposed()) {
                                            viewer.refresh();
                                        }
                                    }
                                });
                            }
                        } catch (InvocationTargetException e) {
                            String msg = NLS.bind(Messages.CustomRefreshActionProvider_invocationTargetExceptionMessage, getClass().getName(), e.getTargetException());
                            throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, IStatus.ERROR, msg, e
                                    .getTargetException()));
                        } catch (InterruptedException e) {
                            return Status.CANCEL_STATUS;
                        }
                        return errorStatus[0];
                    }

                };
                ISchedulingRule rule = op.getRule();
                if (rule != null) {
                    job.setRule(rule);
                }
                job.setUser(true);
                job.schedule();
            }
        };
        refreshAction.setDisabledImageDescriptor(getImageDescriptor("icons/refresh_nav_disabled.gif"));//$NON-NLS-1$
        refreshAction.setImageDescriptor(getImageDescriptor("icons/refresh_nav_enabled.gif"));//$NON-NLS-1$
        refreshAction.setActionDefinitionId(IWorkbenchCommandConstants.FILE_REFRESH);
    }

    /**
     * Returns the image descriptor with the given relative path.
     */
    protected ImageDescriptor getImageDescriptor(String relativePath) {
        return Activator.getIDEImageDescriptor(relativePath);

    }

    @Override
    public void updateActionBars() {
        IStructuredSelection selection = (IStructuredSelection) getContext().getSelection();
        refreshAction.selectionChanged(selection);
    }

}

Messages.java

package customnavigator.popup.actionprovider;

import org.eclipse.osgi.util.NLS;

public class Messages extends NLS {
    private static final String BUNDLE_NAME = "customnavigator.popup.actionprovider.messages"; //$NON-NLS-1$
    public static String        CustomNewActionProvider_popupNewLabel;
    public static String CustomRefreshActionProvider_invocationTargetExceptionMessage;
    static {
        // initialize resource bundle
        NLS.initializeMessages(BUNDLE_NAME, Messages.class);
    }

    private Messages() {
    }
}

messages.properties

CustomNewActionProvider_popupNewLabel=New
CustomRefreshActionProvider_invocationTargetExceptionMessage=Exception in {0}. run: {1}

plugin.xml

<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.4"?>
<plugin>
   <extension
         point="org.eclipse.ui.views">
      <category
            id="customnavigator.category"
            name="%category.name">
      </category>
      <view
            allowMultiple="false"
            category="customnavigator.category"
            class="org.eclipse.ui.navigator.CommonNavigator"
            icon="icons/navigator.png"
            id="customnavigator.navigator"
            name="%view.name">
      </view>
   </extension>
   <extension
         point="org.eclipse.ui.navigator.viewer">
      <viewer
            viewerId="customnavigator.navigator">
         <popupMenu
               id="customnavigator.navigator#PopupMenu">
            <insertionPoint
                  name="group.new">
            </insertionPoint>
            <insertionPoint
                  name="group.build"
                  separator="true">
            </insertionPoint>
         </popupMenu>
      </viewer>
      <viewerContentBinding
            viewerId="customnavigator.navigator">
         <includes>
            <contentExtension
                  pattern="customnavigator.navigatorContent">
            </contentExtension>
         </includes>
      </viewerContentBinding>
      <viewerActionBinding
            viewerId="customnavigator.navigator">
         <includes>
            <actionExtension
                  pattern="customnavigator.popup.actionprovider.CustomNewAction">
            </actionExtension>
            <actionExtension
                  pattern="customnavigator.popup.actionprovider.CustomRefreshAction">
            </actionExtension>
         </includes>
      </viewerActionBinding>
   </extension>
   <extension
         point="org.eclipse.ui.navigator.navigatorContent">
      <navigatorContent
            activeByDefault="true"
            contentProvider="customnavigator.navigator.ContentProvider"
            id="customnavigator.navigatorContent"
            labelProvider="customnavigator.navigator.LabelProvider"
            name="%navigatorContent.name">
         <triggerPoints>
            <instanceof
                  value="org.eclipse.core.resources.IWorkspaceRoot">
            </instanceof>
         </triggerPoints>
         <commonSorter
               class="customnavigator.sorter.SchemaCategorySorter"
               id="customnavigator.sorter.schemacategorysorter">
            <parentExpression>
               <or>
                  <instanceof
                        value="customnavigator.navigator.CustomProjectSchema">
                  </instanceof>
               </or>
            </parentExpression>
         </commonSorter>
      </navigatorContent>
      <actionProvider
            class="customnavigator.popup.actionprovider.CustomNewActionProvider"
            id="customnavigator.popup.actionprovider.CustomNewAction">
         <enablement>
            <or>
               <adapt
                     type="org.eclipse.core.resources.IResource">
               </adapt>
               <adapt
                     type="java.util.Collection">
                  <count
                        value="0">
                  </count>
               </adapt>
            </or>
         </enablement>
      </actionProvider>
      <actionProvider
            class="customnavigator.popup.actionprovider.CustomRefreshActionProvider"
            id="customnavigator.popup.actionprovider.CustomRefreshAction">
         <enablement>
            <or>
               <adapt
                     type="org.eclipse.core.resources.IResource">
               </adapt>
               <adapt
                     type="java.util.Collection">
                  <count
                        value="0">
                  </count>
               </adapt>
            </or>
         </enablement>
      </actionProvider>
      <commonWizard
            type="new"
            wizardId="customplugin.wizard.new.custom">
         <enablement></enablement>
      </commonWizard>
   </extension>

</plugin>

MANIFEST.MF

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: %Bundle-Name
Bundle-SymbolicName: customnavigator;singleton:=true
Bundle-Version: 1.0.2.0
Bundle-Activator: customnavigator.Activator
Require-Bundle: org.eclipse.ui,
 org.eclipse.core.runtime,
 org.eclipse.core.resources,
 org.eclipse.ui.ide;bundle-version="3.6.0",
 org.eclipse.ui.navigator,
 customplugin;bundle-version="1.0.1"
Bundle-ActivationPolicy: lazy
Bundle-RequiredExecutionEnvironment: JavaSE-1.6
Export-Package: customnavigator,
 customnavigator.navigator,
 customnavigator.popup.actionprovider,
 customnavigator.sorter

Writing an Eclipse Plug-in (Part 18 – Take 2): Common Navigator: Adding submenus (Presentation/Behavior)

February 21, 2010 Leave a comment

Well, don’t I feel silly.

During the writing of Part 19, Adding Behavior to the menu items, I discovered something both heartening and disturbing: the addition of behavior to the New Wizards is much easier than I thought. Where I used the org.eclipse.ui.menus extension point to add the menu items to the New menu of the default popup menu I could have used the org.eclipse.ui.navigator.navigatorContent –> commonWizard extension instead. This would take care of both presentation and behavior.

Since using the org.eclipse.ui.menus to accomplish this task is wrong (yes, wrong*) I thought I would correct this obvious affront to civilization and pleasant company before the wrong kind of meme invades too many impressionable minds and causes an aggregation of incorrect behavior leading to that worst of all possible behaviors: rudeness.

Consider this a refactoring of Part 18 as the things discovered in it are still valid and will help in the controlling of the popup menu in other contexts…just not this one.

Okay! Places everyone! This time with feeling!

[Pretend that you just finished reading the brilliant advice that was Part 17 and are looking for the enlightenment that only comes from believing in God, unicorns or the Na’vi.]

And now, the moment you’ve all been waiting for: adding commands to the popup menu.

I know, you are just aquiver in anticipation.

What menus do we need to put in? Just a few. The Custom Navigator popup menu should contain:

  • New – Sub-menu with choices (Custom Project, Schema, and Deployment. Eventually we will change Schema to Tables, Views, Filters)
  • Copy – Ctrl+C (an item in a category or an entire category)
  • Paste – Ctrl+V (an item in a category or an entire category)
  • Remove – Del or Ctrl+X (an item in a category or an entire category)

We will not worry about Import/Export behavior as that will be added to the Import/Export Wizard with the associated additions to the main menu, toolbar, etc. done as appropriate (meaning later, when I care).

The thing to remember is: presentation first, behavior second. Actually, by the time we’re done we will have done both for the price of one (well, at least for the New wizards).

Where should the popup menu configuration/code go: into the customplugin or customnavigator project? Because we are directly affecting the navigator popup menu the configuration will go into the customnavigator plug-in. I promise not to regret it later when someone points out why it would really be in the customplugin project.

How (are we doing it?)

First: let’s add one menu item to the New menu.

  1. customnavigator –> plugin.xml –> Extensions –> org.eclipse.ui.navigator.navigatorContent –> New –> commonWizard
    • type: new
    • wizardId: customplugin.wizard.new.custom
  2. As a sanity check start the runtime workbench, go to the Custom Perspective and right click in the empty Custom Navigator.

    Back to the grindstone.

  3. org.eclipse.ui.navigator.navigatorContent –> New –> commonWizard
    • type: new
    • wizardId: customplugin.wizard.file.schema
  4. org.eclipse.ui.navigator.navigatorContent –> New –> commonWizard
    • type: new
    • wizardId: customplugin.wizard.file.deployment
  5. I know: you probably think that the above was too easy. You’re right. It was. In addition to the presentation we got the behavior for free. And no code so we’re ahead.

Next: let’s add three standard menus: Copy/Paste/Delete. Again, for now we will assign default commands that we will change later.

Let’s use the following icons for the copy/paste/delete menu items (yes, I copied them from Eclipse):

  1. customnavigator –> plugin.xml –> Extensions –> All Extensions –> Add –> org.eclipse.ui.menus
  2. org.eclipse.ui.menus –> New –> menuContribution
    • locationURI: popup:customnavigator.navigator?before=import
  3. popup:customnavigator.navigator?before=import (menuContribution) –> New –> command
    • commandId: org.eclipse.ui.edit.copy
    • label: Copy
    • icon: icons/copy_16x16.png
  4. popup:customnavigator.navigator?before=import (menuContribution) –> New –> command
    • commandId: org.eclipse.ui.edit.paste
    • label: Paste
    • icon: icons/paste_16x16.png
  5. popup:customnavigator.navigator?before=import (menuContribution) –> New –> command
    • commandId: org.eclipse.ui.edit.delete
    • label: Delete
    • icon: icons/delete_16x16.png
  6. popup:customnavigator.navigator?before=import (menuContribution) –> New –> separator
    • name: customnavigator.separator
    • visible: true

The above is a lot to do all at once so feel free to add one section at a time and check the runtime workbench. Once you add the org.eclipse.ui.menus extension along with the menuContribution and one of the commands you should be able to open the Custom Perspective, right click in the Custom Navigator and see a lovely popup menu.

The final result is beautifully displayed in the screen capture (don’t sweat the fact that Copy is not disabled. That is under the control of Eclipse and the clipboard. You will probably have a different item enabled or none).

But first: time to reveal the magic.

Why (did we do it that way?)

If you want to add a menu item to the popup menu the easiest way is to use the org.eclipse.ui.menus extension and add a menuContribution to it (hmm. That would imply there is another way to do this. Yes, using actionSets…which have been deprecated, so don’t). The menuContribution‘s locationURI is the path to the location within the popup where your new menu item should go. If we were adding our menu item at the top level popup we would simply have used a locationURI of popup:customplugin.customnavigator?after=additions; that’s right: simply using the id of the navigator places the menu item into its popup. Using the after=additions piece puts the menu items directly in the popup right after the last item…which is not where we want it.

So the $54,000 question is: what locationURI do we use to put our menu items in the correct spot?

That turns out to be the wrong question (I am starting to dislike that word).

The real question is: if there is a shortcut to adding New Wizards to the main menu, why isn’t there a way to do that with the navigator popup menu? If you recall from Part 15 we added our 3 New Wizards to the main menu by using the org.eclipse.ui.perspectiveExtensions –> perspectiveExtension –> newWizardShortcut. By simply supplying the New Wizard id Eclipse took care of adding it to the main menu File –> New and to the toolbar New button.

The real answer is: there is a way to do it and it involves the org.eclipse.ui.navigator.navigatorContent extension point. It has a child extension named commonWizards that does the equivalent behavior of the newWizardShortcut but for popup menus. On one hand, this is the greatest thing since sliced bread since we don’t have to configure as much (and did I mention no code?), but on the other hand we still have no idea how to add to a popup menu submenu such as New when the behavior we want to add has nothing to do with a wizard.

Let’s speak to that later. [Peek at the end of the post after the configuration files; I speak to the madness needed to implement the method of adding to the default popup menu supplied with the Common Navigator Framework.]

The configuration of the remaining menus now make sense. Add a new menuContribution to the org.eclipse.ui.menus extension and, using the high-level custom navigator path location URI (popup:customnavigator.navigator?before=import), we tell Eclipse to put the menus before the import section. Again, just to supply the required information we assign the internal copy, paste, and delete commandIds to the various menus.

Here are the instructions again:

  • org.eclipse.ui.menus –> New –> menuContribution
    • locationURI: popup:customnavigator.navigator?before=import
  • popup:customnavigator.navigator?before=import (menuContribution) –> New –> command
    • commandId: org.eclipse.ui.edit.copy
    • label: Copy
    • icon: icons/copy_16x16.png
  • popup:customnavigator.navigator?before=import (menuContribution) –> New –> command
    • commandId: org.eclipse.ui.edit.paste
    • label: Paste
    • icon: icons/paste_16x16.png
  • popup:customnavigator.navigator?before=import (menuContribution) –> New –> command
    • commandId: org.eclipse.ui.edit.delete
    • label: Delete
    • icon: icons/delete_16x16.png
  • popup:customnavigator.navigator?before=import (menuContribution) –> New –> separator
    • name: customnavigator.separator
    • visible: true

Doesn’t it look great? It just makes you proud to be a plug-in developer.

The next big question: why did we do it that way? Or more accurately: where is the documentation for this magic?

Part of the answer is easy: instructions on adding to a popup menu can be found here. Part of the answer is weak: I stumbled on the help documentation for commonWizard while I was looking up something else.

Time to wash up:

  • Go to the Overview tab, click on Externalize Strings Wizard and externalize the strings
  • In plugin.xml click on the MANIFEST.MF tab, click on the light bulb in the left hand margin and select Add Missing Packages

Who’s better than you?

What Just Happened?

The popup menu items have been added! With and without behavior! Who says you can’t have your cake and eat it too?

The cat is not only alive, but curious, which leads one to worry about the cat’s future.

(I hope this was a better answer to your question, Augusto. It’s always better the second time…)

* I know, I know: it still works. So what? you may say. So what?! So everything. If I had known about the commonWizard extension sooner I would have used it first as it would have eliminated the need for the Plug-in Spy and the search from the Common Navigator popup menu path to the New menu. While it is still just configuration, it is needless configuration. Needless. Need less. Compare what commonWizard needs:


to what org.eclipse.ui.menus –> menuContribution is looking for:

Use the more extensive extension when extensiveness is called for. Otherwise, strive to be simple. Like a new born baby. Or the brain of a politician.

Code

No code…again.

bundle.properties

#Properties file for customnavigator_1.0.0.7
Bundle-Name = Custom Navigator Plug-in
view.name = Custom Plug-in Navigator
category.name = Custom Projects
navigatorContent.name = Custom Navigator Content
copy.command.label = Copy
paste.command.label = Paste
delete.command.label = Delete

plugin.xml (customnavigator)

<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.4"?>
<plugin>
   <extension
         point="org.eclipse.ui.views">
      <category
            id="customnavigator.category"
            name="%category.name">
      </category>
      <view
            allowMultiple="false"
            category="customnavigator.category"
            class="org.eclipse.ui.navigator.CommonNavigator"
            icon="icons/navigator.png"
            id="customnavigator.navigator"
            name="%view.name">
      </view>
   </extension>
   <extension
         point="org.eclipse.ui.navigator.viewer">
      <viewerActionBinding
            viewerId="customnavigator.navigator">
         <includes>
            <actionExtension
                  pattern="org.eclipse.ui.navigator.resources.*">
            </actionExtension>
         </includes>
      </viewerActionBinding>
      <viewerContentBinding
            viewerId="customnavigator.navigator">
         <includes>
            <contentExtension
                  pattern="customnavigator.navigatorContent">
            </contentExtension>
         </includes>
      </viewerContentBinding>
   </extension>
   <extension
         point="org.eclipse.ui.navigator.navigatorContent">
      <navigatorContent
            activeByDefault="true"
            contentProvider="customnavigator.navigator.ContentProvider"
            id="customnavigator.navigatorContent"
            labelProvider="customnavigator.navigator.LabelProvider"
            name="%navigatorContent.name">
         <triggerPoints>
            <instanceof
                  value="org.eclipse.core.resources.IWorkspaceRoot">
            </instanceof>
         </triggerPoints>
         <commonSorter
               class="customnavigator.sorter.SchemaCategorySorter"
               id="customnavigator.sorter.schemacategorysorter">
            <parentExpression>
               <or>
                  <instanceof
                        value="customnavigator.navigator.CustomProjectSchema">
                  </instanceof>
               </or>
            </parentExpression>
         </commonSorter>
      </navigatorContent>
      <commonWizard
            type="new"
            wizardId="customplugin.wizard.new.custom">
         <enablement></enablement>
      </commonWizard>
      <commonWizard
            type="new"
            wizardId="customplugin.wizard.file.schema">
         <enablement></enablement>
      </commonWizard>
      <commonWizard
            type="new"
            wizardId="customplugin.wizard.file.deployment">
         <enablement></enablement>
      </commonWizard>
   </extension>
   <extension
         point="org.eclipse.ui.menus">
      <menuContribution
            locationURI="popup:customnavigator.navigator?before=import">
         <command
               commandId="org.eclipse.ui.edit.copy"
               icon="icons/copy_16x16.png"
               label="%copy.command.label"
               style="push">
         </command>
         <command
               commandId="org.eclipse.ui.edit.paste"
               icon="icons/paste_16x16.png"
               label="%paste.command.label"
               style="push">
         </command>
         <command
               commandId="org.eclipse.ui.edit.delete"
               icon="icons/delete_16x16.png"
               label="%delete.command.label"
               style="push">
         </command>
         <separator
               name="customnavigator.separator"
               visible="true">
         </separator>
      </menuContribution>
   </extension>

</plugin>

Extra Credit

So you want to add non-Wizard behavior to the default popup menu and aren’t sure how? You’ve come to the right place. Let’s look at what it means to add a new menu item to the New menu item of the default popup menu of our Custom Navigator.

But before I can answer that we have to fix something. Well, some of us have to fix something.

For those of you doing this on Windows: shield your eyes and move on to the section titled CONTINUE HERE (while goto may be considered harmful, continue here isn’t).

Now that we have all of those miscreants out of the way: those of you who need to see the answer with your own eyes, but are using Kubuntu, need to change the Eclipse key binding for the runtime workbench from Shift+Alt+F2 to Shift+Alt+F3. Why? Because in order to discover the correct path to the New menu in the popup we need the Plug-in Spy to work. The Plug-in Spy, at least on Kubuntu, does not work properly because Shift+Alt+F2 doesn’t work properly; changing the key binding from Shift+Alt+F2 to Shift+Alt+F3 works.

How do we change the key binding? Start and do the following from the runtime workbench:

  1. Window –> Preferences –> General –> Keys
  2. In the text field below the Scheme drop down type: shift+alt+f
  3. Press Delete in the Binding field to remove the current key binding
  4. With the cursor in the Binding field press the shift key, the alt key and the F3 key all at the same time.
  5. Click OK to close the Preferences window

Now the bad news: you will have to do this every time you start the runtime workbench if you want to use the Plug-in Spy unless you unset the Launch configuration to clear the workspace; by not clearing the workspace you may find weird behavior that isn’t weird but appears to be weird because the runtime workbench is not cleaning up after itself. I recommend leaving the Launch configuration alone…meaning with the Clear selection checked.

The good news: you don’t need to use the Plug-in spy that often so this manual step is something you will not do too often. Grow up or start taking stronger meds.

CONTINUE HERE

Those of you on Windows: every time I say press Shift+Alt+F3 you must press Shift+Alt+F2. Got it? Shift+Alt+F3 really mean Shift+Alt+F2 (just in Windows). Think you can handle that?

  1. Start the runtime workbench and open the Custom Perspective
  2. Press Shift+Alt+F3. Notice the cursor changes appearance
  3. Right click in the Custom Navigator and select New –> Project
  4. Oh look! A window with wonderful information!

And there it is: the path used by the active contribution location URI: common.new.menu. If you change the current path for the Copy/Paste/Delete command from popup:customplugin.customnavigator?after=additions to popup:common.new.menu?after=additions and start the runtime workbench again you will find that the menus are part of the submenus associated with New.

So where can you find all this great information on inserting a menu item in the popup menu’s New menu?

My answer is going to be very unsatisfying: I don’t know. I went through a number of plug-ins, web sites and help files and found nothing. When I remembered the Plug-in Spy, and fixed the Shift+Alt+F2 problem (Kubuntu, remember?), I was able to discover the path I was looking for.

The cat remained quiet and was of no value whatsoever.

Writing an Eclipse Plug-in (Part 18): Common Navigator: Adding Submenus (Presentation)

February 7, 2010 2 comments

[Before you get too engaged in this post: you might want to skip it. Yes, I realize that the writing is engaging, witty, poetic, philosophical, daring, edgy, and technically astute, but what is all that to being wrong? Just jump forward to Part 18 – Take 2 for a refactoring of this post with many of the wrong parts taken out. However, the writing is still what it is and for that I offer my apologies.]

And now, the moment you’ve all been waiting for: adding commands to the popup menu.

I know, you are just aquiver in anticipation.

What menus do we need to put in? Just a few. The Custom Navigator popup menu should contain:

  • New – Sub-menu with choices (Custom Project, Schema, and Deployment. Eventually we will change Schema to Tables, Views, Filters)
  • Copy – Ctrl+C (an item in a category or an entire category)
  • Paste – Ctrl+V (an item in a category or an entire category)
  • Remove – Del or Ctrl+X (an item in a category or an entire category)

We will not worry about Import/Export behavior as that will be added to the Import/Export Wizard with the associated additions to the main menu, toolbar, etc. done as appropriate (meaning later, when I care).

The thing to remember is: presentation first, behavior second.

For now, we are going to set up the menus with bogus behavior. Since I don’t usually test the presentation this works out well for me. We’ll get around to behavior in the next post. Let’s get the menus in place and then we will snap the commands (next time).

Where should the popup menu configuration/code go: into the customplugin or customnavigator project? Because we are directly affecting the navigator popup menu the configuration will go into the customnavigator plug-in. I promise not to regret it later when someone points out why it would really be in the customplugin project.

How (are we doing it?)

First: let’s add one menu item to the New menu.

  1. customnavigator –> plugin.xml –> Extensions –> All Extensions –> Add –> org.eclipse.ui.menus
  2. org.eclipse.ui.menus –> New –> menuContribution
    • locationURI: popup:common.new.menu?after=additions
  3. popup:common.new.menu?after=additions (menuContribution) –> New –> command
    • commandId: org.eclipse.ui.newWizard
    • label: Custom Project
    • icon: icons/project-folder.png
  4. As a sanity check start the runtime workbench.

    Back to the grindstone.

  5. popup:common.new.menu?after=additions (menuContribution) –> New –> command
    • commandId: org.eclipse.ui.newWizard
    • label: Schema File
    • icon: icons/schema-file_16x16.png (copy this from the customplugin icon folder)
  6. popup:common.new.menu?after=additions (menuContribution) –> New –> command
    • commandId: org.eclipse.ui.newWizard
    • label: Deployment File
    • icon: icons/deployment-file_16x16.png (copy this from the customplugin icon folder)

Next: let’s add three standard menus: Copy/Paste/Delete. Again, for now we will assign default commands that we will change later.

Let’s use the following icons for the copy/paste/delete menu items (yes, I copied them from Eclipse):

  1. org.eclipse.ui.menus –> New –> menuContribution
    • locationURI: popup:customnavigator.navigator?before=import
  2. popup:customnavigator.navigator?before=import (menuContribution) –> New –> command
    • commandId: org.eclipse.ui.edit.copy
    • label: Copy
    • icon: icons/copy_16x16.png
  3. popup:customnavigator.navigator?before=import (menuContribution) –> New –> command
    • commandId: org.eclipse.ui.edit.paste
    • label: Paste
    • icon: icons/paste_16x16.png
  4. popup:customnavigator.navigator?before=import (menuContribution) –> New –> command
    • commandId: org.eclipse.ui.edit.delete
    • label: Delete
    • icon: icons/delete_16x16.png
  5. popup:customnavigator.navigator?before=import (menuContribution) –> New –> separator
    • name: customnavigator.separator
    • visible: true

The above is a lot to do all at once so feel free to add one section at a time and check the runtime workbench. Once you add the org.eclipse.ui.menus extension along with the menuContribution and one of the commands you should be able to open the Custom Perspective, right click in the Custom Navigator and see a lovely popup menu.

The final result is beautifully displayed in the screen capture (don’t sweat the fact that Delete is not disabled. That is under the control of Eclipse and the clipboard. You will probably have a different item enabled or none).

Don’t let the gratuitous use of Ctrl+N upset you. We will take care of messing those up later.

But first: time to reveal the magic.

Why (did we do it that way?)

If you want to add a menu item to the popup menu the easiest way is to use the org.eclipse.ui.menus extension and add a menuContribution to it (hmm. That would imply there is another way to do this. Yes, using actionSets…which have been deprecated, so don’t). The menuContribution‘s locationURI is the path to the location within the popup where your new menu item should go. If we were adding our menu item at the top level popup we would simply have used a locationURI of popup:customplugin.customnavigator?after=additions; that’s right: simply using the id of the navigator places the menu item into its popup. Using the after=additions piece puts the menu items directly in the popup right after the last item…which is not where we want it.

So the $54,000 question is: what locationURI do we use to put our menu items in the correct spot?

Before I can answer that we have to fix something. Well, some of us have to fix something.

For those of you doing this on Windows: shield your eyes and move on to the section titled CONTINUE HERE (while goto may be considered harmful, continue here isn’t).

Now that we have all of those miscreants out of the way: those of you who need to see the answer with your own eyes, but are using Kubuntu, need to change the Eclipse key binding for the runtime workbench from Shift+Alt+F2 to Shift+Alt+F3. Why? Because in order to discover the correct path to the New menu in the popup we need the Plug-in Spy to work. The Plug-in Spy, at least on Kubuntu, does not work properly because Shift+Alt+F2 doesn’t work properly; changing the key binding from Shift+Alt+F2 to Shift+Alt+F3 works.

How do we change the key binding? Start and do the following from the runtime workbench:

  1. Window –> Preferences –> General –> Keys
  2. In the text field below the Scheme drop down type: shift+alt+f
  3. Press Delete in the Binding field to remove the current key binding
  4. With the cursor in the Binding field press the shift key, the alt key and the F3 key all at the same time.
  5. Click OK to close the Preferences window

Now the bad news: you will have to do this every time you start the runtime workbench if you want to use the Plug-in Spy unless you unset the Launch configuration to clear the workspace; by not clearing the workspace you may find weird behavior that isn’t weird but appears to be weird because the runtime workbench is not cleaning up after itself. I recommend leaving the Launch configuration alone…meaning with the Clear selection checked.

The good news: you don’t need to use the Plug-in spy that often so this manual step is something you will not do too often. Grow up or start taking stronger meds.

CONTINUE HERE

Those of you on Windows: every time I say press Shift+Alt+F3 you must press Shift+Alt+F2. Got it? Shift+Alt+F3 really mean Shift+Alt+F2 (just in Windows). Think you can handle that?

  1. Start the runtime workbench and open the Custom Perspective
  2. Press Shift+Alt+F3. Notice the cursor changes appearance
  3. Right click in the Custom Navigator and select New –> Project
  4. Oh look! A window with wonderful information!

And there it is: the path used by the active contribution location URI: common.new.menu. Change the current path from popup:customplugin.customnavigator?after=additions to popup:common.new.menu?after=additions and start the runtime workbench again. The menus are exactly where we want them.

The configuration of the remaining menus now make sense. Add a new menuContribution to the org.eclipse.ui.menus extension and, using the high-level custom navigator path location URI (popup:customnavigator.navigator?before=import), we tell Eclipse to put the menus before the import section. Again, just to supply the required information we assign the internal copy, paste, and delete commandIds to the various menus.

Here are the instructions again:

  • org.eclipse.ui.menus –> New –> menuContribution
    • locationURI: popup:customnavigator.navigator?before=import
  • popup:customnavigator.navigator?before=import (menuContribution) –> New –> command
    • commandId: org.eclipse.ui.edit.copy
    • label: Copy
    • icon: icons/copy_16x16.png
  • popup:customnavigator.navigator?before=import (menuContribution) –> New –> command
    • commandId: org.eclipse.ui.edit.paste
    • label: Paste
    • icon: icons/paste_16x16.png
  • popup:customnavigator.navigator?before=import (menuContribution) –> New –> command
    • commandId: org.eclipse.ui.edit.delete
    • label: Delete
    • icon: icons/delete_16x16.png
  • popup:customnavigator.navigator?before=import (menuContribution) –> New –> separator
    • name: customnavigator.separator
    • visible: true

Doesn’t it look great? It just makes you proud to be a plug-in developer.

The next big question: why did we do that? Or more accurately: where is the documentation for this magic?

My answer is going to be very unsatisfying: I don’t know. I went through a number of plug-ins, web sites and help files and found nothing. When I remembered the Plug-in Spy, and fixed the Shift+Alt+F2 problem (Kubuntu, remember?), I was able to discover the path I was looking for.

However, that is not the reason why this post has taken so long to appear; I have had a lot on my mind and this wasn’t it. In the next post we will add command objects to the New menu items and ignore the Copy/Paste/Delete stuff (those commands need to be configured to recognize our custom types so they copy things properly. They might work our of the box, but since I haven’t thought through all of the implication I will assume that they will have to be changed).

Time to wash up:

  • Go to the Overview tab, click on Externalize Strings Wizard and externalize the strings
  • In plugin.xml click on the MANIFEST.MF tab, click on the light bulb in the left hand margin and select Add Missing Packages

Who’s better than you?

What Just Happened?

The popup menu items have been added! Without behavior!

In addition it was the Plug-in Spy that made it possible for me to create the proper locationURI I needed to insert the new menus in the correct spot.

Don’t underestimate the power of Plug-in Spy.

The cat is not only alive, but curious, which leads one to worry about the cat’s future.

(I hope this answered your question, Augusto.)

Code

No code…again.

bundle.properties

#Properties file for customnavigator
Bundle-Name = Custom Navigator Plug-in
view.name = Custom Plug-in Navigator
category.name = Custom Projects
navigatorContent.name = Custom Navigator Content
customProject.command.label = Custom Project
schemaFile.command.label = Schema File
deploymentFile.command.label = Deployment File
copy.command.label = Copy
paste.command.label = Paste
delete.command.label = Delete

plugin.xml

<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.4"?>
<plugin>
   <extension
         point="org.eclipse.ui.views">
      <category
            id="customnavigator.category"
            name="%category.name">
      </category>
      <view
            allowMultiple="false"
            category="customnavigator.category"
            class="org.eclipse.ui.navigator.CommonNavigator"
            icon="icons/navigator.png"
            id="customnavigator.navigator"
            name="%view.name">
      </view>
   </extension>
   <extension
         point="org.eclipse.ui.navigator.viewer">
      <viewerActionBinding
            viewerId="customnavigator.navigator">
         <includes>
            <actionExtension
                  pattern="org.eclipse.ui.navigator.resources.*">
            </actionExtension>
         </includes>
      </viewerActionBinding>
      <viewerContentBinding
            viewerId="customnavigator.navigator">
         <includes>
            <contentExtension
                  pattern="customnavigator.navigatorContent">
            </contentExtension>
         </includes>
      </viewerContentBinding>
   </extension>
   <extension
         point="org.eclipse.ui.navigator.navigatorContent">
      <navigatorContent
            activeByDefault="true"
            contentProvider="customnavigator.navigator.ContentProvider"
            id="customnavigator.navigatorContent"
            labelProvider="customnavigator.navigator.LabelProvider"
            name="%navigatorContent.name">
         <triggerPoints>
            <instanceof
                  value="org.eclipse.core.resources.IWorkspaceRoot">
            </instanceof>
         </triggerPoints>
         <commonSorter
               class="customnavigator.sorter.SchemaCategorySorter"
               id="customnavigator.sorter.schemacategorysorter">
            <parentExpression>
               <or>
                  <instanceof
                        value="customnavigator.navigator.CustomProjectSchema">
                  </instanceof>
               </or>
            </parentExpression>
         </commonSorter>
      </navigatorContent>
   </extension>
   <extension
         point="org.eclipse.ui.menus">
      <menuContribution
            locationURI="popup:common.new.menu?after=additions">
         <command
               commandId="org.eclipse.ui.newWizard"
               icon="icons/project-folder.png"
               label="%customProject.command.label"
               style="push">
         </command>
         <command
               commandId="org.eclipse.ui.newWizard"
               icon="icons/schema-file_16x16.png"
               label="%schemaFile.command.label"
               style="push">
         </command>
         <command
               commandId="org.eclipse.ui.newWizard"
               icon="icons/deployment-file_16x16.png"
               label="%deploymentFile.command.label"
               style="push">
         </command>
      </menuContribution>
      <menuContribution
            locationURI="popup:customnavigator.navigator?before=import">
         <command
               commandId="org.eclipse.ui.edit.copy"
               icon="icons/copy_16x16.png"
               label="%copy.command.label"
               style="push">
         </command>
         <command
               commandId="org.eclipse.ui.edit.paste"
               icon="icons/paste_16x16.png"
               label="%paste.command.label"
               style="push">
         </command>
         <command
               commandId="org.eclipse.ui.edit.delete"
               icon="icons/delete_16x16.png"
               label="%delete.command.label"
               style="push">
         </command>
         <separator
               name="customnavigator.separator"
               visible="true">
         </separator>
      </menuContribution>
   </extension>

</plugin>

Writing an Eclipse Plug-in (Part 16): Custom Project: Customizing the Perspective Menus (Toolbar)

January 8, 2010 1 comment

In this post we will add three custom toolbar buttons to the main toolbar of the Custom Perspective of the Eclipse workbench.

Before we do that, I would like to point out that one of the default buttons on the toolbar is the New button. When you click on the downward pointing arrow of the New button you will see something shocking (shocking I say!): the same entries as we added to File –> New appear in the New toolbar button. That’s because they are tied together; change one and you change the other. We could disable it, but why not leave well enough alone? While it is true we didn’t mean to turn on this behavior we can bask in the glory of a job accidentally well done.

Life is full of disappointments.

Today we will add three buttons to the toolbar and have them create either a new Custom Project, Schema file or Deployment file. Seems kinda redundant in light of the behavior of the New toolbar button, but I want single custom command buttons in any case.

We need 3 new images; one for each button. I am reusing the images from a Hidden Clause Custom Project, Schema file and Deployment file:

How (are we doing it?)

Let’s add the buttons to the toolbar together with the configuration needed to open the appropriate New Wizards.

  1. Open plugin.xml
  2. Add –> org.eclipse.ui.menus
  3. org.eclipse.ui.menus –> menuContribution
    • locationURI: toolbar:org.eclipse.ui.main.toolbar
  4. toolbar:org.eclipse.ui.main.toolbar (menuContribution) –> toolbar
    • id: customplugin.toolbar
  5. customplugin.toolbar (toolbar) –> command
    • commandId: org.eclipse.ui.newWizard
    • label: New Custom Project
    • icon: icons/project-folder.png
    • tooltip: New Custom Project
    • style: push
  6. New Custom Project (command) –> parameter
    • name: newWizardId
    • value: customplugin.wizard.new.custom
  7. customplugin.toolbar (toolbar) –> command
    • commandId: org.eclipse.ui.newWizard
    • label: New Schema File
    • icon: icons/schema-file_16x16.png
    • tooltip: New Schema File
    • style: push
  8. New Schema File (command)–> parameter
    • name: newWizardId
    • value: customplugin.wizard.file.schema
  9. customplugin.toolbar (toolbar) –> command
    • commandId: org.eclipse.ui.newWizard
    • label: New Deployment File
    • icon: icons/deployment-file_16x16.png
    • tooltip: New Deployment File
    • style: push
  10. New Deployment File (command)–> parameter
    • name: newWizardId
    • value: customplugin.wizard.file.deployment
  11. Save plugin.xml
  12. Start the runtime workbench. Pressing any of the new buttons should open the appropriate New Wizard
  13. Hold the mouse over the buttons; the tooltips should display the string for which we configured each button

Oh oh! A bug! The toolbar buttons appear in all of the perspectives not just in the Custom Perspective. That behavior is just offensive; okay, maybe not offensive, but wrong in this case.

We will fix that with the following configuration.

  1. New Custom Project (command) –> visibleWhen
    • false (visibleWhen) –> with
      • variable: activeWorkbenchWindow.activePerspective
        • activeWorkbenchWindow.activePerspective (with) –> equals
          • value: customplugin.perspective
  2. New Schema File (command)–> visibleWhen
    • false (visibleWhen) –> with
      • variable: activeWorkbenchWindow.activePerspective
        • activeWorkbenchWindow.activePerspective (with) –> equals
          • value: customplugin.perspective
  3. New Deployment File (command)–> visibleWhen
    • false (visibleWhen) –> with
      • variable: activeWorkbenchWindow.activePerspective
        • activeWorkbenchWindow.activePerspective (with) –> equals
          • value: customplugin.perspective

So where did the variable name activeWorkbenchWindow.activePerspective come from? From Eclipse, of course! Oh, okay, take a look at the Command Core Expressions entry in the Eclipse wiki for more interesting variables you can use in your elements. We won’t be using any others…this time.

Why (did we do it that way?)

There is a lot of configuration going on here. There are only two obscure/interesting points.

Step #3:

  • org.eclipse.ui.menus –> menuContribution
    • locationURI: toolbar:org.eclipse.ui.main.toolbar

The locationURI field tells Eclipse where to place the three buttons: in the toolbar (hence the use of a scheme named toolbar) and which toolbar to use (the main toolbar which has an id of org.eclipse.ui.main.toolbar). Makes sense once you know it, but it would be helpful to have more examples. But that’s just me.

Step #1, 2 and 3 take care of hiding the toolbar buttons in all perspectives except the Custom Perspective.

  • false (visibleWhen) –> with
    • variable: activeWorkbenchWindow.activePerspective
      • activeWorkbenchWindow.activePerspective (with) –> equals
        • value: customplugin.perspective

The visibleWhen element works with the usual choices of adapt, and, count, equals, etc. By selecting the with element you have to supply one of the variables listed in the Command Core Expressions listed in the Eclipse wiki. At runtime the variable activeWorkbenchWindow.activePerspective contains the id of the current perspective so including the equals element with the id of the Custom Perspective (customplugin.perspective) means that the only time the selected button will appear is when the Custom Perspective is open.

Don’t forget to open plugin.xml and externalize the new strings otherwise you will be stuck with a bunch of warnings that are not worth tolerating.

What Just Happened?

In today’s episode:

  • we added 3 buttons to the main toolbar
  • configured the toolbar buttons to open the appropriate wizards
  • configured the toolbar buttons to only appear in the custom perspective

Once again, we have managed to add a significant amount of behavior and not had to write any code. It doesn’t get any better than that (well, maybe it does, but I’m not sure if I’m ready to brag about that kind of thing).

Thanks

David Carver and his blog entry Adding Wizards To Toolbars.

Code

Oh, yeah. None.

However, there are the entries in the plugin.xml file.

<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.2"?>
<plugin>
   <extension
         point="org.eclipse.ui.newWizards">
      <category
            id="customplugin.category.wizards"
            name="%category.name">
      </category>
      <wizard
            category="customplugin.category.wizards"
            class="customplugin.wizards.CustomProjectNewWizard"
            finalPerspective="customplugin.perspective"
            icon="icons/project-folder.png"
            id="customplugin.wizard.new.custom"
            name="%wizard.name">
      </wizard>
      <wizard
            category="customplugin.category.wizards"
            class="customplugin.wizards.CustomProjectNewSchemaFile"
            descriptionImage="icons/schema-file_32x32.png"
            icon="icons/schema-file_16x16.png"
            id="customplugin.wizard.file.schema"
            name="%wizard.name.schema">
      </wizard>
      <wizard
            category="customplugin.category.wizards"
            class="customplugin.wizards.CustomProjectNewDeploymentFile"
            descriptionImage="icons/deployment-file_32x32.png"
            icon="icons/deployment-file_16x16.png"
            id="customplugin.wizard.file.deployment"
            name="%wizard.name.deployment">
      </wizard>
   </extension>
   <extension
         point="org.eclipse.ui.perspectives">
      <perspective
            class="customplugin.perspectives.Perspective"
            icon="icons/perspective.png"
            id="customplugin.perspective"
            name="%perspective.name">
      </perspective>
   </extension>
   <extension
         point="org.eclipse.ui.perspectiveExtensions">
      <perspectiveExtension
            targetID="customplugin.perspective">
         <view
               id="customnavigator.navigator"
               minimized="false"
               ratio=".25"
               relationship="left"
               relative="org.eclipse.ui.editorss">
         </view>
      </perspectiveExtension>
   </extension>
   <extension
         id="customplugin.projectNature"
         point="org.eclipse.core.resources.natures">
      <runtime>
         <run
               class="customplugin.natures.ProjectNature">
         </run>
      </runtime>
   </extension>
   <extension
         point="org.eclipse.ui.ide.projectNatureImages">
      <image
            icon="icons/project-folder.png"
            id="customplugin.natureImage"
            natureId="customplugin.projectNature">
      </image>
   </extension>
   <extension
         id="customplugin.contenttype"
         point="org.eclipse.core.contenttype.contentTypes">
      <content-type
            base-type="org.eclipse.core.runtime.xml"
            file-extensions="xml"
            id="customplugin.contenttype.schema"
            name="%content-type.name.schema"
            priority="normal">
         <describer
               class="org.eclipse.core.runtime.content.XMLRootElementContentDescriber2">
            <parameter
                  name="element"
                  value="hc-schema">
            </parameter>
         </describer>
      </content-type>
      <content-type
            base-type="org.eclipse.core.runtime.xml"
            file-extensions="xml"
            id="customplugin.contenttype.deployment"
            name="%content-type.name.deployment"
            priority="normal">
         <describer
               class="org.eclipse.core.runtime.content.XMLRootElementContentDescriber2">
            <parameter
                  name="element"
                  value="hc-deployment">
            </parameter>
         </describer>
      </content-type>
   </extension>
   <extension
         point="org.eclipse.ui.perspectiveExtensions">
      <perspectiveExtension
            targetID="customplugin.perspective">
         <newWizardShortcut
               id="customplugin.wizard.new.custom">
         </newWizardShortcut>
         <newWizardShortcut
               id="customplugin.wizard.file.schema">
         </newWizardShortcut>
         <newWizardShortcut
               id="customplugin.wizard.file.deployment">
         </newWizardShortcut>
      </perspectiveExtension>
   </extension>
   <extension
         point="org.eclipse.ui.menus">
      <menuContribution
            locationURI="toolbar:org.eclipse.ui.main.toolbar">
         <toolbar
               id="customplugin.toolbar">
            <command
                  commandId="org.eclipse.ui.newWizard"
                  icon="icons/project-folder.png"
                  label="%customproject.label"
                  style="push"
                  tooltip="%customproject.tooltip">
               <parameter
                     name="newWizardId"
                     value="customplugin.wizard.new.custom">
               </parameter>
               <visibleWhen
                     checkEnabled="false">
                  <with
                        variable="activeWorkbenchWindow.activePerspective">
                     <equals
                           value="customplugin.perspective">
                     </equals>
                  </with>
               </visibleWhen>
            </command>
            <command
                  commandId="org.eclipse.ui.newWizard"
                  icon="icons/schema-file_16x16.png"
                  label="%schema.label"
                  style="push"
                  tooltip="%schema.tooltip">
               <parameter
                     name="newWizardId"
                     value="customplugin.wizard.file.schema">
               </parameter>
               <visibleWhen
                     checkEnabled="false">
                  <with
                        variable="activeWorkbenchWindow.activePerspective">
                     <equals
                           value="customplugin.perspective">
                     </equals>
                  </with>
               </visibleWhen>
            </command>
            <command
                  commandId="org.eclipse.ui.newWizard"
                  icon="icons/deployment-file_16x16.png"
                  label="%deployment.label"
                  style="push"
                  tooltip="%deployment.tooltip">
               <parameter
                     name="newWizardId"
                     value="customplugin.wizard.file.deployment">
               </parameter>
               <visibleWhen
                     checkEnabled="false">
                  <with
                        variable="activeWorkbenchWindow.activePerspective">
                     <equals
                           value="customplugin.perspective">
                     </equals>
                  </with>
               </visibleWhen>
            </command>
         </toolbar>
      </menuContribution>
   </extension>

</plugin>

OSGI-INF/I10n/bundle.properties

#Properties file for customplugin
Bundle-Name = Customplugin Plug-in
category.name = Custom Wizards
wizard.name = Custom Project
perspective.name = Custom Plug-in Perspective
content-type.name.schema = Hidden Clause Schema Definition
content-type.name.deployment = Hidden Clause Deployment Definition
wizard.name.schema = Schema File
wizard.name.deployment = Deployment File

customproject.label = New Custom Project
customproject.tooltip = New Custom Project
schema.label = New Schema File
schema.tooltip = New Schema File
deployment.label = New Deployment File
deployment.tooltip = New Deployment File