No Business Naming Things

Where sassy women wear jaunty hats.

No Business Naming Things header image 4

Entries Tagged as 'Eclipse'

Security? I Prefer Herring.

March 12th, 2007 · 5 Comments

In the past I have suggested rather… unorthodox… uses of the activities framework, transformation services, and custom splash handlers in the context of “security.” These misguided suggestions made their way into Neil Bartlett’s security talk at this years Eclipsecon. As could be expected, they were shot down.

To my own credit, I never thought these were good ideas. I tried to ensure that the standard “stupid idea” caveat was present whenever I suggested them but in my excitement to give people what they want that may not have always been apparent. In the future I’ll try and keep my off-the-wall suggestions to myself. In preparation for that I must purge myself of some ideas that have been percolating in my head:

  • With judicious use of our new menu story you may reverse unwanted male pattern baldness.
  • Rubbing Working Sets onto a fresh burn will prevent scaring and infection.
  • OSGi acts as an excellent lubricant and has very easy cleanup.
  • In the absence of Cayenne pepper sprinkle on a few Status Handlers. You wont be disappointed.
  • If you’re looking for some quick cash you’ll find that Contexts go for $5/gram on the streets of Santa Clara.
  • Views and Editors? Herb #3 and Spice #9.
  • Decorators could be to console gaming what Berber carpet is to horticulture. You heard it here first.


Tags:

Holding the Fort

March 8th, 2007 · 5 Comments

To all of you enjoying EclipseCon right now all I have to say is:

I hate you so very much.

It’s almost -30C with windchill here in Ottawa! The envy I fee is palpable; I can almost taste it. In fact, I think it tastes like frostbite.

:)



Tags:

I Want To Tell You a Story II

January 31st, 2007 · 12 Comments

So, the story. Well, the story is still progressing. It’s wandered off into a tangent involving the installation of GeSHi and an attempt at finding a new home for examples that aren’t destined for the build. I will show example code now but there are fully functioning example projects that will be made available just as soon as they have a home.

Example 1: An Interactive Splash

Perhaps you want to use the splash screen as a log in screen. The code for such a splash handler might look something like this:

Resulting in:

Example 2: A Browser Splash

Perhaps there is some elaborate flash content you want to show in your browser. The code could look something like this. Note that while the code only points to static old eclipse.org I have tested this with flash animation. Having your Eclipse come up to the tune of “I’ve Got a Big Bag of Crabs” is priceless.

Resulting in:

Example 3: An Extensible Splash

Perhaps you want to have a splash screen that plug-ins can contribute to via an extension mechanism. The code for this may look something like the following:

Resulting in:



Tags:

I Want To Tell You a Story I

January 29th, 2007 · 7 Comments

As Paul Webster likes to say, “I want to tell you a story.” Unlike Paul’s, my story has nothing to do with commands, key bindings, or Ketchup. Mine is about the splash screen.

No, wait. Come back! I promise it’s at least kind of interesting!

Over the past few milestones the SWT and runtime teams have been working on a way to get the splash screen re-implemented in SWT. There are all kinds of reasons why this is hard to do but in the end they got it done and in a most creative way at that. The basic gist of it is that the java process starts the Eclipse framework which uses native code to bring up a window. The window handle of this shell is published as a system property and later (once some heavy lifting has been done by the framework) the workbench takes that handle and wraps it in an SWT shell. We then Do Horrible Things To It such as smear it with progress bars, progress messages, and build ids.

Now for the exciting part - you too can do Horrible Things to the splash!

As of I20070130 there is a new extension point in org.eclipse.ui -

splashHandler

. Splash handler is like Intro in that there can only be one intro implementation per product. The extension point is quite simple. There are two possible elements -

splashHandler

and

splashHandlerProductBinding

. The first describes a splash handler while the later binds a given splash handler to a product. For example:

<extension         point="org.eclipse.ui.splashHandler">      <splashHandler            class="com.xyz.splash.Handler"            id="com.xyz.splash">      </splashHandler>      <splashHandlerProductBinding            productId="com.xyz.product"            splashId="com.xyz.splash">      </splashHandlerProductBinding></extension>

Nothing special, nothing complicated. The only interesting part is the class attribute of the

splashHandler

element. The value of this attribute must point to a class that extends

org.eclipse.ui.splash.AbstractSplashHandler

