Saturday, September 29, 2007

The marriage of GWT with Eclipse-Technologies

I'm currently on the track to explore GWT and started just this morning with it. The first thing I found missing is something like the Viewers-Concept from JFace.

I don't want to learn new things if the one I'm familiar with would make my life much easier. JFace-Viewers is one of my main working areas in Eclipse-land. So I sat down and took a closer look and after 1 hour of work my GWT-code looks like this:


package at.bestsolution.eclipse.jface.example.client;

import java.util.ArrayList;

import org.gwtwidgets.client.util.SimpleDateParser;

import at.bestsolution.eclipse.jface.client.viewers.TreeViewer;
import at.bestsolution.eclipse.jface.example.client.model.Family;
import at.bestsolution.eclipse.jface.example.client.model.Person;
import at.bestsolution.eclipse.jface.example.client.ui.MyContentProvider;
import at.bestsolution.eclipse.jface.example.client.ui.MyLabelProvider;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Tree;

/**
* Entry point classes define onModuleLoad().
*/
public class JFaceExampleApp implements EntryPoint {

/**
* This is the entry point method.
*/
public void onModuleLoad() {

Tree tree = new Tree();
TreeViewer viewer = new TreeViewer(tree);
viewer.setLabelProvider(new MyLabelProvider());
viewer.setContentProvider(new MyContentProvider());
viewer.setInput(createInput());

RootPanel.get().add(tree);
}

private ArrayList createInput() {
SimpleDateParser parser = new SimpleDateParser("yyyy-MM-dd");
ArrayList list = new ArrayList();

Family fam = new Family("Schindl");
Person p = new Person("Tom",parser.parse("1979-05-01"),fam);
p = new Person("Tina",parser.parse("1980-10-14"),fam);
p = new Person("Laura",parser.parse("1991-08-07"),fam);

list.add(fam);

fam = new Family("Hoppichler");
p = new Person("Andreas",parser.parse("1977-11-06"),fam);
p = new Person("Franzi",parser.parse("1978-11-02"),fam);

list.add(fam);

return list;
}
}


And the respective Viewer-classes like this:


package at.bestsolution.eclipse.jface.example.client.ui;

import java.util.ArrayList;

import at.bestsolution.eclipse.jface.client.viewers.ITreeContentProvider;
import at.bestsolution.eclipse.jface.client.viewers.Viewer;
import at.bestsolution.eclipse.jface.example.client.model.Family;
import at.bestsolution.eclipse.jface.example.client.model.Person;

public class MyContentProvider implements ITreeContentProvider {

public Object[] getChildren(Object parentElement) {
if( parentElement instanceof Family ) {
return ((Family)parentElement).getList().toArray();
}
return new Object[0];
}

public Object getParent(Object element) {
if( element instanceof Person ) {
return ((Person)element).getFamily();
}

return null;
}

public boolean hasChildren(Object element) {
return getChildren(element).length > 0;
}

public Object[] getElements(Object inputElement) {
return ((ArrayList)inputElement).toArray();
}

public void dispose() {

}

public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {

}

}



package at.bestsolution.eclipse.jface.example.client.ui;

import org.gwtwidgets.client.util.SimpleDateFormat;

import at.bestsolution.eclipse.jface.client.viewers.ILabelProvider;
import at.bestsolution.eclipse.jface.example.client.model.Family;
import at.bestsolution.eclipse.jface.example.client.model.Person;

public class MyLabelProvider implements ILabelProvider {
private SimpleDateFormat format = new SimpleDateFormat("dd.MM.yyyy");

public String getText(Object element) {
if( element instanceof Family ) {
return ((Family)element).getName();
} else if( element instanceof Person ) {
return ((Person)element).getName() + " ("+format.format(((Person)element).getBirthday())+")";
}

return "";
}

}


Now you think this is cool? Because this was to trivial I thought I'll have to go further and after ~ 6h I had ported the the core-parts from the JFace-Databinding-Framework to GWT! So my UI-Binding-Code looks like this now:


// ....
TextBox box = new TextBox();

TextBoxObservableValue targetValue = new TextBoxObservableValue(box,TextBoxObservableValue.Change);
IObservableValue modelValue = PEMFObservablesFactory.observeDetailValue(Realm.getDefault(), value, "id", String.class);

DataBindingContext ctx = new DataBindingContext();
UpdateValueStrategy targetToModel = new UpdateValueStrategy();
targetToModel.setConverter(new IConverter() {

public Object convert(Object fromObject) {
return fromObject.toString();
}

public Object getFromType() {
return null;
}

public Object getToType() {
return null;
}

});

