This code will show how to draw trees with J2me with the following features:
- collapsible/expandable nodes
- vertical scrolling
You can see a sample midlet showing this code in action on this page.
MenuNode class
To start, we need a class to represent single tree nodes. This class will have also features related to its graphic appearance: even if it's not a best practice, we'll do that to reduce the number of classes within our J2me code.
import java.util.Vector;
public class MenuNode
{
/* graphic properties */
public int x = 0;
public int y = 0;
/* node properties */
public MenuNode parentNode = null;
public String label = null;
public boolean expanded = false;
public int index = 0;
Vector children = null;
public MenuNode(String label)
{
this.label = label;
this.children = new Vector();
}
public void appendChild(MenuNode node)
{
node.parentNode = this;
node.index = this.children.size();
this.children.addElement(node);
}
public int getChildrenNum()
{
return this.children.size();
}
public MenuNode getNextSibling()
{
if(parentNode != null)
{
return parentNode.getChild(index + 1);
}
return null;
}
public MenuNode getPrevSibling()
{
if(parentNode != null)
{
return parentNode.getChild(index - 1);
}
return null;
}
public MenuNode getLastChild()
{
return getChild(getChildrenNum() - 1);
}
public MenuNode getChild(int i)
{
if(i >= 0 && i < this.children.size())
{
return (MenuNode)this.children.elementAt(i);
}
return null;
}
public void removeChildren(int index)
{
this.children.removeElementAt(index);
for(int i = index; i < this.children.size(); i++)
{
this.getChild(i).index--;
}
}
public boolean hasChildren()
{
return this.getChildrenNum() > 0;
}
public void expand()
{
if(this.getChildrenNum() > 0)
{
this.expanded = true;
}
}
public void collapse()
{
this.expanded = false;
}
}
TreeMenu
Now, we'll write down our TreeMenu component, that will accept a root node, width and height within its constructor, and then will manage user key actions through the keyAction() method. The only other public method will be paint(), which will paint our tree on the specified Graphic object.
import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Font;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;
public class ScrollableIconTreeMenu
{
Font font = Font.getDefaultFont();
int foreColor = 0x000000;
int foreFocusedColor = 0xff0000;
int viewportHeight = 0;
int viewportWidth = 0;
int viewportY = 0;
int nodeHeight = 0;
int childPadding = 10;
int verticalPadding = 2;
MenuNode root = null;
MenuNode current = null;
Image expandIcon = null;
Image collapseIcon = null;
Image leafIcon = null;
public static final int ICON_WIDTH = 6;
public static final int ICON_HEIGHT = 6;
public ScrollableIconTreeMenu(MenuNode root, int width, int height)
{
try
{
expandIcon = Image.createImage(getClass().getResourceAsStream("/plus.png"));
collapseIcon = Image.createImage(getClass().getResourceAsStream("/minus.png"));
leafIcon = Image.createImage(getClass().getResourceAsStream("/leaf.png"));
nodeHeight = Math.max(ICON_HEIGHT, font.getHeight());
}
catch(Exception e)
{
}
this.viewportHeight = height;
this.viewportWidth = width;
this.root = root;
this.current = root;
}
public void keyAction(int key)
{
switch(key)
{
case Canvas.LEFT:
keyLeft();
break;
case Canvas.UP:
keyUp();
break;
case Canvas.RIGHT:
keyRight();
break;
case Canvas.DOWN:
keyDown();
break;
}
}
void keyLeft()
{
MenuNode nextNode = null;
if(current.expanded)
{
current.collapse();
}
else
{
nextNode = current.parentNode;
}
setCurrentNode(nextNode);
}
void keyRight()
{
MenuNode nextNode = null;
if(current.expanded)
{
nextNode = current.getChild(0);
}
else
{
current.expand();
}
setCurrentNode(nextNode);
}
void keyDown()
{
MenuNode nextNode = null;
if(current.expanded)
{
nextNode = current.getChild(0);
}
if(nextNode == null)
{
MenuNode searchingSibling = current;
while(searchingSibling != null && nextNode == null)
{
nextNode = searchingSibling.getNextSibling();
searchingSibling = searchingSibling.parentNode;
}
}
setCurrentNode(nextNode);
}
void keyUp()
{
MenuNode nextNode = this.current.getPrevSibling();
if(nextNode == null)
{
nextNode = current.parentNode;
}
else
{
while(nextNode.expanded)
{
nextNode = nextNode.getLastChild();
}
}
setCurrentNode(nextNode);
}
void setCurrentNode(MenuNode node)
{
if(node != null)
{
this.current = node;
if(this.current.y < this.viewportY)
{
this.viewportY = this.current.y;
}
else if(this.current.y + font.getHeight() > this.viewportY + this.viewportHeight)
{
this.viewportY = this.current.y + font.getHeight() - this.viewportHeight;
}
}
}
public void paint(Graphics g)
{
int cx, cy, cw, ch;
cx = g.getClipX();
cy = g.getClipY();
cw = g.getClipWidth();
ch = g.getClipHeight();
g.setClip(0, 0, viewportWidth, viewportHeight);
g.translate(0, - viewportY);
paintNode(g, root, 0, 0);
g.translate(0, viewportY);
g.setClip(cx, cy, cw, ch);
}
int paintNode(Graphics g, MenuNode node, int left, int top)
{
node.x = left;
node.y = top;
Image icon = node.hasChildren() ?
(node.expanded ? collapseIcon : expandIcon) :
leafIcon;
g.drawImage(icon, left, top + nodeHeight / 2, Graphics.LEFT | Graphics.VCENTER);
if(node == current)
{
g.setColor(foreFocusedColor);
}
else
{
g.setColor(foreColor);
}
g.drawString(node.label, left + 2 + ICON_WIDTH, top + (nodeHeight - font.getHeight()) / 2, Graphics.TOP | Graphics.LEFT);
int nodeHeight = 0;
nodeHeight += font.getHeight() + verticalPadding;
if(node.expanded)
{
left += childPadding;
int childrenNum = node.getChildrenNum();
for(int i = 0; i < childrenNum; i++)
{
nodeHeight += paintNode(g, node.getChild(i), left, top + nodeHeight);
}
left -= childPadding;
}
return nodeHeight;
}
}