UFaceKit – 2 (new) interesting features in latest nightly

Like the title already says the latest nightly build comes with 2 amazing features one already part of the code base since a long time (XPath to traverse the UI-DOM) the second one just hit SVN (plugable model-item mapping). I’m going to discuss them in short in this blog posting.

Plugable Model-Item-Mapping

You probably ask yourself. What’s this and why should I care. To understand the problem you must know how the current JFace-Viewers are working internally and which problems this can cause.

JFace-Viewers store strong Java-References between the domain model and the SWT-Item using TableItem#setData(Object)/TreeItem#setData(Object) and additionally if you turn on hashlookup (to speed up setting of selections, …) in an internal HashTable.

The problem with this is that your domain model stays resident in memory as long as the TableItem exists even if no one really needs it until you e.g. want to update the table-item. This implementation detail of current JFace-Viewers makes the use of CDO in UI less ideal because CDO can’t apply it’s clever memory management because your UI code holds strong references into your domain-object graph.

One can overcome this problem in JFace-Viewer world as well by writing some clever Content- and LabelProviders (The implementation is also available from our repository but not part of a build yet) but in my opinion not ideal from a users point of view. Moreover I think a viewer framework should have the possibility to plug-in “your” mapping strategy (e.g. provided by the domain-technoloy project you are using) according to the use case.

That’s why I decided that this feature has to be part of the core UFaceKit-Viewer-Framework and the support of it has just hit the SVN-Repository and is part of my shining new nightly athena build.

/*******************************************************************************
 * Copyright (c) 2010, BestSolution.at and others
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Tom Schindl <tom.schindl@bestsolution.at> - Initial API and implementation
 *******************************************************************************/
package org.eclipse.ufacekit.ui.jface.cdo.viewers;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import org.eclipse.emf.cdo.CDOObject;
import org.eclipse.emf.cdo.common.id.CDOID;
import org.eclipse.emf.cdo.view.CDOView;
import org.eclipse.ufacekit.ui.jface.viewers.Viewer;
import org.eclipse.ufacekit.ui.jface.viewers.mapping.AbstractTableItemModelMapping;

public class CdoModelTableItemMapping<ModelElement extends CDOObject,Item extends org.eclipse.swt.widgets.Item> extends AbstractTableItemModelMapping<ModelElement, Item> {
  private CDOView view;
  private Map<CDOID, Item> map;

  public CdoModelTableItemMapping( CDOView view, Viewer<ModelElement, ?> viewer) {
    super(viewer);
    this.view = view;
    this.map = new HashMap<CDOID, Item>();
  }

  @Override
  public void associate(ModelElement model, Item item) {
    if (map.containsKey(model.cdoID())) {
      throw new IllegalStateException("This mapping only supports one instance of a model element");
    }
    item.setData(model.cdoID());
    map.put(model.cdoID(), item);
  }

  @Override
  public void disassociate(Item item) {
    map.remove(item.getData());
    item.setData(null);
  }

  @Override
  public void disassociateAll() {
    map.clear();
  }

  @SuppressWarnings("unchecked")
  @Override
  public ModelElement lookup(Item item) {
    return (ModelElement) view.getObject( (CDOID)item.getData() );
  }

  @Override
  public Collection<Item> lookup(ModelElement element) {
    Item item = (Item) map.get(element);
    if( item != null ) {
      return Collections.singleton(item);
    }

    return Collections.emptyList();
  }
}

This implementation is completely untested but I think you should get the point because we are not restoring the domain object but look it up from our local CDOView we can once more rely on CDOs clever memory management. Nice isn’t it?

XPath support to traverse your UI-DOM

This feature is part of UFaceKit sources since day one in the SVN-Repository but I added it not into the first nightly builds. I think the XPath support for UIs is a fairly unique feature of UFaceKit and the reflective API makes it extremly easy to implement it operations like e.g. applying changes to a many widgets.

package testproject;

import java.util.Iterator;