UpdateValueStrategy modelToTarget = new UpdateValueStrategy();
modelToTarget.setConverter(new IConverter() {

public Object convert(Object fromObject) {
return fromObject.toString();
}

public Object getFromType() {
return null;
}

public Object getToType() {
return null;
}

});
ctx.bindValue(targetValue, modelValue, targetToModel, modelToTarget);


The GWT-Compiler translates all into JS-Code and because there's no Reflection-API available in this space I had to invent my own concept which I lend from EMF hence the name PEMF (for PoorEMF). Without question much of the Databinding-Framework is not working but at this stage I don't mind. The good thing is that all those low-level modules use fairly only classes already available from GWT so porting the whole framework is only a matter of time.

Tuesday, June 26, 2007

Branding for Windows (How to create .ico files automatically)

After some time not having developed for Win32. I today had to brand a small RCP-application and once more completely forgot how to create this "Programm Launcher icon" using opensource tools.

So I thought I have to restore this information somewhere I can find it later on:

1. Get nice icons:
The starting point for me is one of the open-source icon-collections (www.icon-king.com/) or oxygen.

2. Boot you favorit Linux Distro (Sorry Win-Users for you this story ends here (or you grab CygWin)):
Install the following tools:

  • convert: Chances are high it's already installed

  • icotool: if not installed and not available for your distro (icoutils)

3. Copy the images:
Copy images the images the with the various sizes we need to a directory and given them eaually starting names. In my case these are:

  • icon_16_16.png

  • icon_32_32.png

  • icon_48_48.png

4. Create .ico-File:
The ico-File needed by RCP has to hold 6 images (32bit and 8bit).
So we need to convert the images to 8bit ones:
tom@vinea:icons>

mkdir target

# Workaround because image magick doesn't seem to reduce colors when
# converting from png to png
convert -colors 256 icon_16_16.png target/tmp.gif
convert -colors 256 target/tmp.gif target/icon_16_16_8bit.png

convert -colors 256 icon_32_32.png target/tmp.gif
convert -colors 256 target/tmp.gif target/icon_32_32_8bit.png

convert -colors 256 icon_48_48.png target/tmp.gif
convert -colors 256 target/tmp.gif target/icon_48_48_8bit.png

Copy the original icons over to target-dir:
tom@vinea:icons>
cp -f icon_16_16.png target
cp -f icon_32_32.png target
cp -f icon_48_48.png target

Create the .ico-File using icotool:
tom@vinea:icons>
icotool -c target/icon_16_16_8bit.png target/icon_16_16.png \
target/icon_32_32_8bit.png target/icon_32_32.png \
target/icon_48_48_8bit.png target/icon_48_48.png \
> target/icon.ico

Here's the full script for reference:
#!/bin/sh

rm -rf target
mkdir target

# Workaround because image magick doesn't seem to reduce colors when
# converting from png to png
convert -colors 256 icon_16_16.png target/tmp.gif
convert -colors 256 target/tmp.gif target/icon_16_16_8bit.png

convert -colors 256 icon_32_32.png target/tmp.gif
convert -colors 256 target/tmp.gif target/icon_32_32_8bit.png

convert -colors 256 icon_48_48.png target/tmp.gif
convert -colors 256 target/tmp.gif target/icon_48_48_8bit.png

# Copy images
cp -f icon_16_16.png target
cp -f icon_32_32.png target
cp -f icon_48_48.png target

icotool -c target/icon_16_16_8bit.png target/icon_16_16.png \
target/icon_32_32_8bit.png target/icon_32_32.png \
target/icon_48_48_8bit.png target/icon_48_48.png \
> target/icon.ico

Friday, June 22, 2007

Why do developers need Code-Snippets

Many of you may already have noticed that we started a Snippet collection for JFace-Elements like the one you know from SWT. Yesterday I updated the wiki-page to show all snippets with a short description.

Are Snippets only something we provide because we love our users soooo much? To me the answer is no.

There are multiple reasons why snippets are a great thing:

  • Help for users to get started easily (Users)

  • Useful templates for users to log bugs (Users/Developers)
    Without a reproduceable testcase it is very hard to track bugs. Be prepared that JFace-Devs will insist even more on Snippets because now you have starting points to create Snippets easily. Insisting on snippets we often found out that we are not the one to blame but someone different (including the bugreporter itself)

  • Useful to mark bugs as invalid (Developers)

  • Useful to check no regressions are introduced (Developers)
    We also added snippets to our collection although their Classes are deprecated because this way we had an easy test case that we don't break any backwards contract. In our case we did this for TableTreeViewer

  • Development example for new features (Developers)
    A good example is Snippet026 which was used to develop Cell-Navigation and Editor-Activation throughout the whole 3.3 cycle.

  • Advertise new API

