Is it possible to have per project menus?

Howdy,

I'd like to dynamically create a project menu based on the project contents.  Something like:

project one:
menu: File Edit ... Help MyMenu
                         one
                                                       two

project two:
menu: File Edit ... Help MyMenu
                         three

With menus being controlled by ActionManager, which doesn't use a Project component, so they appear to be global.  So using ProjectListener.projectOpened to add my project specific menu results in the last project opened MyMenu being on all project windows.

Any hints or suggestions?

TIA,
Roy

6 comments
Comment actions Permalink

And the answer is YES.
Basically replace the project menu on gaining focus.
Here's the pattern:

public class MyPluginRegistration implements ApplicationComponent {
    // If you register the MyPlugin class in the <application-components> section of
    // the plugin.xml file, this method is called on IDEA start-up.
    public void initComponent
() {
        ProjectManagerEx projectManager = ProjectManagerEx.getInstanceEx();
        
projectManager.addProjectManagerListener(new MyProjectListener());
        //...
    }
    //...
}

class MyProjectListener implements ProjectManagerListener {
    public void projectOpened(final Project project) {
        // populate menu the first time
        
MyMenu.getInstance().update(project);

        // change the menu to contain the opened project's menu items on focus gain
        
IdeFrame project_frame = WindowManagerEx.getInstanceEx().findFrameFor(project);
        
Component frame_component = project_frame.getComponent();
        
SwingUtilities.windowForComponent(frame_component).addWindowFocusListener(new WindowFocusListener() {
            @Override
            public void windowGainedFocus
(WindowEvent e) {
                MyMenu.getInstance().update(project);
            
}

            @Override
            public void windowLostFocus
(WindowEvent e) {
                // do nothing
            
}
        });
    }
    //...
}


The MyMenu singleton's update(Project) method handles replacing the menu with the project specific actions.

HTH

0
Comment actions Permalink

No, this is wrong, please don't do that!

The correct solution is to register your actions normally, and to hide them by calling presentation.setVisible(false) from your implementation of AnAction.update() when you're not in the right project.

0
Comment actions Permalink

Using AnAction.update almost works.  What happens is AnAction.update is called after the menu is displayed or sometimes on mouse over.  So which menu items are visible gets garbled between projects.

If I use the windowsGainedFocus to send an update event to all the actions attached to the menu then behavior is correct.

    public void windowGainedFocus(WindowEvent e) {
        MyMenu.getInstance().update();
    }

class MyMenu {
    public void update() {
        ActionManager am = ActionManager.getInstance();
        DefaultActionGroup mainMenu = (DefaultActionGroup) am.getAction("MainMenu");
        DefaultActionGroup myMenu = findChild(mainMenu, "MyMenu");
        updateMenu(myMenu);
    }

    private void updateMenu(DefaultActionGroup menu) {
        if (menu != null) {
            for(AnAction action : menu.getChildActionsOrStubs()) {
                DataContext dataContext = DataManager.getInstance().getDataContext();
                action.update(AnActionEvent.createFromAnAction(action, null, ActionPlaces.MAIN_MENU, dataContext));
                if (action instanceof DefaultActionGroup) {
                    updateMenu((DefaultActionGroup) action);
                }
            }
        }
    }
//...
}


A secondary issue is presentation.setVisible(false) just grays out the menu item (to me, grayed out should mean disabled while setVisible(false) should make the menu item invisible.

Thank you

0
Comment actions Permalink

Why are you doing this at all? IntelliJ IDEA will call the update() method of an action by itself, at the correct time. You don't need any code to call the update() method manually.

0
Comment actions Permalink

Because AnAction.update is not being invoked enough.  Try this, put a Log.info in your AnAction.update handlers.  Run the plugin from within the idea.  Open a project.  So far no updates.  Open menu with instrumented actions.  No updates.  Note that the menu is drawn and there have been zero calls to update.  Move mouse down menu and watch update be invoked on mouse over.

Second test.  Open another project.  No updates.  Open menu.  No updates. Mouse over gets updates.  Change focus to first project.  No updates.  Open menu, no updates, and the state is partially from other project.

So to have dynamic per project menus, the update event needs to be generated for the actions prior to the user opening the menu.  Ideally this would be on a project activate event.  Doing it on focus gained for the project's window is the closest I have found to project activation.

Thank you.

0
Comment actions Permalink

If this is true, then it's a bug that needs to be fixed on our side. Opening a menu is supposed to call update() for all the actions in the menu and to hide the ones for which setVisible(false) was called.

0

Please sign in to leave a comment.