Historical Document: Menu and toolbar rendering has graduated to Eclipse4 and continues to evolve there
- 1 Menu and Toolbar Rendering
- 2 Improvements in e4 processing
- 3 Compatibility Usecases
- 4 Rendering
Menu and Toolbar Rendering
We need to work through the many scenarios we have for menus and toolbars, and make sure that we cover off all of them (or decide against them) to support both our e4 menus story and the compatibility menus/actions story.
e4 RCP Menus
In e4 Menus, they can be defined in 3 places in the model, and have contributions added to them. MMenu supports 4 types, MHandledMenuItem, MDirectMenuItem, MMenuSeparator, and sub MMenus.
- An MMenu can be defined on an MWindow. This will be rendered as an SWT Menu bar.
- An MMenu with tag "ViewMenu" can be added to an MPart. This is rendered as an SWT Menu and needs to be shown when the view dropdown menu is provided. We also tag it with ContributionsAnalyzer.MC_MENU when in a CompatibilityView
- an MPopupMenu can be added to an MPart. This is rendered as an SWT Menu. The EMenuService take a Control and the MPopupMenu and associates the SWT Menu with the control after it's rendered.
We then support MMenuContributions. They were processed and added to the model on SWT.Show events, and then removed in an asyncExec(*) after the SWT.Hide.
e4 RCP Toolbars
In e4 Toolbars, they can be added to the MTrimBars of an MTrimmedWindow. They can also be added to an MPart, although in the editor case they're still ignored.
MToolBars support MHandledToolItems, MDirectToolItems, MToolBarSeparators, and MToolControls.
MToolBars support additions through MToolBarContributions. They're applied in the ToolBarRenderer.processContents(*) after the regular children have been processed. The visibility of the contributions is controled by a RunAndTrack on its visibleWhen.
The view toolbars are also managed by some interaction by the StackRenderer which generates an MRenderedToolBar.
For the compatibility layer, we support MRenderedMenu where we stick a MenuManager into the model as a transient object. This is usually done in the CompatibilityView as well, so the view IActionBars MenuManager (which was provided to the view when it was created) has a model element. The MenuServiceFilter would add MMenuContributions to it on an SWT.Show.
For compatibility layer popup menus, we create an MRenderedMenu to hold the MenuManager that was passed in through org.eclipse.ui.internal.PartSite.registerContextMenu(MenuManager, ISelectionProvider) with one child, an MPopupMenu that can be processed by the MenuServiceFilter.
We don't support the EditorMenuManager correctly, which in 3.x is a way for an editor to programmaticly add to the main menu bar.
In the compatibility layer, the IActionBars ToolBarManager for view toolbars is added to an MRenderedToolBar as a transient object in the CompatibilityView code. The RenderedToolBarRenderer won't process any children, but will add MToolBarContributions and maintain their visibility through a RunAndTrack. The RenderedToolBarRenderer also contains code to add a ToolItem directly to the SWT ToolBar (outside of both the model and the ToolBarManager).
We don't support the editor IActionBars ToolBarManager, which talks to the CoolBarManager in 3.x and updates it post-e4-processing in 4.x
Improvements in e4 processing
Some stability of ordering and optimizations can be realized in the e4 processing of MMenus and MToolBars by refining the way contributions are handled.
- MMenuContributions are added to the MMenu model once, instead of being continually added and removed by the MenuServiceFilter. The added items have them visibility and their enabled state updated on the show of that menu only, avoiding generating RunAndTracks.
- MToolBarContributions are added similar to #1. Changing to one RunAndTrack per MTBC for visibility instead of one RunAndTrack per tool item will reduce the number of RATs in the system, and a timer to re-assess enabled state for all of the ToolItems. Another option is to do both visibility and enabled state from the timer.
We have a number of usecases to support the compatility layer. They fall into 2 categories. Programmatic changes and extension points.
Progamatically we update the main menu, main coolbar, actionSets through the perspective API, view dropdown menu, view toolbar, editor menu (a sub-manager that wraps the main menu), editor toolbar (is provided as a single toolbar in the coolbar), a programmatic EditorActionBarContributor, and context menus registered through the part site.
For extensions, we have much more control about what we do with them and how we intepret them. actionSets go into the main menu and main coolbar, viewActions into the view dropdown menu or view toolbar. editorActions go into the main menu/main coolbar when an editor is visible. popupMenus/viewerContributions can target a specific registered context menu that's been tagged with the ID, and popupMenus/objectContributions are filtered and applied to all registered context menus. org.eclipse.ui.menus allows commands to be placed in any of the above menus/toolbars.
When each WorkbenchWindow is created, the ActionBarAdvisor is run on an empty MenuManager and fills in the "skeleton" that all of the extensions can see. Then org.eclipse.ui.menus/menuContributions are applied, followed by org.eclipse.ui.actionSets. We control the lifecycle of this MenuManager, although it is exposed programmaticly through the SubMenuManager/EditorMenuManager available to editors through the IEditorActionBars.
In the compatibility layer, we then call org.eclipse.ui.internal.WorkbenchWindow.fill(MMenu, IMenuManager) to translate the IContributionItems to model:
- MenuManager -> MMenu
- CommandContributionItem -> MHandledMenuItem
- ActionContributionItem -> mostly MHandledMenuItems, some MDirectMenuItems
- group markers -> MMenuSeparator
- general IContributionItem -> MRenderedMenuItem. This is passed to a RenderedMenuItemRenderer for some magic
The actionSets and editorActions are turned into MMenuContributions with appropriate visibleWhen, and applied currently by the MenuServiceFilter. Placement is decent, but not 100% because in the compatibility layer, menuContributions and the actionSets/editorActions are all processed at the same time, as opposed to separately at different times in the lifecycle.
We don't currently deal well with EditorMenuManager changes that happen after the fact.
Similar to the Main Menu, we create the main CoolBarManager and run the ActionBarAdvisor. Then menus/menuContributions are applied, followed by actionSets. We control the lifecycle of this CoolBarManage, and similar to the Main Menu part of it is exposed to the editors through the IEditorActionBars.
In the compatibility layer we do the same translation to the M*ToolItems. Our MRenderedToolItem supports both an IContributionItem and an IMenuCreator, to pass through tool items with dropdowns.
I don't believe we transform the editor's toolbar.
We create the view dropdown MenuManager while creating the view part. The part then gets to fill it in. Then menuContributions and viewActions are added. The part can set the removeAllWhenShown flag although most views don't (as opposed to the registered context menus, where the most set removeAllWhenShown). Part view menus also have to support PageBookView, where each page has a SubMenuManager wrapping the view menu manager.
In the compatibility layer, we turn a view menu into a MRenderedMenu. The RenderedMenuRenderer will not fill in any contained model elements, but MMenuContributions will still be considered on an SWT.Show event. They're added and then removed in an asyncExec(*) after the SWT.Hide. Our positioning of the model elements is almost always wrong, but the Part's programmatic changes are being retained.
We create the view ToolBarManager while creating the view part. The part fills it in, then menuContributions and viewAction are added. During running, the part can update the ToolBarManager and request an update.
In the compatibility layer, we turn this into an MRenderedToolBar
When dealing with the compatiblity layer, we have a couple of outstanding problem areas:
- Menus or toolbars not showing up
- Menu item/toolbar item ordering issues
- Dealing with 3.x IContributionItems (like the list of new windows under the Window menu)
- Controlling visibility of items
The current system renders an MMenu tree, and can render MHandledMenuItems and MDirectMenuItems.
- Missing toolbars/menus breaks down into 4 problems:
- We can't find an insertion point because it is contained in the legacy manager.
- they're dynamic/programmatic based on contribution managers, and don't currently have a translation into the model. ex: EditorMenuManagers
- We put an MRenderedMenuManager into the model, which is a bridge to a MenuManager and doesn't deal with children. This impacts context menus most of all.
- We're not processing visibleWhen correctly, our renderers don't react to all of the SWT events.
- Ordering issues are caused mostly by mixing legacy managers (like View menus or popup menus) with the contribution story. Also caused by the fact that extension points were processed separately and applied using an "insert onto the queue" like algorithm, whereas in 4.1 our processing is all together and a simple increasing index insertion
- We try to render them by moving them from their originating parent to a bogus parent. We don't implement the whole protocol, however, so we get partial success.
- Our *Item renderers don't currently handle the case where the menu or item should be under the control of the renderer, but cannot return a widget. i.e. SWT MenuItems don't have a setVisible(false), you need to dispose them to make the disappear.
When we render a toolbar, we create a Composite and another ToolBar to create the separator handle. Then we run extra postProcessing that can dispose the toolbar and the composite.
If our ToolBar is an MRenderedToolBar (which hosts a ToolBarManager) we add a ToolItem directly to the toolbar (to support the view dropdown menu). This has no model or IContributionItem representation and can be disposed by the ToolBarManager rendering protocol.
MenuManager based rendering
I was looking at a ContributionManager/MenuManager based MenuRenderer system (applies to ToolBarManagers). It involves 2 parts:
The MenuRenderer generates its MenuManager and creates the appropriate SWT Menu (Menu Bar, context menu, etc). It doesn't process its children through the rendering engine, but generates matching ContributionItems for them. This supports the lazy instantiation as menus are shown since the IContributionItem model (which is now the rendered element) support handling visible/not visible. Visibility for MMenuContributions and enablement for the about-to-show items is handled from the menuAboutToShow listener.
Then, in place of MRenderedMenu, a MenuManager can be associated with the model as opposed to being created from the MenuRenderer. Upon a createGui(*) the provided MenuManager will be used to create the widget. This is really useful for view menus and context menus. The MenuManager's existing IContributionItems that are contributed programmatically either for view menu or popups can be represented in the model by MOpaqueMenuItems (for example).
Now when dealing with the compatibility layer, our renderer lines up with what it expects. We don't have to bridge IContributionItems that we can't translate into model elements. We can see the compatibility layer IContributionItems in the model so our insertion of model elements is more likely to go to the correct place.
As context menu MenuManagers are created outside of the framework control (they're passed into us through registerContextMenu(*)) we would be able to work with them, as opposed to trying to convert them tto 100% model. We can build in an understanding of removeAllWhenShown, used by most context menus.
We would also be able to render sub managers used by PageBookViews, and editors like EditorMenuManager, by depending on the MenuManager protocol as temporary solution before figuring out how to represent them in the model.
Other Possible Solutions
To support mixed mode with the compatibility layer and the different scenarios (Menu bar created by us, view menus handed to the users + sub menu managers, context menu manager created by client code) I think we need to support lazy rendering of menus/submenus. That means that toBeRendered placed the model element under control of the renderer and visible makes it appear-disappear (so a widget of null doesn't stop processing on that element). There also needs to be mechanism for the renderer to post events that the framework can respond to, like an SWT.Show so that visibility and enablement can be re-calculated, or in the case of popup menus that remove all when shown, it can be re-created. The SWT.Show would also allow the renderers to emulate the ContributionManager protocol for IContributionItems we weren't able to convert.
Creating a Widget or Item subclass to use as a Proxy. It can be returned from the renderer, and replaced by the event-caused rendering later. That would allow the rendering engine to continue processing.
Move responsibility for the timing of rendering MMenuItems to the MenuRenderer. That way a SWT.Show could cause it to call the appropriate MenuItemRenderer, and the parent renderer pre-calculates the index so it could be easily placed (this helps with the menu ordering issues).
Or instead of indices, provide a before or after sibling widget, so it could simply be instantiated and have newItem.moveAbove(sibling) called(not supported on Items).
In the compatibility layer, perhaps we provide a ModelMenuManager that we can wrap the MMenu model, and allow SubMenuManager to insert/remove IContributionItems which we convert behind the scenes to the appropriate model (to be rendered as above). View dropdown menus could simply use this ModelMenuManager. This would help EditorMenuManager and PageBookView as well, as they generate stuff through SubMenuManagers.
Still pursue converting context MenuManager (which we don't own and so can't replace) items into some model representation, or allow the MenuRenderer index calculation to find IDs in the context MenuManager, track them to widgets, and the provide correct widget indices.