Tuesday, June 12, 2007

I'm perFLEXed

Yesterday Adobe released it's Flex 3 and naturally I went there to see this new cool technology. I downloaded it and got an Eclipse with a GUI-Builder to create SWF-Applications. The nice thing is that Flex uses an XML-Format to store the GUI, ... .



I guess you know what's coming now, don't you? I sat down and looked closer to find out that it would be fairly easy to write a Transformer to convert this in my custom EXSWT-Format used to build SWT/JFace-GUIs. After about 2 hours I now have a minimal XSLT-Stylesheet transforming MXML-Elements (currently mx:label, mx:combo, mx:button) to EXSWT and feeding it into my EXSWT-Lib. The only problem are different heights and widths needed by SWT and SWF but those are minor problems.

Here's the original Flex-Project View one can export as an SWF-Application:



And here the one displayed using SWT-Widgets:



This is only a simple example but it shows how easy it is to define highlevel dialects on top of EXSWT which is a more or less lowlevel XML-Language.

You can as always get the sources from my SVN-Repository and I have once more prepared a .psf-File for all Subclipse users.

Splash-Screen and Threads

Today I thought it would make my application look much more professional if the login-dialog is part of the splash-screen. The new extension point added in eclipse makes this possible and even provided an template implementation I could use without problem.

The process is straight forward:
  • Create a Product Configuration (or use your existing one)
  • Switch to the Splash-Tab
  • Select in the Customization-Section the Interactive-Template
  • Switch to the Overview and "Launch an Eclipse application"
You are done. That was easy, wasn't it? To make this work you wouldn't need any help. The tricky thing I faced starts now. In my case I'm authentificating using a database-server and to not block the UI im doing this in a seperate thread and showing a ProgressBar in the mean while.

When the application starts up the splash should look like this:

And while Logging into Database:

So the first action is to add the "Check Login"-Label and the Progress bar like this:

private Label progressLabel;
private ProgressBar progressBar;

/** many lines of code */

private void createProgressInfo() {
progressLabel = new Label(fCompositeLogin,SWT.NONE);
progressLabel.setText("Überprüfung läuft");
GridData data = new GridData();
data.horizontalIndent = F_LABEL_HORIZONTAL_INDENT - 50;
progressLabel.setLayoutData(data);
progressLabel.setVisible(false);

progressBar = new ProgressBar(fCompositeLogin,SWT.NONE|SWT.INDETERMINATE);
data = new GridData(SWT.NONE, SWT.NONE, false, false);
data.widthHint = F_TEXT_WIDTH_HINT;
data.horizontalSpan = 2;
progressBar.setLayoutData(data);
progressBar.setVisible(false);
}

private void toggelCheckProgress(boolean state) {
progressLabel.setVisible(state);
progressBar.setVisible(state);
fCompositeLogin.layout();
}


We initially set the those two widgets invisible and show them later when we start the authentification. To make this easy we add helper method to turn the visibility on and off.

The next part is to modify the handleButtonOKWidgetSelected()-method like this:

private volatile int loginStatus = -1;

/** many lines of code */

private void handleButtonOKWidgetSelected() {
final String username = fTextUsername.getText();
final String password = fTextPassword.getText();

toggelCheckProgress(true);

Thread t = new Thread() {

public void run() {
if( login(username,password) ) {
loginStatus = 1;
} else {
loginStatus = 2;
}
}
}
t.start();
}

The content of the method is straight forward. It starts a thread and executes a potentially long running task in our case login(String,String). Our task is now to sync back to the gui-thread and:
  1. Proceed with start up (hiding the login details from the splash-screen)
  2. Display Login-Failure to the user
Normally you do this using Display#(a)syncExec() but that's not available in the splash-screen. The work-around I used as you see above is setting a special variable named loginStatus. The trick is now that you add checks for this variable to the Event-Loop method which looks like this afterwards:

private void doEventLoop() {
Shell splash = getSplash();
while (fAuthenticated == false) {
if (splash.getDisplay().readAndDispatch() == false) {
if( loginStatus == 1 ) {
loginSuccess();
} else if( loginStatus == 2 ) {
loginFailure();
}

splash.getDisplay().sleep();
}
}
}


Your are nearly done now the only two methods missing are:

private void loginSuccess() {
toggelCheckProgress(false);
fCompositeLogin.setVisible(false);
fAuthenticated = true;
loginStatus = -1;
}

private void loginFailure() {
toggelCheckProgress(false);
loginStatus = -1;
MessageDialog.openError(
getSplash(),
"Authentification failed",
"Your username or password was wrong");
}


Well that's all. You are done and have a splash screen who is authenticating without blocking the UI-thread without Display#(a)syncExec. I need to thank Kim for pointing me to this solution and she promised that there will be added API in 3.4 to make this more easier.

Saturday, June 09, 2007

Things are evolving

It's been now a few months I started reworking my the first draft implementation which already provided many interesting things but there was still Java-Code needed to bind-POJOs and to interact with the Datasource.

What's new:
  • XBL (XmlBindingLanguage) to connect your model with the help of the databinding framework to SWT-Widgets
  • Switched to work on top of the Eclipse-Command-Framework
  • Added themeing framework
  • API cleanup
  • Refactoring, refactoring
I still see this as a research project which is not useable for production in the near future but if the community likes what I'm doing here these are the next things on my list:
  • implement INSERT/UPDATE/DELETE on top of the COMMAND-Framework
  • evolve XBL (new features, refactoring, ...)
  • Transformer-Framework for EXSWT and XBL (use a highlevel dialect translated to those low-level ones)
  • Implement new provider which is able to fetch XBL and EXSWT files via HTTP. This would make changing the GUI without the need to redeploy your application to the client (what a cool feature RAP without the Browser :-)
  • Refactoring, Refactoring, ....
If you are interested you can checkout the current project from my companies SVN-Repository. When using the Subclipse-Plugin I've added you a .psf-File to the project helping you to easily checkout all needed projects from SVN.

Friday, March 16, 2007

New JFace API is in place

After ~8 months of work we checked in the new JFace-API with many exciting features in to CVS just a few hours ago. To see how powerful this new API is take a look at the this snippet. Test it and report us back problems if there are any.

Wednesday, March 07, 2007

XSWT, Databinding, ... => DataForms

It's sometime ago I started rewriting the current XSWT implementation from David Orme. Now I saw the Blog from Alex about Databinding and thought it's time to let you all know that I'm working on a set of plugins which have the following focus:
  • Use XSWT (or any other Definition Language) to create the GUI
  • Dynamically link together Forms (Master/Detail, ...) using Eclipse Extension Points
  • Provide an XML-Dialect to define the binding and data retrieval in XML
Although it's still a long way to go and many many things are not as smooth as they could be but I finally have something to present using a small application.

Here's a screenshot of the small application. Currently you can only add items to it but that's working really fine.


The key point is how this all works technically and what should I say it simply works. There are still about 200 lines of code needed to make this work because the binding has to be done manually at the moment. Go and get the code from our companies SVN-Repository and give me feedback, I've added a projectSet.psf so you can easily check out all needed modules.

The next things I want to address are the following:
  • Databinding:
    • Adding missing features (Deletion, ...)
    • XML-Language for Data-Retrieval and Binding (XBL)
    • Finishing Event System (= needed for scripting support)
    • Finalizing/Stabiallizing ExtensionPoints and API
    • Adding API for a highly Configurable Privileges System
    • Provide XDOM (= DOM you know from Browsers to easily script Dataforms)
  • EXSWT:
    • Transformation Chain (other highlevel dialects transformed to EXSWT which near to SWT/JFace)
    • Work on XSD-Schema and XML-Editor with Live-Preview
    • WYSIWYG-Editor
Did I spark your interest? Give me your comments, I know there are other projects around with a fairly similar focus but some of the features are unique to this idea I think but are essential for datacentric applications.

Oh in the end I have had written a design document which is not up-to-date but provide informations where I'm coming from and where I'm going to.

Tuesday, March 06, 2007

EclipseCon community awards

I just read the post from Alex about the community awards and couldn't believe finding my name under the ones finally winning the award. Thanks to everybody who voted for me. Now I'm even more disappointed that I couldn't afford attending the eclipse con but maybe I can make it to the Eclipse Summit Europe.

Saturday, March 03, 2007

My first commit

What a great day my first commit to the eclipse cvs-repository. It's a small one patching JavaDoc for a not working API but it's a start. Here's the patch.

Friday, January 26, 2007

TableViewers and Nativelooking Checkboxes

