In this article I'm going to guide you through the process of customization of oXygen Author Component. More specifically I'm going to create a new operation for the DITA framework that will generate and insert an xml fragment into the current document. While you can find the official documentation on this topic very useful, it misses any real code examples. Of course, you can download the author component startup project but it'll help those looking how to integrate the component as a Java Applet, thus, it's slightly irrelevant for us.
Requirements
In order to make a customization for the Author Component you need to have an oXygen standalone installation. For the development of Java customizations you'll need oxygen.jar on the classpath. This library is available in the oXygen installation directory as well as in the Author SDK project and in the author component startup project. Moreover, inside last two resources you can find javadocs and partial sources for oXygen classes. Unfortunately, these resources are not published in any Maven repository. So I've put them to the private corporate one to be able to create a pure Maven project. This is not a must though.
Customization
The customization process includes the following steps:
- First we should implement our own Java extension of the ro.sync.ecss.extensions.api.AuthorOperation interface which will show the custom dialog and then will use oXygen API to insert the proper code fragment at the caret position. The oXygen application is swing-based, so the custom dialog should extend javax.swing.JDialog. However, in our case it'd make more sense to reuse oXygen code and extend ro.sync.ecss.extensions.commons.ui.OKCancelDialog instead.
- Then pack the compiled classes into a JAR, say custom.jar, and put it inside the corresponding oXygen framework directory (or anywhere else of your choice).
- Then run the standalone oXygen installation, navigate to the Preferences->Document Type Associations page, edit the DITA document type and in the Classpath tab add a reference to the custom.jar. Then in the Author tab open the Actions subtab, create a new action based on your operation and then add it to the toolbar and menus in the corresponding subtabs. This configuration change will be saved in the OXYGEN_INSTALL_DIR\frameworks\dita\dita.framework file.
- As soon as a new operation works fine in the oXygen standalone installation, you can bundle the OXYGEN_INSTALL_DIR\frameworks\dita folder with the Author Component by wrapping it to obtain the frameworks.zip.jar archive. This last step is required if you want to distribute your change with a Java Applet.
@API(type = APIType.INTERNAL, src = SourceType.PUBLIC) public class InsertMyImageOperation extends InsertFragmentOperation { private static final String ARGUMENT_FRAGMENT = "fragment"; public InsertMyImageOperation () { super(); } /** * Loads the xml fragment and inserts it at the caret position. */ @Override public void doOperation(AuthorAccess authorAccess, ArgumentsMap args) throws AuthorOperationException { String xmlFragment = loadImageXmlFragment(authorAccess, args); super.doOperation(authorAccess, argsWithXmlFragment(args, xmlFragment)); } /** * @see ro.sync.ecss.extensions.api.Extension#getDescription() */ @Override public String getDescription() { return "Insert a custom image"; } /** * Creates the custom dialog that returns the dialog info object. * The dialog info is used for xml fragment generation. */ private String loadImageXmlFragment(AuthorAccess authorAccess, ArgumentsMap args) { InsertMyImageDialog myImageDialog = new InsertMyImageDialog( (Frame) authorAccess.getWorkspaceAccess().getParentFrame(), authorAccess.getAuthorResourceBundle(), true); myImageDialog.setLocationRelativeTo( (Component) authorAccess.getWorkspaceAccess().getParentFrame()); MyDialogInfo info = myImageDialog.showDialog(); return MyImageFragmentGenerator.generateXml(info); } /** * Supplies the parent class arguments with the custom xml fragment. */ private ArgumentsMap argsWithXmlFragment(final ArgumentsMap args, final String xmlFragment) { return new ArgumentsMap() { @Override public Object getArgumentValue(String argumentName) { if (argumentName.equals(ARGUMENT_FRAGMENT)) { return xmlFragment; } else { return args.getArgumentValue(argumentName); } } }; } }
So the operation simply shows a custom dialog to the user, waits until it returns the required information, generates an xml fragment based on the user-supplied data and uses the oXygen API to insert the fragment properly.
I've reused a complex logic of ro.sync.ecss.extensions.commons.operations.InsertFragmentOperation that not only inserts the fragment for me but also provides some useful arguments that can be adjusted in the oXygen settings.
Here is the code snippet for the custom dialog:
I've reused a complex logic of ro.sync.ecss.extensions.commons.operations.InsertFragmentOperation that not only inserts the fragment for me but also provides some useful arguments that can be adjusted in the oXygen settings.
Here is the code snippet for the custom dialog:
@API(type = APIType.INTERNAL, src = SourceType.PUBLIC) public class InsertMyImageDialog extends OKCancelDialog { public InsertMyImageDialog(Frame parentFrame, AuthorResourceBundle authorResourceBundle, boolean modal) { super(parentFrame, "My title", modal); JPanel mainPanel = new JPanel(new GridBagLayout()); // Here custom components are being created and added to the mainPanel. add(mainPanel); configureSize(); } /** * Shows the dialog and returns the information * that is required to generate the xml fragment. */ public MyDialogInfo showDialog() { setVisible(true); return (getResult() == RESULT_OK) ? createMyDialogInfo() : null; } /** * Creates the information object with the dialog data. */ private MyDialogInfo createMyDialogInfo() { MyDialogInfo info = new MyDialogInfo(); // Here info object is being filled with the data from dialog components. return info; } /** * Sets proper dialog size. */ private void configureSize() { setResizable(true); setMinimumSize(new Dimension(0, 0)); pack(); setMinimumSize(getSize()); } }
I've cleaned up the code to show the structure better. I've used two more custom classes above: MyDialogInfo and MyImageFragmentGenerator. Their meaning and logic must be clear from the code so I've left them out.
Hi Ivan,
ReplyDeleteGreat blog post, your details are concise and exact.
One more thing, for Oxygen 16 we are working to offer the Author Component project as a Maven archetype.
Regards,
Radu
Radu Coravu
XML Editor, Schema Editor and XSLT Editor/Debugger
http://www.oxygenxml.com
Thanks, Radu! Actually you helped a lot with your comments in emails while I was working on this. Maven support would be very useful indeed.
DeleteWe also had a similar but not that detailed topic in the Oxygen users manual:
ReplyDeletehttp://www.oxygenxml.com/doc/ug-oxygen/#tasks/addCustomActionHowTo.html