Thursday, June 10, 2010

Leveraging org.eclipse.ui.intro.samples extension point

Eclipse provides excellent samples framework that is part of 'Welcome' experience, unfortunately docs in the extension point state that This extension point is currently provided for internal Eclipse SDK usage only. It should not be used for anything else except Eclipse SDK examples. What this means is that:
  • Projects are downloaded from eclipse.org so you can't easily define alternate location of the zip files
  • The whole UI revolves around assumption that your project is an Eclipse plug-in or a project that contains some kind of code
Fortunately there is a way to manipulate all these features for your own needs.

Define location
As I stated above default url location for samples locations is eclipse.org which is defined as ShowSampleAction.UPDATE_SITE in org.eclipse.pde.internal.ui.samples. ShowSampleAction class is responsible for downloading zip file that is associated with the sample entry. All that is needed to make downloading sample from you own repository is to copy and modify ShowSampleAction with your own definitions of feature id, feature version and update site. All these constants are defined at the top of the class.

public class ShowSampleAction extends Action implements IIntroAction {
private static final String SAMPLE_FEATURE_ID = "org.eclipse.sdk.samples"; //$NON-NLS-1$
private static final String SAMPLE_FEATURE_VERSION = "3.3.0"; //$NON-NLS-1$
private static final String UPDATE_SITE = "http://dev.eclipse.org/viewcvs/index.cgi/%7Echeckout%7E/pde-ui-home/samples/"; //$NON-NLS-1$


Customize download message
In addition you might want to change the message that appears in the dialog when user clicks on the sample link. This is done in ShowSampleAction.ensureSampleFeaturePresent() method


private boolean ensureSampleFeaturePresent() {
if (checkFeature())
return true;
// the feature is not present - ask to download
if (MessageDialog.openQuestion(PDEPlugin.getActiveWorkbenchShell(), PDEUIMessages.ShowSampleAction_msgTitle, PDEUIMessages.ShowSampleAction_msgDesc)) {
return downloadFeature();
}
return false;
}


Plugin' it in
So now that we have our custom action defined how do we tell framework about it? When defining your samples in org.eclipse.ui.intro extension point the xml file that creates samples configExtension is the place we are interested in. I am not going to go into detail of how to implement the extension point here as this is covered vastly elsewhere. So here are the contents of my samples xml file:






description







The part we are interested in is the url attribute of the link element, this is where our custom action is defined:


url="http://org.eclipse.ui.intro/runAction?pluginId=com.iwaysoftware.integration.tools.samples&class=com.iwaysoftware.integration.tools.samples.actions.ShowSampleAction&id=com.iwaysoftware.samples.scifi"


Notice definition of the pluginId and class, this is what makes your custom action execute when link in the samples page is clicked.

This takes care of downloading and importing the project part.

Tweaking UI
After project is imported into your workspace from samples page there are number of things that can happen in the framework which is controlled in the IntroURL.doExecute() method: a welcome view can be closed, browser opened or another action ran. Set a break point in the method and interact with samples page to see what happens. By default IntroURL.SHOW_STANDBY is executed, but how and who controls what action is executed? Why it's our friend ShowSampleAction! Take a look at ShowSampleAction.switchToSampleStandby() method.

private void switchToSampleStandby(SampleWizard wizard) {
StringBuffer url = new StringBuffer();
url.append("http://org.eclipse.ui.intro/showStandby?"); //$NON-NLS-1$
url.append("pluginId=com.iwaysoftware.integration.tools.samples"); //$NON-NLS-1$
url.append("&"); //$NON-NLS-1$
url.append("partId=com.iwaysoftware.samples.SamplesStandbyPart"); //$NON-NLS-1$
url.append("&"); //$NON-NLS-1$
url.append("input="); //$NON-NLS-1$
url.append(sampleId);
IIntroURL introURL = IntroURLFactory.createIntroURL(url.toString());
if (introURL != null) {
introURL.execute();
ensureProperContext(wizard);
}
}


This is where url is constructed and passed to IntroURL. So if you change http://org.eclipse.ui.intro/showStandby to http://org.eclipse.ui.intro/close after sample is imported no welcome view will be docked to the left of your Eclipse instance. As I mentioned you can explore all available actions in IntroURL class.

So why is this important? Because samples extension point pre-wired to work with java projects, which might not be the case as it is in our product. Our product is a collection of xml files that are manipulated by user via GEF editors. So when a project is imported into workspace a java-project-centric welcome view appears that is not necessarily what you want to happen in your case because there is no source to browser for example. One obvious thing you can do is to call close action in the ShowSampleAction and be done with it, but you can also customize the view for your own needs.

Customizing the view
The content of the welcome view page are loaded from a class that implements IStandbyContentPart, particularly org.eclipse.pde.internal.ui.samples.SampleStandbyContent. The content of the page is defined in the createPartControl() method which could be as simple as name and description of the sample or as complicated as launching run shortcut with predefined parameters. What is nice here is you are in total control of what happens in the view.
Once you define the view, how do you integrate your changes into the framework? It took some debugging time to find the answer - org.eclipse.ui.internal.intro.impl.parts.StandbyPart.showContentPart() is the place, specifically


IntroStandbyContentPart standbyPartContent = ExtensionPointManager
.getInst().getSharedConfigExtensionsManager()
.getStandbyPart(partId);


this is the extension point from which implementer of IStandbyContentPart is loaded


String standbyContentClassName = standbyPartContent.getClassName();
String pluginId = standbyPartContent.getPluginId();

Object standbyContentObject = ModelLoaderUtil.createClassInstance(
pluginId, standbyContentClassName);


And the place where magic happens is in extension point world, where sample defining xml is set:


point="org.eclipse.ui.intro.configExtension">
configId="org.eclipse.ui.intro.universalConfig"
content="intro/samplesExtensionContent.xml">

id="com.iwaysoftware.samples.SamplesStandbyPart"
class="com.iwaysoftware.integration.tools.samples.content.SamplesStandbyContent"
pluginId="com.iwaysoftware.integration.tools.samples"/>



As you can see our custom SamplesStandbyContent is defined and will be loaded by the extension point mechanism when it's time to display the view.

Disclaimer :)
Any or all information mentioned above could become useless since samples extension point and its supporting classes are internal, so if something changes there this can tumble like a house of cards, I hope it will not. Fingers crossed.

