SourceProviders

In einem vorigen Blogeintrag habe ich mir mal die PropertyTester genauer angeschaut. In diesem Blogeintrag soll ein weiterführendes Konzept betrachtet werden. In den ContributionExamples von Eclipse habe ich das Konzept der SourceProviders entdeckt.

Über Source-Providers funktioniert der Eclipse Expression Mechanismus. Für alle in dem Interface „ISources“ definierten Konstanten für Expressions gibt es genau einen SourceProvider. Jetzt ist natürlich interessant, inwiefern dieses Konzept für eigene RCP-Anwendungen verwendet werden kann.

Schauen wir uns das Konzept mal genauer an:

Um einen SourceProvider zu implementieren, sind folgende Schritte notwendig.

  • Implementierung von ISourceProvider (besser AbstractSourceProvider)
  • Deklaration des SourceProviders für den Extension-Point „org.eclipse.ui.services“ und hierbei Definition einer Variablen.

Über einen SourceProvider wird eine Instanz eines Objektes als Variable global zur Verfügung gestellt. Weiterhin gibt es die Möglichkeit, Änderungen über entsprechende SourceListener zu propagieren. Hierbei können beispielsweise CommandHandler aktiviert/deaktiviert werden, wenn der Zustand eines als Source deklarierten Objektes sich ändert.

Sehen wir uns das doch mal an einem Beispiel an (das Beispiel ist anglehnt an das Beispiel aus dem Plugin „org.eclipse.ui.examples.contributions“ aus dem Eclipse CVS Repository.

Wir möchten gerne, das bestimmte Handler aktiviert / deaktiviert werden, wenn eine Person-Objekt selektiert wird, und in diesem Person-Objekt eine bestimmte Rolle vergeben ist. FÜr Person nehmen wir folgende Implementierung an:

package de.md.domain;

import java.util.ArrayList;
import java.util.List;

public class Person {

private String name;
private String email;
private int salary;
private boolean ugly;
private List<Person> children;
private Person parent;

public Person(){
children = new ArrayList<Person>();
}

public void setName(String name){
this.name = name;
}

public String getName(){
return name;
}

public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}

public int getSalary() {
return salary;
}

public void setSalary(int salary) {
this.salary = salary;
}

public boolean isUgly(){
return ugly;
}

public void setUgly(boolean ugly){
this.ugly = ugly;
}

public List<Person> getChildren(){
return children;
}

public void addChild(Person person){
this.children.add(person);
}

public Person getParent(){
return parent;
}

public void setParent(Person person){
this.parent = person;
}

@Override
public String toString() {
return name;
}

}

Das Attribut, welches uns hierbei besonders interessiert ist das Attribut „isUgly“. Wir definieren 2 Handler, „addPersonHandler“ und „removePersonHandler“. Der RemovePersonHandler darf nur aktiviert werden, wenn die aktuell selektierte Person ugly ist. Das Beispiel ist ziemlich bescheuert, aber zur Veranschaulichung tut´s.

Die so definierten Handler könnten so aussehen:

package de.md.domain;

import java.util.Map;

import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.ui.ISourceProviderListener;
import org.eclipse.ui.ISources;
import org.eclipse.ui.commands.IElementUpdater;
import org.eclipse.ui.handlers.HandlerUtil;
import org.eclipse.ui.menus.UIElement;
import org.eclipse.ui.services.IEvaluationService;

public class AddPersonHandler extends AbstractHandler implements
IElementUpdater, ISourceProviderListener {

@Override
public boolean isHandled() {
return true;
}

@Override
public boolean isEnabled() {
return true;
}

@Override
public void updateElement(UIElement element, Map parameters) {

}

@Override
public Object execute(ExecutionEvent event) throws ExecutionException {
Person p = (Person) ((IStructuredSelection) HandlerUtil
.getCurrentSelection(event)).getFirstElement();
if (p == null)
return null;
p.setUgly(!p.isUgly());
p.addChild(p);
return null;
}

@Override
public void sourceChanged(int sourcePriority, Map sourceValuesByName) {
// TODO Auto-generated method stub

}

@Override
public void sourceChanged(int sourcePriority, String sourceName,
Object sourceValue) {

}
}

Der RemovePersonHandler kann entsprechend implementiert werden, auf die Logik kommt es hierbei gar nicht so sehr an, die entsprechende Deklaration in der plugin.xml sieht so aus:

<extension
point=“org.eclipse.ui.handlers“>
<handler
class=“de.md.domain.AddPersonHandler“
commandId=“de.md.addPerson“>
</handler>
<handler
class=“de.md.domain.RemovePersonHandler“
commandId=“de.md.removePerson“>
</handler>
</extension>

