»
 
 
Articles in Code
May 06, 2010
»

Drawing Custom NSButton in Cocoa

In my spare time I’m designing my first “real” Cocoa appplication and I’ve decided to be rather open about this app and it’s implementation details prior and after the release.

I’m not going to disclose anything about the app just yet. Well, maybe only that it will come with open-source cloud component deployable (or automatically deployed) to AppEngine.

While I’m still siting in Photoshop trying to come up with interface design, I wanted to create actual clickable buttons from mockup. Here is more or less step-by-step guide of drawing them.

But now about NSButton custom drawing.

buttons.png

In Cocoa NSButton is subclass of NSControl which are drawn using NSCells. So we need a new NSButtonCell subclass to do our drawing:

#import "AMDarkButtonCell.h"

@implementation AMDarkButtonCell

- (void)drawImage:(NSImage*)image 
        withFrame:(NSRect)frame 
           inView:(NSView*)controlView
{
}

- (NSRect)drawTitle:(NSAttributedString*)title 
          withFrame:(NSRect)frame 
             inView:(NSView*)controlView
{
}

- (void)drawBezelWithFrame:(NSRect)frame 
                    inView:(NSView *)controlView
{
}

@end

My not so great custom button consists of only of image and bezel so I’m not going to override -drawTitle:withFrame:inView:.

Lets start with bezel. It’s composed from:

  • Outer gradient stroke
  • Background gradient
  • Border dark stroke
  • Inner light stroke

Cocoa provides:

  • NSBezierPath for drawing, stroking and clipping-to rounded rectangles
  • NSGradient for drawing multiple point gradients

That’s basically all we need to draw whole background composite.

- (void)drawBezelWithFrame:(NSRect)frame inView:(NSView *)controlView
{
  NSGraphicsContext *ctx = [NSGraphicsContext currentContext];

  CGFloat roundedRadius = 3.0f;

  // Outer stroke (drawn as gradient)

  [ctx saveGraphicsState];
  NSBezierPath *outerClip = [NSBezierPath bezierPathWithRoundedRect:frame 
                                                            xRadius:roundedRadius 
                                                            yRadius:roundedRadius];
  [outerClip setClip];

  NSGradient *outerGradient = [[NSGradient alloc] initWithColorsAndLocations:
                               [NSColor colorWithDeviceWhite:0.20f alpha:1.0f], 0.0f, 
                               [NSColor colorWithDeviceWhite:0.21f alpha:1.0f], 1.0f, 
                               nil];

  [outerGradient drawInRect:[outerClip bounds] angle:90.0f];
  [outerGradient release];
  [ctx restoreGraphicsState];
 
  // Background gradient

  [ctx saveGraphicsState];
  NSBezierPath *backgroundPath = 
    [NSBezierPath bezierPathWithRoundedRect:NSInsetRect(frame, 2.0f, 2.0f) 
                                    xRadius:roundedRadius 
                                    yRadius:roundedRadius];
  [backgroundPath setClip];

  NSGradient *backgroundGradient = [[NSGradient alloc] initWithColorsAndLocations:
                                    [NSColor colorWithDeviceWhite:0.17f alpha:1.0f], 0.0f, 
                                    [NSColor colorWithDeviceWhite:0.20f alpha:1.0f], 0.12f, 
                                    [NSColor colorWithDeviceWhite:0.27f alpha:1.0f], 0.5f, 
                                    [NSColor colorWithDeviceWhite:0.30f alpha:1.0f], 0.5f, 
                                    [NSColor colorWithDeviceWhite:0.42f alpha:1.0f], 0.98f, 
                                    [NSColor colorWithDeviceWhite:0.50f alpha:1.0f], 1.0f, 
                                    nil];

  [backgroundGradient drawInRect:[backgroundPath bounds] angle:270.0f];
  [backgroundGradient release];
  [ctx restoreGraphicsState];

  // Dark stroke

  [ctx saveGraphicsState];
  [[NSColor colorWithDeviceWhite:0.12f alpha:1.0f] setStroke];
  [[NSBezierPath bezierPathWithRoundedRect:NSInsetRect(frame, 1.5f, 1.5f) 
                                   xRadius:roundedRadius 
                                   yRadius:roundedRadius] stroke];
  [ctx restoreGraphicsState];

  // Inner light stroke

  [ctx saveGraphicsState];
  [[NSColor colorWithDeviceWhite:1.0f alpha:0.05f] setStroke];
  [[NSBezierPath bezierPathWithRoundedRect:NSInsetRect(frame, 2.5f, 2.5f) 
                                   xRadius:roundedRadius 
                                   yRadius:roundedRadius] stroke];
  [ctx restoreGraphicsState];        

  // Draw darker overlay if button is pressed

  if([self isHighlighted]) {
    [ctx saveGraphicsState];
    [[NSBezierPath bezierPathWithRoundedRect:NSInsetRect(frame, 2.0f, 2.0f) 
                                     xRadius:roundedRadius 
                                     yRadius:roundedRadius] setClip];
    [[NSColor colorWithCalibratedWhite:0.0f alpha:0.35] setFill];
    NSRectFillUsingOperation(frame, NSCompositeSourceOver);
    [ctx restoreGraphicsState];
  }
}

To draw image I’ve chosen to use it only as a mask for 2 draw operations:

  • Image as solid color
  • It’s shadow as solid color
- (void)drawImage:(NSImage*)image withFrame:(NSRect)frame inView:(NSView*)controlView
{
  NSGraphicsContext *ctx = [NSGraphicsContext currentContext];
  CGContextRef contextRef = [ctx graphicsPort];
  
  NSData *data = [image TIFFRepresentation]; // open for suggestions
  CGImageSourceRef source = CGImageSourceCreateWithData((CFDataRef)data, NULL);
  if(source) {
    CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, 0, NULL);
    CFRelease(source);
    
    // Draw shadow 1px below image
    
    CGContextSaveGState(contextRef);
    {
      NSRect rect = NSOffsetRect(frame, 0.0f, 1.0f);
      CGFloat white = [self isHighlighted] ? 0.2f : 0.35f;
      CGContextClipToMask(contextRef, NSRectToCGRect(rect), imageRef);
      [[NSColor colorWithDeviceWhite:white alpha:1.0f] setFill];
      NSRectFill(rect);
    } 
    CGContextRestoreGState(contextRef);
    
    // Draw image
    
    CGContextSaveGState(contextRef);
    {
      NSRect rect = frame;
      CGContextClipToMask(contextRef, NSRectToCGRect(rect), imageRef);
      [[NSColor colorWithDeviceWhite:0.1f alpha:1.0f] setFill];
      NSRectFill(rect);
    } 
    CGContextRestoreGState(contextRef);        
    
    CFRelease(imageRef);
  }
}

