facebook
Dimitry Karpenko
Java/Eclipse developer in MyEclipse and Webclipse teams.
Posted on May 30th 2016


In previous articles, I’ve discussed how to create custom XML bindings and custom editing UI for an editor based on the great Eclipse Sapphire framework. Now let’s take a look at another aspect of editor UI creation, customizing standard actions like Add or Browse, or creating completely custom actions for your Sapphire-based editor.

Prerequisites

As in previous articles, it is good to know basic Eclipse plug-in development and Sapphire framework principles.

In this example, Sapphire 9 is used. To download it, go to https://eclipse.org/sapphire/releases/9/.

For Sapphire documentation, refer to https://eclipse.org/sapphire/releases/9/documentation/index.html.

Also, this article doesn’t completely describe action or browsing customization API supported by Sapphire, for more information refer to the following:
https://eclipse.org/sapphire/releases/9.0.5/documentation/actions/index.html
https://eclipse.org/sapphire/releases/9.0.5/documentation/focus/browsing/index.html

Sample Project

Let’s create an editor for plugin_info.xml—syntethic sample file, which contains the project id from the workspace and the list of plugins on which this project depends. You need to create an Eclipse plugin project making UI contributions and put standard Sapphire editor infrastructure inside it. Please refer to the sample below or Sapphire help for details.

Model

There are only 2 model classes in our sample project—IPluginsInfo is a root model class containing ProjectName string value property for the project name and Plugins list property containing the plugins list. Each element of that list is an instance of IPluginDescr model class, which only contains a single property, Name.

Editing

So, let’s start and create an editor for the project name property. For user convenience, let’s add the ability to select a project from the workspace. To do this, we’ll need to specify a custom Browse action handler in the sdef file.

<property-editor>
        <property>ProjectName</property>
        <action-handler>
   	        <action>Sapphire.Browse</action>
   	        <impl>SelectProjectActionHandler</impl>
        </action-handler>
</property-editor>


Handler implementation is extending SapphireActionHandler and has only one method, run, which is pretty straightforward.

protected Object run(Presentation context) {
   	 try {
   		 SwtPresentation presentation = (SwtPresentation) context;
   		 IJavaModel model = JavaCore.create(ResourcesPlugin.getWorkspace().getRoot()); //Create JDT model for the workspace
   		 Set<IJavaProject> projects = Arrays.stream(model.getJavaProjects()).collect(Collectors.toSet()); //Get list of available projects
   		 ProjectSelectionDialog dialog = new ProjectSelectionDialog(presentation.shell(), projects); //Create project selection dialog
   		 dialog.setTitle("Select project");
   		 dialog.setTitle("Select project from the workspace");
   		 if (dialog.open() == Window.OK) {
   			 Property property = context.part().getModelElement().property("ProjectName"); //Get Property to store selected value
   			 String result = ((IJavaElement) dialog.getFirstResult()).getElementName(); //Get selected project name
   			 ((Value<String>) property).write(result,true); //Write value to property
   		 }
   	 } catch (JavaModelException e) {
   		 CustomActionsActivator.log(e);
   	 }
   	 return null;
}


Now let’s say we want to create an action which isn’t overriding any standard action, but is completely custom, launching some code on a button click. Custom actions can be added to several different areas of the UI, including the editor page itself, section, etc. Let’s add one more action button to the right of our editing field, which would allow us to launch the import wizard and import a project into the workspace.

Now we’ll need to add two elements into our <property-editor> tag.

 
<action>
     <id>ImportProject</id>
     <label>Import Project</label>
     <image>icons/import_wiz.gif</image>
     <key-binding-behavior>local</key-binding-behavior>
</action>
<action-handler>
     <action>ImportProject</action>
     <impl>ImportProjectActionHandler</impl>
</action-handler>   	 
	