Natürlich brauchen wir für die Verwendung von Handlern noch entsprechende Commands:

<extension
point=“org.eclipse.ui.commands“>
<command
id=“de.md.addPerson“
name=“Person hinzufügen“>
</command>
<command
id=“de.md.removePerson“
name=“Person entfernen“>
</command>
</extension>

Die Extensions packen wir in die lokale Toolbar.

<extension
point=“org.eclipse.ui.menus“>
<menuContribution
locationURI=“toolbar:org.eclipse.ui.main.toolbar“>
<toolbar
id=“mypersontoolbar“>
<command
commandId=“de.md.addPerson“
icon=“icons/alt_window_16.gif“
label=“addPerson“
style=“push“>
</command>
<command
commandId=“de.md.removePerson“
icon=“icons/alt_window_16.gif“
label=“Person entfernen“
style=“push“>
</command>
</toolbar>
</menuContribution>
</extension>

Zur Veranschaulichung definieren wir noch einen View (das ist im Prinzip der View, wenn ihr ein neues Projekt anlegt („Plug-In with a View“), angepasst zur Anzeige von Personen.

package actioncontribution;

import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.menus.IMenuService;
import org.eclipse.ui.part.ViewPart;

import de.md.domain.Person;

public class View extends ViewPart {
public static final String ID = „de.md.myview“;

private TreeViewer viewer;
private Person rootPerson;

public View(){
rootPerson = new Person();
rootPerson.setName(„Hans Klarin“);
rootPerson.setUgly(true);

Person secondLevel1 = new Person();
secondLevel1.setName(„Herbert Pröll“);
secondLevel1.setUgly(false);
secondLevel1.setParent(rootPerson);
rootPerson.addChild(secondLevel1);

Person secondLevel2 = new Person();
secondLevel2.setName(„Gerhard Hail“);
secondLevel2.setUgly(false);
secondLevel2.setParent(rootPerson);
rootPerson.addChild(secondLevel2);

}

/**
* The content provider class is responsible for providing objects to the
* view. It can wrap existing objects in adapters or simply return objects
* as-is. These objects may be sensitive to the current input of the view,
* or ignore it and always show the same content (like Task List, for
* example).
*/
class ViewContentProvider implements ITreeContentProvider {

@Override
public Object[] getChildren(Object parentElement) {
if (parentElement instanceof Person) {
return ((Person) parentElement).getChildren().toArray();
}
return new Object[0];
}

@Override
public Object getParent(Object element) {
if (element instanceof Person) {
return ((Person) element).getParent();
}
return null;
}

@Override
public boolean hasChildren(Object element) {
return ((Person) element).getChildren().size() > 0;
}

@Override
public Object[] getElements(Object inputElement) {
return rootPerson.getChildren().toArray();
}

@Override
public void dispose() {
// TODO Auto-generated method stub

}

@Override
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
// TODO Auto-generated method stub

}

}

class ViewLabelProvider extends LabelProvider implements
ITableLabelProvider {
public String getColumnText(Object obj, int index) {
return getText(obj);
}

public Image getColumnImage(Object obj, int index) {
return getImage(obj);
}

public Image getImage(Object obj) {
return PlatformUI.getWorkbench().getSharedImages().getImage(
ISharedImages.IMG_OBJ_ELEMENT);
}
}

/**
* This is a callback that will allow us to create the viewer and initialize
* it.
*/
public void createPartControl(Composite parent) {
viewer = new TreeViewer(parent);
viewer.setLabelProvider(new ViewLabelProvider());
viewer.setContentProvider(new ViewContentProvider());
viewer.setInput(rootPerson);
getSite().setSelectionProvider(viewer);

}

/**
* Passing the focus request to the viewer’s control.
*/
public void setFocus() {
viewer.getControl().setFocus();
}
}

Und deklaration des Views in der Plugin.xml

<extension
point=“org.eclipse.ui.views“>
<view
name=“View“
class=“actioncontribution.View“
id=“de.md.myview“>
</view>
</extension>

So, ziemlich viel Vorarbeit, bevor wir zum eigentlichen Punkt kommen.

Jetzt stellen wir die aktuell selektierte Person als Variable über einen Source-Provider zur Verfügung.

package de.md.domain;

import java.util.HashMap;
import java.util.Map;

import org.eclipse.ui.AbstractSourceProvider;

