I was asked some very good questions in response my post about the JavaFX makeover for the NetBeans Platform! Questions with long and interesting answers, questions that sparked more questions in my mind. So here is the first post in response to those questions. 🙂
Is it possible to reuse normal NetBeans Platform actions?
For the purpose of this exploration, I decided to create a simple NetBeans Platform application to work with. A newly created one, with all of the latest infrastructure available (including annotations for registration). With one action for use today, and one window for another post to follow. So, we start off with this little application:

The action that I created is registered using annotations:
[java]
@ActionID(
category = “Edit”,
id = “za.co.pellissier.someaction.SomeAction”
)
@ActionRegistration(
iconBase = “za/co/pellissier/someaction/clickme.png”,
displayName = “#CTL_SomeAction”
)
@ActionReferences({
@ActionReference(path = “Menu/File”, position = 1300),
@ActionReference(path = “Toolbars/File”, position = 200)
})
@Messages(“CTL_SomeAction=Action!”)
public final class SomeAction implements ActionListener {
[/java]
At compile time, these annotations are transformed into entries in the generated layer file. And at run time that gets loaded into the System FileSystem. Most days, you would not need to know any of that to work with actions. But today is not most days… 🙂
If you look at the module project where the action was created in Files view (after cleaning and building the module), you will see the generated-layer.xml file:

Looking at the content of the generated layer file, we can see where the action will be registered at runtime:
[xml]
An internet search for the system file system reveals this wiki page which gives us the hint that the FileUtil class is going to be a useful one. And finding usages of that class reveals the following interesting method in org.netbeans.core.windows.view.ui.MainWindow:
[java]
private static JMenuBar getCustomMenuBar() {
try {
String fileName = Constants.CUSTOM_MENU_BAR_PATH;
if (fileName == null) {
return null;
}
FileObject fo = FileUtil.getConfigFile(fileName);
if (fo != null) {
DataObject dobj = DataObject.find(fo);
InstanceCookie ic = (InstanceCookie)dobj.getCookie(InstanceCookie.class);
if (ic != null) {
return (JMenuBar)ic.instanceCreate();
}
}
} catch (Exception e) {
Exceptions.printStackTrace(e);
}
return null;
}
[/java]
This method is a great and simple example of how to call instanceCreate() for an item registered on the file system. So together with the information from last time, we now have the pieces of information to start implementing the solution. This time I created a file called mainwindow.fxml (remember to update JavaFXWindowManager to load this file):
[xml]
[/xml]
The easiest place to call the code that loads the menu items is in the initialize() method of the controller class. Note that some of the menus, like Window, causes a StackOverflowException when being loaded in this context. It happens because it in turn calls on the Window System that is being loaded. So for now, lets exclude those menus. I also created a layer file and removed some of the File menu actions (see below) for the same reason.
[xml]
[/xml]
So here is the controller class that does the basics of loading the actions and adding them to the JavaFX GUI, after a lot of debugging and hunting for ways to access the user friendly names for the actions.
You will notice several TODOs in the code, and there are probably more things to implement that I haven’t thought of yet. But at least it will point you in the right direction.
[java]
public class MainWindowController implements Initializable {
@FXML
private MenuBar menuBar;
@FXML
private TabPane tabPane;
/**
* Initializes the controller class.
*/
@Override
public void initialize(URL url, ResourceBundle rb) {
FileObject systemConfigFile = FileUtil.getSystemConfigFile(“Menu”);
FileObject[] menus = systemConfigFile.getChildren();
for (FileObject menu : menus) {
// skip the menus that cause a StackOverflowException
if (menu.getName().equals(“Window”) || menu.getName().equals(“GoTo”)
|| menu.getName().equals(“Edit”) || menu.getName().equals(“View”)) {
continue;
}
Menu newMenu = new Menu(menu.getName());
menuBar.getMenus().add(0, newMenu);
FileObject[] items = menu.getChildren();
// TODO Load sub-menus with their own menu items
for (FileObject item : items) {
try {
DataObject dobj = DataObject.find(item);
InstanceCookie ic = (InstanceCookie) dobj.getCookie(InstanceCookie.class);
if (ic != null) {
Object instance = ic.instanceCreate();
if (instance instanceof ActionListener) {
ActionedMenuItem menuItem = new ActionedMenuItem((ActionListener)instance);
newMenu.getItems().add(menuItem);
} else if (instance instanceof JSeparator) {
// TODO Add logic to avoid two consecutive separators
newMenu.getItems().add(new SeparatorMenuItem());
}
}
} catch (IOException | ClassNotFoundException ex) {
Exceptions.printStackTrace(ex);
}
}
}
}
private static class ActionedMenuItem extends MenuItem
{
private final ActionListener listener;
private ActionedMenuItem(ActionListener instance) {
String displayName = “”;
if (instance instanceof AbstractAction) {
displayName = ((AbstractAction) instance).getValue(“displayName”).toString();
} else if (instance instanceof SystemAction) {
displayName = ((SystemAction) instance).getName();
}
// TODO Deal with & and mnemonics
setText(displayName);
listener = instance;
setOnAction(new EventHandler
@Override
public void handle(ActionEvent e) {
try {
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
// TODO Create proper action
listener.actionPerformed(null);
}
});
} catch (InterruptedException | InvocationTargetException ex) {
Exceptions.printStackTrace(ex);
}
}
});
}
}
}
[/java]