import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.equinox.app.IApplication;
import org.eclipse.equinox.app.IApplicationContext;
import org.eclipse.ufacekit.core.xpath.common.XPathContext;
import org.eclipse.ufacekit.core.xpath.common.XPathContextFactory;
import org.eclipse.ufacekit.ui.core.UIDesktop;
import org.eclipse.ufacekit.ui.core.UIFactory;
import org.eclipse.ufacekit.ui.core.UIRunnable;
import org.eclipse.ufacekit.ui.core.UIWidget;
import org.eclipse.ufacekit.ui.core.controls.UIApplicationWindow;
import org.eclipse.ufacekit.ui.core.controls.UIButton;
import org.eclipse.ufacekit.ui.core.controls.UIComposite;
import org.eclipse.ufacekit.ui.core.controls.UIInputField;
import org.eclipse.ufacekit.ui.core.controls.UILabel;
import org.eclipse.ufacekit.ui.core.controls.UIApplicationWindow.ApplicationWindowUIInfo;
import org.eclipse.ufacekit.ui.core.controls.util.Rectangle;
import org.eclipse.ufacekit.ui.core.form.UIGridFormBuilder;
import org.eclipse.ufacekit.ui.core.layouts.GridLayoutData;
import org.eclipse.ufacekit.ui.core.layouts.UIFillLayout;
import org.eclipse.ufacekit.ui.core.layouts.GridLayoutData.Alignment;
import org.eclipse.ufacekit.ui.core.xpath.UFacekitXPathContextFactory;
import org.eclipse.ufacekit.ui.jface.core.JFaceFactory;
import org.eclipse.ufacekit.ui.uform.UBeanForm; 

public class Application implements IApplication {

  public Object start(IApplicationContext context) throws Exception {
    JFaceFactory factory = new JFaceFactory();
		
    final UIDesktop desktop = factory.newDesktop();
    desktop.runWithDefaultRealm(new UIRunnable<UIDesktop>() {
      @Override
      protected IStatus run(UIDesktop arg0) {
        createUI(arg0);
        return Status.OK_STATUS;
      }
    });
    desktop.run();

    return IApplication.EXIT_OK;
  }
	
  private void createUI(UIDesktop d) {
    UIFactory<?> f = d.getFactory();
		
    UIFillLayout l = f.newFillLayout();
    final UIApplicationWindow window = f.newApplicationWindow(d, new ApplicationWindowUIInfo(l));
    window.setText("UFaceKit - Hello World");
		
    UIComposite comp = f.newComposite(window, new UIComposite.CompositeUIInfo(null, f.newGridLayout(1)));
    UILabel label = f.newLabel(comp, new UILabel.LabelUIInfo(GridLayoutData.fillHorizontalData()));
    label.setText("Form Example");
		
    UBeanForm form = new UBeanForm(f);		
		
     UIGridFormBuilder builder = UIGridFormBuilder.newInstance(comp, GridLayoutData.fillHorizontalData(), form);
     builder.newLabel("Firstname");
     builder.newInputField(UIInputField.InputFieldBindingInfo.newTextFieldInfo(form.detailValue(Person.FIRSTNAME, String.class)) );
		
      builder.newLabel("Surname");
      builder.newInputField(UIInputField.InputFieldBindingInfo.newTextFieldInfo(form.detailValue(Person.SURNAME, String.class)) );
		
      UIButton button = f.newButton(comp, new UIButton.ButtonUIInfo(new GridLayoutData(Alignment.END, Alignment.DEFAULT)));
      button.setText("Save");
      button.setActionRunnable(new UIRunnable<UIButton>() {

      @Override
      protected IStatus run(UIButton b) {
        XPathContextFactory<UIWidget> factory = UFacekitXPathContextFactory.newInstance();
        XPathContext context = factory.newContext(b.getParent());
				
        Iterator<?> iterator = context.iterate("UIComposite/UIInputField");
        boolean flag = true;
        while( iterator.hasNext() ) {
          UIInputField field = (UIInputField) iterator.next();
          if( field.getText().equals("") ) {
            flag = false;
            field.getStyle().setBackground("#ff0000");	
          } else {
            field.getStyle().setBackgroundColor(null);
          }
        }
				
        if( ! flag ) {
          b.getDesktop().showErrorDialog(
            b.getWindow(), 
            "Validation Error", 
            "Required fields are marked read", 
            new Status(IStatus.ERROR, Activator.PLUGIN_ID, ""),
            null );
        }
				
        return Status.OK_STATUS;
      }
    });
    window.open();
    window.setBounds(new Rectangle(500, 400, 400, 250));
  }

  public void stop() {
    // nothing to do
  }
}

This creates an UI like this:

Consumeable UFaceKit-Builds for SWT

This a remarkable day in the history of UFaceKit because since today we can provide consumeable nightly builds to install or create a target platform.

A big thank you to Chris Aniszczyk and Pascal Rapicault who helped me in getting my build in shape – I hope to meet you at EclipseCon and pay you some beers.