Ok, now we have drawing code in place, lets move to Interface Builder and wire up this custom NSButtonCell for buttons which should be drawn with this style.

Add some buttons:

button_window.png

Set Image attribute in “Button Attributes” panel:

button_attrs.png

Click one more time on button in window to select it’s Cell Identity:

cell_window.png

And set cell Class to our custom implementation:

cell_attrs.png

That’s it.

Download example Xcode project (64Kb)

May 06, 2010
»

hi, i make macintosh software.

mininfo-1.gif

Few weeks ago I finished my first Mac application — Saucejas.app. It is small (about 1000 lines of code) app which renders pretty image and video slideshow to secondary display using CoreAnimation.

saucejas-app.png

Application itself doesn’t have notably pretty UI or implementation. It was written basically in one day for use in Saucejas show as a informative & mood-setting background. Here is video featuring Saucejas and previous version of app in background which I wrote earlier in Processing.

Nevertheless it was a really good exercise for me and here are few things I’ve learned:

General tips

  • Use Bindings — they’re great!
  • Objective-C is not Java (I’m still primary a Java person), do not try to organize code in Java style
  • Xcode plist editor is not the easiest to use tool for NSArray of NSDictionaries of 2-3 key-value pairs — consider different format for large lists or create custom editor for plist

Scaling NSImage (proportionally)

NSSize SCProportionalSizeForTargetSize(NSSize size, NSSize target) {
   if(NSEqualSizes(size, target)) {
      return size;
   }

   float widthFactor  = target.width  / size.width;
   float heightFactor = target.height / size.height;

   float scalingFactor;

   if (widthFactor < heightFactor) {
      scalingFactor = widthFactor;
   } else {
      scalingFactor = heightFactor;
   }

   return NSMakeSize(size.width  * scalingFactor, size.height * scalingFactor);
}

