Bug 485931 - Context sub-menu items appear twice when using ExtensionContributionFactory
Summary: Context sub-menu items appear twice when using ExtensionContributionFactory
Status: NEW
Alias: None
Product: Platform
Classification: Eclipse Project
Component: UI (show other bugs)
Version: 4.5.1   Edit
Hardware: PC Windows 10
: P3 normal with 5 votes (vote)
Target Milestone: ---   Edit
Assignee: Platform-UI-Inbox CLA
QA Contact:
URL:
Whiteboard:
Keywords: bugday, helpwanted
Depends on:
Blocks:
 
Reported: 2016-01-15 08:57 EST by Phil Beauvoir CLA
Modified: 2020-05-01 20:18 EDT (History)
6 users (show)

See Also:


Attachments
Test project demonstrating the bug (9.94 KB, application/winzip)
2016-01-15 09:00 EST, Phil Beauvoir CLA
no flags Details
Screenshot showing the duplicate (5.89 KB, image/png)
2016-01-15 09:02 EST, Phil Beauvoir CLA
no flags Details
Anim gif: created twice at first show, then only once. (339.07 KB, image/gif)
2016-04-21 07:48 EDT, Patrik Suzzi CLA
no flags Details
Double menus in Archi (11.11 KB, image/png)
2016-06-09 14:12 EDT, Phil Beauvoir CLA
no flags Details
Test to show duplicated menu, 2 views for easier debugging (28.46 KB, application/x-zip-compressed)
2019-03-28 07:27 EDT, Thomas Berger CLA
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Phil Beauvoir CLA 2016-01-15 08:57:06 EST
Register a MenuManager, context menu and sub-menu in a View. Then declare an extension using an ExtensionContributionFactory. The sub-menu item appears twice when activated.
Comment 1 Phil Beauvoir CLA 2016-01-15 09:00:24 EST
Created attachment 259201 [details]
Test project demonstrating the bug

Attached is a test project showing the problem. It's based on the example RCP project.

I added some code in View.java:

    /**
     * Hook into a right-click menu
     */
    private void hookContextMenu() {
        MenuManager menuMgr = new MenuManager("#ViewPopupMenu");
        menuMgr.setRemoveAllWhenShown(true);
        
        menuMgr.addMenuListener(new IMenuListener() {
            public void menuAboutToShow(IMenuManager manager) {
                fillContextMenu(manager);
            }
        });
        
        Menu menu = menuMgr.createContextMenu(viewer.getControl());
        viewer.getControl().setMenu(menu);
        
        getSite().registerContextMenu(menuMgr, viewer);
    }
    
    
    private void fillContextMenu(IMenuManager manager) {
        IStructuredSelection selection = ((IStructuredSelection)viewer.getSelection());
        Object selected = selection.getFirstElement();
        boolean isEmpty = selected == null;
        
        MenuManager newMenu = new MenuManager("New", "new");
        manager.add(newMenu);
        getSite().registerContextMenu("new_menu", newMenu, viewer);
        newMenu.add(new Separator("new_additions")); //$NON-NLS-1$
    }

And added a class TestExtensionContributionFactory, and declared it in plugin.xml:

   <extension
         point="org.eclipse.ui.menus">
      <menuContribution
            allPopups="true"
            class="test.TestExtensionContributionFactory"
            locationURI="popup:new_menu?after=new_additions">
      </menuContribution>
   </extension>
Comment 2 Phil Beauvoir CLA 2016-01-15 09:02:12 EST
Created attachment 259202 [details]
Screenshot showing the duplicate

Screenshot
Comment 3 Phil Beauvoir CLA 2016-02-22 05:21:28 EST
Note - this bug exists in 4.5 but not in 4.4.
Comment 4 Lukas Dvorok CLA 2016-02-22 06:19:56 EST
Same here... anybody working on this? :)
Comment 5 Phil Beauvoir CLA 2016-04-20 12:25:44 EDT
Hi, I'm wondering what it takes for a possible fix for this. I've done the best I can by filing the bug report and providing a test project. I'm afraid that I am unable to fix it myself. If it's not possible to fix then OK, I'll move on. Just would like to know if it's something that can be fixed. :-)
Comment 6 Patrik Suzzi CLA 2016-04-21 07:48:07 EDT
Created attachment 261148 [details]
Anim gif: created twice at first show, then only once.

Verified in Neon (4.6)
Build id: I20160419-0800

ExtensionContributionFactory#createContributionItems(..) is called twice: one when showing the parent menu and one when showing the submenus.

See attached animation and related code below.

