Sunday, November 6, 2011

Adding a P2 Repository Dynamically at Runtime

After experiencing the pains of dealing with a homegrown thick-client update system, we decided to give Eclipse's P2 provisioning system a try. For Eclipse 3.7 (Indigo), the P2 API received quite a bit of TLC, especially on the UI end, so we felt like it was mature enough for us to take a look at. It was very easy to create an Eclipse Plugin/Feature/Product that gets updated from a static P2 repository with a URI known at build time. All you have to do is create a p2.inf file and populate it with the correct repository locations, as explained in step 7 of Ralf Eberts excellent tutorial. We, however, had a situation where each customer would have their own private repository, so we had to set the update location dynamically at run time. Here's how we did it:

First, we had to obtain the P2 provisioning agent:

private IProvisioningAgent getProvisioingAgent() {
final IProvisioningAgent agent = (IProvisioningAgent) ServiceHelper.getService(FraudPlugin.bundleContext,
IProvisioningAgent.SERVICE_NAME);
if (agent == null) {
LogHelper.log(new Status(IStatus.ERROR, Activator.PLUGIN_ID,
"No provisioning agent found. This application is not set up for updates."));
}
return agent;
}


Once we have the provisioning agent, we can use it to get the metadata manager and artifact manager. You need both managers because the equinox provisioning systems allows for separate locations for each. Once we have the managers, we can simply add the URIs that we retrieved at run time via some external property file, database, preference store, etc.:

private void addRuntimeP2Repository(IProgressMonitor monitor, ProvisioningSession session, URI runtimeRepoURI) throws ProvisionException, OperationCanceledException {
//Create Metadata repository manager and add the new repository location
IMetadataRepositoryManager metaDataRepoManager = ProvUI.getMetadataRepositoryManager(session);
metaDataRepoManager.loadRepository(runtimeRepoURI, monitor);
//Create artifact repository manager and add the new repository location
IArtifactRepositoryManager artifactDataRepoManager = ProvUI.getArtifactRepositoryManager(session);
artifactDataRepoManager.loadRepository(runtimeRepoURI, monitor);
}
view raw AddP2Repo.java hosted with ❤ by GitHub



You'll need to be sure and add the following imports:

  • import org.eclipse.equinox.internal.p2.core.helpers.LogHelper;
  • import org.eclipse.equinox.internal.p2.core.helpers.ServiceHelper;
  • import org.eclipse.equinox.internal.p2.ui.ProvUI;
  • import org.eclipse.equinox.p2.core.IProvisioningAgent;
  • import org.eclipse.equinox.p2.core.ProvisionException;
  • import org.eclipse.equinox.p2.operations.ProvisioningJob;
  • import org.eclipse.equinox.p2.operations.ProvisioningSession;
  • import org.eclipse.equinox.p2.operations.UpdateOperation;
  • import org.eclipse.equinox.p2.repository.artifact.IArtifactRepositoryManager;
  • import org.eclipse.equinox.p2.repository.metadata.IMetadataRepositoryManager;

and you'll also need to make sure your plugin has the following dependencies:


  • org.eclipse.equinox.p2.core
  • org.eclipse.equinox.p2.operations
  • org.eclipse.equinox.p2.metadata
  • org.eclipse.equinox.p2.engine
  • org.eclipse.equinox.p2.ui
  • org.eclipse.equinox.p2.metadata.repository
  • org.eclipse.equinox.p2.repository

Sunday, February 13, 2011

Animated Visual Effects in an SWT Table

For the project I am currently working on, we have a view that contains a large table.  When an item in that table is selected, it drives what data shows up in the other views inside the perspective.  We needed a way to notify the user of what the context of his selection was, even if he changed focus to another view.  I wanted something with a little visual flair, rather than just the normal, mundane icon.  What I came up with was a "pulsing" background color that fades in at out slowly (keyword: slowly).  It draws the user's attention if the user is looking for his selection, but does not distract away from other views.

This approach presented two main challenges: first, I wanted to develop a clean algorithm for fading between any two colors so that the speed of the pulsing would be consistent regardless of colors, and 2nd, all the heavy duty work would need to be done in a separate thread from the GUI thread so I wouldn't freeze up the interface.