. This class has three interesting methods that you can override:

        /**  * Initialize this splash implementation. This is called very early in the  * workbench lifecycle before any window is created. The provided shell will  * already have a background image provided to it but subclasses are free to  * customize the shell in whatever way they see fit. Subclasses should  * ensure that they call the base implementation of this method at some  * point after their own method is invoked.  *  * Calls to this method will be made from the UI thread  *   * @param splash  *            the splash shell  */ public void init(Shell splash);

 /**  * Signal the handler to end the splash and dispose of any resources.  * Subclasses should ensure that they call the base implementation of this  * method at some point after their own method is invoked.  *  * Calls to this method will be made from the UI thread.  */ public void dispose();

 /**  * Return the progress monitor responsible for showing bundle loading.  * Default implementation returns a null progress monitor.  *   * Calls made to methods on this progress monitor may be made from non-UI  * threads so implementors must take care to ensure proper synchronization  * with the UI thread if necessary.  *   * @return the progress monitor  * @see NullProgressMonitor  */ public IProgressMonitor getBundleProgressMonitor();

The first,

init()

, is where you can add custom controls to your splash screen. For instance, you can add progress bars, animation, icons, or even custom controls. Additionally, you could have a splash that contains user name and password fields. This splash implementation would block until a user enters something in both boxes.

The second method,

dispose()

, can be used to perform custom cleanup. This method will be invoked sometime after the workbench has finished initializing itself. In addition to cleanup, this method could be used to block closure of the splash until some condition has been satisfied. For instance, you could have a splash screen that didn’t return from this method until the user hit the space bar.

The final method,

getBundleProgressMonitor

is where you can hook progress controls created in the init method to a progress monitor. This monitor will be called by the workbench when plug-ins are loaded.

The story continues tomorrow (provided anyone is still awake).



Tags:

Splash Build ID

January 12th, 2007 · 4 Comments

Now that we can wrap our native splash window in an SWT Shell I’ve started to work on some API to allow an easily extensible workbench splash. As a small test of that I’ve added a touch to our splash screen - providing that you have the

org.eclipse.ui.workbench/SHOW_BUILDID_ON_STARTUP

preference set to true the build ID will appear just below the word “Europa” in the splash screen. This is not API yet and it’s hard-coded only to work with our splash screen at the moment but it’s enabled in the SDK product for the time being. Depending on the reception I’ll look at making it an API constant along with the progress location/message location constants in

org.eclipse.ui.branding.IProductConstants


Tags:

Discussion Topic

January 9th, 2007 · 4 Comments

iPhone is to sexy as Zune is to a pig in a dress. Discuss.



Tags:

New Working Set API

December 18th, 2006 · No Comments

Near the end of M4 I added API to working sets that allowed them to express interest in certain types of objects. The intent was that you could ask the working set if a given object was suitable for containment. This would allow us to write UIs where the selection of objects could be added to working sets of a particular type only. Ie: you couldn’t add breakpoints to the resource working set. I didn’t advertise the API at the time because I wasn’t convinced that it was right and after some thought and discussion with downstream clients decided it wasn’t.

Rather than answering whether or not a given item was applicable to a working set it would be much more useful for the working set to return an object that WAS applicable. For instance, attempting to add a breakpoint to a resource working set would instead add the resource that the breakpoint corresponded to. The following API was added to address this issue.

First, a new attribute was added to the working set plugin.xml schema. This optional attribute, “elementAdapterClass”, takes a class that implements IWorkingSetElementAdapter as a value. This interface has the following definition:

/**
* Interface that describes a mechanism that may be provided by working set
* implementors to help manage the addition of elements to working sets.
* Instances of this class are capable of transforming possible working set
* content into the most applicable form.
*
* @since 3.3
*/
public interface IWorkingSetElementAdapter {

/**
* Converts the given elements for addition to /removal from the working
* set.
*
* @return the (possibly adapted) elements to add to /remove from the
* working set
*/
IAdaptable[] adaptElements(IWorkingSet ws, IAdaptable[] elements);

/**
* Disposes of this element adaptor.
*/
void dispose();
}

It is the responsibility of this interface to inspect IAdaptables and return IAdaptables derived from the originals that are suitable for inclusion in working sets of a given type. The adaptables may be the same objects, new objects, or no objects at all if the original adaptables are not suitable.

This interface is accessed indirectly via a new method on IWorkingSet:

/**
* Transforms the supplied elements into elements that are suitable for
* containment in this working set. This is useful for UI elements which
* wish to filter contributions to working sets based on applicability. This
* is a hint, however, and is not considered when the
* {@link #setElements(IAdaptable[])} method is invoked.
*
* @param objects
* the objects to transform
* @return an array of transformed elements that be empty if no elements
* from the original array are suitable
* @since 3.3
*/
public IAdaptable[] adaptElements(IAdaptable[] objects);

Note the comment in this method - this is only a hint for IWorkingSet clients, not a definitive filter. IWorkingSet.setElements() will continue to take any array of IAdaptable objects you throw at it.

This new interface behaves similarly to the existing IWorkingSetUpdater interface in that its specification will not cause eager plug-in loading. If your plug-in defines an IWorkingSetElementAdapter class it will not be used until your plug-in is loaded via some other means. Until that time the IWorkingSet.adaptElements() method will return the passed array of IAdaptables when invoked. To help get around this limitation a default implementation of IWorkingSetElementAdapter has been provided in the UI plugin. This class, BasicWorkingSetElementAdapter, is an IExecutableExtension and may be customized by client plug-ins in their plugin.xml file. The format for this customization is as follows:

