Skip to content

Eclipse Process Framework (EPF) on Linux 64 bit

Eclipse RCPJava

This question has been discussed over and over again: How to run the Eclipse Process Framework on Linux 64 bit? The good news: I found a solution. The bad news: It's not free, it involves CrossOver Linux for €48, which I am more than happy to pay. You can download the 2-week-trial to see whether it works for you.

Here are the step-by-step instructions:

  • Download and install CrossOver
  • Add a new Bottle of Type Windows XP (others may work, too).
  • Right-click and select "Install Software into Bottle"
  • Find and select"Java7" (it works for me, even though it states that it does not)
  • Important: Also install Internet Explorer 8. Without this, it will crash as soon as you try to use Rich Text
  • Download EPF for Windows (I tried 1.5.1.7, which works)
  • Open C: (right-click "Open C: Drive") and extract EPF to a location of your choice (I leave it in C:).
  • Test the installation from the Linux File Browser. Important: Don't double-click, instead right-click and select "Open With... | CrossOver (Run)". Make sure that you load/create and open an element with rich text, e.g. a Role. If the browser is not correctly installed, then EPF will crash.
  • Create a Linux launcher, now that everything works. I usually create a .desktop file in .local/share/applications. The launcher command line is:
  • /opt/cxoffice/bin/wine --bottle #BOTTLE_NAME# --cx-app "C:\epf-composer\epf.exe" -data "Y:\epf-workspace"
  • The -data argument is optional, I use it to set the workspace to the location of my liking. Y: is mapped to the Linux user home, so adjust the path as you see fit.

I hope you find these instructions useful. Please let me know if things can be improved!

Writing Eclipse Wizards

Eclipse RCP

I've been creating a lot of Eclipse Wizards lately, and it's quite a challenge to keep the code clean, understandable and testable.  Eventually, I came up with a pattern that seems to work reasonably well.  I document it here for my and everybody else's benefit.

Here are the key ideas:

  • Eclipse Facade:I am not a big fan of Eclipse Plug-In tests (vs. regular unit tests).   Therefore, everything that's Eclipse-related should be as simple as possible, which we do by building a Facade.
  • Data Binding: We use data binding in the Wizard pages to keep them as simple as possible
  • Per-page Settings: Each page has a corresponding Settings object that manages the page's data.  Specifically, it also validates the values for that page. There are no Eclipse- or GUI-dependencies in the Settings, so they can easily be tested.
  • Central settings store: The per-page settings are managed in a central settings store that contains all data  This allows us to persist the wizard settings (if desired), and also contains the finish() method that completes the execution of the wizard. This store has no GUI or Eclipse-dependencies either.

Here is a class diagram, for one page, but of course there could be multiple:

Here is a brief description of each element:

Wizard

The Wizard instantiates the Facade and the Store (which takes the Facade as an argument), and then creates the pages (which take the store as an argument).  It also calls performFinish() on the Store, which does all the hard work.

public class MyWizard extends Wizard implements IImportWizard {

	private ExchangeStore store;

	@Override
	public void init(IWorkbench workbench, IStructuredSelection selection) {
		IEclipseFacade facade = new EclipseFacade(selection);
		store = new ExchangeStore(facade);

		addPage(new SelectProjectPage(store));
		addPage(new ImporterPage2SelectFile(store));
		addPage(new ImporterPage3SelectAttributes(store));
	}

	@Override
	public boolean performFinish() {
		return store.performFinish();
	}
}

AbstractSettings

The AbstractSettings provide the Bean functionality, as described by Lars Vogel. It manages the registration of PropertyListeners and provides firePropertyChange() to announce changes.

Pages

I use an abstract base class for setting up the way I like to build GUIs.  But important is updateStatus(), which propagates the validation result.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
	protected void updateStatus(IStatus status) {
		setErrorMessage(null);  // Clear previous error messages - also braindead.
		setMessage(status.getMessage(), status.getSeverity());
		if (status.getSeverity() == Status.OK) {  // Braindead: The message is not empty on ok!
			setMessage(null, Status.OK);
			setPageComplete(true);
		} else if (status.getSeverity() == Status.INFO) {
			setPageComplete(true);
		} else {
			setPageComplete(false);
		}
	}