Many of us have faced the same problem that if you needed to use checkboxes in your TableViewer/TreeViewer they look not native because they are pictures. I had some free minutes and thought that it's time to create a LabelProvider which is able to create platform look-and-feel images out of the box. It's not 100% native but it's not far away. The trick is to automatically create screenshots from CheckBox-Buttons and use them. This way the checkboxes look native on all platforms and correspond to the current look and feel of the platform.

Here's the code if someone is interested:


package at.bestsolution.jface.viewers;

import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.ColumnViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Shell;

public abstract class EmulatedNativeCheckBoxLabelProvider
extends ColumnLabelProvider
{
private static final String CHECKED_KEY = "CHECKED";
private static final String UNCHECK_KEY = "UNCHECKED";

public EmulatedNativeCheckBoxLabelProvider(ColumnViewer viewer) {
if( JFaceResources.getImageRegistry().getDescriptor(CHECKED_KEY) == null ) {
JFaceResources.getImageRegistry().put(UNCHECK_KEY,
makeShot(viewer.getControl().getShell(),false));
JFaceResources.getImageRegistry().put(CHECKED_KEY,
makeShot(viewer.getControl().getShell(),true));
}
}

private Image makeShot(Shell shell, boolean type) {
Shell s = new Shell(shell,SWT.NO_TRIM);
Button b = new Button(s,SWT.CHECK);
b.setSelection(type);
Point bsize = b.computeSize(SWT.DEFAULT, SWT.DEFAULT);
b.setSize(bsize);
b.setLocation(0, 0);
s.setSize(bsize);
s.open();

GC gc = new GC(b);
Image image = new Image(shell.getDisplay(), bsize.x, bsize.y);
gc.copyArea(image, 0, 0);
gc.dispose();

s.close();

return image;
}

public Image getImage(Object element) {
if( isChecked(element) ) {
return JFaceResources.getImageRegistry().
getDescriptor(CHECKED_KEY).createImage();
} else {
return JFaceResources.getImageRegistry().
getDescriptor(UNCHECK_KEY).createImage();
}
}

protected abstract boolean isChecked(Object element);
}

I haven't really tested this (currently only on WinXP) but I suppose it's working on all platforms. You can also get the code from my svn-repository which holds some other interesting utilities and viewer classes.

Thursday, January 18, 2007

The new faces of JFace (part2)

The last time I showed you the new programming model we introduced to JFace to make it feel like programming SWT.

But has this been the only reason that we decided to add this new API beside the existing one (ILabelProvider, ITableLabelProvider, IColorProvider, ITableColorProvider, IFontProvider, ITableFontProvider) .
Sure enough it was not the only reason. First of all many newcomers have been confused by all those NON MANDATORY interfaces to control different aspects of items and we faced the problem that whenever SWT-Provided a new feature e.g. Owner Draw Support in 3.2 we would have to add a new interface type and to support new features e.g. ToolTip support for table/tree cells we also had to provide a new interface. We decided that this is not the way to go for the future.

So we sat down and thought about a complete new structure below JFace tree and table support. We added the idea of rows (ViewerRow) and cells (ViewerCell) abstracting common things provided of TreeItem and TableItem and completely hiding the widget specific things into this class. The abstraction level this provided to us made it possible to provide a common class named ColumnViewer.

So what have we learned so far:
  1. We don't need all those interfaces any more
  2. We have a new abstraction level for column based viewers (ViewerRow, ViewerCell)
  3. We have a new base class named ColumnViewer
So you may ask what you should use if you can't use the old interfaces any more. Well the answer is ColumnLabelProvider or if you want to implement the whole cell behaviour your own it's abstract base class named CellLabelProvider. The new ColumnLabelProvider combines all currently know interfaces (ILabelProvider, IFontProvider, IColorProvider). There's no base class visible to you supporting ITable*-interfaces because those interface put limitations to tables I'll show you later let's now explore how to use the new ColumnLabelProvider interface and how it is used.

TableViewer viewer = new TableViewer(parent,SWT.FULL_SELECTION);

TableViewerColumn column = new TableViewerColumn(viewer,SWT.NONE);
column.setLabelProvider(new StockNameProvider());
column.getColumn().setText("Name");

TableViewerColumn column = new TableViewerColumn(viewer,SWT.NONE);
column.setLabelProvider(new StockValueProvider());
column.getColumn().setText("Modification");

public class StockNameProvider extends ColumnLableProvider {
@Override
public String getText(Object element) {
return ((Stock)element).name;
}
}