<workingSet elementAdapterClass=”org.eclipse.ui.BasicWorkingSetElementAdapter:some.package.ClassName1[;adapt=true|false],some.package.ClassName2[;adapt=true|false],…”
… />

From the documentation for BasicWorkingSetElementAdapter.adaptElements():

“When invoked this method will iterate over all classes specified as IExecutableExtension arguements to this class in order and compare with the elements. If the element is directly assignable to the provided class then it is added to the result array as is. If the class has specified “adapt=true” as an argument and there is an available adapter in the platform IAdapterManager then it is returned. Finally, if “adapt=true” and the class is already loaded (determined by inspecting exported bundles via the platform PackageAdmin) a direct query for the adapter is made on the object and if it is not

null

then it is returned.

A consequence of the above is that it is possible for this method to return differing results based on the state of bundles loaded within the system.”

For example, here is the declaration for the IDE Resource Working Set elementAdapterClass:

org.eclipse.ui.BasicWorkingSetElementAdapter:org.eclipse.core.resources.IResource;adapt=true

This means that the Resource Working Set will only accept elements that are IResources and these may be obtained either by direct assignment or by checking for an adapter.

If there are any comments on this API please log defects and I’ll respond to them ASAP. The API deadline is fast approaching so if you do have issues it’s better to express them now rather than later. Thanks!

(crossposted to platform-ui-dev@eclipse.org)



Tags:

Toolbar Toggle buttons on OS X

October 30th, 2006 · 1 Comment

Another question from the inbox:

I have an SWT app on OS X that wants to have the “toggle toolbar” button in the upper right corner of the window trim. I see that Eclipse has this button but I don’t see API in SWT to make it happen. How can I have this button in my app?”

Unfortunately, there is no API for this feature. Back in 3.2 we added this feature by adding code to the org.eclipse.ui.carbon fragment that called SWT internals. The code to do this for your own SWT app would look something like this:

package scratch;

import org.eclipse.swt.SWT;
import org.eclipse.swt.internal.Callback;
import org.eclipse.swt.internal.carbon.OS;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Widget;

