/***************************************************************************** gui.ObjectList *****************************************************************************/ package gui; import java.awt.*; import java.applet.*; import java.util.*; /** ************************************************************************** ObjectList is a similar to awt.List, with the primary difference that list items can be general Objects, rather than being restricted to Strings. Along with this change, however, comes a number of additional capabilities, and possible customizations. Items in an ObjectList must be unique, and null is not allowed.

The List operations such as adding, deleting, selecting and replacing items are supported. In addition individual items may enabled or disabled (meaning they are `grayed out' and can't be selected). Items may be hidden or shown; hidden items are not visible, but keep their original position in the sequence. ObjectList can also support a hierarchical structure; children of an opened item are displayed, indented, below the parent. The necessary information is provided by subclassing and overriding the following methods:

  public Object[] getChildren(Object item);
  public Object getParent(Object item);
 
These return the children or parent objects respectively, out of which ObjectList will construct the necessary relationships.

The presentation of items can be also be customized. Items can be drawn graphically, and in different sizes, by overriding:

  public Dimension paintItem(Graphics g, Object item);
 
It returns the size needed to display the item. The default is to draw a string obtained from getString(item). That later may also be overridden; it's default uses the toString() method.

When an item is clicked with the mouse, the select state is toggled and a LIST_SELECT or LIST_DESELECT event is posted. A double click on an item selects the item and posts an ACTION_EVENT event. The event's arg is the item. itemBounds(item) gives the position of the item in the window, so that the position of the mouse within the item can be determined from the event's x and y coordinates, allowing different operations to be associated with different parts of the item.

To facilitate `mouse documentation', ie. highlighting the selectable item under the mouse and/or showing descriptive text in an applet's status line, the MOUSE_MOVE and MOUSE_EXIT events are translated into new events. Events with id ObjectList.MOUSE_ENTER_ITEM, MOUSE_EXIT_ITEM and MOUSE_MOVE_ITEM are generated when the mouse enters, leaves or moves within an item (the latter only being useful when items have different `parts'). The methods highlightRect and itemBounds may be useful in handling these events. @see gui.ObjectList#getChildren @see gui.ObjectList#getParent @see gui.ObjectList#paintItem @see gui.ObjectList#getString @author Bruce R. Miller (bruce.miller@nist.gov) @author Contribution of the National Institute of Standards and Technology, @author not subject to copyright. */ public class ObjectList extends Panel { ObjectListNode roots[] = new ObjectListNode[10]; int nRoots = 0; Hashtable nodes = new Hashtable(); Vector selected = new Vector(); int nColumns, nRows; // requested size in characters // The sequence of nodes presents a virtual `scroll'. // The scrollbars control a viewport into that window. Rectangle viewport = new Rectangle(); // size of viewport onto the scroll. Dimension scroll = new Dimension(); // size of the virtual scroll ObjectListNode topNode = null; // the top node Scrollbar xscroll,yscroll; /** id for Events which call for highlighting an item. */ public static final int MOUSE_ENTER_ITEM = 10000; /** id for Events which call for un-highlighting an item. */ public static final int MOUSE_EXIT_ITEM = 10001; /** id for Events which call for a change in highlighting due to mouse * movement within an item. */ public static final int MOUSE_MOVE_ITEM = 10002; boolean multipleSelections = false; /** Indicates a scrollbar should be displayed only if needed. */ public static final int IFNEEDED = 0; /** Indicates a scrollbar should always be displayed. */ public static final int ALWAYS = 1; /** Indicates a scrollbar should never be displayed. */ public static final int NEVER = 2; int xscrolling = IFNEEDED, yscrolling = IFNEEDED; /** Whether duplicated items should be ignored, or signal an error.*/ boolean ignoringDuplicates = false; /** Creates a new ObjectList of nColumns*nRows characters and not allowing multiple selections. */ public ObjectList(int nColumns, int nRows) { this(nColumns, nRows, false); } /** Creates a new ObjectList of nColumns*nRows characters and specifying whether multiple selections are allowed or not. * @param nColumns the number of columns in characters. * @param nRows the number of rows of items. * @param multipleSelections if true then multiple selections are allowed. */ public ObjectList(int nColumns, int nRows, boolean multipleSelections) { this.nColumns = nColumns; this.nRows = nRows; this.multipleSelections = multipleSelections; // Make scroll bars, etc. add(yscroll = new Scrollbar(Scrollbar.VERTICAL, 0,1,0,0)); add(xscroll = new Scrollbar(Scrollbar.HORIZONTAL, 0,1,0,0)); } /* **************************************** Indentation used for each level in the tree heirarchy. */ int indentation = 15; /** Get the indentation (in pixels) for each level of the hierarchy. */ public int getIndentation(int i) { return indentation; } /** Set the indentation (in pixels) for each level of the hierarchy. */ public void setIndentation(int i) { indentation = i; needRepaint(true); } /** Specify whether x scrollbars should be displayed always, never or only * when needed. */ public void setXScrolling(int xscrolling) { if (this.xscrolling != xscrolling) { this.xscrolling = xscrolling; invalidate(); }} /** Returns the code for whether X (horizontal) scroll bar should appear.*/ public int getXScrolling() { return xscrolling; } /** Specify whether y scrollbars should be displayed always, never or only * when needed. */ public void setYScrolling(int yscrolling) { if (this.yscrolling != yscrolling) { this.yscrolling = yscrolling; invalidate(); }} /** Returns the code for whether Y (vertical) scroll bar should appear.*/ public int getYScrolling() { return yscrolling; } /** Returns whether duplicated items inserted into the list are ignored.*/ public boolean duplicatesIgnored() { return ignoringDuplicates; } /** Specifies whether duplicated items inserted into the list should be * ignored instead of signalling an error.*/ public void ignoreDuplicates(boolean value) { ignoringDuplicates = value; } /* ********************************************************************** Size & Layout. */ int margin = 2; public Insets insets() { return new Insets(margin,margin,0,0); } /** Returns the preferred dimensions needed for the object list. */ public Dimension preferredSize() { return minimumSize(); } /** Returns the minimum dimensions needed for the object list. */ public Dimension minimumSize() { Font font = getFont(); FontMetrics m = getFontMetrics(font); Insets i = insets(); return new Dimension( i.left+i.right+yscroll.minimumSize().width + nColumns*m.charWidth('x'), i.top+i.bottom+xscroll.minimumSize().height + nRows*m.getHeight()); } /** layout the panel.*/ public void layout() { Dimension S = size(); int W = S.width, H = S.height; if ((W == 0) || (H == 0)) return; int sw = yscroll.minimumSize().width, sh = xscroll.minimumSize().height; viewport.resize(W - margin - sw, H - margin - sh); yscroll.reshape(margin+viewport.width,margin,sw,viewport.height); xscroll.reshape(margin,viewport.height+margin,viewport.width,sh); setYScroll(viewport.y); setXScroll(viewport.x); } /** What's going on with `inheritance' of enabled/disabled? */ public boolean isEnabled() { if (!super.isEnabled()) return false; Container c = getParent(); while (c != null) { if (!c.isEnabled()) return false; c = c.getParent(); } return true; } /* ********************************************************************** Table nodes o: item -> ObjectListNode */ /** Find the node associated with item. */ ObjectListNode getNode(Object item) { return (ObjectListNode) nodes.get(item); } /** Create a new node and install in table.*/ ObjectListNode makeNode(Object item, ObjectListNode parent) { if (nodes.get(item) != null) { if (ignoringDuplicates) return null; throw(new Error("Item already present in ObjectList " + item)); } ObjectListNode node = new ObjectListNode(item,parent); nodes.put(item,node); return node; } /* ********************************************************************** Roots: array of the top level objects */ /** Find the index of node in roots, or -1 if it isn't in roots. */ int findRoot(ObjectListNode node) { for(int i=0; i roots.length) { ObjectListNode noo[] = new ObjectListNode[Math.max(nRoots+n,2*roots.length)]; System.arraycopy(roots,0,noo,0,roots.length); roots = noo; } if ((index >=0) && (index < nRoots)) System.arraycopy(roots,index,roots,index+n,nRoots-index); } /* ********************************************************************** Adding & Removing items. These operations determine the Root set of items in the List. */ /** Adds item as a root node at the end. */ public synchronized void addItem(Object item) { addItem(item, -1); } /** Adds item as a root node at a particular position in the list. */ // And if the item is already in? public synchronized void addItem(Object item, int index) { makeRootsRoom(1,index); ObjectListNode node = makeNode(item,null); if (node == null) return; if (index < 0 || index >= nRoots) { // add to end. if (nRoots > 0) roots[nRoots-1].append(node); roots[nRoots] = node; } else { roots[index] = node; roots[index+1].prepend(node); } nRoots++; needRepaint(true); } /** Adds a sequence of items as roots at the end of object list. * They all are enabled and hidden according to the two given flags.*/ public synchronized void addItems(Object items[], boolean enabled, boolean hidden) { makeRootsRoom(items.length,nRoots); ObjectListNode node; for(int i=0; i 0) roots[nRoots-1].append(node); roots[nRoots++] = node; }} needRepaint(true); } /** Adds a sequence of items as roots at the end of object list. */ public void addItems(Object items[]) { addItems(items,true,false); } /** Replaces the root oldItem with newItem. */ public synchronized void replaceItem(Object newItem, Object oldItem) { int index = findRoot(getNode(oldItem)); if (index != -1) { delItem(oldItem); addItem(newItem, index); }} /** Clears the list. */ public synchronized void clear() { nRoots = 0; viewport.y = 0; scroll.height = 0; topNode = null; selected.removeAllElements(); nodes.clear(); needRepaint(true); } /** Delete a root item from the list. */ public synchronized void delItem(Object item) { ObjectListNode node = getNode(item); delNode(node); node.remove(); // now remove all references to this item. int i = findRoot(node); if (i < nRoots-1) System.arraycopy(roots,i+1,roots,i,nRoots-i-1); nRoots--; needRepaint(true); } /** Delete this node from the roots. */ void delNode(ObjectListNode node) { ObjectListNode kid = node.firstChild(); while (kid != null) { delNode(kid); kid = kid.next(); } nodes.remove(node.item); } /* ********************************************************************** Item selection. A selected item is drawn reverse video; it generally represents the noun(s) in an upcoming command. Selected items should be shown and enabled. */ /** Get the selected item or null if no item is selected. */ public synchronized Object getSelectedItem() { return (selected.size() == 1) ? selected.elementAt(0) : null; } /** Returns the selected items on the list. */ public synchronized Object[] getSelectedItems() { Object s[] = new Object[selected.size()]; selected.copyInto(s); return s; } /** Selects the item. */ public synchronized void select(Object item) { if (!selected.contains(item)) { if (multipleSelections || selected.isEmpty()) selected.addElement(item); else selected.setElementAt(item,0); needRepaint(false); }} /** Deselects the item. */ public synchronized void deselect(Object item) { if (selected.contains(item)) { selected.removeElement(item); needRepaint(false); }} /** Deselect all selected items. */ public synchronized void deselectAll() { selected.removeAllElements(); needRepaint(false); } /** Deselect a node and its descendents.*/ void deselectRec(ObjectListNode node) { deselect(node.item); ObjectListNode kid = node.firstChild(); while (kid != null) { deselectRec(kid); kid = kid.next(); }} /** Returns true if the item has been selected; false otherwise. */ public synchronized boolean isSelected(Object item) { return selected.contains(item); } /** Returns true if this list allows multiple selections. */ public boolean allowsMultipleSelections() { return multipleSelections; } /** Sets whether this list should allow multiple selections or not.*/ public void setMultipleSelections(boolean v) { if (v != multipleSelections) multipleSelections = v; } /* ********************************************************************** Enabling & Disabling items. */ /** Is this item enabled? */ public boolean isEnabled(Object item) { ObjectListNode node = getNode(item); return (node == null ? false : node.isEnabled()); } /** Enable this item. A disabled item is typically grayed out; it is not * available for selection. */ public void enable(Object item) { ObjectListNode node = getNode(item); if ((node != null) && node.changeEnabled(true)) needRepaint(false); } /** Disable this item. A disabled item is typically grayed out; it is not * available for selection. */ public void disable(Object item) { ObjectListNode node = getNode(item); if ((node != null) && node.changeEnabled(false)) needRepaint(false); } /** Enable or Disable this item according to value. */ public void enable(Object item, boolean value) { if (value) enable(item); else disable(item); } /* ********************************************************************** Opening & Closing items. Design Q: Presumably kids should be closed when a parent is closed? Else, when the parent is reopened, should the kids reopen as well? */ /** Get the children associated with item. This method is intended to * be overridden by a subclass: It should return an array of the * children objects of item; the default returns null (no children). */ public Object[] getChildren(Object item) { return null; } /** Get the parent associated with item. This method is intended to * be overridden by a subclass: It should return the parent object * of item; the default returns null (ie. a top level item). */ public Object getParent(Object item) { return null; } /** Is this item opened? */ public boolean isOpen(Object item) { ObjectListNode node = getNode(item); return (node == null ? false : node.isOpen()); } /** Is this item able to be opened? */ public boolean isOpenable(Object item) { ObjectListNode node = getNode(item); Object ch[]; return (((node == null) || !node.isOpen()) && ((ch =getChildren(item)) != null) && (ch.length >0)); } /** Open this item, revealing its children (if any). */ // Should this show, as well? public void open(Object item) { Object p = getParent(item); // open parent (if any) if (p != null) open(p); ObjectListNode node = getNode(item); if ((node != null) && !node.isOpen()) { if (node.firstChild() == null) { // Install children for this item. Object ch[] = getChildren(item); if ((ch != null) && (ch.length >0)) { ObjectListNode prev = null,kid; for(int i=0; iy1 is range we want to show y1 = (next != null ? next.y : scroll.height); if ((y0 < viewport.y) || (y1 >= viewport.y + viewport.height)) { setYScroll(Math.min(y0,(y0+y1-viewport.height)/2)); repaint(); }}} /** Flag: do we need to recount items to find top node in view?*/ boolean needRecount = false; /** Set the top line of the viewport.*/ void setYScroll(int top) { viewport.y = top = Math.max(0,Math.min(top,scroll.height-viewport.height)); if (needRecount) checkCounting(); else { // find a topNode that's at viewport.y if (topNode == null) topNode = firstVisible(); while((topNode != null) && (top > topNode.y + topNode.height)) topNode = topNode.nextVisible(); while((topNode != null) && (top < topNode.y)) topNode = topNode.prevVisible(); yscroll.setValues(viewport.y,viewport.height,0, // Correction for 1.1 thanks Daniel drasin@appliedreasoning.com Math.max(0,scroll.height)); }} // Math.max(0,scroll.height-viewport.height)); }} void setXScroll(int left) { viewport.x = Math.max(0,Math.min(left,scroll.width-viewport.width)); xscroll.setValues(viewport.x,viewport.width,0, // Correction for 1.1 thanks Daniel drasin@appliedreasoning.com Math.max(0,scroll.width)); } // Math.max(0,scroll.width-viewport.width)); } /** Do recount to find topmost node in the viewport.*/ void checkCounting() { if (needRecount) { int y = 0, h=0; scroll.width = viewport.width; topNode = null; ObjectListNode node = firstVisible(); while(node != null) { if (node.height == 0) node.resize(paintItem(null,node.item)); node.move(indentation*node.depth(),y); y += node.height; if ((node.y <= viewport.y) && (y > viewport.y)) topNode = node; scroll.width = Math.max(scroll.width,node.width); node=node.nextVisible(); } scroll.height = y; needRecount = false; setXScroll(viewport.x); setYScroll(viewport.y); // make sure topNode is in range & set scroller }} /* ********************************************************************** Painting. */ // Compute an average of two colors. Color averageColor(Color c1, Color c2) { return new Color((c1.getRed() +c2.getRed())/2, (c1.getGreen()+c2.getGreen())/2, (c1.getBlue() +c2.getBlue())/2); } /** Assert that we need to repaint and possibly recount.*/ void needRepaint(boolean recount) { needRecount |= recount; repaint(); } /** Update the display.*/ public void update(Graphics g) { paint(g); } ObjectListNode litNode = null; int litDx,litDy; /** Paint the display.*/ public void paint(Graphics g) { // Is this a kludge or what??? if ((viewport.width==0) && (viewport.height==0)) layout(); Color fore = getForeground(), back = getBackground(), mid = averageColor(fore,back); boolean enabled = isEnabled(); Dimension d; checkCounting(); // Borders around List area & fill in L where scroll bars would be g.setColor(enabled ? back.darker() : mid); g.drawRect(0,0,viewport.width+2*margin-1,viewport.height+2*margin-1); Container p = getParent(); if (p != null) { g.setColor(p.getBackground()); d = size(); int x = viewport.width+2*margin, y = viewport.height+2*margin; g.fillRect(0,y,d.width,d.height-y); g.fillRect(x,0,d.width-x,y); } // Make scrollbars visible if they should be. xscroll.show((xscrolling == ALWAYS) || ((xscrolling == IFNEEDED) && (scroll.width > viewport.width))); yscroll.show((yscrolling == ALWAYS) || ((yscrolling == IFNEEDED) && (scroll.height > viewport.height))); g.clipRect(margin,margin,viewport.width,viewport.height); ObjectListNode node = topNode; int ymax = viewport.y + viewport.height; if (node != null) g.translate(margin,margin+node.y-viewport.y); for( ; (node != null) && (node.y < ymax); node = node.nextVisible()) { boolean en = enabled && node.isEnabled(); boolean sel= isSelected(node.item); g.setColor( sel ? fore : back); g.fillRect(0,0,viewport.width,node.height); g.setColor( en ? (sel ? back : fore) : mid); g.translate(node.x-viewport.x,0); node.resize(d = paintItem(g,node.item)); if (d.height != node.height) // size Changed! needRepaint(true); g.translate(viewport.x-node.x,node.height); } if (scroll.height < ymax) { g.setColor(getBackground()); g.fillRect(0,0,viewport.width,ymax-scroll.height); } if (litNode != null) // if a node was highlighted, redo it. transformEvent(null,litNode,MOUSE_ENTER_ITEM,litDx,litDy); } /* ********************************************************************** Event Handling. */ ObjectListNode findNode(int sx, int sy) { int x = sx + viewport.x - margin, y = sy + viewport.y - margin; if (!viewport.inside(x,y)) return null; ObjectListNode node = topNode; while((node != null) && !node.inside(x,y)) node = node.nextVisible(); return node; } void transformEvent(Event e, ObjectListNode node, int id,int x, int y) { postEvent(new Event(this,(e != null ? e.when : 0),id,x,y, (e != null ? e.key : 0), (e != null ? e.modifiers : 0), node.item)); } /** Event handler: handles item selection and mouse documentation. */ public boolean handleEvent(Event e) { // Scroll bar events. if ((e.target == xscroll) && (xscroll.getValue() != viewport.x)) { setXScroll(xscroll.getValue()); repaint(); } else if ((e.target == yscroll) && (yscroll.getValue() != viewport.y)){ setYScroll(yscroll.getValue()); repaint(); } // MOUSE_MOVE & MOUSE_EXIT are translated into MOUSE_ENTER_ITEM,etc, events // according to whether the mouse enters, exits or moves within an item. else if (e.id == Event.MOUSE_EXIT) { if (litNode != null) transformEvent(e,litNode,MOUSE_EXIT_ITEM,litDx,litDy); litNode = null; } else if (e.id == Event.MOUSE_MOVE) { ObjectListNode node = findNode(e.x,e.y); if ((litNode != null) && (node != litNode)) transformEvent(e,litNode,MOUSE_EXIT_ITEM,litDx,litDy); if (node != null) { if (node == litNode) transformEvent(e,node,MOUSE_MOVE_ITEM,e.x,e.y); else transformEvent(e,node,MOUSE_ENTER_ITEM,e.x,e.y); } litNode = node; litDx = e.x; litDy = e.y; } // Handlers for mouse clicks. else if (e.id == Event.MOUSE_DOWN) { ObjectListNode node = findNode(e.x,e.y); if (node != null) { Object item = node.item; int id = (e.clickCount > 1 ? Event.ACTION_EVENT : (isSelected(item) ? Event.LIST_DESELECT : Event.LIST_SELECT)); if (id == Event.LIST_DESELECT) deselect(item); else select(item); transformEvent(e,node,id,e.x,e.y); }} else return super.handleEvent(e); return true; } /********************************************************************** Customization section. */ /** Paint item at the current position in g. This method may be * overridden by a subclass. The graphics object has already * been prepared as to position and colors according to selection and * enabling and the background has already been cleared. * The default draws the string returned by getString. * Note that this method may be called with g = null in order to determine * the size of the node; in this case nothing should be drawn. * @see gui.ObjectList#getString */ public Dimension paintItem(Graphics g, Object item) { String text = getString(item); FontMetrics m = getFontMetrics(getFont()); if (g != null) g.drawString(text,0,m.getHeight()-m.getDescent()); return new Dimension(m.stringWidth(text),m.getHeight()); } /** Return a descriptive string for item. This method may * be overridden by a subclass. The string is used by the default * paintItem and in mouse documentation. * The default is to use toString(). */ public String getString(Object item) { return item.toString(); } /********************************************************************** The following are useful for customizing highlighting. */ /** Returns the bounding rectangle of the item on the ObjectList. */ public Rectangle itemBounds(Object item) { ObjectListNode node = getNode(item); return (node == null ? null : new Rectangle(node.x-viewport.x+margin,node.y-viewport.y+margin, node.width,node.height)); } /** Draws a highlighting rectangle, using XOR, with the given position * and size. */ public void highlightRect(int x, int y, int width, int height) { Graphics g = getGraphics(); g.setXORMode(getBackground()); g.setColor(Color.black); g.clipRect(margin,margin,viewport.width,viewport.height); g.drawRect(x,y,width,height); } /** Draws a highlighting rectangle, using XOR. */ public void highlightRect(Rectangle r) { if (r != null) highlightRect(r.x,r.y,r.width,r.height); } } /********************************************************************** ObjectListNode: class containing the items in an ObjectList. **********************************************************************/ class ObjectListNode extends Rectangle { Object item; // the actual item that this node represents ObjectListNode parent = null; // the parent node (or null, if root). public ObjectListNode(Object item, ObjectListNode parent) { this.item = item; this.parent = parent; } /* ************************************************** Remember size of displayed node. */ public void resize(Dimension d) { resize(d.width,d.height); } /* ************************************************** Flags */ int flags = 0; static final int DISABLED = 1; // bit = 1 -> disabled static final int OPEN = 2; // bit = 1 -> open static final int HIDDEN = 4; // bit = 1 -> hidden /* ************************************************** Flag for Enabling/Disabling a node */ /** Is this node (and it's ancestors) enabled? */ boolean isEnabled() { return ((flags & DISABLED) == 0) && ((parent == null) ? true : parent.isEnabled()); } boolean changeEnabled(boolean value) { if (((flags & DISABLED) == 0) != value) { flags ^= DISABLED; return true; } else return false; } /* ************************************************** Flag for showing/hiding a node */ /** Is this node visible (not hidden)? */ boolean isVisible() { return (flags & HIDDEN) == 0; } /** Is this node showing (visible and parent is open and showing)? */ boolean isShowing() { return ((flags & HIDDEN) == 0) && (parent == null ? true : parent.isOpen() && parent.isShowing()); } boolean changeVisibility(boolean value) { if (((flags & HIDDEN) == 0) != value) { flags ^= HIDDEN; return true; } else return false; } /* ************************************************** Maintain doubly linked list between siblings. */ ObjectListNode _prev = null, // the previous & next siblings. _next = null; /** Removes this node from between it's neighbors. */ void remove() { if (_prev != null) _prev._next = _next; if (_next != null) _next._prev = _prev; _prev = _next = null; } /** Insert node behind this one. */ void append(ObjectListNode node) { if (_next != null) _next._prev = node; node._prev = this; node._next = _next; _next = node; } /** Insert node in front of this one. */ void prepend(ObjectListNode node) { node._next = this; node._prev = _prev; if (_prev != null) _prev._next = node; _prev = node; } /* ************************************************** Maintaining links to children */ ObjectListNode kids[]; // array[2]: first & last child. ObjectListNode firstChild() { return kids != null ? kids[0] : null; } ObjectListNode lastChild() { return kids != null ? kids[1] : null; } void setChildren(ObjectListNode kid) { ObjectListNode k0 = kid, k1 = kid; kids = new ObjectListNode[2]; while(k0._prev != null) k0 = k0._prev; while(k1._next != null) k1 = k1._next; kids[0] = k0; kids[1] = k1; } /** Is this node opened? */ boolean isOpen() { return (flags & OPEN) != 0; } void open() { flags |= OPEN; } /** Close this node and it's children. */ void close() { if ((flags & OPEN) != 0) { flags &= ~OPEN; ObjectListNode kid = kids[0]; while (kid != null) { kid.close(); kid = kid._next; }}} /** How deep is this node in hierarchy? (root is 0). */ int depth() { return parent == null ? 0 : 1+ parent.depth(); } /** How many descendents are `showing'.*/ int descendentsHeight() { int n=0; if (isOpen()) { ObjectListNode kid = kids[0]; while (kid != null) { if (kid.isVisible()) n += height+kid.descendentsHeight(); kid = kid._next; }} return n; } /* ************************************************** Traversing the tree */ /** Find the next node following node.*/ ObjectListNode next() { return _next; } /** Find the next visible node following node.*/ ObjectListNode nextVisible() { ObjectListNode node = this; while(true) { if ((node.flags & (HIDDEN | OPEN)) == OPEN) node = node.kids[0]; else { while((node != null) && (node._next == null)) node = node.parent; if (node != null) node = node._next; } if ((node == null) || ((node.flags & HIDDEN)==0)) return node; }} /** Find the node preceeding node.*/ ObjectListNode prev() { return _prev; } /** Find the next visible node preceeding node.*/ ObjectListNode prevVisible() { ObjectListNode node = this; ObjectListNode prev; while(true) { prev = node._prev; if (prev == null) node = node.parent; else { while((prev.flags & (HIDDEN | OPEN)) == OPEN) prev = prev.lastChild(); node = prev; } if ((node == null) || ((node.flags & HIDDEN)== 0)) return node; }} /* public String toString() { return "ObjectListNode@"+hashCode(); } */ }