In <action> tag, action’s id, textual label and image are specified. In <action-handler> tag, we specify a particular action handler for this action. Handler class is like the previous one, containing overridden run method and one helper method responsible for showing a wizard using its id.

 

protected Object run(Presentation context) {
   	 SwtPresentation presentation = (SwtPresentation) context;
   	 String name = openWizard("org.eclipse.ui.wizards.import.ExternalProject", presentation.shell());
   	 if (name != null) { //If user successfully imported some project - set it's name to ProjectName field
   		 Property property = context.part().getModelElement().property("ProjectName"); //Get property to put value to
   		 ((Value<String>) property).write(name,true); //Write property value
   	 }
   	 return null;
}
    
private String openWizard(String id, Shell parentShell) {

   	 IWizardDescriptor descriptor = PlatformUI.getWorkbench().getImportWizardRegistry()
   				 .findWizard(id);
   	 try {
   		 if (descriptor != null) { // Then if we have a wizard, open it.
   			 IWizard wizard = descriptor.createWizard();
   			 WizardDialog wd = new WizardDialog(parentShell, wizard); //Create wizard dialog for wizard   			
   			 if (wd.open() != Window.CANCEL) {
   				 WizardProjectsImportPage page = (WizardProjectsImportPage) wizard.getPage("wizardExternalProjectsPage"); //Get wizard page ref
   				 ProjectRecord[] projectRecords = page.getProjectRecords(); //Get selected projects
   				 if (projectRecords.length > 0) { //If there are some projects se;ected, return a name of the first one
   					 return projectRecords[0].getProjectName();
   				 }
   			 }
   		 }
   	 } catch (CoreException e) {
   		 e.printStackTrace();
   	 }
   	 return null;
}


Let’s see how our editor looks after all our customizations:

SapphireProjectName

Handler Parametrization

We can use the same action handler in several places across the editor with little customizations, like different title, initial selections, etc. In these cases, handler can easily be parametrized. Let’s customize dialog title for our project selection dialog—add a param to our sdef file.

<action-handler>
    <action>Sapphire.Browse</action>
    <impl>SelectProjectActionHandler</impl>
    <param>
        <name>dialog-title</name>
        <value>Choose project</value>
    </param>
</action-handler>

To load given param, add init method to your handler.

@Override
public void init(SapphireAction action, ActionHandlerDef def) {
    super.init(action, def);
    dialogTitle = def.getParam("dialog-title");
}

dialogTitle here is a field, which is set to dialog when creating it. See attached source for more details.

Another customization option is obtaining value from some annotation used with model property. For this, we’ll need to have a simple annotation containing only a string value—DialogTitle, and add it to the model’s ProjectName property:

// *** Project name ***
@XmlBinding(path = "@projectName")
@Label(standard = "Project name")
@Required
@DialogTitle("Choose project")
ValueProperty PROP_PROJECT_NAME = new ValueProperty(TYPE, "ProjectName"); //$NON-NLS-1$

Also, let’s slightly modify init method to obtain given annotation from the model.

PropertyEditorPart propDef = getPart().nearest(PropertyEditorPart.class); //Get property editor part
DialogTitle annotation = propDef.property().definition().getAnnotation(DialogTitle.class); //Get property definition for it and obtain necessary annotation
if (annotation != null) {
      dialogTitle = annotation.value();
}

As you can see, using nearest(), property(), definition(), etc. you can walk through Sapphire data and UI models to obtain different settings specified in model or sdef.

Let’s see, how our dialog would look after one of these customizations:

SapphireProjectList

List Editing

But what if we want to customize a bit more complex stuff, like adding a new item to the list of items? This is also possible, however it would require a bit more effort. For the list property editor, we’ll need to specify Action Handler Factory, which would be responsible for creating a custom action handler for the action we want to override. So, our plugin list property editor in sdef file should look like following:

<property-editor>
    <property>Plugins</property>
    <child-property>Name</child-property>
    <show-label>false</show-label>
    <action-handler-factory>
        <context>Sapphire.ListPropertyEditor</context>
        <action>Sapphire.Add</action>
        <impl>com.genuitec.sapphire.customactions.actions.CustomAddActionHandlerFactory</impl>
    </action-handler-factory>
</property-editor>


CustomAddActionHandlerFactory is very simple, and just returns a list containing custom handlers—only one handler in our case:

public List<SapphireActionHandler> create() {
   	 return Collections.singletonList(new SelectPluginActionHandler());
}

Handler itself is opening a plugin selection dialog with the ability to select a single plugin, and, if selected, add a new item to the list property containing the selected plugin’s id:

@Override
protected Object run(Presentation context) {
    SwtPresentation presentation = (SwtPresentation) context;
    PluginSelectionDialog dialog = new PluginSelectionDialog(presentation.shell(), false,false); //Create PDE's plugin selection dialog
    if (dialog.open() == Window.OK) { //If user selected smth...
        Property property = context.part().getModelElement().property("Plugins");
   	ElementList<IPluginDescr> listProp = (ElementList<IPluginDescr>) property; //Get list property, to which we want to add
   	IPluginDescr descr = listProp.insert(); //Create new element for it
   	Property nameProp = descr.property("Name"); //Get Name prop for this element...
   	((Value<String>) nameProp).write(dialog.getFirstResult().toString(),true); //...and specify it's value
    }
    return null;
}
    
@Override
public void init(SapphireAction action, ActionHandlerDef def) {
    setId("AddSelectPlugin"); //Set action id
    super.init(action, def);
    setLabel("plugin"); //Set label, for hint "Add plugin" to be displayed
}

Let’s launch our editor and see, whether it works:

SapphirePluginList

Hmm… Why after clicking “+” do we see two items instead of our custom dialog? That’s because we haven’t actually overridden standard add action handler, and a shortcut for it is also being added to “+” action. To get rid of it, we’ll need to add Action Handler Filter to our list property editor.

To do this, let’s add the following under </action-handler-factory> tag in sdef:

<action-handler-filter>
        <impl>com.genuitec.sapphire.customactions.actions.CustomAddActionHandlerFilter</impl>
</action-handler-filter>

Filter class extends SapphireActionHandlerFilter and contains one small method checking handler’s id to cut off unnecessary handlers:

@Override
public boolean check(SapphireActionHandler handler) {
   	 return !handler.getId().startsWith("Sapphire.Add.");
}

Now, let’s try it…works like a charm!

Can we add a fully custom action to the list property editor? Sure, the way of doing it is quite the same as for the value property editor. Let’s make an action, adding several plugins at once to our list. Add the following inside the <property-editor> tag for Plugins property:

<action>
    <id>AddMultiple</id>
    <label>Add plugins</label>
    <tooltip>Add several plugins</tooltip>
    <image>icons/multi_plugins.gif</image>
</action>
<action-handler>
    <action>AddMultiple</action>
    <id>AddMultiple.Handler</id>
    <impl>com.genuitec.sapphire.customactions.actions.SelectMultiplePluginsActionHandler</impl>
</action-handler>

The handler class for it is pretty much like the handler for adding a single plugin name, except configuring the dialog for selecting multiple plugins, and adding selected plugin id’s to a list inside a loop.

Let’s try it:

SapphirPluginSelect

Works perfectly. So, finally our editor would look like the following:

SapphireFinalEditor

Attachment

sapphire_customactions_tutorial—Sample project

Related Articles

Coding Tip: Using Custom Bindings for Property Editing in Sapphire
Creating Custom Editors in Sapphire
Creating a Custom Eclipse Sapphire Editor to Edit Two XML Files Simultaneously

Let Us Hear from You!

If you have any comments or questions, we would love to hear from you @MyEclipseIDE on twitter or via the MyEclipse forum. Happy coding!

If you’re not already subscribing to our blogs, why not do it today?