public class ToolbarShellHandler {

public static void main(String[] args) {
Display display = new Display();

Shell shell = new Shell();
shell.setLayout(new GridLayout());
Button button = new Button(shell, SWT.PUSH);
button.setText(”Toolbar”);

Text text = new Text(shell, SWT.READ_ONLY);
text.setText(”Shell contents”);

ToolbarShellHandler target = new ToolbarShellHandler();
target.hookHandler(shell);

shell.open();

while (!shell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
}

private void hookHandler(Shell shell) {
// create a callback that will be invoked when the button is pressed.
final Callback commandCallback = new Callback(this, “toolbarProc”, 3); //$NON-NLS-1$
int commandProc = commandCallback.getAddress();
if (commandProc == 0) {
commandCallback.dispose();
return; // give up
}
shell.getDisplay().disposeExec(new Runnable() {
public void run() {
commandCallback.dispose();
}
});

// add the button to the window trim
int windowHandle = OS.GetControlOwner(shell.handle);
OS.ChangeWindowAttributes(windowHandle, OS.kWindowToolbarButtonAttribute, 0);

int[] mask = new int[] {
OS.kEventClassWindow, OS.kEventWindowToolbarSwitchMode
};
// register the handler with the OS
OS.InstallEventHandler(OS.GetApplicationEventTarget(), commandProc, mask.length / 2, mask, 0, null);
}

public int toolbarProc(int nextHandler, int theEvent, int userData) {
int eventKind = OS.GetEventKind(theEvent);
if (eventKind != OS.kEventWindowToolbarSwitchMode) // only handle the
// toolbar events
return OS.eventNotHandledErr;

int[] theWindow = new int[1];
// get the even window
OS.GetEventParameter(theEvent, OS.kEventParamDirectObject, OS.typeWindowRef, null, 4, null, theWindow);

int[] theRoot = new int[1];
// find the root control for the window
OS.GetRootControl(theWindow[0], theRoot);
Widget widget = Display.getCurrent().findWidget(theRoot[0]);

if (!(widget instanceof Shell)) { // only proceed if its a shell we’ve
// found
return OS.eventNotHandledErr;
}
Shell shellAffected = (Shell) widget; // do cool stuff with the shell

boolean visible = !shellAffected.getChildren()[0].getVisible();
shellAffected.getChildren()[0].setVisible(visible);
return OS.noErr;
}
}

There are two basic steps to the process. First you tweak the application shell to have the correct style bits. This causes the button to appear. This is accomplished by the OS call

OS.ChangeWindowAttributes(windowHandle, OS.kWindowToolbarButtonAttribute, 0)

. Next, you register with the OS a handler that will be called in the event that the

kEventClassWindow/
kEventWindowToolbarSwitchMode

Apple events are fired. We do this by creating a Callback class that points at a target object (in this case, the

ToolbarShellHandler

instance) and is given a method name (”toolbarProc”). When this even is received the Callback object reflectively calls the provided method. In this method is where you do your interesting work. In our example above this method looks for the window control responsible for generating the event, finds its corresponding SWT Shell object, and hides/un-hides the first control as appropriate.

You can use such hacks for more than just the toolbar toggle button, however. Look at the CarbonUIEnhancer class for code that hooks not only the toogle button but also the application preference and about commands.



Tags:

Link To Editor

October 27th, 2006 · 4 Comments

Another question from the inbox (paraphrased just a little):

Yo, Kim. What’s the deal with Link To Editor functionality? How do I make my view exhibit this behavior? PS: I love your hat.

In case you aren’t familiar with “Link To Editor”, it is the notion that certain views can repopulate/select content within themselves based on the active editor. You see it often in views such as the Package Explorer and the History View. When an editor is activate the file is selected and scrolled to in the Package Explorer and the History View refreshes and shows new history.

There isn’t really any “deal” with this functionality. Despite its pervasiveness there isn’t any official API support for Link To Editor behavior. In all cases the support is home rolled. Thankfully, it’s pretty easy to accomplish.

Step one, define a “Link To Editor” action in your view. This often appears on the view Toolbar as a toggle action can be added in your views

createPartControl

method as follows:

public void createPartControl(Composite parent) {
// add interesting controls
getViewSite().getActionBars().getToolBarManager().add(
new Action(”Link With Editor”, IAction.AS_CHECK_BOX) {
public void run() {
toggleLinking(isChecked());
}
});
}

The toggleLinking method will look something like this:

protected void toggleLinking(boolean checked) {
this.linking = checked;
if (checked)
editorActivated(getSite().getPage().getActiveEditor());
}

This method is pretty straight forward. It preserves the linking state and then if we’re now linking calls

editorActivated

with the currently active editor (if any). This method does the bulk of our work and is called here to refresh the state of our view based on the active editor. It looks like this:

private void editorActivated(IEditorPart editor) {
if (!linking || !getViewSite().getPage().isPartVisible(this))
return;

IEditorInput input = editor.getEditorInput();

IFile file = ResourceUtil.getFile(input);
if (file != null) {
// do something interesting
}
}

The above implementation of

editorActivated

assumes that our view is a resource view of some kind but that doesn’t have to be the case. You can implement linked views based on any editor input you want. For example, where “

// do something interesting

” is above you could create a

org.eclipse.jface.viewers.StructuredSelection

and set your views TreeViewer selection to that object. What the linking itself means is up to your view but the above pattern will establish the behaviour. Also note the eager return at the beginning of the method. If we aren’t currently in linking mode or we’re not visible then we shouldn’t do the linking work. Good citizens of the Eclipse ecosystem are coded to only do work when necessary. If we’re not visible then there’s no need for us to do the heavy work of linking editor content to our own.

We now have a link that is refreshed whenever the action is toggled. To make it useful we’ll need to hook the

editorActivated

method up to editor activation. First, add the following field to your view class:

private IPartListener2 partListener2 = new IPartListener2() {
public void partActivated(IWorkbenchPartReference ref) {
if (ref.getPart(true) instanceof IEditorPart)
editorActivated(getViewSite().getPage().getActiveEditor());
}

public void partBroughtToTop(IWorkbenchPartReference ref) {
if (ref.getPart(true) == LinkedWithEditorView.this) editorActivated(getViewSite().getPage().getActiveEditor());
}

public void partClosed(IWorkbenchPartReference ref) {}

public void partDeactivated(IWorkbenchPartReference ref) {}

public void partOpened(IWorkbenchPartReference ref) {
if (ref.getPart(true) == LinkedWithEditorView.this) editorActivated(getViewSite().getPage().getActiveEditor());
}

public void partHidden(IWorkbenchPartReference ref) {}

public void partVisible(IWorkbenchPartReference ref) {
if (ref.getPart(true) == LinkedWithEditorView.this) editorActivated(getViewSite().getPage().getActiveEditor());
}

public void partInputChanged(IWorkbenchPartReference ref) {}
};

In the

createPartControl

method add the following code sometime after the addition of the toggle action:

getSite().getPage().addPartListener(partListener2);

Finally, in the views dispose method add the following:

getSite().getPage().removePartListener(partListener2);

The listener above perform the following actions. When an editor is opened, the

editorActivated

method is called and our view may be updated (if linking is enabled and it’s visible). Also, if a part is opened and it’s our view then the

editorActivated

method is called. This refreshes the linked state on startup and delayed opening of our view. Finally, if our part is made visible the linked state is refreshed. This can happen in certain circumstances when plug-in code calls the

org.eclipse.ui.IWorkbenchPage.showView

method with the

IWorkbenchPage.VIEW_VISIBLE

flag as an argument. We now have a view that implements remedial Link To Editor behavior. The one thing that this example is missing is the persistence of the link state between restarts. This could be done via preferences or via the

org.eclipse.ui.IMemento

object passed to the

init

and

saveState

methods.

Something I want to point out (that isn’t specific to this example) is the use of abstract inner class listeners on global collections (such as workbench page). In the above example you could easily make the mistake of creating the

IPartListener2

implementations in the

createPartControl

method and neglect to dispose of them. This is a VERY BAD THING. Without the explicit disposal your view will never go away. Even if the user closes the view it will still reside in memory, forever reacting to part events that are no longer relevant to it. Abstract inner classes retain a reference to their host class and without proper disposal the leak of an object of an inner class also leaks the host object. Always take out your trash!

Here is view code in its entirety that simply spits out the name of the current file if Link To Editor is enabled:

import org.eclipse.core.resources.IFile;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IPartListener2;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchPartReference;
import org.eclipse.ui.ide.ResourceUtil;
import org.eclipse.ui.part.ViewPart;

public class LinkedWithEditorView extends ViewPart {

private Text text;

private boolean linking;

private IPartListener2 partListener2 = new IPartListener2() {
public void partActivated(IWorkbenchPartReference ref) {
if (ref.getPart(true) instanceof IEditorPart) {
editorActivated(getViewSite().getPage().getActiveEditor());
}

public void partBroughtToTop(IWorkbenchPartReference ref) {
if (ref.getPart(true) == LinkedWithEditorView.this)
editorActivated(getViewSite().getPage().getActiveEditor());
}

public void partClosed(IWorkbenchPartReference ref) {}

public void partDeactivated(IWorkbenchPartReference ref) {}

public void partOpened(IWorkbenchPartReference ref) {
if (ref.getPart(true) == LinkedWithEditorView.this)
editorActivated(getViewSite().getPage().getActiveEditor());
}

public void partHidden(IWorkbenchPartReference ref) {}

public void partVisible(IWorkbenchPartReference ref) {
if (ref.getPart(true) == LinkedWithEditorView.this)
editorActivated(getViewSite().getPage().getActiveEditor());
}

public void partInputChanged(IWorkbenchPartReference ref) {}
};

public LinkedWithEditorView() { }

public void createPartControl(Composite parent) {
text = new Text(parent, SWT.READ_ONLY);
getViewSite().getActionBars().getToolBarManager().add(
new Action(”Link With Editor”, IAction.AS_CHECK_BOX) {

public void run() {
toggleLinking(isChecked());
}
});

getSite().getPage().addPartListener(partListener2);
}

public void dispose() {
getSite().getPage().removePartListener(partListener2);
}

protected void toggleLinking(boolean checked) {
this.linking = checked;

if (checked)
editorActivated(getSite().getPage().getActiveEditor());

}

private void editorActivated(IEditorPart editor) {
if (!linking || !getViewSite().getPage().isPartVisible(this))
return;

IEditorInput input = editor.getEditorInput();

IFile file = ResourceUtil.getFile(input);
if (file != null)
text.setText(file.getLocation().toOSString());

}

public void setFocus() {
text.setFocus();
}
}

Edit: based on feedback I’ve changed the solution to only use one listener instead of two.



Tags:

A Context, a Capability, and a Perspective Walk Into a Bar: Capabilities

October 11th, 2006 · 5 Comments

I previously glossed over context, capabilities, and perspectives with the intention of explaining them further. Well, that was almost a year ago, so I figured it was time to get to that. I decided to start with capabilities for several reasons. First is that they are probably the most esoteric of the three and have the least relevant documentation and secondly I wrote the code. Yah, ego. Live with it. :)

Crafting Activity Definitions

Capabilities are little more than state-full identifiers that are bound to one or more regular expressions. If a given UI contribution matches one of these regular expressions and the capability is enabled then the UI contribution appears and if it is disabled then it is hidden. This is pretty simple in theory although the practice is a little more tricky due to the expertise required to write regular expressions and the verbosity in describing capabilities and their relationships to one another. An example might be helpful.

First, let’s imagine that there is a plug-in that provides very simple tooling for a new programming language (conveniently called Foo). Some extensions from this plug-in (

org.foo.tooling

) might look like the following:

<extension         point="org.eclipse.ui.editors">      <editor            class="org.foo.tooling.editor.FooEditor"            contributorClass="org.foo.tooling.editor.FooContributor"            extensions="foo"            icon="foo.gif"    default="true"            id="org.foo.tooling.editor.fooEditor"            name="Foo Editor">      </editor></extension><extension         point="org.eclipse.ui.perspectives">      <perspective            class="org.foo.tooling.editor.FooPerspectiveFactory"            icon="fooPerspective.gif"            id="org.foo.tooling.fooPerspective"            name="Foo">      </perspective></extension><extension         point="org.eclipse.ui.newWizards">      <wizard            canFinishEarly="true"            class="org.foo.tooling.wizards.NewFooWizard"            hasPages="true"            icon="foo.gif"            id="org.foo.tooling.fooWizard"            name="Foo Project"            preferredPerspectives="org.foo.tooling.fooPerspective"            project="true">      </wizard></extension>

In addition to the basic tooling the plug-in author has devised some extended (complicated, painful, super-frickin’cool) editing abilities that they’ve isolated in their own plug in (

org.foo.tooling.advanced

):

<extension         point="org.eclipse.ui.editors"><editor            class="org.foo.tooling.editor.AdvancedFooEditor"            contributorClass="org.foo.tooling.editor.AdvancedFooContributor"            extensions="foo"            icon="foo.gif"            id="org.foo.tooling.editor.advancedFooEditor"            name="Foo Editor">      </editor></extension>

There are probably some other extensions - commands, key bindings, additional wizards, utility views - that look very similar to this.

If you were trying to generate capabilities for your product and your product happened to make use of the Foo plug-ins you might decide to make a Foo capability to control the visibility of the Foo components. Of course since you are including functionality for other development tooling you want to integrate the activities for Foo into the overall capability story for your product. The definition of that capability might look something like this:

<extension         point="org.eclipse.ui.activities">      <category            description="Capabilities relating to development in various languages."            id="my.product.development"            name="Development">      </category>      <activity            description="Functionality used in development of projects using the Foo language."            id="my.product.fooActivity"            name="Foo Development">      </activity>      <defaultEnablement            id="my.product.fooActivity">      </defaultEnablement>      <activity            description="Advanced development in the Foo language."            id="my.product.fooActivity.advanced"            name="Advanced Foo Development">      </activity>      <activityRequirementBinding            activityId="my.product.fooActivity.advanced"            requiredActivityId="my.product.fooActivity">      </activityRequirementBinding>      <categoryActivityBinding            activityId="my.product.fooActivity"            categoryId="my.product.development">      </categoryActivityBinding>      <categoryActivityBinding            activityId="my.product.fooActivity.advanced"            categoryId="my.product.development">      </categoryActivityBinding>      <activityPatternBinding            activityId="my.product.fooActivity"            pattern="org\.foo\.tooling/.*">      </activityPatternBinding>      <activityPatternBinding            activityId="my.product.fooActivity.advanced"            pattern="org\.foo\.tooling\.advanced/.*">      </activityPatternBinding></extension>

Before I explain this markup let me first describe some intentions we had with the activity extension. First and foremost was our intention that those who are writing plug-ins NOT define activities for them in the plug-in that define their functionality. Any activity definitions provided by the plug-in developer should be isolated so that product developers (those who aggregate plug-ins from various sources) are able to totally own the capabilities for their product. Depending on the focus of the product the capabilities could be drawn along component lines, experience lines, role lines, or any number of other dimensions. Another thing to note is the verbosity of this extension point. It takes a lot of elements to define your activities and while it can be bothersome to those using it, it’s intentional. This verbosity allows us a great deal of flexibility when it comes to how we lay out our activities. For instance, it’s possible for a top-level architect to lay out all the activity categories in one plug-in and have sub-teams contribute activities in their own. Alternatively, it’d be possible such an architect to define a few key activities (Beginner, Intermediate, and Advanced) and simply have the sub-teams contribute pattern bindings.

First, the <category>. Categories are hints that tell Eclipse how to display capabilities within the UI should it desire to show them. Beyond a logical grouping these elements serve no other purpose in the system. In our example our product architect has decided that having a Development category would be appropriate. In fact, the Eclipse SDK ships with a Development category of its own that contains activity definitions for Java and Plug-In development.

Second, the <activity>. Each activity element represents one state-full identifier in the activity system. By default these identifies are disabled but this can be changed via the <defaultEnablement>. In our example, we define two activities - Foo Development and Advanced Foo Development. The later is made enabled by default via a subsequent <defaultEnablement> tag.

Next we have the <activityRequirementBinding> tag. The arrangement of activities is a hierarchical one. It is possible to specify that the enablement of one activity requires the enablement of another. In our example, we specify that the enablement of the Advanced Foo Development activity requires the enablement of the Foo Development activity.

Next we have <categoryActivityBinding> tags. These elements place the specified activities in the specified categories.

Finally we have the <activityPatternBinding> tag. This is probably the most interesting element in the schema and definitely the trickiest to author. Every activityPatternBinding element binds a regular expression (following the Java regex package syntax) to a given activity. While it’s possible to bind any regular expression to an activity the Eclipse workbench is designed to work with a particular pattern. In particular, the workbench will attempt to match strings of the form “plug-in id/contribution id” against these expressions. For instance, when the workbench sees the Foo Editor listed above it will create an identifier “org.foo.tooling/org.foo.tooling.editor.fooEditor” to pass to the activity framework. If that identifier matches any pattern binding associated with an enabled activity or NOT associated with a disabled activity than the Foo Editor will be available. For instance, in the above XML, we bind the expression “org\.foo\.tooling/.*” to the “my.product.fooActivity” activity. If “my.product.fooActivity” is enabled then the Foo Editor is available. If “my.product.fooActivity” is not enabled, then Foo Editor is hidden.

The pattern “plug-in id/.*” is often seen in activity definitions because it is often the case that activities are grossly drawn along plug-in lines. The pattern effectively says “everything in the plug-in”. Of course, it is possible to define more granular bindings (”org\.foo\.tooling/org\.foo\.tooling\.editor\.fooEditor”) or more general ones (”org.foo.*/.*”). One thing to note with the more specific bindings is the nature of the contribution id after the “/” character. This never applies to the id attribute of the extension tag (which is rarely used within the Eclipse UI extensions) but rather the id of the relevant component. For instance, when filtering on editors the id attribute of the <editor> element is used.

Using The Framework

As someone who wants to write activity bindings the above is all you need to know about activities. If you’re a plug-in developer that wishes to leverage capability filtering in your own extension points there is more to story. There are two aspects of this support. The first is using the activity support to filter disabled contributions from your UI. The second is providing a means in your UI to enable activities. The first part is easy. Let’s imagine our advanced Foo editor. Imagine there are language extensions to the Foo language that the editor can use. These extensions are contributed to an extension point in the foo plug-in called “languageExtensions”. The languageExtension schema is very simple and a languageExtension extension might look like this:

<extension         point="org.foo.tooling.languageExtension">      <languageExtension           id="myExtension" ...>  <contributions .../>  </languageExtension></extension>

Imagine that the Foo language was graphical and that each languageExtension would contribute some number of items to a palette editor. As a client of the activities framework, the Foo plug-in developer would want to suppress palette items that are bound to disabled activities. Their code might look like this:

for (int i = 0; i < contributions.length; i++) { FooPaletteContribution c = contributions[i]; final String pluginId = c.getDefiningPluginId(); final String contributionId = c.getId();

 IPluginContribtion pc = new IPluginContribution() {

  public String getLocalId() {   return contributionId;  }

  public String getPluginId() {   return pluginId;  }};

 if (WorkbenchActivityHelper.filterItem(pc))  continue; showContribution(c);}

This code is pretty straightforward. First, we grab the defining plug-in id and the extension id from the FooPaletteContribution and create an IPluginContribution object with those values. This interface is used by the activity framework to construct ids for comparison. We pass this object to the WorkbenchActivity helper which compares it against all of the activities in the system and answers the question “should this object be hidden from the user”. If the answer is yes, we do nothing further with this item but if it’s false we show the item in some capacity. If we were smart we could make this code cleaner by having FooPaletteContribution implement IPluginContribution directly but otherwise this is the standard pattern for filtering contributions via the activity framework.

The above code shows how to populate a static list with contributions that are potentially filtered by activities but the activity system is not static. Activity enablement can change over the course of the application life-cycle so it is up to those using the system to monitor the system for changes. The easiest way to do this is to hook a listener onto the activity support that refreshes the presentation:

PlatformUI.getWorkbench().getActivitySupport()    .getActivityManager()    .addActivityManagerListener(new IActivityManagerListener() {

 public void activityManagerChanged(  ActivityManagerEvent activityManagerEvent) {  if (activityManagerEvent.haveEnabledActivityIdsChanged())   repopulatePresentation();      }});

Don’t forget to unhook the listener when you’re finished showing contributions.

So now that we have these extensions being filtered how will the user ever see them? They could go to the preferences and specifically enable the appropriate activities but this isn’t ideal. What we need is for the Foo editor to provide some means to enable the defined language extensions. This is where trigger points come into play.

Using trigger points is a two-step affair. The first step is to define the trigger point in plug-in XML. Since this trigger is owned by the Foo tooling plug-in, it should reside in its plug-in XML. The extension would look like this:

<extension         point="org.eclipse.ui.activitySupport">      <triggerPoint            id="org.foo.tooling.languageExtensionTriggerPoint">         <hint               id="interactive"               value="true">         </hint>      </triggerPoint>   </extension>

Note that these extensions reside on the org.eclipse.ui.activitySupport extension point rather than the org.eclipse.ui.activities extension point. Trigger point definitions are pretty simple. They are really nothing more than an id value and some optional hints. In the above example we’ve defined the language extension trigger point with a an interactive hint. To use the trigger point in our code is also straightforward. Imagine that we have a series of toggle buttons in our editor that specifies the language extensions we want to use. This list seems like a logical place to hook our trigger point. In our event listener we’d have code that looks something like this:

public void widgetSelected(SelectionEvent e) { final LanguageExtension ext = (LanguageExtension) e.widget.getData(); IPluginContribtion pc = new IPluginContribution() {

  public String getLocalId() {   return ext.getId();  }

  public String getPluginId() {   return ext.getDefiningPluginId();  }};

 ITriggerPoint triggerPoint = PlatformUI.getWorkbench()   .getActivitySupport()   .getTriggerPointManager()   .getTriggerPoint(     "org.foo.tooling.languageExtensionTriggerPoint");

 if (!WorkbenchActivityHelper.allowUseOf(triggerPoint,   pc)) {  ((Button) e.widget).setSelected(false); }}

The above should be fairly straightforward. In our button listener we get the LanguageExtension the user wishes to use and create an

IPluginContribution

from it. We then obtain the languageExtension trigger point from the activity system and hand both off to the workbench, asking the question “should this operation proceed”. If the answer is no, we should abort the selection event and reset the button selection to false. If the answer is yes, we do nothing. The

WorkbenchActivityHelper.allowUseOf()

method has some interesting effects that we need to discuss. When called, the plug-in contribution is compared against the known activity bindings and a collection of disabled activities is collected. This collection, if enabled, would cause a subsequent call to

WorkbenchActivityHelper.filterItem()

with the same

IPluginContribution

to return false. If this collection is not empty, the activity support consults the trigger point. If the interactive hint is set a dialog is opened informing the user that if they wish to proceed the collection of activities will be enabled. If they agree, the collection is enabled and

WorkbenchActivityHelper.allowUseOf()

returns true. If they do not, no activities are enabled and

WorkbenchActivityHelper.allowUseOf()

returns false. If the interactive hint is not set, then all activities are enabled and

WorkbenchActivityHelper.allowUseOf()

returns true. We’ve now hooked up code that will enable activities based on the use of our extensions.

Let’s run through a scenario that shows both the filtering and trigger points in action. We have a disabled activity A that binds to plug-in P. P contains a language extension called “My Extension.” Our Foo editor is showing a list of language extensions that contains My Extension in the disabled state. The palette in the Foo editors shows contributions from other language extensions but not those contained within My Extension. The user clicks on My Extension and the activity system is consulted with the interactive foo trigger point. It finds that IPluginContribution “P/My Extension” is bound to activity A. Since A is disabled, we bring up a dialog asking the user to confirm that they wish to enable activity A. They consent, My Extension becomes toggled in the list and the palette contributions contained within the extensions appear in the palette.

Extending The Framework

We’ve now covered capabilities from two sides - the capability set architect and the plug-in developer that wishes to use the framework. There is one other way in which the capabilities framework can be extended, however. In the above example we prompted the user with a dialog in the event that we encountered an interactive trigger points that had disabled activities. In most cases this is appropriate but in others (RCP applications that make use of capabilities, for instance) this isn’t desirable. To accommodate such rare cases we’ve made it so that the entire trigger point engine can be replaced by those applications that require it. This is done via the org.eclipse.ui.activitySupport extension point:

<triggerPointAdvisor            class="my.product.MyTriggerPointAdvisor"            id="my.product.myTriggerPointAdvisor">      </triggerPointAdvisor>      <triggerPointAdvisorProductBinding            productId="my.product"            triggerPointAdvisorId="my.product.myTriggerPointAdvisor">      </triggerPointAdvisorProductBinding>

We define the advisor by an id and an implementing class (which we will cover shortly). In addition to the definition we also bind the advisor to a particular product. I wont go into product documentation here as it’s a topic unto itself. There is plenty of documentation existing for this, however, such as the Rich Client Tutorial written by Ed Burnette.

The implementation for the advisor must implement

org.eclipse.ui.activities.ITriggerPointAdvisor

. This interface has one method,

Set allow(ITriggerPoint triggerPoint, IIdentifier identifier);

which is consulted whenever someone calls

WorkbenchActivityHelper.allowUseOf()

. Example:

public Set allow(ITriggerPoint triggerPoint, IIdentifier identifier) { if (triggerPoint.getId().equals("some.trigger.point"))  return null; else  return identifier.getActivityIds();}

The above advisor would disallow any usage from the trigger point with the identifier “some.trigger.point” but would silently enable usage from any other trigger point.

The majority of the capability scenarios are covered by the above three solutions. There are other uses for the activity framework (poor mans provisioning, for instance) but I wont go into those here.

This time next year: contexts.



Tags: