How Moonlight|3D creates the menu bar
This applies to the new layout management developed since the release of Moonlight 0.1.2
The following is a short description on how Moonlight|3D populates its menu bar. The menu bar contents is provided entirely by plugins, so the core program cannot make many assumptions about the menu layout. Therefore, the core program contains a mechanism to populate the menu bar from contributions defined by plugins.
Internal representation, the Action, ActionGenerator and MenuNode classes
Menu entries consist of two parts, the menu entry itself and the code for the action to be performed. The menu entry is defined by an instance of the MenuNode class and the associated code is encapsulated in a subclass of the Action class, which derives a lot of its functionality from Qt's QAction class and it is a good idea to look up this class in the Qt documentation now.
Note: at the time of this writing the functionality of the MenuNode and the Action class overlap quite a bit, particularly concerning the name shown in the menu bar. This is subject to cleanups in the future.
The MenuNode class is the basic building block of the tree structure that forms the menu bar. There can be several types of types of MenuNodes (these correspond to the values of the MenuNode.Type enum):
- Menu
- Entry
- Separator
- Generator
- ExtensionPoint
- Contribution
The Menu, Entry and Separator node types are pretty much self-explanatory: a MenuNode of type Menu defines another level of the pulldown menu structure. It's name attribute will be displayed as the name of the menu bar entry that opens this new menu level (e.g. File or Help), a MenuNode of type Entry defines an Action that is to appear at this position in the menu (referenced by the MenuNode's action name), and a Separator MenuNode will turn into a visual separation at this position in the menu bar (to separate logical groups of operations).
The Generator type is somewhat special because it does not itself define some entry in the menu bar. Instead, a MenuNode of this type refers to a ActionGenerator which can generate menu bar entries on the fly, so that portions of the menu bar can actually become dynamic, depending on some internal state unknown to the user interface.
Note: to change the contents of the menu bar that is provided by a generator, a menu bar rebuild must still be triggered explicitely at the moment. But no change of the MenuNode tree is required
The last two types that remain are ExtensionPoint and Contribution. These two types are very important to the assembling of the menu bar. ExtensionPoint MenuNodes merely are markers which are not converted into visible menu bar items. Instead they mark positions in the menus where additional menu bar items may be added for a certain context. These are defined as children of Contribution MenuNodes which reference the extension point they belong to by name. Contributions to a specific extension point are sorted by their name in order to provide a stable ordering of menu bar entries independently of the order in which contributions are added (e.g. the load order of plugins may change unexpectedly due to several external factors, not all of them immediately obvious).
Menu bar contributions are not added to the MenuNode tree like the other nodes. Instead they should be passed to the corresponding FrameLayout instance's addContribution() method which will then search for the correct extension point in its menu bar definition and add the contribution at the correct position among the extension point's children.
Note: Newly created frame layouts define an extension point named "menubar" to provide a starting point for adding contributions.
Plugin initialisation
Plugins should add their contributions to the respective frame layouts during initialisation. You have to be careful to name the extension point correctly and get plugin dependencies right. If the requested extension point is not found the contribution is silently ignored and the menu entries will not appear on screen.
This means that you have to check for extension point/contribution relationships in your plugins and check if these add additional dependencies for your plugins.
Window creation
Windows in Moonlight|3D are always instances of ApplicationWindow. These are constructed from a given frame definition, of which they keep a private copy. The menu bar is created by walking through the MenuNode tree provided by that particular FrameLayout recursively. At this time action generators are quried for their lists of actions.
The menu bar of a window that already has been initialised can be updated manually by calling the ApplicationWindow's rebuildMenuBar() method.
Example code
Let's examine the code for creating the Render menu in Moonlight|3D 0.1.4 as an example of building a menu contribution. The following code snippet is taken from load() method of the the plugin class ml.plugins.rendering.ui.Plugin and is executed when the plugin is loaded:
UIManager manager=State.getInstance().getUIManager();
manager.registerAction(new RenderSettingsAction(null));
manager.registerActionGenerator(new RenderActionGenerator());
MenuNode contribution=new MenuNode(MenuNode.Type.Contribution,"Render","menubar");
MenuNode renderMenu=new MenuNode(MenuNode.Type.Menu,"Render","");
MenuNode renderUsingMenu=new MenuNode(MenuNode.Type.Menu,"Render using","");
renderUsingMenu.add(new MenuNode(MenuNode.Type.Generator,"RenderActionGenerator",""));
renderMenu.add(new MenuNode(MenuNode.Type.Entry,"Render Settings","RenderSettingsAction"));
renderMenu.add(new MenuNode(MenuNode.Type.Separator,"",""));
renderMenu.add(renderUsingMenu);
renderMenu.add(new MenuNode(MenuNode.Type.ExtensionPoint,"renderMenuExtensionPoint",""));
contribution.add(renderMenu);
manager.getLayout("mainwindow").addMenuContribution(contribution);
TODO: describe what this code does
