Inline tree filters6341280Abstract A common interface solution to the problem of burgeoning information sets, referred to as inline tree filters, structures the represented data in a hierarchical tree. Inline filters are information-filtration devices that are applied to a traditional interface element, such as a hierarchical tree viewer. The invention provides a technique that enables the user to choose selectively the data that they want to display, while not otherwise altering the behavior and interaction of the tree viewer itself. Inline tree filters are suitable for any graphical user interface that could potentially use a tree viewer. Claims What is claimed is: Description BACKGROUND OF THE INVENTION
TABLE 1
FilterPanel.java
import java.awt.*;
public class FilterPanel extends Panel
{
FilterableTreeNode branch;
boolean firstTime;
int height;
Label label;
TextField textField;
Button button;
public FilterPanel(FilterableTreeNode branch)
{
this.branch = branch;
this.firstTime = true;
setLayout(new FlowLayout(9));
setFont(new Font("PrimaSans", Font.PLAIN, 11));
add(label = new Label("Show items containing:",
Label.LEFT));
add(textField = new TextField(8));
add(button = new Button("Filter"));
setBackground(new Color(229, 229, 229));
}
public void paint(Graphics g)
}
if (firstTime) {
doLayout( );
height = preferredSize( ).height;
//System.err.printIn("filterPanel height is " +height);
}
}
public boolean handleEvent(Event event)
{
if ((event.id == Event.ACTION_EVENT) &&
(event.target == button)) {
doFilter(textField.getText( ));
return true;.
}
return super.handleEvent(event);
}
public void doFilter(String what)
{
for (int i = 0; i.about.branch.childcount; i++) {
FilterableTreeNode child = branch.children[i];
if (child.text.indexOf(what) != -1) {
child.setShowing(true);
} else {
child.setShowing(false);
}
}
branch.setChanged( );
}
}
FilterableTreeNode This module (see Table 2) defines the class FilterableTreeNode, an extension of the standard Java Swingset toolkit's panel class. It in effect defines a new tree widget, much like the tree widget defined as a standard part of the swingset toolkit, but with the specialized filtering functionality and a custom look and feel that mimics that used in Netscape server products. It defines the visual look of each node in the tree, as well as the look of the control for expanding and collapsing controls. Specifically, the class initially does setup work to load custom icons for the expand/collapse controls, open and closed folder (or subtree) nodes, and leaf nodes. It also loads two images that are composited together to make up the lines that visually connect the tree together. It then defines the colors for the tree background, node text, and selected versions thereof. It then defines the a geometry or layout of the tree. The second part of the class definition defines the structure of the tree and keeps track of each node and who its parents are. Note that all the functionality defined so far is standard for any tree node or tree class. The filter-specific functionality is defined in the methods setFiltering, setFilterPanelVisibility, and setChanged. setFiltering is a boolean value that keeps track of whether a particular node wishes to be filtered. setFilterPanelVisibility checks to see if a node is expanded and filtering. If so, then the panel (defined in FilterPanel.java) should be visible in the tree. This function is called recursively so that child nodes with filters have their panels hidden as well. setchanged is a method that causes the tree to be redrawn after filter criteria have been input via the FilterPanel class. It then calls an update function to redraw the tree to reflect the new filtered contents. The third and last part of the class definition contains standard methods to actually draw each part of the tree, such as icons, expand collapse controls and other graphic elements on screen.
TABLE 2
FilterableTreeNode
import java.awt.*;.
import java.util.*;
import java.applet.Applet;
// what the world needs now
// is a-nother tree widget
// like i need
// a hole in the head
//
// - cracker
public class FilterableTreeNode extends Panel
{
// what the +/- look like
static Image expanderImage;
static Image collapserImage;
// opened and closed folder icons
static Image expandedImage;
static Image collapsedImage;
// icon for an item in the tree
static Image leafImage;
// images for the curved and straight pieces
static Image hookImage;
static Image hookLineImage;
// load all images for the various parts of the tree
static void loadImages(Applet applet)
{
MediaTracker tracker = new MediaTracker(new Frame( ));
expanderImage = loadImage("tree- expander.gif", applet);
tracker.addImage(expanderImage, O);
collapserImage = loadImage("tree-collapser.gif", applet);
tracker.addImage(collapserImage, O);
expandedImage = loadImage("folder-open.gif", applet);
tracker.addImage(expandedImage, O);
collapsedImage = loadImage("folder-closed.gif", applet);
tracker.addImage(collapsedImage, O);
leafImage = loadImage("document.gif", applet);
tracker.addImage(leafImage, O);
hookImage = loadImage("hook.gif", applet);
tracker.addImage(hookImage, O);
hookLineImage = loadImage("hook-line.gif", applet);
tracker.addImage(hookLineImage, O);
try {
tracker.waitForAll( );
} catch (InterruptedException e) {
e.printStackTrace ( );
}
}
static Image loadImage(String filename, Applet applet)
{
if (apples == null) {
return Toolkit.getDefaultToolkit( ).getImage("images/"}
+ filename);
} else {
return applet.getImage(applet.getCodeBase( ), "images/"
+ filename);
}
}
// coloring for the hash marks on the tree
static final Color hashColor = new Color(153, 153, 153);
static final Color hashLighterColor = new Color(204, 204, 204);
// colors for the background, text, and selected versions thereof
static final Color backgroundColor = Color.white;
static final Color textColor = Color.black;
static final Color backgroundColorSelected = new Color(204, 204,
255);
static final Color textColorSelected = Color.black;
// height of each row, indent for each subtree
static final int ROW_HEIGHT = 18; static final int INDENT_WIDTH = 26;
// the +/- symbols
static final int TRIGGER_DIMENSION = 7;
static final int TRIGGER_TOP = 7; static final int TRIGGER_LEFT = 1;
// icons for the objects
static final int ICON_DIMENSION = 16;
static final int ICON_TOP = 1; static final int ICON_LEFT = 9;
// the rope thingy that extends from beneath the node
static final int VINE_LEFT = ICON_LEFT + ICON_DIMENSION / 2;
// rect for a selected node
static final int SELECTION_TOP = 2;
static final int SELECTION_HEIGHT = 15;
static final int SELECTION_LEFT = 24;
static final int SELECTION WIDTH EXTRA = 3;
// font sizing information
static final int TEXT_LEFT = 28;
static int fontAscent; static int fontHeight;
static Font font; static FontMetrics fontMetrics;
static int textBaseline;
static {
font = new Font("PrimaSans", Font.PLAIN, 11);
fontMetrics =
Toolkit.getDefaultToolkit( ).getFontMetrics(font);
fontAscent = fontMetrics.getAscent( );
fontHeight = fontAscent + fontMetrics.getDescent( );
textBaseline = ((ROW_HEIGHT - fontHeight + 1)/2) +
fontAscent:
}
// information for this subtree
String text; // text for the item in the tree
boolean expanded;
boolean leaf; // independent of whether
childCount == 0
boolean selected;
boolean showing; // `visible` is used by Component
FilterPanel filterPanel; // filterable = (filterPanel !=
null)
boolean filtering;
//ins filterPanelHeight;
FilterableTreeNode mom;
int childCount;
FilterableTreeNode children[ ];
int xoffset;
int yoffset;
int height;
public FilterableTreeNode(String text)
{
this(text, null, null);
}
public FilterableTreeNode(String text, FilterableTreeNode mom)
{
this(text, mom, null);
}
public FilterableTreeNode(String text, FilterableTreeNode mom,
FilterPanel filterPanel)
{
if (leafImage == null) {
loadImages(null );
}
this.text = text;
this.expanded = false;
this.leaf = true;
this.filterPanel = filterPanel;
this.filtering = false;
this.mom = mom;
this.childCount = 0;
this.children = null;
this.selected = false;
this.showing = true;
}
public void setText(String text)
[
this.text = text;
}
public void setExpanded(boolean expanded)
{
this.expanded = expanded;
setFilterPanelVisibility(expanded);
}
// if a node is expanded and filtering, then the panel should be
// visible. this function is called recursively so that child
// nodes with filters have their panels hidden as well.
public void setFilterPanelVisibility(boolean state)
{
if (filterPanel != null) {
//System.err.printIn(((state & filtering) ? "showing"
:"hiding ") + this);
filterPanel.setVisible(state & expanded & filtering);
}
// setFilterPanelVisibility is a separate function because
of
// what's seen below -- need to collapse the child filters,
even
// though the children retain their expanded/collapsed state
for (int i = 0; i c childCount; i++) {
children[i].setFilterPanelVisibility(state &
expanded);
}
}
public void toggleExpanded( )
{
setExpanded(!expanded);
}
public void setLeaf(boolean leaf)
{
this.leaf = leaf;
}
public void setFilterPanel(FilterPanel filterPanel)
{
this.filterPanel = filterPanel;
}
public void setFiltering(boolean filtering)
{
this.filtering = filtering;
//filterPanel.setVisible(filter ing);
setFilterPanelVisibility(filter ing);
// if filtering, hide everybody, if not
// filtering, show everybody.
for (int i = 0; i c childCount; i++) {
children[i].showing = !filtering;
}
// don't draw if we're just out of the gate . . .
if (mom != null) {
setChanged( );
}
}
public boolean isFilterable( )
{
return (filterPanel != null);
}
public void setParent(FilterableTreeNode mom)
{
this.mom = mom;
}
public void addChild(String childText)
{
prepareChildren( );
children[childCount++] = new FilterableTreeNode(childText,
this);
this.leaf = false;
}
public void addChild(FilterableTreeNode child)
{
prepareChildren( );
children[childCount++] = child;
child.setParent(this);
this.leaf = false;
if (child.filterPanel != null) {
findRoot( ).add(child.filterPa nel);
}
}
public void addFilterChild(String childText, FilterPanel
childPanel)
}
prepareChildren( );
children[childCount++] = new FilterableTreeNode(childText
this, childPanel);
this.leaf = false;
// not visible by default, and node was just made above
findRoot( ).add(childPanel);
}
private void prepareChildren( )
{
if (children == null) {
children = new FilterableTreeNode[10];
return;
}
if (children.length != childCount)
return;
FilterableTreeNode[ ] temp = new
FilterableTreeNode[childCount*2];
System.arraycopy(children, 0, temp, 0, childCount);
children = temp;
}
public FilterableTree findRoot( )
{
return mom.findRoot( );
}
public void setSelected(boolean selected)
this.selected = selected;
}
public void setShowing(boolean showing)
{
this.showing = showing;
}
// called by the FilterPanel
public void setChanged( )
{
mom.setChanged( );
}
public void update( )
{
mom.update( );
}
public void paintVisibleFilterPanels(Graphics g)
{
if (filtering & expanded) {
//filterPanel.paintComponent s(g);
filterPanel.invalidate( );
}
for (int i = 0; i < childCount; i++) {
children[i].paintVisibleFilterPane Is(g);
}
}
public void calcDimension(Dimension dim, int left)
{
dim. height += ROW_HEIGHT;
dim.width = Math.max(dim.width, left + TEXT_LEFT +
fontMetrics.stringWidth(text));
if (expanded) {
if (filtering) {
Dimension fpd = filterPanel.getPreferredSize( );
dim.height += fpd.height;
dim.width = Math.max(dim.width, left + ICON_LEFT
+ fpd.width);
}
for (int i = 0; i < childCount; i++) {
if (children[i].showing) {
children[i].calcDimension(dim, left +
INDENT_WIDTH);
}
}
}
}
public int paint (Graphics g, int x, int y)
{
xoffset = x;
yoffset = y;
// draw this node
if (leaf) {
g.drawImage(leafImage, x + ICON_LEFT, y + ICON_TOP,
null);
} else {
g.drawImage(expanded ? collapserImage : expanderImage,
x + TRIGGER_LEFT, y + TRIGGER_TOP, null);
g.drawImage(expanded ? expandedImage : collapsedImage,
x + ICON_LEFT, y + ICON_TOP, null);
}
if (selected) {
g.setColor(backgroundColorSelected);
g.fillRect(x + TEXT_LEFT, y + SELECTION_TOP,
fontMetrics.stringWidth(text) +
SELECTION_WIDTH_EXTRA,
SELECTION_HEIGHT);
g.setColor(textColorSelected);
} else {
g.setColor(textColor);
}
g.drawstring(text, x + TEXT_LEFT, y + textBaseline);
y += ROW_HEIGHT;
// draw filter (if visible)
if (filtering) {
if (expanded) {
FilterableTree.java This module (see Table 3) defines the class FilterableTree, an extension of the FilterableTreeNode class defined in the first module. A FilterableTree is just a special FilterableTreeNode (the top or root node of a tree) with special handlers for drawing the scrolibar and actual contents of the tree.
TABLE 3
FilterableTree.java
import java.awt.*;
import java.awt.event.*;
import iava.util.*;
// extends FilterableTreeNode because it's actually a
// node itself (the root node) but adds the handlers for
// the scrollbar and the actual contents of the panel
// i wish that FilterableTreeNode didn't have to be a
// panel, only this class needs to be a panel, but
// java doesn't allow for multiple inheritance. oh well.
public class FilterableTree extends FilterableTreeNode
implements ItemListener, ActionListener
{
// used for double-buffering the image to the screen
Image offscreen;
// size of the actual tree being shown currently
Dimension dimension;
// flags to wipe out the offscreen buffer and re-draw
boolean changed;
// currently selected items
Vector selections = new Vector( );
// popup so that user can turn filtering on and off
PopupMenu popup;
CheckboxMenuItem filterOn;
CheckboxMenuItem filterOff;
FilterableTreeNode popupRow; // last seen at . . .
public FilterableTree(String text)
{
super(text);
setup( );
}
public FilterableTree(String text, FilterPanel filterPanel)
{
super(text, null, filterPanel);
setup( );
}
protected void setup( )
{
//setBackground(Color.whi te);
// layout has to be null because the filterPanels
// get moved around to explicit coordinates
setLayout(null);
//addMouseListener(this);
popup = new PopupMenu( );
popup.add(filterOn = new CheckboxMenuItem("Filtering On"));
filterOn.addItemListener(this);
popup.add(filterOff = new CheckboxMenuItem("Filtering
Off"));
filterOff.add ItemListener(t his);
this.add(popup);
popup.addActionListener(th is);
enableEvents(AWTEvent.MOUSE_EVENT_MASK .vertline.
AWTEvent.KEY EVENT MASK);
}
public FilterableTree findRoot( )
{
return this;
}
// called by the FilterPanel
public void setChanged( )
{
changed = true;
//offecreen = null
update( );
}
public Dimension calcDimension( )
{
Dimension dim = new Dimension(0, 0);
calcDimension(dim, o);
return dim;
}
public void update( )
{
update(this.getGraphics ( ));
}
public void update(Graphics screen)
{
paint(screen);
}
// this function is optimized for scrolling and resizing the
// window. the offscreen image is only re- allocated if nodes
// are added to the tree. if the offscreen image is too big,
// that's no big deal, but if it's too small and is reallocated
// each time, that's a big performance hit.
public void paint(Graphics screen)
{
if (changed) {
offscreen = null;
}
if (offscreen == null) {
changed = true;
}
if (changed) {
dimension = calcDimension( );
offscreen = createImage(dimension.width,
dimension.height);
Graphics g = offscreen.getGraphics( );
g.setFont(font);
g.setColor(backgroundColor);
g.fillRect(0, 0, dimension.width, dimension.height);
paint(g, 0, 0);
getParent( ).doLayout( );
changed = false;
}
Dimension view = size( );
screen.setColor(backgroundColor);
if (view . width > dimension . width) {
screen.fillRect(dimension.width, 0, view.width - dimension.width,
view.height);
}
if (view.height > dimension. height) {
screen.fillRect(O, dimension.height, view.width,
view.height - dimension.height);
}
screen.drawImage(offacreen, O, O, this);
paintVisibleFilterPanels(screen);
}
public void actionPerformed(ActionEvent evt)
}
//System.out.printIn(evt.getActionCommand( ));
}
protected void processMouseEvent(MouseEvent event)
{
if (event.isPopupTrigger( )) {
handlePopupTrigger(event);
}
if (event.getID( ) == MouseEvent.MOUSE_PRESSED) {
if (((event.getModifiers( ) & InputEvent.BUTTON2_MASK)
>O) .parallel.
((event.getModifiers( ) &
InputEvent.BUTTON3_MASK) > 0) .parallel.
(event.isAltDown( )))
handlePopupTrigger(event);
else
handleMousePressed(event);
}
}
protected void handlePopupTrigger(MouseEvent event)
{
int x = event.getX( );
int y = event.getY( );
FilterableTreeNode row = findRow(y);
if (row == null) {
return;
}
if (row.isFilterable( )) {
filterOn.setState(row.filtering);
filterOff.setState(!row.filtering);
popup.show(this, x, y);
popupRow = row;
}
}
protected void handleMouse.Pressed(MouseEvent event)
{
int x = event.getX( );
int y = event.getY( );
FilterableTreeNode row = find Row(y);
if (row == null) return;
x -= row.xoffset;
y -= row.yoffset;
it ((x > TRIGGER_LEFT-1) && (x .about. TRIGGER_LEFT +
TRIGGER_DIMENSION+2) &&
(y > TRIGGER TOP-1) && (y < TRIGGER_TOP +
TRIGGER_DIMENSION+2)) {
//System.err.printIn("trigger");
//row.setExpanded(!row.expanded);
row.toggleExpanded( );
changed = true;
} else if ((x > ICON_LEFT) &&
(y > ICON_TOP) && (y .about. ICON_TOP +
ICON_DIMENSION) &&
(x < TEXT_LEFT +
fontMetrics.stringWidth(row.text))) {
//if control-click, add this to a vector of currently
//selected items. otherwise, deselect everyone in the
//vector, and add just this row back in.
//FilterableTreeNode root = findRootNode( );
//if (root.selections == null) {
// root.selections = new Vector( );
//}
//Vector selections = root.selections;
if (event.isControlDown( )) {
if (row.selected) {
row.setSelected(false);
selections.removeElement(row);
} else {
row.setSelected(true);
selections.addElement(row);
}
//} else if (event.isMetaDown( )) {
// // this tells us if it's a right-click
// tryPopup(row, x + row.xoffset, y + row.yoffset);
} else {
if (!row.selected) {
deselectAll( );
row.setSelected(true);
selections.addElement(row);
} else {
// do nothing . . .
}
}
changed = true;
} else {
deselectAll( );
changed = true;
}
if (changed) update( );
}
// from ItemListener, handles popup menu events
public void itemStateChanged(ItemEvent event)
{
//System.err.printIn(event.getItem( ).getClass( ));
if (event.getItem( ).equals("Filtering On")) {
//System.err.printIn("filter on");
popupRow.setFiltering(true);
} else if (event.getItem( ).equals("Filtering Off")) {
//System.err.printIn("Filter Off");
popupRow.setFiltering(false);
}
}
public Dimension getPreferredSize( )
}
if (dimension == null) {
dimension = calcDimension( );
}
return dimension;
}
protected void deselectAll( )
{
Enumeration e = selections.elements( );
while (e.hasMoreElements( )) {
FilterableTreeNode node = (FilterableTreeNode)
e.nextElement( );
node.setSelected(false);
}
selections.removeAllElements( ):
}
}
module FilterTreeDemo.java This module (see Table 4) defines the class FilterableTreeDemo, which does all the underlying work of putting the parts of the filter tree together and drawing the filter tree in a standard java frame on screen. In this demo case, the structure for the first level-nodes of the tree are hard-coded into the class, and the second-level filterable nodes are read in from an LDIF data file. To be completely specific, a class, Filterable TreeDemo is defined. This class's purpose is to instantiate a new instance of a FilterableTree named Messaging Server. It also instantiates four FilterableTreeNodes named Users, Folders, Logs, and Alarms, respectively. In addition, the FilterableTreeDemo class contains a while loop that instantiates hundreds of FilterableTreeNodes beneath the Users node by looping over the user data found in the file users.txt. The data in users.txt is in standard LDIF format.
TABLE 4
FilterableTreeDemo.java
import java.awt*;
import java.awt.event.*;
import java.io.*;
public class FilterableTreeDemo extends Frame
{
static public void main(String args[ ])
{
try {
FileReader fileReader = new FileReader("users.txt");
BufferedReader reader = new
BufferedReader(fileReader);
Frame frame = new FilterableTreeDemo(reader, null);
}
catch (Exception e) {
e.printStackTrace ( );
}
}
public FilterableTreeDemo(BufferedReader reader, Applet apples)
throws IOException
{
super("Inline Tree Filter Demo");
*FilterableTree.loadImages(applet);
FilterableTree tree = new FilterableTree("Messaging
Server")
FilterableTreeNode temp = new FilterableTreeNode("Users");
temp.setFiltering(true);
FilterPanel filterPanel = new FilterPanel(temp).
//filterPanel.invalidate( );
temp.setFilterPanel(filterPanel);
tree.addChild(temp);
//System.err.printIn(temp.parent);
String line;
while ((line = reader.readLine( )) != null) {
int commaIndex = line.indexOf(",");
if (commaIndex == -1) break;
String name = line.substring(4, commaIndex);
FilterableTreeNode kid = new FilterableTreeNode(name);
kid.setShowing(false);
temp.addChild(kid);
// throw away another line, i only need about 500
reader.readLine( );
}
reader close( );
temp = new FilterableTreeNode("Folders");
temp.addChild("hi there");
tree.addChild(temp);
temp = new FilterableTreeNode("Logs");
temp.addChild("leave me alone");
tree.addChild(temp);
temp = new FilterableTreeNode("Alarms");
temp.addChild("nosy, aren't you?");
tree.addChild(temp);
ScrollPane sp = new ScrollPane( );
sp.setSize(350, 450);
sp.add(tree);
this.setLayout(new BorderLayout( ));
//this.resize(350, 450);
this.move(50, 50);
this.add(sp);
this.pack( );
this.show( );
this.addWindowListener(new WindowAdapter( ) {
public void windowClosing(WindowEvent e) {
//System.exit(O);
dispose( );
}
});
}
}
module FilteringTreeDemoApplet.java This module (see Table 5) only has one purpose, to define and instantiate an instance of the class FilterableTreeDemo. Invoking this module creates an applet containing the entire working application of filterable trees.
TABLE 5
FilterableTreeDemoApplet.java
import java.awt.*;
import java.applet.*;
import java.io.*;
import java.net.*;
public class FilterableTreeDemoApplet extends Applet
{
public void init( )
{
showStatus("Loading users for tree, please wait . . . ");
try {
URL url = new URL(getCodeBase( ), "users.txt")
InputStream inputStream = url.openStream( );
InputStreamReader inputReader =
new inputStreamReader(inputStream);
BufferedReader reader = new
BufferedReader(inputReader);
Frame frame = new FilterableTreeDemo(reader
}
catch (Exception e) {
e.printStackTrace( );
}
showStatus("Inline Tree Filter Demo.");
}
}
Although the invention is described herein with reference to the preferred embodiment, one skilled in the art will readily appreciate that other applications may be substituted for those set forth herein without departing from the spirit and scope of the present invention. Accordingly, the invention should only be limited by the Claims included below.
|
Same subclass Same class Consider this |
||||||||||