public class PersonSourceProvider extends AbstractSourceProvider{

public static final String ID = „de.md.contributions.services.personsourceprovider“;
public static final String PERSON_ID = „currentPerson“;
private Person currentPerson;

public PersonSourceProvider() {

}

@Override
public void dispose() {
currentPerson = null;
}

@Override
public Map getCurrentState() {
Map personMap = new HashMap();
personMap.put(PERSON_ID, currentPerson);
return personMap;
}

@Override
public String[] getProvidedSourceNames() {
return new String[]{ID};
}
}

Über die Methode „getCurrentState“ wird vom Framework abgefragt, welchen aktuellen Status die „Source“ hat, in diesem Fall geben wir in einer Map einfach die aktuell selektierte Person zurück.

Um auf die Property „ugly“ zu testen, geben wir einen PropertyTester an.

public class PersonPropertyTester extends PropertyTester{

public static final String IS_UGLY = „ugly“;

@Override
public boolean test(Object receiver, String property, Object[] args,
Object expectedValue) {
if(IS_UGLY.equals(property)){
Person p = (Person)receiver;
return p.getUgly();
}
return false;
}
}

Und dessen deklaration:

<extension
point=“org.eclipse.core.expressions.propertyTesters“>
<propertyTester
class=“de.md.domain.PersonPropertyTester“
id=“de.md.contributions.services.personsourceprovider“
namespace=“theNameSpace“
properties=“ugly“
type=“de.md.domain.Person“>
</propertyTester>
</extension>

Wir weisen dem Handler eine Expression zu, die die Aktivierung abhängig von unserem SourceProvider macht.

<handler
class=“de.md.domain.RemovePersonHandler“ commandId=“de.md.removePerson“>
<activeWhen>
<with
variable=“currentPerson“>

<test
forcePluginActivation=“true“
property=“theNameSpace.ugly“
>
</test>
</with>
</activeWhen>

</handler>

Zur Rekapitulation, bisher definiert haben wir eine SourceProvider, der eine Instanz einer Person global zur Verfügung stellt, und zwar unter der ID „theNameSpace.ugly“.

Einen PropertyTester, der genau das testet.

Jetzt muss man aufpassen, das man nicht durcheinanderkommt. Die vom SourceProvider bereitgestellte Variable wird in folgendem CodeFragment bereitgestellt (aus der Methode getCurrentState)

personMap.put(PERSON_ID, m);

Und genau das PERSON_ID (in unserem Fall „currentPerson“) ist der Variablenname, über den später referenziert werden kann.

Dies bedeutet für die Aktivierungsexpression des Handlers:

<with
variable=“currentPerson“>

<test
forcePluginActivation=“true“
property=“theNameSpace.ugly“
>
</test>
</with>

Man referenziert die Variable „currentPerson“, und in dieser über das <test/>-Element und das Attribut „property“ (mit Wert PropertyTester <namespace>.<attributname>) das entsprechende Attribut, und so wird die Expression ausgewertet.

Um jetzt eine Auswertung dieser Expression anzustossen, kann folgendes gemacht werden:

Es muss eine Methode (bzw. eine Möglichkeit geboten werden), wie sich die Variable im SourceProvider ändern kann. Beispielsweise könnte der SourceProvider auf aktuelle Selektionen hören..etc…

Ändert sich der Wert im SourceProvider, kann über

fireSourceChanged(ISources.ACTIVE_CURRENT_SELECTION, PERSON_ID, p);

eine Änderung für die entsprechende Expression propagiert werden., wobei klar „PERSON_ID“ der Name der geänderten Variable ist und p genau der neue Wert derselben.

Den entsprechenden SourceProvider bekommt man übrigens (in einem Handler), allgemein einfach über den Workbench-ServiceLocator und das Interface „ISourceProviderService“:

ISourceProviderService s = (ISourceProviderService)HandlerUtil.getActiveWorkbenchWindow(event).getService(ISourceProviderService.class);
PersonSourceProvider ps = (PersonSourceProvider)s.getSourceProvider(PersonSourceProvider.ID);

Oh, was ich vergessen habe, um einen SourceProvider über den EvaluationService verwenden zu können muss dieser dem EvaluationService hinzugefügt werden, wenn man hierfür nicht den Extension-Point verwenden möchte

final IEvaluationService evaluationService = (IEvaluationService) getSite()
.getWorkbenchWindow().getWorkbench().getService(
IEvaluationService.class);
evaluationService.addSourceProvider(sp);

als auch
final IHandlerService handlerService = (IHandlerService) getSite()
.getWorkbenchWindow().getWorkbench().getService(
IHandlerService.class);
handlerService.addSourceProvider(new new MySourceProvider());

Und zwar kann das mit allen Services gemacht werden, die das Interface „org.eclipse.ui.services.IServiceWithSources“ implementieren.

Advertisements

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s