public class StockValueProvider extends ColumnLabelProvider {

@Override
public String getText(Object element) {
return ((Stock)element).modification.doubleValue() + "%";
}

@Override
public Color getForeground(Object element) {
if( ((Stock)element)modiciation.doubleValue() < 0 ) {
return getDisplay().getSystemColor(SWT.COLOR_RED);
} else {
return getDisplay().getSystemColor(SWT.COLOR_GREEN);
}
}
}

You may now argue that this was easier with the old ITable*-API and you are true but from the point of reusability this version is much more flexible and you can reuse your LabelProviders for many different TableViewers because they don't hold any index informations. Another thing is that you needed a bunch of custom code if you wanted the columns to be reordable with the old API this is not an issue any more with the new API because the LabelProvider is directly connected to the column and moves with it.

Next time I'll continue with some nice new LabelProvider features like ToolTip support and OwnerDraw.

Friday, January 05, 2007

What items are visible in Table/Tree?

Have you often asked yourself which items are visibile in Table/Tree. Maybe you need these informations to dispose system-resources (e.g. if you have different images in every row you can easily run out of system resources).

What to do? There's no SWT-API available to calculate this information. So we need a bunch of custom code to make this work. The following codefragements are only a first rough test case and they need JFace from 3.3M4 and a patch from bug 151295 .

Step 1: Setup the infrastructure
- Event object to inform consumers about the change of visible rows
public class ViewerRowStateChangedEvent extends EventObject {
private static final long serialVersionUID = 1L;

public ArrayList itemsHidden;

public ArrayList itemsVisible;

public ViewerRowStateChangedEvent(Object source) {
super(source);
}
}
- ViewerRowStateChangeListener for consumers to implement
public interface ViewerRowStateChangeListener {
public void itemStateChangedListener(ViewerRowStateChangedEvent event);
}
Step 2: Create an AbstractClass implementing the visible row logic on base of ViewerRow and the new API whichn will hopefully added by bug 151295.
public abstract class AbstractViewerRowVisibilityStateSupport {
private ArrayList currentItems = new ArrayList();

private ColumnViewer columnViewer;

private ListenerList listenerList = new ListenerList();

public AbstractViewerRowVisibilityStateSupport(ColumnViewer columnViewer) {
this.columnViewer = columnViewer;

Listener l = new Listener() {
public void handleEvent(Event event) {
ArrayList list = recalculateVisibleItems();
ArrayList itemsVisible = new ArrayList();

Iterator it = list.iterator();
Object obj;

while( it.hasNext() ) {
obj = it.next();

if( ! currentItems.remove(obj) ) {
itemsVisible.add(obj);
}
}

ArrayList hiddenItems = currentItems;
currentItems = list;

if( itemsVisible.size() > 0 || hiddenItems.size() > 0 ) {
if( ! listenerList.isEmpty() ) {
ColumnViewer v;
v = AbstractViewerRowVisibilityStateSupport.this.columnViewer;

ViewerRowStateChangedEvent ev = new ViewerRowStateChangedEvent(v);
ev.itemsHidden = hiddenItems;
ev.itemsVisible = itemsVisible;

Object[] listeners = listenerList.getListeners();
ViewerRowStateChangeListener l;

for( int i = 0; i < listeners.length; i++ ) {
l = (ViewerRowStateChangeListener)listeners[i];
l.itemStateChangedListener(ev);
}
}
}
}
};
addListeners(getControl(),l);
}

protected abstract void addListeners(Scrollable control, Listener l);

protected abstract ViewerRow getTopRow();

protected Scrollable getControl() {
return (Scrollable)columnViewer.getControl();
}

public void addItemStateListener(ViewerRowStateChangeListener listener) {
listenerList.add(listener);
}

private ArrayList recalculateVisibleItems() {
ArrayList list = new ArrayList(100);
ViewerRow topRow = getTopRow();

if( topRow != null ) {
int totalHeight = getControl().getClientArea().height;
int itemHeight = topRow.getBounds().height;

list.add(topRow);

int tmp = topRow.getBounds().x+itemHeight;
// tmp += itemHeight;
// this would be more precise but half rows
// would be marked as non-visible

// run until we reached the end of the client-area
while( tmp < totalHeight ) {
tmp += itemHeight;
topRow = topRow.getNeighbor(ViewerRow.BELOW, false);

if( topRow == null ) {
break;
}

list.add(topRow);
}
}

return list;
}
}
We simply listen to events who can change the items shown in the Scrollable make a diff to the last state and inform all consumers about the change.

But what are the events how modify the items shown? This is delegated to specialized classes for Table and Tree because those may differ from control to control.