The following code creates a listener that, upon being notified of a Table Mouse double click event, starts a new thread that controls a pulsing background that fades between two colors:
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableItem;
public class ContextPulsePaintListener implements Listener {
private PulseThread pulseThread = null;
public ContextPulsePaintListener() {
}
public void handleEvent(Event event) {
switch (event.type) {
case SWT.MouseDoubleClick: {
Table table = (Table) event.widget;
TableItem ti = table.getSelection()[0];
if (pulseThread != null) {
pulseThread.endPulsing();
}
pulseThread = new PulseThread(ti);
pulseThread.start();
break;
}
}
}
class PulseThread extends Thread {
private int waitTime = 10;
boolean pulsing = false;
TableItem item = null;
private boolean fadeIn = false;
private final Color color1 = new Color(Display.getDefault(), 150, 255, 150);
private final Color color2 = new Color(Display.getDefault(), 240, 255, 240);
public PulseThread(TableItem item) {
this.item = item;
waitTime = getTime(waitTime);
}
private int getTime(int wait) {
wait = Math.max(waitTime, (2000 / Math.max(Math.abs(color1.getRed() - color2.getRed()), 1)));
wait = Math.max(waitTime, (2000 / Math.max(Math.abs(color1.getGreen() - color2.getGreen()), 1)));
wait = Math.max(waitTime, (2000 / Math.max(Math.abs(color1.getBlue() - color2.getBlue()), 1)));
return wait;
}
public TableItem getItem() {
return item;
}
public boolean isFadeIn() {
return fadeIn;
}
@Override
public void run() {
pulsing = true;
if (!item.isDisposed()) {
item.getDisplay().syncExec(new Runnable() {
public void run() {
item.setBackground(color1);
}
});
}
while (pulsing && !item.isDisposed()) {
item.getDisplay().syncExec(new Runnable() {
public void run() {
if (!item.isDisposed()) {
Color color = item.getBackground();
item.setBackground(makeNewColor(color));
}
}
});
waitFor(waitTime);
}
if (!item.isDisposed()) {
item.getDisplay().syncExec(new Runnable() {
public void run() {
item.setBackground(item.getParent().getBackground());
}
});
}
}
public void endPulsing() {
this.pulsing = false;
}
private Color makeNewColor(Color current) {
int redCurrent = current.getRed();
int greenCurrent = current.getGreen();
int blueCurrent = current.getBlue();
Color target = fadeIn ? color1 : color2;
Color original = !fadeIn ? color1 : color2;
int redTarget = target.getRed();
int greenTarget = target.getGreen();
int blueTarget = target.getBlue();
int redAdjuster = makeAdjuster(redTarget, original.getRed());
int greenAdjuster = makeAdjuster(greenTarget, original.getGreen());
int blueAdjuster = makeAdjuster(blueTarget, original.getBlue());
int mult = 1;
if (fadeIn) {
if (redCurrent == redTarget && greenCurrent == greenTarget && blueCurrent == blueTarget) {
fadeIn = false;
}
} else {
if (redCurrent == redTarget && greenCurrent == greenTarget && blueCurrent == blueTarget) {
fadeIn = true;
}
}
int newRed = redCurrent;
int newGreen = greenCurrent;
int newBlue = blueCurrent;
if (redAdjuster != 0) {
newRed = redAdjuster > 0 ? Math.min(target.getRed(), redCurrent + (redAdjuster * mult)) : Math.max(
target.getRed(), redCurrent + (redAdjuster * mult));
}
if (greenAdjuster != 0) {
newGreen = greenAdjuster > 0 ? Math.min(target.getGreen(), greenCurrent + (greenAdjuster * mult)) : Math
.max(target.getGreen(), greenCurrent + (greenAdjuster * mult));
}
if (blueAdjuster != 0) {
newBlue = blueAdjuster > 0 ? Math.min(target.getBlue(), blueCurrent + (blueAdjuster * mult)) : Math
.max(target.getBlue(), blueCurrent + (blueAdjuster * mult));
}
return new Color(current.getDevice(), newRed, newGreen, newBlue);
}
}
private int makeAdjuster(int target, int current) {
return target == current ? 0 : target > current ? 1 : -1;
}
public static void waitFor(long s) {
try {
Thread.sleep(s);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}