YourClass extends ExtensionContributionFactory {

    public TestExtensionContributionFactory() {
    	super();
    }

    @Override
    public void createContributionItems(IServiceLocator serviceLocator, 
    		IContributionRoot additions) {
		System.err.println("createContributionItems");
		System.err.println(additions.toString());
		additions.addContributionItem(new MyItem(), null);//<-- HERE!
    }
 //..
Comment 7 Phil Beauvoir CLA 2016-06-09 14:12:44 EDT
Created attachment 262347 [details]
Double menus in Archi

This is something of a blocker for delivering my RCP app on Neon. See screenshot.

Anyone know of a workaround?
Comment 8 Phil Beauvoir CLA 2016-06-10 04:24:26 EDT
This bug occurs also when not using an ExtensionContributionFactory - when declaring a popup sub-menu via the "org.eclipse.ui.menus" extension point.

How are other plug-ins managing it?
Comment 9 Phil Beauvoir CLA 2016-06-10 05:20:37 EDT
Here are the interesting parts of the stack traces of it being called twice.

First stack track trace when createContributionItems is called first time:

com.archimatetool.canvas.NewCanvasExtensionContributionFactory.createContributionItems(NewCanvasExtensionContributionFactory.java:58)
org.eclipse.ui.internal.menus.ContributionFactoryGenerator.compute(ContributionFactoryGenerator.java:72)
org.eclipse.e4.ui.workbench.renderers.swt.ContributionRecord.mergeFactoryIntoModel(ContributionRecord.java:271)
org.eclipse.e4.ui.workbench.renderers.swt.ContributionRecord.mergeIntoModel(ContributionRecord.java:215)
org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRenderer.processAddition(MenuManagerRenderer.java:544)
org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRenderer.generateContributions(MenuManagerRenderer.java:523)
org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRenderer.processContributions(MenuManagerRenderer.java:491)
org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRenderer.processContributions(MenuManagerRenderer.java:494)
org.eclipse.ui.internal.PopupMenuExtender.addMenuContributions(PopupMenuExtender.java:410)
org.eclipse.ui.internal.PopupMenuExtender.menuAboutToShow(PopupMenuExtender.java:382)
org.eclipse.jface.action.MenuManager.fireAboutToShow(MenuManager.java:339)
org.eclipse.jface.action.MenuManager.handleAboutToShow(MenuManager.java:470)
org.eclipse.jface.action.MenuManager.access$1(MenuManager.java:465)
org.eclipse.jface.action.MenuManager$2.menuShown(MenuManager.java:497)


Second stack trace when createContributionItems is called second time:

com.archimatetool.canvas.NewCanvasExtensionContributionFactory.createContributionItems(NewCanvasExtensionContributionFactory.java:58)
org.eclipse.ui.internal.menus.ContributionFactoryGenerator.compute(ContributionFactoryGenerator.java:72)
org.eclipse.e4.ui.workbench.renderers.swt.ContributionRecord.mergeFactoryIntoModel(ContributionRecord.java:271)
org.eclipse.e4.ui.workbench.renderers.swt.ContributionRecord.mergeIntoModel(ContributionRecord.java:215)
org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRenderer.processAddition(MenuManagerRenderer.java:544)
org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRenderer.generateContributions(MenuManagerRenderer.java:523)
org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRenderer.processContributions(MenuManagerRenderer.java:491)
org.eclipse.ui.internal.PopupMenuExtender.addMenuContributions(PopupMenuExtender.java:410)
org.eclipse.ui.internal.PopupMenuExtender.menuAboutToShow(PopupMenuExtender.java:382)
org.eclipse.jface.action.MenuManager.fireAboutToShow(MenuManager.java:339)
org.eclipse.jface.action.MenuManager.handleAboutToShow(MenuManager.java:470)
org.eclipse.jface.action.MenuManager.access$1(MenuManager.java:465)
org.eclipse.jface.action.MenuManager$2.menuShown(MenuManager.java:497)


So, in the first stack trace we notice that processContributions is called twice:

org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRenderer.processContributions(MenuManagerRenderer.java:491)
org.eclipse.e4.ui.workbench.renderers.swt.MenuManagerRenderer.processContributions(MenuManagerRenderer.java:494)
Comment 10 Phil Beauvoir CLA 2016-06-10 05:23:30 EDT
So, given that processContributions being called twice is causing us problems, I hacked a really horrible workaround:

    public void createContributionItems(IServiceLocator serviceLocator, IContributionRoot additions) {
        
        int methodCount = 0;
        for(StackTraceElement stackTraceElement : Thread.currentThread().getStackTrace()) {
            if(stackTraceElement.getMethodName().equals("processContributions")) {
                methodCount++;
                if(methodCount == 2) {
                    return;
                }
            }
        }
        
        // Add contribution items here...
    }

Yes, it's horrible, but as Eclipse bugs are not being fixed what am I to do?
Comment 11 Phil Beauvoir CLA 2016-06-15 06:40:26 EDT
I think we have to accept that this bug will not be fixed and try to workaround it.
Comment 12 Patrik Suzzi CLA 2016-06-16 04:43:19 EDT
(In reply to Phil Beauvoir from comment #11)
> I think we have to accept that this bug will not be fixed and try to
> workaround it.

Wait, I see you signed CLA..
Why don't you clean a bit the code and submit a patch?

Instructions are here: 
https://wiki.eclipse.org/Platform_UI/How_to_Contribute
Comment 13 Phil Beauvoir CLA 2016-06-16 04:50:51 EDT
(In reply to Patrik Suzzi from comment #12)
> (In reply to Phil Beauvoir from comment #11)
> > I think we have to accept that this bug will not be fixed and try to
> > workaround it.
> 
> Wait, I see you signed CLA..
> Why don't you clean a bit the code and submit a patch?
> 
> Instructions are here: 
> https://wiki.eclipse.org/Platform_UI/How_to_Contribute

:sigh: That old chestnut.

There are many ways to contribute. Spending many hours to create a reproducible test case and submitting a bug report is one way to contribute, as I have done. If were able to understand and fix the underlying code I would have done.

Bu, hey, that's the "Eclipse way", isn't it? "Fix it yourself."
Comment 14 Patrik Suzzi CLA 2016-06-17 05:41:14 EDT
(In reply to Phil Beauvoir from comment #13)

> There are many ways to contribute. Spending many hours to create a
> reproducible test case and submitting a bug report is one way to contribute,
> as I have done. 

Phil, thank you very much for your efforts. 
I really appreciate!

> But, hey, that's the "Eclipse way", isn't it? "Fix it yourself."

Eh he he :D - I think this is to increase the number of developers that can push code. It was harder in last years. Now, with Oomph, the setup is a breeze.

TBH: Nowadays I think that the hardest parts are: 
- identify the issues (-->use Ctrl+Shift+R, Ctrl+Alt+G, Spies, etc..)
- push to Gerrit (-->this requires some configuration)
Comment 15 Phil Beauvoir CLA 2016-06-17 05:52:26 EDT
(In reply to Patrik Suzzi from comment #14)
> (In reply to Phil Beauvoir from comment #13)
> 
> > There are many ways to contribute. Spending many hours to create a
> > reproducible test case and submitting a bug report is one way to contribute,
> > as I have done. 
> 
> Phil, thank you very much for your efforts. 
> I really appreciate!
> 
> > But, hey, that's the "Eclipse way", isn't it? "Fix it yourself."
> 
> Eh he he :D - I think this is to increase the number of developers that can
> push code. It was harder in last years. Now, with Oomph, the setup is a
> breeze.
> 
> TBH: Nowadays I think that the hardest parts are: 
> - identify the issues (-->use Ctrl+Shift+R, Ctrl+Alt+G, Spies, etc..)
> - push to Gerrit (-->this requires some configuration)

Sure. I can only be part of the solution. I can find the bug, provide a test case etc, and report the bug. The code for this bug is beyond my understanding, so I cannot do that part. If I knew what caused the bug I would submit a patch. Perhaps someone else would like to join me in this quest?
Comment 16 Thomas Berger CLA 2019-03-28 07:24:30 EDT
I have created a small test case for Eclipse 4.11 (2019-03), using a menu defined in plugin.xml as "popup:xxx" with a <VisibleWhen> test. This allows me to show the double menu is only duplicated the first time after opening the view (and right-click) on the item. Subsequent click, the menu is not duplicated any more. For my test, use the menu File to load data into the view.
Comment 17 Thomas Berger CLA 2019-03-28 07:27:37 EDT
Created attachment 278055 [details]
Test to show duplicated menu, 2 views for easier debugging

Developed with 2019-03. Use the menu File to load data into both of the views. Right click-to see the duplicated menu. The menu is entirely generated by Eclipse, defined as "popup:" command handler in plugin.xml. Close the views, and re-load data (menu File) for continous testing.
Comment 18 Thomas Berger CLA 2019-03-28 07:30:37 EDT
===1st click
menuModel.widget= {} 
Thread [main] (Suspended (breakpoint at line 548 in MenuManagerRenderer))	
	MenuManagerRenderer.processAddition(MMenu, MenuManager, MMenuContribution, HashSet<String>, HashSet<String>, boolean) line: 548	
	MenuManagerRenderer.generateContributions(MMenu, ArrayList<MMenuContribution>, boolean) line: 528	
	MenuManagerRenderer.processContributions(MMenu, String, boolean, boolean) line: 496	
	MenuManagerRenderer.createWidget(MUIElement, Object) line: 415	
	MenuService.registerMenu(Control, MPopupMenu, IEclipseContext) line: 67	
	MenuManagerRendererFilter.safeHandleEvent(Event) line: 140	
	MenuManagerRendererFilter.access$1(MenuManagerRendererFilter, Event) line: 107	
	MenuManagerRendererFilter$SafeWrapper.run() line: 93	
	SafeRunner.run(ISafeRunnable) line: 45	
	MenuManagerRendererFilter.handleEvent(Event) line: 104	
	EventTable.sendEvent(Event) line: 89	
	Display.filterEvent(Event) line: 1121	
	Display.sendEvent(EventTable, Event) line: 4358	
	Menu(Widget).sendEvent(Event) line: 1512
menuModel.widget={Import...2}		
	
now menu is added a second time 
Thread [main] (Suspended (breakpoint at line 548 in MenuManagerRenderer))	
	MenuManagerRenderer.processAddition(MMenu, MenuManager, MMenuContribution, HashSet<String>, HashSet<String>, boolean) line: 548	
	MenuManagerRenderer.generateContributions(MMenu, ArrayList<MMenuContribution>, boolean) line: 528	
	MenuManagerRenderer.processContributions(MMenu, String, boolean, boolean) line: 496	
	PopupMenuExtender.addMenuContributions(IMenuManager) line: 413	
	PopupMenuExtender.menuAboutToShow(IMenuManager) line: 385	
	MenuManager.fireAboutToShow(IMenuManager) line: 342	
	MenuManager.handleAboutToShow() line: 473	
	MenuManager.access$1(MenuManager) line: 468	
	MenuManager$2.menuShown(MenuEvent) line: 500	
	TypedListener.handleEvent(Event) line: 259	
	EventTable.sendEvent(Event) line: 89	
	Display.sendEvent(EventTable, Event) line: 4363	
	Menu(Widget).sendEvent(Event) line: 1512	
menuModel.widget={Import...2, Import...2}

===2nd click
menuModel.widget= {Import...2, Import...2}
Thread [main] (Suspended (breakpoint at line 548 in MenuManagerRenderer))	
	MenuManagerRenderer.processAddition(MMenu, MenuManager, MMenuContribution, HashSet<String>, HashSet<String>, boolean) line: 548	
	MenuManagerRenderer.generateContributions(MMenu, ArrayList<MMenuContribution>, boolean) line: 528	
	MenuManagerRenderer.processContributions(MMenu, String, boolean, boolean) line: 496	
	PopupMenuExtender.addMenuContributions(IMenuManager) line: 413	
	PopupMenuExtender.menuAboutToShow(IMenuManager) line: 385	
	MenuManager.fireAboutToShow(IMenuManager) line: 342	
	MenuManager.handleAboutToShow() line: 473	
	MenuManager.access$1(MenuManager) line: 468	
	MenuManager$2.menuShown(MenuEvent) line: 500	
	TypedListener.handleEvent(Event) line: 259	
	EventTable.sendEvent(Event) line: 89	
	Display.sendEvent(EventTable, Event) line: 4363	
	Menu(Widget).sendEvent(Event) line: 1512	
	Menu(Widget).sendEvent(int, Event, boolean) line: 1535	
	Menu(Widget).sendEvent(int) line: 1516	
menuModel.widget={Import...2}

IMHO the bug is in ContributionRecord.mergeIntoModel() line: 209
at line 251 the duplicate menu entry is added
} else {
	generatedElements.add(copy);
	renderer.linkElementToContributionRecord(copy, this);
	menuModel.getChildren().add(idx++, copy);
}

The root cause seems to be that on the first click two events are fired:
1: prepare to open (which adds the popup menu once)
2: do open (which adds it a second time)

Eliminating the first event would fix the bug, but that can cause other bugs.

The easiest way seems to be to not add the menu a second time, by adding a check
before calling generatedElements.add(copy);
The check could use the menu ID?
Comment 19 Scott Ryder CLA 2020-05-01 20:18:10 EDT
Not sure if this was ever resolved. I just recently ran into the same thing. I'd right-click on a JList to bring up a context pop-up menu. The action would fire consistently in the following pattern:

1st click = 1 fire
2nd click = 2 fires
3rd click = 4 fires
4th click = 8 fires
5th click = 16 fires

What I realized is that after the pop-up and subsequent actions were complete, the main window would regain focus and fire window actions. I had a listener and code in windowActivated. One of which was to add controls and action listeners after the window was activated for the first time.

The result is that it kept added action listeners to the control. I put a boolean check in the windowActivated to make sure the routine would not fire after the initial window creation.  This solved the problem for me.

Not saying this is the case for you, but I'd run through the program to see if you have something similar...in this case, the root cause was no where near where the problem manifested.