Step 3: Provide specialized implementation for Tree and Table
- An implementation for SWT-Table
public class TableViewerRowVisibilityStateSupport extends
AbstractViewerRowVisibilityStateSupport {
public TableViewerRowVisibilityStateSupport(TableViewer columnViewer) {
super(columnViewer);
}

protected void addListeners(Scrollable control, Listener l) {
control.getVerticalBar().addListener(SWT.Selection, l);
control.addListener(SWT.Resize, l);
control.addListener(SWT.KeyUp, l);
}

protected ViewerRow getTopRow() {
Table t = (Table)getControl();
int index = t.getTopIndex();
TableItem topItem = t.getItem(index);

if( topItem != null ) {
return (ViewerRow) topItem.getData(ViewerRow.ROWPART_KEY);
}

return null;
}
}
- An implementation for SWT-Tree
public class TreeViewerRowVisibilityStateSupport extends
AbstractViewerRowVisibilityStateSupport {
public TreeViewerRowVisibilityStateSupport(TreeViewer columnViewer) {
super(columnViewer);
}

protected void addListeners(Scrollable control, Listener l) {
control.getVerticalBar().addListener(SWT.Selection, l);
control.addListener(SWT.Resize, l);
control.addListener(SWT.MouseUp, l);
control.addListener(SWT.KeyUp, l);
}

protected ViewerRow getTopRow() {
TreeItem topItem = ((Tree)getControl()).getTopItem();
if( topItem != null ) {
return (ViewerRow) topItem.getData(ViewerRow.ROWPART_KEY);
}
return null;
}
}
As said this is very rough first draft how this could work. I'll post this got some coments about possible issues this could provoke.

Wednesday, December 06, 2006

the new Faces of JFace (part 1)

Some of you may have noticed JFace provides new API to streamline JFace usage and to support new features like:
  • custom owner draw
  • tooltips for tree/table-cells
  • keyboard editing support for viewers
To provide all those new features we (Tod Creasey, Boris Bokowski and me) decide to refactor the underling JFace code, provide new classes and to move things from specialized classes to more generice ones. The central of the whole refactoring brought up a whole bunch of new classes where the most important are the following:
  • ColumnViewer: Provides all things common to Viewers who deal with the concept of columns and rows
  • ViewerRow: Represents a row in the column viewer and wraps TableItem and TreeItem from SWT
  • ViewerCell: Represents one cell in the table/tree

We decide to create those new classe because it gives us the possibility to push as much code in the widget independent ColumnViewer instead of the widget centric classes derived from it.

All the changes mentionned above might not be interesting to you because most of the time you want use them directly so let's take a look at the more interesting things from a user point of view.

The most interesting thing to most of you might be that JFace has now adopted the programming style from SWT and JFace-Coding feels now much more like SWT-Coding. Look at the next few lines and you understand what I mean:

Composite parent = ...;
TableViewer v = new TableViewer(parent);
v.getTable().setLinesVisible(true);

TableViewerColumn vColumn = new TableViewerColumn(v,SWT.NONE);
vColumn.getColumn().setWidth(200);
vColumn.setLabelProvider(new MyLabelProvider());

v.setContentProvider(new MyContentProvider());
v.setInput(model);

Wednesday, November 15, 2006

Are OS-ToolTips not flexible enough / Use JFace ToolTips

How often have you wished that OS-Tooltips would provide more functionalities like MultiLine-Text, Background-Color, Embedded Images, ... . The time you faced those problems is over since today where JFace ToolTips are available to the public.

Usage is fairly straight forward:
Text text = new Text(parent,SWT.BORDER);
text.setText("Hello World");
DefaultToolTip toolTip = new DefaultToolTip(text);
toolTip.setText("Hello World");
toolTip.setBackgroundColor(parent.getDisplay().getSystemColor(SWT.COLOR_RED));

Easy isn't it but there are many many more things available to you. You can add any control you want to the tooltip by subclassing org.eclipse.jface.window.ToolTip and providing your own implementation for createToolTipContentArea.
public class MyToolTip extends ToolTip {
private Shell parentShell;

public MyToolTip(Control control) {
super(control);
this.parentShell = control.getShell();
}

protected Composite createToolTipContentArea(Event event, Composite parent) {
Composite comp = new Composite(parent,SWT.NONE);
comp.setLayout(new FillLayout());

Button b = new Button(comp,SWT.PUSH);
b.setText("Say Hello");
b.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
hide();
MessageBox box = new MessageBox(parentShell,SWT.ICON_INFORMATION);
box.setMessage("Hello World!");
box.setText("Hello World");
box.open();
}
});
}
}

