Monday, April 27, 2009

Why Do You Need A Variable Sized List?

Last week the forum member juliofabio sent this gorgeous looking video to the forum showing a variable size list component. This looks really stunning and shows off how much you can do with LWUIT when you put your mind to it.

What I'm not exactly sure about is why did he choose to use a List?

Why not container and components like I used in my recent Tree demo?

Why List? Even if we think of our data as a list it might not always be appropriate for a list component.

Specifically in this case the set of elements within the "list" seem to be predefined and small hence there is no advantage to using a model. In fact I would assume that writing that code using a model and renderer paradigm would be very unintuitive. This set will never reach a very large size so the advantages of a huge million element list won't be realized either. It seems from the demo that even list animations were disabled, this is something that can be acheived using containers though.

Using Container.replace() can allow for most of the special effects within this list and can provide even more impressive effects (such as 3d). Building something like that can be trivial without getting into all of the complexities related to the list component, so why would someone want such a list?

Thursday, April 23, 2009

JavaVerified: LWUIT's Killer Feature?

I'm generally very excited about Nokia's upcoming Ovi store, I think it has the potential to empower smaller developers to deliver content to end users.

One of the decisions made by Nokia is to go with Java Verified for the QA and signing of application for Ovi. Java Verified has an issue with requiring a cost for every tested device & every failure. This cost can become remarkably high the more devices need certification/signing, here LWUIT really shines. Its approach of "One JAR for all devices" makes a world of difference in the Java Verified cost for signing, it can make the difference between turning a profit and losing money when publishing smaller applications.

I've always viewed LWUIT as a tool to assist the smaller shops and individual hackers to reach mass market device deployment and this is a concrete example of this perspective.

Tuesday, April 14, 2009

LWUIT Model Tree

Since the forum is down (as usual) my responses to some of the comments in the forum just never made it past the mailing list, one of the questions in the forum that comes up every now and again is "how to create a tree in LWUIT?"

We get this question every now and again and generally our response is that we don't but would like to get such a contribution. However, looking at our samples it seems that most of our component building explanations revolve around "small" components rather than composites which would fit a tree much better.

A composite component is really a Container which we would assemble into a specialized component by tailoring together smaller components. Naturally we could also perform custom painting for such a composite but that often defeats the purpose...

These components aren't suitable for everything but for things such as a tree we gain several huge advantages by using Container nesting to build a tree:
  • We represent the nodes using custom components allowing subclasses to manipulate the look of the tree to a great degree.
  • We hardly need to write any code and styles, touch support, keyboard navigation etc. are all built in
  • We can leverage transitions and component nesting for really stunning effects
The tree uses a simple model component with some dummy data, mapping it to a filesystem or any other such element is trivial. All you need to do is implement TreeModel appropriately and it only contains 2 methods (I spent allot of time with the Swing tree model...). Its not as powerful as the Swing model since it doesn't support editing or mutation (from the model) but these are relatively easy to add. It took me roughly 2 hours to write everything including this post, it was so long because I discovered a minor LWUIT bug in scrolling/animation that is triggered when a tree starts off small and ends up being rather large.

Lets go strait to the code, first we need the model:
/**
* Arranges tree node objects, a node can essentially be anything
*
* @author Shai Almog
*/

interface TreeModel {
/**
* Returns the child objects representing the given parent, null should return
* the root objects
*/

public Vector getChildren(Object parent);

/**
* Is the node a leaf or a folder
*/

public boolean isLeaf(Object node);
}

I think thats pretty self explanitory...
Then we would like the implementation:
class Node {
Object[] children;
String value;

public Node(String value, Object[] children) {
this.children = children;
this.value = value;
}

public String toString() {
return value;
}
}