The pages themselves build the GUI and bind it to the Settings.  Key here is the DataBindingContext:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
		DataBindingContext ctx = new DataBindingContext();

		final Combo combo = new Combo(form.getBody(), SWT.READ_ONLY);
		combo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

		ctx.bindList(WidgetProperties.items().observe(combo), BeanProperties
				.list("projectNames").observe(settings));

		ctx.bindValue(WidgetProperties.text().observe(combo), BeanProperties
				.value("projectName").observe(settings));

Note that we bind a List and a Value: The first is populating the combo with values, the second reacts to selections.

PageSettings

The PageSettings need getters for all the bound Widgets, and optionally setters, if they can change. Setters must fire, if they change a value.  Many values are just propagated from the Store, which may have an impact on the PageSettings.  If they have an impact, the PageSettings must register a listener on the Store to react to changes.  For example, Page 1 may allow the user to select a project, which would be set on the Store, which in turn would fire an event.  Page 2 may show the list of files.  Thus, Page 2 would register a listener on the Store that reacts to changes in the Project. Upon a change the list of files would be updated, which triggers firing of the corresponding property.  As an example, here the Settings for selecting a Project:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class SelectProjectSettings extends ExchangeSettings {

	private ExchangeStore store;

	public SelectProjectSettings(ExchangeStore store) {
		if (store == null) throw new NullPointerException();
		this.store = store;
	}
	
	public List<String> getProjectNames() {
		return store.getProjectNames();
	}

	public String getProjectName() {
		return store.getProjectName();
	}

	public void setProjectName(String projectName) {
		String oldProjectName = store.getProjectName();
		store.setProjectName(projectName);
		firePropertyChange("projectName", oldProjectName, getProjectName());
	}

	public IStatus validatePage() {
		if (getProjectNames().contains(getProjectName())) {
			return ValidationStatus.ok();
		} else {
			return ValidationStatus.warning("Please select a Project");
		}
	}
}

 ExchangeStore

 The ExchangeStore is obviously very project specific.  But I want to demonstrate how it delegate Eclipse-specific information.  For instance, it contains the names of all Projects in the Workspace, which are used to populate the combo box in the wizard.  As this is Eclipse-specific, it is delegated to the Facade:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class ExchangeStore extends ExchangeSettings {

	private final IEclipseFacade facade;

	public ExchangeStore(IEclipseFacade facade) {
		this.facade = facade;
	}
	
	public List<String> getProjectNames() {
		return facade.getProjectNames();
	}

        ... much more stuff ...
}

Facade

And last, the facade abstracts Eclipse-specific, project-specific functionality. To pick up the example again, this is how we get the project names in the actual implementation:

1
2
3
4
5
6
7
8
9
	@Override
	public List<String> getProjectNames() {
		List<String> list = new ArrayList<String>();
		IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
		for (IProject project : projects) {
			list.add(project.getName());
		}
		return list;
	}


When to use WizardPageSupport: probably never (#Eclipse #JFace)

Eclipse RCPJava

Admitted, what Lars Vogel shows in his tutorial looks tempting: Provide neat error messages in Eclipse Wizards using data binding.  But as so often (unfortunately), the code is half-baked, and therefore barely usable.  Data Binding works fine, but Validation, as described, sucks.

In this particular case, WizardPageSupport works well if you have Widgets that can be validated individually: For instance, making sure that an Email field contains emails.  But it breaks down, as soon as the validation takes multiple Widgets into account. Consider an Address field, for instance, where City and Zip Code (and many other fields) have co-dependencies.  Setting the the Zip code should clear the error message on the City, but I have not seen a straight forward way of doing this.

So I dropped WizardPageSupport altogether, and instead added a page-specific validation method to my model.  Then I added a listener to call the validation when anything changes:

public class Page1 extends WizardPage {
    public Page1() {
        ...
        model.addPropertyChangeListener(new PropertyChangeListener() {
            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                IStatus status = model.validatePage1();
                setMessage(status.getMessage(), status.getSeverity());
            }
        });
    }
    ...
}

And ironically, my code got shorter and cleaner, compared to using WizardPageSupport.  Frustrating, as the idea behind this class is neat.

tweetbackcheck