Let’s take a look how one get his/her hands dirty on UFaceKit for SWT:

  • Create a new target platform with the following steps
    • Open the target platform properties page
    • Select the RCP-With-Source-Template
    • Select Add… on the page to add additional plugins and select “Software Site” on the opened dialog
    • Click once more the Add…-Button
    • Insert as URL http://download.eclipse.org/ufacekit/updates-nightly/
    • Check the top most checkbox in the tree shown and VERY IMPORTANT UNCHECK “Include required software”
    • Enter a name for the target
    • Activate the new target

    Now we are ready to create our first UFaceKit-Application:

    • Select Plugin-Project
    • Give the Project a Name
    • Uncheck “This plug-in will make contributions to the UI”
    • Select the Headless template
    • Open the MANIFEST.MF and add the following Bundles:
      • org.eclipse.ufacekit.ui.jface.core
      • org.eclipse.ufacekit.ui.core
      • org.eclipse.ufacekit.core.util
      • org.eclipse.swt;bundle-version
      • org.eclipse.ufacekit.core.ubean

The project is now configured appropriately. Now let’s create an HelloWorld-Application. Open the Application.java-File and make it look like this:

import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.equinox.app.IApplication;
import org.eclipse.equinox.app.IApplicationContext;
import org.eclipse.ufacekit.core.util.DataRunnable;
import org.eclipse.ufacekit.ui.core.UIDesktop;
import org.eclipse.ufacekit.ui.core.UIRunnable;
import org.eclipse.ufacekit.ui.core.controls.UIApplicationWindow;
import org.eclipse.ufacekit.ui.core.controls.UIButton;
import org.eclipse.ufacekit.ui.core.controls.UIButton.ButtonUIInfo;
import org.eclipse.ufacekit.ui.core.controls.UIApplicationWindow.ApplicationWindowUIInfo;
import org.eclipse.ufacekit.ui.core.controls.util.Rectangle;
import org.eclipse.ufacekit.ui.core.layouts.UIFillLayout;
import org.eclipse.ufacekit.ui.jface.core.JFaceFactory;

public class Application implements IApplication {

  public Object start(IApplicationContext context) throws Exception {
    JFaceFactory f = new JFaceFactory();
		
    final UIDesktop dk = factory.newDesktop();
		
    UIFillLayout layout = f.newFillLayout();
    ApplicationWindowUIInfo d = new ApplicationWindowUIInfo(layout);
    final UIApplicationWindow window = f.newApplicationWindow(dk, d);
    window.setText("UFaceKit - Hello World");
    
    ButtonUIInfo d1 = new UIButton.ButtonUIInfo(null)
    UIButton button = f.newButton(window, d1);
    button.setText("Click me");
    button.setActionRunnable(new UIRunnable<UIButton>() {
      @Override
      protected IStatus run(UIButton arg0) {
        DataRunnable<Boolean> run = new DataRunnable<Boolean>() {
          public void run(Boolean arg0) {
            if( arg0 ) {
              System.out.println("Good Boy/Girl");
            } else {
              System.out.println("Too bad.");
            }
          }
        }
        dk.showQuestionDialog(window, "Question", "Do you like UFaceKit?", run);
        return Status.OK_STATUS;
      }
    });
    window.open();
    window.setBounds(new Rectangle(500, 400, 400, 100));
    dk.run();

    return IApplication.EXIT_OK;
  }

  public void stop() {
    // nothing to do
  }
}

I think the code is quite self-explanatory but there are some remarkable things you should have noticed:

  1. No SWT-Imports
  2. Beside the factory creation all code is UI-Technology agnostic (and it’s quite easy to get rid of this by using Declarative Services because UFaceKit is designed with the idea of “UI as a service”)
  3. Blocking operations like showQuestionDialog() get passed a callback – this is needed because one of the target platforms is Web-Browsers who have no concept of an event loop

I’m going to transfer this content over to UFaceKit-Wiki in the following days and write other tutorials on how to exploit other features of UFaceKit like:

  • Declarative Styling
  • Applying XPaths to your UI-Dom
  • Built-in databinding

One more note: The UFaceKit-SWT port is feature complete and stable since about 6 months and there hardly any todos before releasing 1.0.0 of the SWT-Port so it’s really useable and I hope that some people give it a try and report back problems so that I can release a very stable build.

The only real blocker for a 1.0.0 release is that the test suite is not completed yet and I won’t release before having a complete test suite which helps me to avoid regressions in future versions and ensure compability of future platform ports to the API contract defined.

QxViewers – The first steps towards a UFaceKit-Webport

I’ve talked a lot (1,2,3,4) lately about my progress on QxWT towards release 1.0.0.2. Now that all the codeing is done more or less I can restart working on my master plan:

Single APIing for Web and Desktop programing use Eclipse Libraries like Eclipse Databinding – In short UFaceKit