Thursday, November 12, 2009

Eclipse SVN merge



When merging branch to trunk follow these steps:
  • Commit all branch changes to svn
  • Checkout trunk to workspace
  • Right-click on trunk project in workspace: Team -> Merge
  • In from url select branch to be merged to trunk
  • In to url check 'Use "From:" URL'
  • In from revision choose last revision to get from branch. Think about it, you want revision that has all the changes that were done since last merge to trunk
  • Click Merge
  • If there are any conflicts, they will show up in project file tree, resolve them by: Team -> Edit Conflicts... on the file in question.
  • Once conflicts are resolved, Team -> Marked Resolved... on the file.
  • After all conflicts are resolved, and everything works, commit!

Wednesday, October 28, 2009

SVN Hooks

I am working on a project where I need directory of my cakePHP application on the server update on each svn commit. Why? Because I am lazy. I don't want to login to remote server every time I commit something, run svn update...or worse ftp. So I've been reading about svn hooks, and it's a very powerful thing.

These are the steps to make it work:



  • Create and compile c program that will perform svn update, and save it somewhere let's say in /svn/autoupdate


    #include
    #include
    #include
    int main(void)
    {
    execl("/usr/local/bin/svn", "svn", "update",
    "${dirNameToUpdate}",
    (const char *) NULL);
    return(EXIT_FAILURE);
    }



  • Add post-commit to svnrepo/hooks where compiled c program is referenced:

    #!/bin/sh
    /svn/autoupdate/autoupdate


  • chmod +x post-commit


Try to commit something to svn on remote server and watch svn hooks magic in action.

Tuesday, September 15, 2009

Install RMagick on Ubuntu


sudo aptitude install -y imagemagick
sudo aptitude install -y libmagick9-dev
sudo gem install rmagick

Bind a key binding that is already defined

To correctly bind a key that is already defined for example by platform - F5 for refresh, the important thing is to create your own context:


name="refresh" parentId="org.eclipse.ui.contexts.window">




Once correct context is defined, it needs to be activated. For example, in view:


View.createPartControl(Composite parent) {
...
...
IContextService contextService = (IContextService) getSite().getService(IContextService.class);
contextService.activateContext("com.iwaysoftware.explorer.refresh");
}


Then the usual, command is created that is tied to a handler that extends AbstractHandler, command is tied to a binding, and the binding is in turn uses earlier created custom context.



defaultHandler="com.iwaysoftware.explorer.handler.RefreshHandler" id="com.iwaysoftware.explorer.refreshCommand"
name="Refresh">



sequence="F5" schemeId="org.eclipse.ui.defaultAcceleratorConfiguration"
contextId="com.iwaysoftware.explorer.refresh" />


name="refresh" parentId="org.eclipse.ui.contexts.window">


Saturday, September 12, 2009

Kill process that is using paricular port

Find process id and kill it

ruby@t61:~/dev/ruby/demo$ sudo netstat -lpn | grep 3000
[sudo] password for ruby:
tcp 0 0 127.0.0.1:3000 0.0.0.0:* LISTEN 5358/ruby
ruby@t61:~/dev/ruby/demo$ kill -9 5358

Monday, August 31, 2009

Disabling nodes in CheckboxTreeViewer

Working on a project at work I had to use CheckboxTreeViewer, one of the requirements was that some nodes in the tree are disabled. I thought that this would be easy enough because surely CheckboxTreeViewer has some method that I can call to disable some checkboxes. It turns out it does not.

The first thing I obviously did was googled for possible solution. Unfortunately the solutions were not what I needed, so I decided to try the problem myself. Luckily there are two methods that are of help in CheckboxTreeViewer: setGrayed and setChecked, setGrayed nicely grays out checkbox so the user knows that this checkbox is disabled.

Here is the code in pastebin, since it does not render well in my template.

The disable mechanism revolves around value in model's isEnabled field. My model is roughly based on example from this article, but any model will do as long as there is variable that sets a particular model element not enabled. Screen shot on the left shows how this code works in my application.

Eclipse 3.5 has interface ICheckStateProvider that pretty much does what I wanted to do here. However, I am currently at Eclipse 3.4, so will have to wait for this once I move to 3.5.

Blogger Syntax Highliter