@implementation NSImage (SCAdditions)

  - (NSImage *)imageByScalingProportionallyToSize:(NSSize)target
  {
     NSSize size = self.size;

     NSSize proportionalSize = SCProportionalSizeForTargetSize(size, target);
     NSImage *scaledImage = [[NSImage alloc] initWithSize:proportionalSize];
     [scaledImage setCacheMode:NSImageCacheNever];

     [scaledImage lockFocus];
     [[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh];
     [self drawInRect:NSMakeRect(.0f, .0f, proportionalSize.width, proportionalSize.height) 
             fromRect:NSMakeRect(.0f, .0f, size.width, size.height) 
            operation:NSCompositeSourceOver 
             fraction:1.0f];
     [scaledImage unlockFocus];

     return [scaledImage autorelease];
  }

@end
  • Drawing to NSImage can also be done in background thread
  • Cache mode must be NSImageCacheNever otherwise all scaled NSImages are cached
  • Set graphics context interpolation mode to High for better results

    h2. Disabling screensaver
// Periodically called by NSTimer
- (void)disableScreenSaverTimerDidUpdate:(NSTimer *)timer
{
   UpdateSystemActivity(OverallAct);
}

Getting all connected displays

#import <IOKit/graphics/IOGraphicsLib.h>
  
@implementation SCDisplay

+ (NSArray *)displays
{
   NSMutableArray *displays = [NSMutableArray array];
   
   CGDirectDisplayID *onlineDisplays = malloc(
      sizeof(CGDirectDisplayID) * 
      kSCMaxDisplayCount);

   CGDisplayCount displayCount;
   CGDisplayErr listErr = CGGetActiveDisplayList(kSCMaxDisplayCount, 
                                                 onlineDisplays, 
                                                 &displayCount);

   if(listErr == kCGErrorSuccess) {
      for(int i=0; i<displayCount; i++) {
         CGDirectDisplayID displayId = onlineDisplays[i];

         CGRect bounds = CGDisplayBounds(displayId);
         
         NSString *name = nil;
         io_service_t port = CGDisplayIOServicePort(displayId);
         
         CFDictionaryRef infoRef = IODisplayCreateInfoDictionary(port, 
                                   kIODisplayOnlyPreferredName);
         
         if(infoRef != NULL) {
            CFDictionaryRef productNameDictionaryRef = CFDictionaryGetValue(infoRef, 
                                                       CFSTR(kDisplayProductName));
            if(productNameDictionaryRef != NULL) {
               CFIndex count = CFDictionaryGetCount(productNameDictionaryRef);
               if(count > 0) {
                  const void **values = (const void **)malloc (sizeof(void *) * count);
                  CFDictionaryGetKeysAndValues(productNameDictionaryRef, NULL, values);
                  
                  CFTypeRef nameRef = (CFTypeRef)values[0];
                  name = [NSString stringWithString:(NSString *)nameRef];
                  
                  free(values);
               }
            }
            CFRelease(infoRef);
         }
         
         if(!name) {
            name = @"Unknown";
         }
         
         SCDisplay *display = [[SCDisplay alloc] initWithDirectDisplayId:displayId 
                                                                  bounds:bounds 
                                                                    name:name];
         [displays addObject:display];
         [display release];
      }
   } else {
      NSLog(@"%@ %s • CGGetOnlineDisplayList failed: %u", [self className], _cmd, listErr);
   }
   free(onlineDisplays);
   
   return displays;
}

@end

Capturing and releasing a display

// CGDirectDisplayID displayId_; from CGGetActiveDisplayList

// Caputure
CGDisplayErr captureErr = CGDisplayCapture(self.displayId);
if(captureErr == kCGErrorSuccess) {
   self.captured = YES;
} else {
   NSLog(@"%@ %s • CGDisplayCapture failed: %u", [self className], _cmd, captureErr);
}

// Release
CGDisplayErr err = CGDisplayRelease(self.displayId);
if(err == kCGErrorSuccess) {
   self.captured = NO;
   released = YES;
} else {
   NSLog(@"%@ %s • CGDisplayRelease failed: %u", [self className], _cmd, err);
}

Full-screen NSWindow on captured display

CGFloat mainDisplayHeight = [[NSScreen mainScreen] frame].size.height;
CGRect displayBounds = self.display.bounds; // from CGDisplayBounds

// secondary displays are "below" main screen
NSRect rect = NSMakeRect(displayBounds.origin.x, 
                         mainDisplayHeight - displayBounds.size.height, 
                         displayBounds.size.width, 
                         displayBounds.size.height);

NSWindow *window = [[NSWindow alloc] initWithContentRect:rect
                                               styleMask:NSBorderlessWindowMask
                                                 backing:NSBackingStoreBuffered
                                                   defer:NO];

[window setLevel:CGShieldingWindowLevel()];
  • See -[NSScreen frame] & -[NSScreen visibleFrame] maybe they calculate correct rect for window
  • To get NSScreen from displayId I assume -[NSScreen deviceDescription] can be used

References:

March 18, 2010
»

Gaeds 1.2 Changes #5 - Query property names

Gaeds is my tiny AppEngine for Java low-level DatastoreService wrapper. This is fifth post about new features that will form 1.2 final release. The previous posts are: Inheritance, GaedsKey, Configuration and Converters.

Gaeds Model attribute to Entity property mapping is done using @Property annotation. By default attribute (class field) names are used also as Entity property names but this can be customized using @Property(name = "differentName"). So lets consider a small example:

@Model
public class User {

  @PrimaryKey
  private GaedsKey<User> key;
  
  @Property(name = "fullName")
  private String login;
  
  @Property
  private String password;

}

User user = new User();
user.setLogin("ampatspell");
user.setPassword("zeebaneighba");

sess.put(user);

Entities will have the following properties:

User user = sess.get(user);
Entity entity = Gaeds.asModel(user).getEntity(); // accessing Entity directly

assertEquals("ampatspell", entity.getProperty("fullName"));
assertEquals("zeebaneighba", entity.getProperty("password"));

And now the breaking change: query filters and sort parameters now use attribute names instead of entity property names. This means that to filter by login attribute, the "login" must be used, not "fullName":

User user;

// SELECT * from User WHERE fullName = ampatspell
user = sess.query().model(User.class).eq("login", "ampatspell").singleResult();
assertNotNull(user);

// SELECT * from User WHERE password = zeebaneighba
user = sess.query().model(User.class).eq("password", "zeebaneighba").singleResult();
assertNotNull(user);

Please check your DAO’s for queries which filter by custom property names and update them to attribute names.

I’m sorry that I found this bug only now. The behavior could be left as is but I feel this breaking change is needed.

March 12, 2010
»

Gaeds 1.2 Changes #4 - Inheritance

Gaeds is my tiny AppEngine for Java low-level DatastoreService wrapper. This is forth post about new features that will form 1.2 final release.

The first three are:

Both new features are related to inheritance handling so lets start with 3 models:

@Model
public class Transaction {
}

@Model(discriminator = "handled")
public class HandledTransaction extends Transaction {
}

@Model(discriminator = "amount")
public class AmountTransaction extends HandledTransaction {
}

Those three models shares single kind of "Transaction".

Query keysOnly() implementation change

Now it is possible to query for keysOnly() while getting (more) correct model classes:

Iterable<HandledTransaction> iterable = getSession().query().
  model(HandledTransaction.class).
  eq("failed", false).
  eq("otherTransactionKey", null).
  limit(limit).
  keysOnly().
  asIterable();

for (HandledTransaction transaction : iterable) {
  // ...
}

Prepared query is:

SELECT __key__ 
  FROM Transaction 
  WHERE _discriminator = handled 
    AND failed = false 
    AND otherTransactionKey = NULL

While keysOnly() query doesn’t return actual type (In this case it can also be AmountTransaction — see next feature why) at least it is not base Transaction instance anymore.

Here is all three queries:

session.query().model(<Model>.class).keysOnly().asList();
Model and returned instance class DatastoreService Query
Transaction.class SELECT __key__ FROM Transaction
HandledTransaction.class SELECT __key__ FROM Transaction WHERE _discriminator = handled
AmountTransaction.class SELECT __key__ FROM Transaction WHERE _discriminator = amount

Of course this also works for SELECT * FROM queries with the difference that returned instance clases will be actual sub-classes:

session.query().model(<Model>.class).asList();
Model and returned instance class DatastoreService Query
Transaction.class SELECT * FROM Transaction
HandledTransaction.class SELECT * FROM Transaction WHERE _discriminator = handled
AmountTransaction.class SELECT * FROM Transaction WHERE _discriminator = amount

Discriminator as list property (when needed)

As we saw previously to query any HandledTransaction (this includes all HandledTransaction and also AmountTransaction entities) we need to query with two different "_discriminator" values. This is not supported in AppEngine Datastore. Instead if given model is subclass of model which is already a model what has discriminator value set, the "_discriminator" property becomes list property listing all discriminators:

Model Discriminators
Transaction.class
HandledTransaction.class “handled” — String
AmountTransaction.class [“amount”, “handled”] — List<String>

This allows to query any entity that is HandledTransaction or AmountTransaction.

Upgrading Entities (if needed, optional)

To take advantage of those new features, enties what has parent entity with discriminator set (in this example only AmountTransaction), needs to be migrated.

To migrate just get or query them followed by put afterwards:

Session session = getSession();

// Query will work because this was the _discriminator value that was saved before
// SELECT * FROM Transaction WHERE _discriminator = amount
Iterable<AmountTransaction> list = session.query().
  model(AmountTransaction.class).
  limit(500).
  asIterable();

// gaeds will re-populate _discriminator value
session.put(list);
March 10, 2010
»

Gaeds 1.2 Changes #3 - GaedsKey<T>

Gaeds is my tiny AppEngine for Java low-level DatastoreService wrapper. This is third post in three post series about new features that will form 1.2 final release.

The first two are:

Google AppEngine Datastore Entity primary keys are Key instances. Each Key is composed from String kind and either long id or String keyName. Also Key can be encoded as String and then decoded back to Key instance using KeyFactory.

For me the most important new gaeds feature is GaedsKey<T> generic class that wraps Key and respective model class. It is impossible to create GaedsKey with mismatched model class (see notes below about inheritance).

GaedsKey interface is declared like this:

public interface GaedsKey<T> extends Comparable<GaedsKey<T>> {

  Key getKey();

  Class<? extends T> getModelClass();

  <R> GaedsKey<R> cast(Class<R> type);

  <P> GaedsKey<P> getParent(Class<? extends P> model);

  String encode();
  
  // and few other methods

}

To create GaedsKey from Key or from encoded String GaedsKeyFactory must be used:

GaedsKeyFactory kf = session.key(); // or @Inject GaedsKeyFactory directly

// from String or Key
GaedsKey<Task> key = kf.create(string, Task.class);       // from encoded datastore key
GaedsKey<Task> key = kf.create(datastoreKey, Task.class); // from datastore key

// Root keys
GaedsKey<Task> key = kf.create(Task.class, "something");  // with keyName
GaedsKey<Task> key = kf.create(Task.class, 102l);         // with id

// Keys with parent Key or GaedsKey
GaedsKey<Task> key = kf.create(parent, Task.class, "something"); // with keyName
GaedsKey<Task> key = kf.create(parent, Task.class, 102l);        // with id

First two DatastoreKeyFactory#create calls ensures that provided key matches model class by comparing Key kind to kind declared for model. If kind don’t match, GaedsKeyRuntimeException is thrown. This behavior ensures that encoded keys coming from web request matches expected model kind. If kinds doesn’t match, exception is thrown before Datastore get/put/query operation (fail fast). By using Key directly get operations would quite possibly just fail with ClassCastException after touching Datastore.

String string = keyToString(createKey("User", 101));

// Creates "User(101)" key
Key key = stringToKey(string);
// Throws ModelNotFoundException or ClassCastException after get.
Task task = sess.get(key);

// Throws GaedsKeyRuntimeException
GaedsKey<Task> key = kf.create(string, Task.class);
// Not reached
Task task = sess.get(key);

Gaeds models has direct relationship to Entity keyNames. It is possible to use GaedsKey<T> without any changes to Model classes but more beneficial they are if @PrimaryKey, @ParentKey and all @Property Key declarations also are declared as GaedsKey<T>. Lets consider 2 models – Person and Task:

@Model
public static class Person {

  @PrimaryKey
  private GaedsKey<Person> key;

  // getters & setters
  
}

@Model
public static class Task {

  @PrimaryKey
  private GaedsKey<Task> key;

  @Property
  private String title;

  @Property
  private GaedsKey<Person> owner;

  // getters & setters

}

And some straight-forward operations on them:

// First request

// create person
Person person = new Person();
sess.put(person);

// create task with person set as owner
Task task = new Task();
task.setTitle("Hello world");
task.setOwner(person.getKey());
sess.put(task);

String encoded = task.getKey().encode();

// Second request

// Create Task key from encoded string (will throw if encoded Key is not for Task kind)
GaedsKey<Task> taskKey = sess.key().create(encoded, Task.class);

// get Task, get Person
Task task = sess.get(taskKey);
Person person = sess.get(task.getOwner());
Task{
  key=GaedsKey{Task(2) com.ampatspell.gaeds.test.Task}, 
  title='Hello world', 
  owner=GaedsKey{Person(1) com.ampatspell.gaeds.test.Person}
}

Person{
  key=GaedsKey{Person(1) com.ampatspell.gaeds.test.Person}
}

As we can see, the GaedsKey Session API is the same as for Key and that we needed to access GaedsKeyFactory only once — for string decoding. While GaedsKey API is identical to Key based there are a bit more going on behind the scenes.

Not only GaedsKey creation is validated to conform model-kind relationship, also all model attributes that has GaedsKey type are validated before put and after get, query.

Put

Lets consider a bit different example involving gaeds model inheritance:

@Model
public static class Person {

  @PrimaryKey
  private GaedsKey<? extends Person> key;

}

@Model(discriminator = "administrator")
public static class Administrator extends Person {
}

We have a new class Administrator which extends Person. Gaeds implements inheritance by transparently adding "_discriminator" property to saved entities and because there is no way knowing which entity type is before getting actual Entity, GaedsKey can be created with incorrect key+type pair:

Administrator admin = new Administrator();
sess.put(admin);

Person person = new Person();
sess.put(person);

String key = person.getKey().encode();

// creating a key with Person Datastore Key and Administrator type
GaedsKey<Administrator> admin = kf.create(key, Administrator.class);

// Throws an GaedsKeyRuntimeException
sess.get(admin);

After Entity is retrieved from Datastore, its type is compared to required type for GaedsKey, if GaedsKey type is not the same or subclass of fetched Model’s class, GaedsKeyRuntimeException is thrown. This prevents getting ClassCastException or incorrect Model when sess.get returns. This also works for messed up GaedsKey unchecked casts:

@Model
public static class Task {

  // ...

  @Property
  private GaedsKey<Administrator> owner;

}
Person person = new Person();
sess.put(person);

// Unchecked cast
GaedsKey<Administrator> ownerKey = (GaedsKey) person.getKey();

Task task = new Task();
task.setOwner(ownerKey);

// throws GaedsKeyRuntimeException
sess.put(task);
com.ampatspell.gaeds.internal.key.GaedsKeyRuntimeException: 
  GaedsKey{ExamplePerson(2) com.ampatspell.gaeds.test.Person} model class is not assignable 
  to com.ampatspell.gaeds.test.Administrator

Also GaedsKey exposes cast(T targetType) method that allows up- and down-casting GaedsKey instances but it enforces that:

  • GaedsKey<Person> cannot be up-casted to GaedsKey<Administrator>
  • GaedsKey<Administrator> can be down-casted to GaedsKey<Person> and then up-casted back to GaedsKey<Administrator>

But note: it is possible to create GaedsKey<Administrator> with Person Key and Administrator type, save it in Datastore as GaedsKey<Administrator>. Try fetching entity using that GaedsKey before, only if fetch succeeds, we can be confident that given GaedsKey<Administrator> actually points to Administrator.

Get & Query

For gets and queries, model @Property attributes there is a bit different story. Lets start with creating Task with Zeeba as an owner instead of Administrator:

Entity task = new Entity("Task");
task.setProperty("owner", createKey("Zeeba", "fake"));
Key dsKey = ds.put(task);

GaedsKey<Task> key = kf.create(dsKey, Task.class);
Task model = sess.get(key);

assertTrue(Gaeds.asModel(model).getIncompatibleProperties().contains("owner"));
WARNING: Entity to model mapping cast errors
Model:  com.ampatspell.gaeds.test.Task
Entity: Task(1)
 * 'owner' 
      com.google.appengine.api.datastore.Key => 
      com.ampatspell.gaeds.GaedsKey com.ampatspell.gaeds.test.Administrator (Zeeba("fake"))

As we can see, this is also considered incompatibility because Session wouldn’t allow persisting GaedsKey<Zeeba> instead of GaedsKey<Administrator>. Useful for Entity migration.

API

  • All Session and Session#query() methods now takes either Key or GaedsKey
  • GaedsKeyFactory can be injected (@Inject) directly or accessed from Session#key()
  • GaedsBpSupport and GaedsDaoSupport inherits from AbstractGaedsSupport which also implements #key()

GaedsKey @Property, @PrimaryKey and @ParentKey can be declared as:

  • GaedsKey<Task>
  • GaedsKey<? extends Task>
  • GaedsKey<T> for generic classes (@Model class Task<T> { ... })

Release

I’ve migrated 10 or so models to GaedsKey for one of the projects I’m currently working on. All tests pass, everything looks fine but I’ll wait few more days before releasing 1.2 final.

March 09, 2010
»

Gaeds 1.2 Changes #2 - Converters

Gaeds is my tiny AppEngine for Java low-level DatastoreService wrapper. This is second post in three post series about new features that will form 1.2 final release.

Since first release of gaeds it has Model property value conversion support from-to Entity properties using Converter. There are few default converters and its easy to write additional ones. To declare that property needs converter, @Property annotation has type setting:

@Model
public class Something {

  @Property(type = Text.class)
  private String text;

}

For Something.text property before creating or updating Entity Converter<String, Text> will kick in and convert String value to Text and after get or query operations, the same converter will convert Text back to String and set it to model property.

But now lets consider the following property:

@Model
public class Something {

  @Property(field = Serializable.class, type = Blob.class)
  private Map<String, Integer> paymentDetails;

}

The Converter lookup is done using newly added @Property.field, if it is not provided, using actual field type. Because in this example @Property.field is set, the Converter<Serializable, Blob> is used instead of Converter<Map, Blob>.

Also see Gaeds 1.2-SNAPSHOT Changes #2 — Converters to learn about changes in Converter configuration.

March 04, 2010
»

Gaeds 1.2 Changes #1 - Configuration

Gaeds is my tiny AppEngine for Java low-level DatastoreService wrapper. I’ve started the work on version 1.2 what will have few nice additions but for now a small breaking change.

Previous versions was configured something like this:

public class GaedsModule extends AbstractModule {

  @Override
  protected void configure() {
    install(new GaedsBaseModule());
    
    bind(DatastoreService.class).annotatedWith(Gaeds.class).
      toProvider(DatastoreServiceProvider.class);
      
    bind(IncompatiblePropertiesLogger.class).
      to(IncompatiblePropertiesLoggerImpl.class);
  }

  @Provides
  @Singleton
  public List<Converter> provideConverters(
      @Named("default") List<Converter> defaultConverters) {
      
    return defaultConverters;
  }

  @Provides
  @ModelClasses
  @Singleton
  public List<Class<?>> provideModelClasses() {
    List<Class<?>> classes = new ArrayList<Class<?>>();

    classes.add(Zeeba.class);
    classes.add(Larry.class);

    return classes;
  }

}

Now configuration looks a lot better:

public class GaedsModule extends AbstractGaedsModelsModule {

  @Override
  protected void configureModels() {
    install(new GaedsBaseModule());

    bind(DatastoreService.class).annotatedWith(Gaeds.class).
      toProvider(DatastoreServiceProvider.class);
    
    bind(IncompatiblePropertiesLogger.class).
      to(IncompatiblePropertiesLoggerImpl.class);

    // Models

    model(Zeeba.class);
    model(Larry.class);

    // Converters

    useDefaultConverters(true);

    converter(SomeCustomConverter.class);
  }

}

In addition to to cleaner gaeds module, model classes and converters are bound using Multibinder so it’s perfectly fine to install multiple AbstractGaedsModule derived modules in one Guice Injector. There are only two things to keep in mind:

  • Model class and Converter duplicates are not allowed (guice will complain)
  • you may call useDefaultConverters(boolean) only once (in one AbstractGaedsModule)
March 04, 2010
»

Models Library Current Status

This post is about Models — the GWT library which hopefully will greatly help to build client-server communication, model properties-to-view binding and in application architecture in general by simplifying all boilderplate code needed to manage downloaded object graphs, presenting them to users and allowing users to act on them. What I have currently is initial implementation of KVC, KVO, Bindings, ForwardingModel, ListController, ObjectController and DataSource (with DataStore). Nothing are release ready. Controllers and DataSource are so badly implemented I’m a bit ashamed to show them.

The main purpose of post (aside from loud-thinking) is to try to get some feedback before API is locked. So if you have some comments, critique or just want to say hi, feel free to mail me to ampatspell@gmail.com.

Lets start from lowest level API — Key-Value Coding and Key-Value Observing (the terms come from Foundation framework in Cocoa). Basically this is easy to use property change observation and property value setting by name support for classes. In whole Model library context the classes are model objects either fetched from DataSource (server) or created locally.

Observing

The basic model property change observation looks like this:

User user = GWT.create(User.class);

ObserverRegistration registration = asReflection(user).observe("address.country", 
  String.class, 
  new Observer<String>() {
    public void onValueChange(KeyPath keyPath, Operation operation, IndexSet indexes, 
                              String oldValue, 
                              String newValue) {
      // called after value change
    }
  });

registration.remove();

ObservableReflection reflection = ((Observable) entry).getReflection();

Observer<T> callback is called when either of following are called:

  • user.setAddress(newAddress);
  • user.getAddress().setCountry("New country");

Callback is not called with initial value. See bindings why this is not needed.

Operation can be one of SET, INSERT, REMOVE, CLEAR and so on (SET for properties, others for List operations).

IndexSet is Range like class for defining List operation indexes (list.addAll(int index, E element) notifies observers about INSERT operation and IndexSet with single index).

asReflection(Object) is statically imported helper method what just casts user instance to Observable.

Value setting by keypath

The second low-level building block is Key-Value Coding that allows to set Observable object properties not only by using setters directly but also by key path. “address.country” in previous code snippet is key path.

Lets look at small example using the same user entity:

asReflection(user).setValueForKeyPath("address.country", "Zeebaburg");

this is the same as calling user.getAddress().setCountry("Zeebaburg") (with null checking) but can be used to dynamically set values for arbitrary keys (properties).

GWT.create

Of course for both of those things to work GWT generator is used. This is the reason why GWT.create() is required and so this code will only work in GWT runtime and unittests run with GWTTestCase. Currently I don’t have Java reflection and cglib based implementations for Observable but it can be created to support standalone JUnit unittests. For now I’m ignoring this while I’m not finished with API design.

Bindings

Next low-level API is Bindings. This feature allows to “bind” two targets (BindingTarget) so if one of them is changed, the other is notified with new optionally converted value. Also when binding is created initial value is set from “left” target to “right”.

Currently I have three notable binding targets:

  • ObservableTarget — for Observable models
  • HasValueTarget<T> — for view bindings
  • HasTextTarget — for view bindings

Others are easy to write.

As you might guessed this main purpose is to bind model properties to GWT Widgets. But actually it turns out that binding ObservableTarget to other ObservableTarget is as important as Widget targeted ones.

Lets look at two low-level Bindings usage examples:

Observable to Observable:

bindings.bind(
  observable(list, "selection", BlogEntry.class), 
  observable(selected, "content", BlogEntry.class)
);

Observable to HasValue

bindings.bind(
  observable(model, "key", String.class), 
  hasText(view.key())
);

// transformer is required because binding target types differ
// otherwide the code won't compile
bindings.bind(
  observable(model, "status", Integer.class), 
  hasText(view.status()), 
  new StatusToStringArrayTransformer()
);

"bindings" variable is instance of Bindings which allows to create and bind new bindings (and store them in DeferredCollection which allows concurrent modifications by deferring adds, removes, clear after iterator has finished) and unbind them with one bindings.clear() call.

The binding itself is created like this:

Binding<L, R> binding = new Binding<L, R>(left, right, transformer);
binding.bind();
// ...
binding.unbind();

Bindings and Binding by themselselves doesn’t require GWT.create at least now but ObservableTarget needs Observable of course.

Forwarding

The last low-level tool is Forwarding which is Observable proxy object that represents other Observable object while trying to be invisible for its clients. This is sometimes needed for current DataSource API implementation and I think this won’t change.

To create Model (Observable) and it’s Forwarding instance we start with 2 classes. GWT.create doesn’t take parameters so Forwarding must be additional class (or better yet interface. But for now it is class.)

public class BlogEntry implements IsModel {
  
  String title;
  
  public String getTitle() {
    return title;
  }
  
  public void setTitle(String title) {
    this.title = title;
  }
  
}

public class BlogEntryForwarding extends BlogEntry implements IsModel, IsForwarding {
}

Now we can GWT.create them and use:

BlogEntry model = GWT.create(BlogEntry.class);

BlogEntry proxy = GWT.create(BlogEntryForwarding.class);
// asForwarding is just "safe" cast to Forwarding w/ assert

Forwarding proxyForwarding = Forwardings.asForwarding(proxy);
proxyForwarding.setForwardingTarget(model);

// the same for model observers (except setForwardingTarget change)
Observables.asReflection(proxy).observe("title", String.class, new Observer<String>(){
  void onValueChange(KeyPath keyPath, Operation operation, IndexSet indexes, 
                     T oldValue, T newValue) {
                    
    // 1st property change
    assertEquals(null,       oldValue);
    assertEquals("Title #1", newValue);
    
    // 2nd property change
    assertEquals("Title #1", oldValue);    
    assertEquals("Title #2", newValue);
    
    // result of setForwardingTarget. Old model is _not_ notified about this change obviosly
    assertEquals("Title #2", oldValue);
    assertEquals("New Title", newValue);
  }
});

// model property change

model.setTitle("Title #1");
assertEquals("Title #1", proxy.getTitle());

// proxy property change

proxy.setTitle("Title #2");
assertEquals("Title #2", model.getTitle());

// proxy target change

BlogEntry newModel = GWT.create(BlogEntry.class);
newModel.setTitle("New Title");

proxyForwarding.setForwardingTarget(newModel);

And that’s it. We can move on to Model Controllers.

Model Controllers

Before we talk about Model controllers I need to draw a distinction between Observable and Model. First Model is Observable, second it has status represented as int bitfield (yes, not EnumSet or whatever. This I’m almost certain should be called premature optimization). Status is observable property.

Ok, finally model controllers.

Now API has 2 (really badly implemented) ModelControllers:

  • ListController
  • ObjectController

Both should be created using GWT.create to enable observation support for them.

ListController has “content” and “selection” (only single selection currently). Both properties are observable, both publicly available. When ListController content changes, selection is set to null.

ObjectController has observable “content” property.

// bind ListController.selection to ObjectController.content
bindings.bind(observable(list, "selection", BlogEntry.class), observable(selected, "content", BlogEntry.class));

// observe ObjectController.content changes
asReflection(selected).observe("content", BlogEntry.class, new Observer<BlogEntry>() {
  public void onValueChange(KeyPath keyPath, Operation operation, IndexSet indexes, 
                            BlogEntry oldValue, BlogEntry newValue) {

    if (newValue != null)
      ds.fetchDetails(newValue);
  }
});

Also both controllers allows Presenters (or whatever else) to add itself as Target. List controller target has callbacks to:

  • clear – called when list is cleared (maybe speeds up DOM modifications, haven’t checked)
  • create – called when Widget for new list controller entry should be created. It is passed to bind afterwards
  • bind – called when Widget needs to be rebound to new entry (only bound, no rebinding yet)
  • remove, insert and other things not exposed yet

Here is one ListController to UI Target implementation (declared in Presenter):

blogEntries.addTarget(new ListControllerTarget<BlogEntry, BlogEntryListView>() {

  public void clear() {
    view.clearListEntries();
  }

  public BlogEntryListView create() {
    return view.addListEntry();
  }

  public void bind(Bindings bindings, BlogEntry model, BlogEntryListView entry) {

    bindings.bind(
      observable(model, "title", String.class), 
      hasText(entry.subject())
    );
    
    bindings.bind(
      observable(model, "published", Boolean.class), 
      hasText(entry.marker()), 
      new BooleanToPublishedStatusTransformer()
    );

    // when rebind will be supported, HandlerRegistration will be managed by ListController
    entry.selectionClickHandlers().addClickHandler(new ClickHandler() {
      public void onClick(ClickEvent clickEvent) {
        if (view != null) {
          int index = view.getListEntryIndex(clickEvent);
          blogEntries.setSelection(index);
        }
      }
    });
  }
});

hasText is statically imported method which asks HasText and returns HasTextTarget.

While this ListControllerTarget interface is too specific for controller-view binding it suffices for now.

From example we can see how easy is to bind to list entries and how terrible current bindings api look. For Model-* related bindings I’m planning to implement easier to use and reuse API. See below.

ObjectController Target interface is similar:

registration = selection.addTarget(new ObjectControllerTarget<BlogEntry>() {
  public void bind(Bindings bindings, final BlogEntry model) {

    // general info

    bindings.bind(
      observable(model, "key", String.class), 
      hasText(view.key())
    );
    
    bindings.bind(
      observable(model, "status", Integer.class), 
      hasText(view.status()), 
      new StatusToStringArrayTransformer()
    );
    
    bindings.bind(
      observable(model, "title", String.class), 
      hasText(view.title())
    );

    // 
    bindings.bind(
      observable(model, "hasDetails", Boolean.class), 
      hasBoolean(view.showDetails())
    );
      
    // details

    bindings.bind(
      observable(model, "body", String.class), 
      hasText(view.body())
    );
    
    bindings.bind(
      observable(model, "created", Date.class), 
      hasText(view.created()), 
      new DateToStringTransformer()
    );
    
    bindings.bind(
      observable(model, "published", Boolean.class), 
      hasText(view.published()), 
      new BooleanToPublishedStatusTransformer()
    );
    
  }
});

Called when “content” is changed. null value will have separate callback also create callback will be here to support switching UI from null placeholder to actual view.

For general Target API — specific user Target API I’m thinking of something along those lines:

registration = selection.addTarget(simpleBindableTarget(new SimpleBindableTarget<BlogEntry(){
  public void onBind(Bindings bindings, BlogEntry model) {
    /// ...
  }
}));

where simpleBindableTarget is statically imported method that takes simplified, more targeted to usage interface and implements general ObjectControllerTarget.

The other approach would be just using ObjectController observation directly (still by using some simpleBindableTarget as a helper) and to bind UI against ObjectController itself (which would forward events to and from its content). Not yet decided.

So far we have observable models and controllers that helps to map model graph and selection to UI components. Now I’ll try to describe DataSource and Model state.

DataSource

As stated previously, Model has state integer. It has the following values:

int READY = 1 << 1;
int DIRTY = 1 << 2;
int BUSY = 1 << 3;
int ERROR = 1 << 4;

int LOADING = 1 << 5;
int SAVING = 1 << 6;
int DESTROYING = 1 << 7;
int REFRESHING = 1 << 8;

int NEW = 1 << 9;
int DESTROYED = 1 << 10;

When model is locally created (transient), it has “NEW | DIRTY” state, when it is currently loading from DataSource (async), the state is set to “BUSY | LOADING”. The state is observable, so view can show “Please wait” while loading, refreshing or deleting model objects.

DataSource has a bit different approach managing asynchronous operations. Basically all DataSource methods are seemingly synchronous. Lets look how the list fetching happens:

public class BlogEntryDataSource extends DispatchDataSource<BlogEntry> {

  public List<BlogEntry> fetchAll() {
    return fetchList(
      new GetBlogEntriesAction(), 
      new DispatchDataSourceCallback<GetBlogEntriesResult, DataSourceList<BlogEntry>>() {
        public void prepare(DataSourceList<BlogEntry> model, GetBlogEntriesResult result) {
          populateList(model, result.getEntries());
        }
      });
  }

}

As we can see from previous code snippet, fetchAll() returns immediately. It returns empty ListModel<BlogEntry> instance which can be set as a content to some ListController. Also the list implements IsModel so it has state property. It is set to “BUSY | LOADING”. When asynchronous call returns, DispatchDataSourceCallback#prepare is called with Result (Command pattern) and the same list what method returned previously. Now its time to add entries to the list. When it happens, List → ListController → Presenter → UI chain is invoked so new entries automatically appears in view.

In similar fashion also single models are fetched:

public class BlogEntryDataSource extends DispatchDataSource<BlogEntry> {

  public BlogEntry fetchLatestEntry() {
    return fetchSingle(
      new GetLatestBlogEntry(), 
      new DispatchDataSourceCallback<GetBlogEntryResult, BlogEntry>() {
        public void prepare(BlogEntry model, GetLatestBlogEntryResult result) {
          populateModel(model, result.getBlogEntry());
        }
      });
  }

}

Returned BlogEntry has all properties set to null and state “BUSY | LOADING”. All would be fine but this way if fetchLatestBlogEntry() is called multiple times, multiple BlogEntry instances for one actual blog entry row/entity would be created. And not always unique model primary key is known prior fetching it (as in this example).

So here comes small trick – instead of returning actual BlogEntry, the BlogEntryForwarding proxy is returned with null or dummy BlogEntry as target. When async returns, DispatchDataSourceCallback#populateModel(BlogEntry, GetLatestBlogEntryResult) is called and it updates BlogEntry key what can be checked for uniqueness in local DataStore. If model was already fetched previously, its properties are updated with new ones, BlogEntryForwarding proxy target is changed to that previous model instance but latest fetched is simply discarded. This process also works for each list item fetched.

Key comparison is done using IsModel#getKey() method what must be implemented by each model.

If model key is known prior fetching (fetchBlogEntryByKey(String key)), DataSource returns model instance without creating a proxy.

The benefits are:

  • zero or one model instance for each key (not counting possible proxies)
  • subsequent fetches will automatically notify view about updated model properties
  • DataSource can transparently cache models

The things that needs to be implemented are:

  • predicates/query names so list fetching can be cached (“fetchLatestEntry” – DataStore should know that “latestEntry” is fetched already before fetching it and not only after fetch find out that model with given key already exists in DataStore. This also applies to lists (“fetchAllEntries” with GAE DataStore cursor or Hibernate/JDBC page number)
  • fetch v.s. refresh (DataSource should be aware of difference)

Prettier Bindings For UI

While basic binding api can look so technical, bindings from observable/model to view should have special approach. Firstly bindings are both target type agnostic, for model-view bindings left side is always model.

Previously I had model-view bindings what looks like this:

binder.model(User.class).with(model(new ModelBuilderDelegate<User>() {
 public void bind(ModelBinder<User> b, User model) {

   b.property("key", String.class).
     to(hasValue(view.getKey())).
     to(hasText(view.getKeyValue()));

   b.property("name", String.class).
     to(hasValue(view.getName())).
     to(hasText(view.getNameValue()));

   b.property("login", String.class).
     to(hasValue(view.getLogin())).
     to(hasText(view.getLoginValue()));

   b.model("address", Address.class).with(model(new ModelBuilderDelegate<Address>() {
     public void bind(ModelBinder<Address> b, Address model) {

       b.property("street", String.class).
         to(hasValue(view.getStreet())).
         to(hasText(view.getStreetValue()));

       b.property("country", String.class).
         to(hasValue(view.getCountry())).
         to(hasText(view.getCountryValue()));

       b.model("details", Details.class).with(model(new ModelBuilderDelegate<Details>() {
         public void bind(ModelBinder<Details> b, Details model) {

           b.property("description", String.class).
             to(hasValue(view.getAddressDescription())).
             to(hasText(view.getAddressDescriptionValue()));

         }
       }));
     }
   }));
 }
}));

Now I haven’t touched that code yet but here is that test form running (also with list handling): http://models.latest.ampatspell-test.appspot.com/

Few notes about validation

While gwt-pectin and nearly all other binding frameworks also handle validation I feel that validation support should/must be separated from model-view bindings simply because it would be great to run the same validation definition in server-side. I deem it very useful. Also if validators are not instantiated directly, few validators what can not be run in GWT, can be skipped there and only run at server-side.

bones-validator does that (but API is far from great).

public class UserValidationModule extends AbstractValidationModule<UserDto> {

  @Override
  public void prepare(UserDto o) {
    field("name", o.getName()).with(Unique.class); // runs only in server side
    field("name", o.getName()).with(NotBlank.class);
    // allowBlank() is EmailValidator instance method call for this particular field
    field("email", o.getEmail()).with(Email.class).allowBlank();
  }

}

If model-view bindings are aware of validation (if needs to be), I think it can be done while keeping bindings and validation APIs clean.

February 24, 2010
»

Maven Archetypes Updated

Just deployed latest SNAPSHOT of maven archetypes:

  • bones-google-quickstart-archetype (Google GWT & AppEngine Web App With Bones and Gaeds)
  • google-quickstart-archetype (Google GWT & AppEngine Web App)

They’re finally updated to latest dependencies and even compile and run.

mvn archetype:generate -DarchetypeCatalog=http://www.amateurinmotion.com/repository

To try them out make sure you have Maven 2.2.1 and run:

$ mvn archetype:generate -DarchetypeCatalog=http://www.amateurinmotion.com/repository

...

Choose archetype:
1: http://www.amateurinmotion.com/repository -> google-quickstart-archetype               ↩ 
    (Google GWT & AppEngine Web App)
2: http://www.amateurinmotion.com/repository -> bones-google-quickstart-archetype         ↩
    (Google GWT & AppEngine Web App With Bones and Gaeds)
Choose a number:  (1/2): 2

...

Define value for groupId: : com.zeeba
Define value for artifactId: : zeeba
Define value for version:  1.0-SNAPSHOT: : 
Define value for package:  com.zeeba: : 
Confirm properties configuration:
groupId: com.zeeba
artifactId: zeeba
version: 1.0-SNAPSHOT
package: com.zeeba
 Y: : 

...

$ cd zeeba
$ mvn install # compiles GWT module, packs war ready for AppEngine deployment

...

[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO] ------------------------------------------------------------------------
[INFO] Bones Quickstart ...................................... SUCCESS [1.631s]
[INFO] Quickstart -- Shared .................................. SUCCESS [1.480s]
[INFO] Quickstart -- GWT ..................................... SUCCESS [30.084s]
[INFO] Quickstart -- Server .................................. SUCCESS [2.208s]
[INFO] Quickstart -- War ..................................... SUCCESS [4.255s]
[INFO] ------------------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 40 seconds

...

$ cd zeeba-gwt
$ mvn google:gwt-run          # to run GWT in Development Mode

$ cd zeeba-war
$ mvn google:appengine-run    # to run AppEngine local server with compiled GWT module

Deploy to AppEngine

To deploy app to AppEngine, set your login, password in ~/.m2/settings.xml:

<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
                      http://maven.apache.org/xsd/settings-1.0.0.xsd">
  <profiles>
    <profile>
      <id>appengine</id>
      <properties>
        <appengine-email> ... </appengine-email>
        <appengine-password> ... </appengine-password>
      </properties>
    </profile>
  </profiles>
  <activeProfiles>
    <activeProfile>appengine</activeProfile>
  </activeProfiles>
</settings>

Run:

$ mvn deploy # needs maven distributionManagement/repository

or 

$ mvn install
$ cd zeeba-war
$ mvn google:appengine-deploy

Info about google-maven-plugin can be found at plugin information site.

Plugin and Bones also can be used with Springframework outside AppEngine environment.

February 24, 2010
»

ListController 1st Working Prototype

Just got working the first prototype implementation of ListController for GWT (name comes from Cocoa NSArrayController).

Here is small screencast that shows List<BlogEntry> observation and “reaction” to element adds (ListController) and element property changes (Bindings). Video has no sound, better viewed full-screen.

ListController<T> takes content observable List<T> and observes ADD (later will also handle REMOVE, REPLACE, CLEAR) operations. When it happens, calls:

  • ListControllerTarget#create that should add the widget to the screen
  • ListControllerTarget#bind that should bind widget to model properties
blogEntries.addDelegate(new ListControllerTarget<BlogEntry, BlogEntryListView>() {

  public BlogEntryListView create() {
    return view.addListEntry();
  }

  public void bind(Bindings bindings, BlogEntry model, BlogEntryListView view) {
    // bind BlogEntry#title to HasText subject()
    bindings.bind(observable(model, "title", String.class), hasText(view.subject()));
    
    // bind BlogEntry#published to HasText marker() using boolean to marker text transformer
    // (which transforms true to "Published" and false to "Draft")
    bindings.bind(observable(model, "published", Boolean.class), hasText(view.marker()), 
      new BooleanToMarkerTransformer());
  }
});

ObservableList<T> is an ArrayList<T> subclass which implements IsObservable and overrides add, remove mutators and notifies about element changes.

BlogEntry implements IsModel which extends IsObservable and also is observable.

Observation and Bindings are based on IsObservable » Observable:

BlogEntry entry = GWT.create(BlogEntry.class) // needs GWT generator

ObserverRegistration reg = ((Observable) entry).getReflection().observe("title", String.class, new Observer<String>() {
  public void onValueChange(KeyPath keyPath, Operation operation, IndexSet indexes, String oldValue, String newValue) {
    // "title" value has changed
  }
});

reg.remove(); // to remove observer when it is not needed anymore

Next: ListController selection, ObjectController, DataSource a la NSManagedObjectContext / SproutCode DataSource or something like that.

 
Internet Explorer 6
Are you serious?