Having completed the SWT-Port more or less since some time, having portotype implementations for Qt and Swing (to check if the concept can be transfer easily to other Desktop-UI-Frameworks) the missing thing is a port for a Web-UI-Framework.

The problem is in the Web/GWT-UI space that GWT itself comes with a very restricted UI library missing important things like Table/TreeTables for example and one needs an external framework. I couldn’t find a framework that fully satisfied my needs (most important a clear, reliable license scheme allowing me to use it in commercial applications) so I started my work on QxWT and now that this framework enters in a stable phase I can once more shift sources back to UFaceKit.

To get to a first UFaceKit-Webport I have the following workitems:

  1. Done: Eclipse Databinding 3.5.2 for GWT – (no builds until Eclipse 3.5.2 itself is declared)
  2. In progress: UFaceKit-Viewers for QxWT-Structured-Controls – this gives automagically support for ufacekit-viewers-databinding because UFaceKit-Viewers are split in generic and widget-toolkit parts and ufacekit-viewers-databinding only talks to the generic part
  3. Not started: UFaceKit-Wrapper for QxWT – the thoughest thing might be to write write the native layouts

As the blog title suggests I’ve made good progress today and I can show off the first demo results from this work. The implementations are not finished yet but for people who are familiar with JFace-Viewers lines like the following might look familiar (beside the obvious Java5 features of UFaceKit-Viewers):

/** 
 * Combo Viewer Setup
 */
ComboViewer<Person, Collection<Person>> viewer = new ComboViewer<Person, Collection<Person>>();
viewer.setContentProvider(new CollectionContentProvider<Person>());
viewer.setLabelConverter(new LabelConverter<Person>() {
  @Override
  public String getText(Person element) {
    return element.getFirstname() + ", " + element.getLastname();
  }
});

Collection<Person> ps = new ArrayList<Person>();
for (int i = 0; i < 300; i++) {
  ps.add(new Person("First " + i, "Last" + i, i));
}
viewer.setInput(ps);

/**
 * TableViewerSetup
 */
TableViewer<Person, Collection<Person>> tableViewer = new TableViewer<Person, Collection<Person>>();
tableViewer.setContentProvider(new CollectionContentProvider<Person>());

TableViewerColumn<Person> col = new TableViewerColumn<Person>(tableViewer, "Firstname");
col.setLabelConverter(new LabelConverter<Person>() {
  @Override
  public String getText(Person element) {
    return element.getFirstname();
  }
});

// ...
Collection<Person> ps = new ArrayList<Person>();
for (int i = 0; i < 300; i++) {
  ps.add(new Person("First " + i, "Last" + i, i));
}
tableViewer.setInput(ps);

/**
 * TreeViewer setup
 */
TreeViewer<Person, Collection<Person>> viewer = new TreeViewer<Person, Collection<Person>>();
viewer.setLabelConverter(new LabelConverter<Person>() {
  @Override
  public String getText(Person element) {
    return element.getLastname() + ", " + element.getFirstname() + " ("+ element.getAge() + " years)";
  }
});

viewer.setContentProvider(new ITreeContentProvider<Person, Collection<Person>>() {
  public Collection<Person> getChildren(Person parentElement) {
    return parentElement.getChildren();
  }

  public Person getParent(Person element) {
    return element.getParent();
  }

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

  public void dispose() {
  }

  public Collection<Person> getElements(Collection<Person> inputElement) {
    return inputElement;
  }

  public void inputChanged(IViewer<Person> viewer, Collection<Person> oldInput, Collection<Person> newInput) {
  }
});
viewer.setInput(setupData());

The widgets look like this:

I’d like to point out once more that UFaceKit is structured in a way that you can use all parts independently from each other. So if you are in need to a Databinding-Framework for GWT and a light weight Domain-Technology like UBean then you can use them, you only need Java5 ready viewers for SWT and Databinding integration, … all of them are useable standalone with minimal dependencies.

Beside that I’m still struggeling with setting up a build for the SWT-Port on the Eclipse-Servers so that people can start consume stuff from there but that’s a problem many small project suffer.

ESE 09 – My Slides

Back from ESE 09 – once more a nice event and got to know even more people. I was quite busy this year taking part in 5 Sessions so a short review from my side:

EMF Tutorial

I think Ed, Eike and me did a good job though there’ve been too many people so working through examples was not possible. The next time (EclipseCon 09) we need probably skip some parts. Beside that I think I could demostrate fairly good how flexible Eclipse-Databinding can be used in RCP-Applications.

E4 Symposia