TreeModel model = new TreeModel() {
Node[] sillyTree = {
new Node("Root 1", new Node[] {
new Node("Child 1", new Node[] {
new Node("Gand Child 1", new Node[] {
}),
new Node("Gand Child 2", new Node[] {
}),
new Node("Gand Child 3", new Node[] {
}),
new Node("Gand Child 4", new Node[] {
}),
}),
new Node("Child 2", new Node[] {
new Node("Something Else", new Node[] {
}),
new Node("More of the same", new Node[] {
}),
}),
new Node("Child 3", new Node[] {
}),
new Node("Child 4", new Node[] {
}),
}),
new Node("Root 2", new Node[] {
new Node("Something Else", new Node[] {
}),
new Node("More of the same", new Node[] {
}),
}),
new Node("Root 3", new Node[] {
new Node("Something Else", new Node[] {
}),
new Node("More of the same", new Node[] {
}),
}),
new Node("Root 4", new Node[] {
}),
};
Thats a bit long and tedious but its mostly unrelated to LWUIT, the main code is the tree component code which is really rather simple:
/**
* LWUIT Tree component sample
*
* @author Shai Almog
*/

public class TreeComponent extends Container {
private static final String KEY_OBJECT = "TREE_OBJECT";
private static final String KEY_PARENT = "TREE_PARENT";
private static final String KEY_EXPANDED = "TREE_NODE_EXPANDED";
private static final String KEY_DEPTH = "TREE_DEPTH";

private ActionListener expansionListener = new ActionListener() {
public void actionPerformed(ActionEvent evt) {
Component c = (Component)evt.getSource();
Object e = c.getClientProperty(KEY_EXPANDED);
if(e != null && e.equals("true")) {
collapseNode(c);
} else {
expandNode(c);
}
}
};
private TreeModel model;
private Image folder;
private Image nodeImage;
private int depthIndent = 15;

public TreeComponent(TreeModel model) {
try {
this.model = model;
folder = Image.createImage("/folder.png");
nodeImage = Image.createImage("/page_white.png");
setLayout(new BoxLayout(BoxLayout.Y_AXIS));
buildBranch(null, 0, this);
setScrollableY(true);
} catch (IOException ex) {
ex.printStackTrace();
}
}

private void expandNode(Component c) {
c.putClientProperty(KEY_EXPANDED, "true");
int depth = ((Integer)c.getClientProperty(KEY_DEPTH)).intValue();
Container parent = c.getParent();
Object o = c.getClientProperty(KEY_OBJECT);
Container dest = new Container(new BoxLayout(BoxLayout.Y_AXIS));
Label dummy = new Label();
parent.addComponent(BorderLayout.CENTER, dummy);
buildBranch(o, depth, dest);
parent.replace(dummy, dest, CommonTransitions.createSlide(CommonTransitions.SLIDE_VERTICAL, true, 300));
}

private void collapseNode(Component c) {
c.putClientProperty(KEY_EXPANDED, null);
Container p = c.getParent();
for(int iter = 0 ; iter < p.getComponentCount() ; iter++) {
if(p.getComponentAt(iter) != c) {
Label dummy = new Label();
p.replaceAndWait(p.getComponentAt(iter), dummy, CommonTransitions.createSlide(CommonTransitions.SLIDE_VERTICAL, false, 300));
p.removeComponent(dummy);
}
}
}

/**
* Adds the child components of a tree branch to the given container.nt
*/

protected void buildBranch(Object parent, int depth, Container destination) {
Vector children = model.getChildren(parent);
int size = children.size();
Integer depthVal = new Integer(depth + 1);
for(int iter = 0 ; iter < size ; iter++) {
Object current = children.elementAt(iter);
Button nodeComponent = createNodeComponent(current, depth);
if(model.isLeaf(current)) {
destination.addComponent(nodeComponent);
} else {
Container componentArea = new Container(new BorderLayout());
componentArea.addComponent(BorderLayout.NORTH, nodeComponent);
destination.addComponent(componentArea);
nodeComponent.addActionListener(expansionListener);
}
nodeComponent.putClientProperty(KEY_OBJECT, current);
nodeComponent.putClientProperty(KEY_PARENT, parent);
nodeComponent.putClientProperty(KEY_DEPTH, depthVal);
}
}

protected Button createNodeComponent(Object node, int depth) {
Button cmp = new Button(node.toString());
if(model.isLeaf(node)) {
cmp.setIcon(nodeImage);
} else {
cmp.setIcon(folder);
}
updateNodeComponentStyle(cmp.getSelectedStyle(), depth);
updateNodeComponentStyle(cmp.getUnselectedStyle(), depth);
return cmp;
}

protected void updateNodeComponentStyle(Style s, int depth) {
s.setBorder(null);
s.setPadding(LEFT, depth * depthIndent);
}
}


Then all we need is to use it in order to produce the video on the right:
Form treeForm = new Form("Tree Test");
treeForm.setLayout(new BorderLayout());
TreeComponent tree = new TreeComponent(model);
treeForm.addComponent(BorderLayout.CENTER, tree);
treeForm.setScrollable(false);
treeForm.show();