Usage is the same than before:
Text text = new Text(parent,SWT.BORDER);
text.setText("Hello World");

MyToolTip myTooltipLabel = new MyToolTip(text);
myTooltipLabel.setShift(new Point(-5, -5));
myTooltipLabel.setHideOnMouseDown(false);
myTooltipLabel.activate();

The small difference is that you configure the tip to not hide when the user clicks into the ToolTip the ToolTip-Code needs to ensure the hiding by its own by calling hide().

There are other nice feature available to you:

  • possibility to define delay before the ToolTip pops up

  • possibility to define delay how long the ToolTip is shown

  • possibility to define the pop up position

  • possibility to define a shift like shown in the code above

  • possibility to completely control the popup position by overloading ToolTip#getLocation(Point,Event)

  • configure whether the bounds of the display and/or monitors should be:

    • setRespectDisplayBounds(boolean)

    • setRespectMonitorBounds(boolean)



Tuesday, September 26, 2006

at.bestsolution.jface32-0.0.1 released

As promised I have released my JFace backport from 3.3 just a view days after 3.2 came out. You can get the ready plugin from my companies SVN-Repository.

Saturday, September 16, 2006

JFace and features not part of Eclipse

Today I sit down and thought it is time to provide features JFace won't provide (some of them are marked as later or are refused) in an own project named at.bestsolution.jface. Well 2 features I already had around somewhere in my workspaces:
  • TableViewer working with multiple instances of the same object
  • Dialog which could restore it's values because it's only hidden
All people interested in the work could as always fetch the sources from my companies SVN-Server. At the moment this plugin only depends on JFace but it's likely that this will change in future and org.eclipse.swt.nebula and at.bestsolution.jface32 will be added as dependencies. I haven't thoroughly tested because I never use it in my daily work so the QA is your part ;-)

Tuesday, September 12, 2006

How much can happen within one week?

Back from holiday I see JFace's new viewer API evolving from week to week and getting more stable and userfriendly but some parts are not as smooth as they should be some work has to spent. The most markable things changed is that the caching of column-indices is gone completely.

Another important news is that Boris sorted out some VIRTUAL-Table bugs. As of this writing I have integrated all those changes to at.bestsolution.jface32 project. Simply fetch all changes from my companies (BestSolution Systemhaus GmbH) public SVN-Repository.

At the moment all things from JFace HEAD are integrated and because Tod, Boris and I all think that my widget-independency work can go in as is I've already integrated it. JFace integration will follow at the beginning 3.3M3 timeframe.

And last but not less important a new API-function has been added to set multiple filters at once this should avoid us to use all those nasty hacks to avoid flickering, ... .

I think I'll release a first version of the plugin with M2.

Finally to a new JFace project providing snippets to all show common JFace. The new project has been approved by the eclipse PMC and can be found in eclipse's CVS with contribution from Tod and myself

Sunday, August 27, 2006

Viewer-Features form 3.3 in Eclipse-3.2

There are many many exciting features added in 3.3 cycle to jface but many people need a solution now and not in 3.3-Timeframe. I myself have a need for some of those feature so I started back-porting them which is at the moment not really hard because at the moment JFace-Viewers doesn't use anything not available in 3.2-sources.

I decided to make them publicly available because I think many also have a need for them. So for those who want to use exciting features added in 3.3 can fetch the sources at in my companies SVN-Directory. Don't forget to do your own QA to ensure everything is working as expected. If you find bugs you can file them to my companies bug-tracking system at http://phpot.bestsolution.at/mantis/login_page.php and I'll try to incooperate them in eclipse bug-tracking system providing a patch for current HEAD-Trunk of CVS.

Before releasing a first version I'm trying to get the patches to 3.3-CVS-HEAD:

The current features/bug fixes against 3.2:

This project will only continue until 3.3 is out afterwards people will need to recompile their code replacing at.bestsolution.jface32.* through org.eclipse.jface.*.

At the end please note the following things:
  • this is nothing official from Eclipse and I only thought that there are maybe others who have the same needs than I have.
  • There's a great likelihood that new API added in 3.3 at some point will change while 3.3 evolves so the API you find now is maybe a subject to change in the next release.

Thursday, August 24, 2006

Viewers-Refactoring

Banging my head against Viewers since about a month we finally reached a state where it's possible the first time to use viewers with different widget than org.eclipse.swt.widgets.Table. At the moment only TableViewer supports widget independency but once this is in head. TreeViewer is the next to come.