Boris and me had many people in our symposia and I think we could explain those people our vision of e4 and how the internals are structured. Boris gave a short overview about the 20 things, how DI is working using the IEclipseContext and WebUI and I talked about our EMF-driven Application model and our flexible rendering story.
I think people start to get our point that E4 is a flexible Application Framework they can build any UI-Application (the UI technology doesn’t really matter) if you start accepting that rendering is just another service like it is the selection, logging, … service.

What’s in E4

This was an overview talk given by Boris, Kai, Hallvard, Yves and me on e4. I gave a short overview about the reasoning and how the application platform is designed around our EMF-Model. I think we could at least reduce the confusion in the community.

e4 in Detail – The model workbench and it’s possibilities

In this talk I tried to explain in more detail how the e4-application platform uses EMF, DI and the rendering services to create a flexible UI-Application. The slides are available here.

UFaceKit – A uniform UI development model for different UI and Runtime platforms

This was my last talk where I explain the reasons and various cool things you can with UFaceKit which since a week before can be optionally backed up by an Ecore-Model so that Application can developed (and of needed deployed) using EMF-Technologies. The slides are here.

Build for Databinding4GWT available

Following an discussion on stackoverflow.com where people stated that we don’t offer binary builds I sat down yesterday and wrote a short ant script to create binary builds people can consume out of the box easily.

I didn’t use the Eclipse build system but a simple ant script which fetches the sources from SVN and builds them. I also could figure out how to promote this stuff to the eclipse-download servers so I released them to our official page.

You can fetch the latest binary gwt builds from here or find a link to them on our release page. I’m going to add binary builds for other UFaceKit stuff hopefully soon though I’d prefer to put them on Eclipse.org Servers if I can figure out how to do it.

Please don’t forget to give us feedback on problems you encounter. We can’t fix problems you are not reporting to us.

UFaceKit and Java5-Viewers

I had some time today to once more work a bit on UFaceKit and it’s viewer implementation and I think I added a feature which makes sense to show of.

I was working on the rewrite of an old application we wrote 3 years ago to use the Highlevel-Databinding-API I’m developing as part of the UFaceKit-Project. One of the common problems I had in my RCP-Application was that SWT-Combo-Widgets are not friendly if you have many entries because native keyboard selection is not supported on by default.

Inspired by a proposed component for JFace I wrote such a Viewer-Implementation from scratch which can be used like this:

/**
  * @param args
  */
public static void main(String[] args) {
  final Display d = new Display();
  Shell shell = new Shell(d);
  shell.setLayout(new GridLayout());
		
  Text text = new Text(shell,SWT.BORDER);
  text.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
		
  final Collection<Person> persons = new ArrayList<Person>();
  persons.add(new Person(1, "Tom"));
  persons.add(new Person(2, "Udo"));
  persons.add(new Person(3, "Bernd"));
  persons.add(new Person(4, "Tom"));
  persons.add(new Person(5, "Thomas"));
		
  final ProposalViewer<Person, Collection<Person>> viewer = 
    new ProposalViewer<Person, Collection<Person>>(text);

  viewer.setLabelConverter(new LabelConverter<Person>() {
    @Override
    public String getText(Person element) {
      return element.name + " ( ID-"+element.id+")";
    }
  });
  viewer.setContentProvider(
    new CollectionContentProvider<Person>()
  );
  viewer.setInput(persons);
  viewer.addSelectionChangedListener(
    new ISelectionChangedListener<Person>() {
      public void selectionChanged(
        SelectionChangedEvent<Person> event) {
          if( event.getSelection().isEmpty() ) {
            System.out.println("<Empty>");
          } else {
            for( Person p : event.getSelection() ) {
              System.out.println(p.name);
            }	
          }
      }
  });

  Button b = new Button(shell,SWT.PUSH);
  b.setText("Selection");
  b.addSelectionListener(new SelectionAdapter() {
    @Override
    public void widgetSelected(SelectionEvent e) {
      viewer.setSelection(
        new StructuredSelection<Person>(
          persons.iterator().next()
        )
      );
    }
  });
  shell.open();

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

There are some interesting things to notice about the Viewers and their implementation:

  • Java5 Language Features like Generics, for-loop above Selection
  • A generic interface definition making it widget-toolkit agnostic (I’ve already implemented some Swing, Qt, SWT, GWT, SmartGWT-Viewers)
  • Automatic Databinding support because it works on the Interface-Definition

How nice this new SWT-Proposal-Viewer integrates itself into my application take a look at this small screencast:

and for the readers of blog aggregators here as an image Screenshot