ORIGINAL DRAFT

This month, our component provides a framework to handle one of the most common UI paradigms. The navigation power that comes form the basic explorer-style views seems ubiquitous. When the left pane of a window is a navigator object, say a tree or a list, and the right pane changes based on selections in the navigator, the user can select from a large set of possibilities with relative ease. This approach is very common in file systems, extends well to properties, file viewers, program editors, specialty applications, and so on. The JExplore component provides the building blocks you need to make this kind of work as painless as possible.

Figure 1: JExplore using JListNavigator in Action.

Figure 1: JExplore using JListNavigator in Action.

Figure 1 shows one potential application for JExplore. In this scenario, we use a JList implementation of the ExploreNavigator interface, though you can use any object that implements this interface. We’ll provide both a JListNavigator and a JTreeNavigator as part of our framework. Other possibilities include a radio button navigator or an outlook bar style navigator. The basic premise is that when you make an ExploreNavigator selection, the right panel displays a suitable view for the selected object. In the Figure 1 example, each node is actually a file, so we use an ExploreView implementation which shows file attributes along with the content, if we’re looking at a text file.

Interfaces

Two primary interfaces make it possible for you to work with navigators and views within this framework. The ExploreNavigator interface in Listing 1 enforces the need to support addition and removal of NavigationListener objects. A navigator must support these methods so the framework can register to listen for events that affect the view selection. In addition to the addNavigationListener and removeNavitationListener methods, we provide a mechanism for retrieving the component that implements the interface. This makes it easier to pass ExploreNavigator references around since we can always call getComponent to reach a reference to the actual component implementation.

The ExploreView interface in Listing 2 must be implemented by any view that can be presented in the right pane of our framework. The net effect is that any component supporting a suitable interface can be used in each relative pane and the JExplore framework will automatically take care of the rest of the mechanics. Only two methods need to be implemented, the getComponent methods exists for the same reasons it’s implemented in the ExploreNavigator. The update methods allows us to pass user-defined objects from individual nodes to the view and have the view display custom data. In the case of a file node, for example, the file name may be passed by the update method as a user-defined object so that the view knows which file to read and display.

Figure 2 shows the architectural layering of objects in the JExplore component. We use a Swing JSplitPane to allow the navigator and view areas to be resized. Components which implement the ExploreNavigator and ExploreView interfaces are positioned inside the left and right panes. Two concrete navigator classes are provided with the framework, JTreeNavigator and JListNavigator. Both use the ExploreNode interface to store element information. As you can see from Figure 2, the nodes include text information, along with open and close icons and user-defined objects. The last, and most important value held by an ExploreNode is a reference to an ExploreView, which will be displayed based on a programmer-defined context, when the node is selected by the user.

Figure 2: Architectural Layers.

Figure 2: Architectural Layers.

Listing 3 shows the NavigationListener interface, which depends on the NavigationEvent from Listing 4. The NavigationEvent, like most UI events, extends the Java EventObject class. The constructor expects a reference to the event source and an ExploreNode object. We expose two methods, getNode and getView so that we can easily get the information we need at processing time. It might have been sufficient to implement the getNode method only, since you can get at the view through the ExploreNode object, but this is such a common requirement that convenience easily outweighs the overhead.

The NavigationListener interface supports a single method called viewSelected, which passes a NavigationEvent as its only argument. Because the JExplore component is implemented as a NavigationListener, we can respond effectively to user selections as they happen. When the framework receives this even, it can change the visible view in the right pane dynamically. When the event is received, we have enough information about the activated ExploreNode and associated ExploreView to make this happen without any programming intervention.

The ExploreNode interface in Listing 5 is required to identify the node name, icons for open and closed states, an associated view and optional user object. This interface implements accessor methods for each of the elements it needs to store. By developing your selections as ExploreNode elements, you can let the JExplore component do most of the work for you. Listing 6 shows a concrete implementation called DefaultNode which extends Swing’s DefaultMutableTreeNode. If you choose to create your own implementation, you can use virtually any object. The DefaultNode is particularly useful for JTree navigation and simpler object like a JList entry can share the same implementation in combination with suitable renderers.

Figure 3: JExplore Event Sequence.

Figure 3: JExplore Event Sequence.

Figure 3 shows the basic event sequence for node selection in an ExploreNavigator. The viewSelected message is sent to the JExplore component and picked up by its implementation of the NavigationListener. The viewSelected event causes a lookup of the view and user objects from the selected ExploreNode. The view is then updated with user-defined data before being activated and displayed. The most important implication is that the user-defined data and the associated view must be compatible or the view will not be able to present useful information.

Given the ExploreNavigator and ExploreView interfaces, you can create any navigator or view object. The ExploreNode interface and the DefaultNode implementation allows us to choose from two of the most useful navigator implementation, a JListNavigator and a JTreeNavigator.

Listing 7 shows the JListNavigator implementation which extends JList and implements both the ListSelectionListener and the ExploreNavigator interfaces. The ListSelectionListener allows us to respond to user selection events. The ExploreNavigator interface forces us to implement the ability to register and unregister NavigationListeners along with the getComponent method. We add an internal fireNavigationEvent method to send notification that a NavigationEvent has occurred and call this method when the selection event is triggered. The only other thing worth noting is that we set the cell renderer to our custom ExploreRenderer in the constructor.

The JTreeNavigator in Listing 8 is almost identical to the JListNavigator code, though it extends the JTree class instead. We register as a TreeSelectionListener and set our custom ExploreRenderer in the constructor. When we receive a TreeSelectionEvent we get the tree path and find the selected node using a convenience method called getSelectedNode, which returns a suitable ExploreNode object. Like JListNavigator, we use a method called fireNavigationEvent to send notification to the framework.

Listing 9 shows the ExploreRenderer. Since it might be used in either a JTree or JList, we implement both the Swing ListCellRenderer and TreeCellRenderer interfaces. Fortunately, these share a lot of code, so the implementation is pretty simple. We use a JPanel object to increase flexibility, though the same effect would be achievable using a JLabel implementation. By separating the icon and text display object, we retain more control and may later decide to extend this class more effectively, supporting check box selection without affecting the text or icon rendering, for example.

Of course, you can use any ExploreNode and ExploreNavigator combination you like, so long as the interfaces are properly implemented. In our case, we want to make sure the two most common navigator elements are easily within reach when ever we need to use the JExplore component. We’ve provided two common navigator objects and a default node class so you should only need to implement suitable views to make this function in your own application. The online code includes a number of views for demonstration purposes, which you can download from www.java-pro.com.

The Component

Listing 10 shows the JExplore class, which is the main class you will use to make all this work for you. Three constructors are provided, allowing you to optionally specify the orientation and ExploreNavigator to use. By default, we assume a horizontal orientation but no assumptions are made about the navigator object. Because you may need to initialize the navigator before you can use it with JExplore, you can use the setNavigator method to set it after construction a suitable JExplore instance. The setNavigator method does all the visual component set up, with the nested objects/panels shown previously in Figure 2.

The viewSelected event is the key to making view selection as seamless as possible. When the event listener is called, we get the ExploreNode and ExploreView from the NavigationEvent and call the ExploreView object with the update method to set the user-defined data, after which we make sure no other views are currently part of our container and set the view as the center element in our BorderLayout. Before repainting the view, we make sure the view is using the current look-and-feel and revalidate the display to force a layout. The look-and-feel may be an issue because of the late display tricks we engage in, since the view object was not necessarily initialized along with the other objects that were part of the component tree when the application was first run.

Figure 4: JExplore with a nested JExplore/FolderView.

Figure 4: JExplore with a nested JExplore/FolderView.

Figure 4 shows an example of using a nested JExplore combination, where the JTreeNavigator nodes are associated with a FolderView which extends the JExplore class, displaying files from a JListNavigator selection.

Basic Views

As part of the development for this framework, I put together four (4) views which implement the ExploreView interface. The FileView class does little more than show a few File attributes in a grid layout with a border. The TextView class extends FileView and uses the JTextArea to display the actual text in a file. The ImageView class also extends FileView and adds the ability to view an image in a scrolling area. These views are relatively simple and provide an excellent starting point for your own view development.

The FolderView is only slightly more complicated than the previous views. FolderView extends JExplore, which allows us to display a vertically separated file list for a given path. By combining the JTreeNavigator and FolderView, we can explore a file system. By using the FolderView as an additional JExplore object, we can further drill down into the file system by selecting files with the JListNavigator, using the simpler views to look at text and image files and the attributes of any file type.

The JExploreTest class produces the display in Figure 4. We use two inner classes to construct extensions to the navigator objects for the list and tree classes, respectively. The FileListNavigator sets up a view of files in the root directory, distinguishing between gif and jpg image files, txt and log text files, or other files. In each case, the DefaultNode uses a suitable icon and ExploreView instance, statically created earlier. The FolderTree navigator collects folders from the file system, starting the root of the C: drive. You’ll have to change this if you want to run the test on a different OS. We traverse only two levels down to avoid long wait times, for demonstration purposes. In a real application, you’d want to make the nodes more intelligent when looking for child nodes, constructing them on-the-fly for performance and usability reasons.

Summary

The JExplore component provides an infrastructure for developing the common explorer interface without the overhead of putting together navigation elements, custom renderers, editors, views and more. Since all you have to do is use an existing navigator, populate it with the desired nodes, each associated with a suitable view, the task is very simple. You’ll still want to develop various views, but at least the navigation will be taken care of for you.

Listing 1

import javax.swing.*;

public interface ExploreNavigator
{
  public void addNavigationListener(NavigationListener listener);
  public void removeNavigationListener(NavigationListener listener);
  public JComponent getComponent();
}
</PRE>
<PRE><B>Listing 2</B>

import java.awt.*;

public interface ExploreView
{
  public Component getComponent();
  public void update(Object userObject);
}

Listing 3

import java.util.*;

public interface NavigationListener extends EventListener
{
  public void viewSelected(NavigationEvent event);
}

Listing 4

import java.util.*;

public class NavigationEvent extends EventObject
{
  protected ExploreNode node;
  
  public NavigationEvent(Object source, ExploreNode node)
  {
    super(source);
    this.node = node;
  }
  
  public ExploreNode getNode()
  {
    return node;
  }
  
  public ExploreView getView()
  {
    return node.getView();
  }
}

Listing 5

import javax.swing.*;
import javax.swing.tree.*;

public interface ExploreNode
{
  public String getText();
  public Icon getOpenIcon();
  public Icon getClosedIcon();
  public ExploreView getView();
  public Object getUserObject();
  public void setText(String text);
  public void setOpenIcon(Icon openIcon);
  public void setClosedIcon(Icon closedIcon);
  public void setView(ExploreView view);
  public void setUserObject(Object obj);
}

Listing 6

import javax.swing.*;
import javax.swing.tree.*;

public class DefaultNode extends DefaultMutableTreeNode
  implements ExploreNode
{
  protected Icon openIcon, closedIcon;
  protected ExploreView view;
  protected String text;
  
  public DefaultNode(String text,
    Icon icon, Object data, ExploreView view)
  {
    this(text, icon, icon, data, view);
  }
  
  public DefaultNode(String text,
    Icon openIcon, Icon closedIcon,
    Object data, ExploreView view)
  {
    super(data);
    this.text = text;
    this.openIcon = openIcon;
    this.closedIcon = closedIcon;
    this.view = view;
  }
  
  public String getText()
  {
    return text;
  }
  
  public Icon getOpenIcon()
  {
    return openIcon;
  }
  
  public Icon getClosedIcon()
  {
    return closedIcon;
  }
  
  public ExploreView getView()
  {
    return view;
  }
  
  public void setText(String text)
  {
    this.text = text;
  }
  
  public void setOpenIcon(Icon openIcon)
  {
    this.openIcon = openIcon;
  }
  
  public void setClosedIcon(Icon closedIcon)
  {
    this.closedIcon = closedIcon;
  }
  
  public void setView(ExploreView view)
  {
    this.view = view;
  }
}

Listing 7

import java.awt.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;

public class JListNavigator extends JList
  implements ExploreNavigator, ListSelectionListener
{
  protected Vector navListeners = new Vector();	
  
  public JListNavigator()
  {
    addListSelectionListener(this);
    setCellRenderer(new ExploreRenderer());
  }
  
  public JComponent getComponent()
  {
    return this;
  }
  
  public ExploreNode getSelectedNode()
  {
    return (ExploreNode)getSelectedValue();
  }
  
  public void valueChanged(ListSelectionEvent event)
  {
    if (!event.getValueIsAdjusting())
      fireNavigationEvent(getSelectedNode());
  }
  
  public void addNavigationListener(NavigationListener listener)
  {
    navListeners.addElement(listener);
  }

  public void removeNavigationListener(NavigationListener listener)
  {
    navListeners.removeElement(listener);
  }

  public void fireNavigationEvent(ExploreNode node)
  {
    if (node == null) return;
    NavigationEvent event = new NavigationEvent(this, node);
    Vector listeners = (Vector)navListeners.clone();
    NavigationListener listener;
    for (int i = 0; i &lt; listeners.size(); i++)
    {
      listener = (NavigationListener)listeners.elementAt(i);
      listener.viewSelected(event);
    }
  }
}

Listing 8

import java.awt.*;
import java.util.*;
import javax.swing.*;
import javax.swing.tree.*;
import javax.swing.event.*;

public class JTreeNavigator extends JTree
  implements ExploreNavigator, TreeSelectionListener
{
  protected Vector navListeners = new Vector();	
  
  public JTreeNavigator()
  {
    addTreeSelectionListener(this);
    setCellRenderer(new ExploreRenderer());
    setShowsRootHandles(true);
  }
  
  public JComponent getComponent()
  {
    return this;
  }
  
  public void setSelectionPath(TreePath path)
  {
    getSelectionModel().setSelectionPath(path);
  }
  
  public ExploreNode getSelectedNode()
  {
    TreePath path = getSelectionModel().getSelectionPath();	
    if (path == null) return null;
    return (ExploreNode)path.getLastPathComponent();
  }
  
  public void valueChanged(TreeSelectionEvent event)
  {
    TreePath path = event.getPath();
    fireNavigationEvent(getSelectedNode());
  }
  
  public void addNavigationListener(NavigationListener listener)
  {
    navListeners.addElement(listener);
  }

  public void removeNavigationListener(NavigationListener listener)
  {
    navListeners.removeElement(listener);
  }

  public void fireNavigationEvent(ExploreNode node)
  {
    if (node == null) return;
    NavigationEvent event = new NavigationEvent(this, node);
    Vector listeners = (Vector)navListeners.clone();
    NavigationListener listener;
    for (int i = 0; i &lt; listeners.size(); i++)
    {
      listener = (NavigationListener)listeners.elementAt(i);
      listener.viewSelected(event);
    }
  }
}

Listing 9

import java.awt.*;
import javax.swing.*;
import javax.swing.tree.*;
import javax.swing.border.*;

public class ExploreRenderer extends JPanel
  implements ListCellRenderer, TreeCellRenderer
{
  private final static LineBorder normalBorder =
    new LineBorder(Color.white, 1);
  private final static LineBorder focusBorder =
    new LineBorder(Color.blue, 1);
  protected JLabel icon, label;
  
  public ExploreRenderer()
  {
    setOpaque(true);
    setLayout(new BorderLayout(2, 0));
    add(BorderLayout.WEST, icon = new JLabel());
    add(BorderLayout.CENTER, label = new JLabel());
  }
  
  public Component getListCellRendererComponent(
    JList list, Object value, int index,
      boolean isSelected, boolean hasFocus)
  {
    ExploreNode node = (ExploreNode)value;
    label.setText(node.getText());
    icon.setIcon(node.getClosedIcon());
    drawSelection(list, isSelected, hasFocus);
    return this;
  }

  public Component getTreeCellRendererComponent(
    JTree tree, Object value, boolean isSelected,
    boolean isExpanded, boolean isLeaf, int row,
    boolean hasFocus)
  {
    if (value instanceof ExploreNode)
    {
      ExploreNode node = (ExploreNode)value;
      label.setText(node.getText());
      icon.setIcon(isExpanded ?
       node.getOpenIcon() : node.getClosedIcon());
    }
    else
    {
      label.setText(value.toString());
      icon.setIcon(null);
    }
    drawSelection(tree, isSelected, hasFocus);
    return this;
  }
  
  private void drawSelection(Component host,
    boolean isSelected, boolean hasFocus)
  {
    if (isSelected)
    {
      setBorder(focusBorder);
      if (hasFocus)
      {
        setBackground(Color.blue);
        label.setForeground(Color.white);
      }
      else
      {
        setBackground(host.getBackground());
        label.setForeground(host.getForeground());
      }
    }
    else
    {
      setBorder(normalBorder);
      setBackground(host.getBackground());
      label.setForeground(host.getForeground());
    }
  }
}

Listing 10

import java.io.*;
import java.awt.*;
import javax.swing.*;

public class JExplore extends JPanel
  implements SwingConstants, NavigationListener
{
  private static final ExploreView
    defaultView = new DefaultView();
  
  protected int orientation = HORIZONTAL;
  protected JComponent nav, panel;
  protected JSplitPane split;
  
  public JExplore()
  {
    this(null, HORIZONTAL);
  }
  
  public JExplore(int orientation)
  {
    this(null, orientation);
  }
  
  public JExplore(ExploreNavigator navigator)
  {
    this(navigator, HORIZONTAL);
  }
  
  public JExplore(ExploreNavigator navigator, int orientation)
  {
    this.orientation = orientation;
    setLayout(new BorderLayout());
    if (navigator != null) setNavigator(navigator);
  }
  
  public void setNavigator(ExploreNavigator navigator)
  {
    panel = new JPanel();
    panel.setLayout(new BorderLayout());
    panel.setPreferredSize(new Dimension(180, 120));
    nav = navigator.getComponent();
    JScrollPane navPane = new JScrollPane(nav);
    navPane.setPreferredSize(new Dimension(180, 120));
    JSplitPane split;
    if (orientation == VERTICAL)
    {
      split = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
      split.setTopComponent(navPane);
      split.setBottomComponent(panel);
    }
    else
    {
      split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
      split.setLeftComponent(navPane);
      split.setRightComponent(panel);
    }
    add(BorderLayout.CENTER, split);
    navigator.addNavigationListener(this);
  }

  public void viewSelected(NavigationEvent event)
  {
    ExploreNode node = event.getNode();
    ExploreView view = event.getView();
    if (view == null) view = defaultView;
    view.update(node.getUserObject());
    if (panel.getComponentCount() &gt; 0)
      for (int i = 0; i &lt; panel.getComponentCount(); i++)
        panel.remove(panel.getComponent(i));
    panel.add(BorderLayout.CENTER, view.getComponent());
    SwingUtilities.updateComponentTreeUI(view.getComponent());
    revalidate();
    panel.repaint();
  }
}

Listing 11

import java.io.*;
import java.awt.*;
import javax.swing.*;
import javax.swing.border.*;

public class FileView extends JPanel
  implements ExploreView
{
  protected JLabel name, path, type, size;
  protected JTextArea text;
  
  public FileView()
  {
    JPanel attr = new JPanel();
    attr.setBorder(new TitledBorder(&quot;Attributes&quot;));
    attr.setLayout(new GridLayout(4, 2));
    attr.add(new JLabel(&quot;Name: &quot;, JLabel.RIGHT));
    attr.add(name = new JLabel());
    attr.add(new JLabel(&quot;Path: &quot;, JLabel.RIGHT));
    attr.add(path = new JLabel());
    attr.add(new JLabel(&quot;Type: &quot;, JLabel.RIGHT));
    attr.add(type = new JLabel());
    attr.add(new JLabel(&quot;Size: &quot;, JLabel.RIGHT));
    attr.add(size = new JLabel());

    setLayout(new BorderLayout());
    add(BorderLayout.NORTH, attr);
  }

  public Component getComponent()
  {
    return this;
  }
  
  public void update(Object userObject)
  {
    if (userObject != null &amp;&amp; userObject instanceof File)
    {
      File file = (File)userObject;
      name.setText(file.getName());
      path.setText(file.getParent());
      type.setText(file.isFile() ? &quot;file&quot; : &quot;folder&quot;);
      size.setText(&quot;&quot; + file.length());
    }
  }
}

Listing 12

import java.io.*;
import java.awt.*;
import javax.swing.*;
import javax.swing.border.*;

public class TextView extends FileView
{
  protected JTextArea text = new JTextArea();
  
  public TextView()
  {
    JPanel panel = new JPanel(new BorderLayout());
    panel.add(BorderLayout.CENTER, new JScrollPane(text));
    panel.setBorder(new TitledBorder(&quot;Text&quot;));
    add(BorderLayout.CENTER, panel);
    text.setEditable(false);
  }

  public void update(Object userObject)
  {
    super.update(userObject);
    if (userObject != null &amp;&amp; userObject instanceof File)
    {
      File file = (File)userObject;
      text.setText(readTextFile(file));
      text.setCaretPosition(0);
    }
  }

  private String readTextFile(File file)
  {
    try
    {
      FileInputStream input = new FileInputStream(file);
      ByteArrayOutputStream output = new ByteArrayOutputStream();
      int count = 0;
      byte[] buffer = new byte[4096];
      while ((count = input.read(buffer)) &gt; -1)
        output.write(buffer, 0, count);
      output.close();
      input.close();
      return output.toString();
    }
    catch (FileNotFoundException e)
    {
      return &quot;ERROR: File Not Found Exception!&quot;;
    }
    catch (IOException e)
    {
      return &quot;ERROR: IO Exception!&quot;;
    }
  }
}

Listing 13

import java.io.*;
import java.awt.*;
import javax.swing.*;
import javax.swing.border.*;

public class ImageView extends FileView
{
  protected JLabel display = new JLabel();
  
  public ImageView()
  {
    JPanel panel = new JPanel(new BorderLayout());
    panel.add(BorderLayout.CENTER, new JScrollPane(display));
    panel.setBorder(new TitledBorder(&quot;Image&quot;));
    add(BorderLayout.CENTER, panel);
  }

  public void update(Object userObject)
  {
    super.update(userObject);
    if (userObject != null &amp;&amp; userObject instanceof File)
    {
      String filename = ((File)userObject).getPath();
      display.setIcon(new ImageIcon(filename));
    }
  }
}

Listing 14

import java.io.*;
import java.awt.*;
import java.util.*;
import javax.swing.*;
import javax.swing.border.*;

public class FolderView extends JExplore
  implements ExploreView
{
  private static final Icon fileIcon =
    new ImageIcon(&quot;icons/File16x16.gif&quot;);
  private static final Icon textIcon =
    new ImageIcon(&quot;icons/Text16x16.gif&quot;);
  private static final Icon imageIcon =
    new ImageIcon(&quot;icons/Image16x16.gif&quot;);
  
  private static final ExploreView
    fileView = new FileView();
  private static final ExploreView
    textView = new TextView();
  private static final ExploreView
    imageView = new ImageView();

  protected JListNavigator listNavigator;
  
  public FolderView()
  {
    super(VERTICAL);
    setNavigator(listNavigator = new JListNavigator());
  }

  public Component getComponent()
  {
    return this;
  }
  
  public void update(Object userObject)
  {
    if (userObject != null &amp;&amp; userObject instanceof File)
    {
      File path = (File)userObject;
      File[] list = path.listFiles();
      Vector items = new Vector();
      for (int i = 0; i &lt; list.length; i++)
      {
        if (list[i].isFile())
        {
          String name = list[i].getName().toLowerCase();
          if (name.endsWith(&quot;.gif&quot;) || name.endsWith(&quot;.jpg&quot;))
            items.addElement(new DefaultNode(list[i].getName(),
              imageIcon, imageIcon, list[i], imageView));
          else if (name.endsWith(&quot;.txt&quot;) || name.endsWith(&quot;.log&quot;))
            items.addElement(new DefaultNode(list[i].getName(),
              textIcon, textIcon, list[i], textView));
          else items.addElement(new DefaultNode(list[i].getName(),
              fileIcon, fileIcon, list[i], fileView));
        }
      }   
      listNavigator.setListData(items);
    }
  }
}

Listing 15

import java.io.*;
import java.awt.*;
import java.util.*;
import javax.swing.*;
import javax.swing.tree.*;

public class JExploreTest
{
  private static final Icon driveIcon =
    new ImageIcon(&quot;icons/Drive16x16.gif&quot;);
  private static final Icon folderIcon =
    new ImageIcon(&quot;icons/Folder16x16.gif&quot;);
  private static final Icon fileIcon =
    new ImageIcon(&quot;icons/File16x16.gif&quot;);
  private static final Icon textIcon =
    new ImageIcon(&quot;icons/Text16x16.gif&quot;);
  private static final Icon imageIcon =
    new ImageIcon(&quot;icons/Image16x16.gif&quot;);
  
  private static final ExploreView
    defaultView = new DefaultView();
  private static final ExploreView
    fileView = new FileView();
  private static final ExploreView
    folderView = new FolderView();
  private static final ExploreView
    textView = new TextView();
  private static final ExploreView
    imageView = new ImageView();
  
  public JTreeNavigator createTreeNavigator()
  {
    return new FolderTreeNavigator();
  }
  
  public class FolderTreeNavigator extends JTreeNavigator
  {
    public FolderTreeNavigator()
    {
      DefaultNode root = new DefaultNode(&quot;C:&quot;,
       driveIcon, new File(&quot;C:&quot;), defaultView);
      
      File path = new File(&quot;/&quot;);
      buildNode(root, path, 2);
      setModel(new DefaultTreeModel(root));
    }
      
    private void buildNode(DefaultNode root, File path, int depth)
    {
      if (depth == 0) return;
      
      File[] list = path.listFiles();
      for (int i = 0; i &lt; list.length; i++)
      {
        if (list[i].isDirectory())
        {
          DefaultNode node =  new DefaultNode(list[i].getName(),
            folderIcon, list[i], folderView);
          root.add(node);
          buildNode(node, list[i], depth - 1);
        }
      }
    }	
  }

  public JListNavigator createListNavigator()
  {
    return new FileListNavigator();
  }
  
  public class FileListNavigator extends JListNavigator
  {
    public FileListNavigator()
    {
      File[] list = (new File(&quot;/&quot;)).listFiles();
      Vector items = new Vector();
      for (int i = 0; i &lt; list.length; i++)
      {
        if (list[i].isFile())
        {
          String name = list[i].getName().toLowerCase();
          if (name.endsWith(&quot;.gif&quot;) || name.endsWith(&quot;.jpg&quot;))
            items.addElement(new DefaultNode(list[i].getName(),
              imageIcon, list[i], imageView));
          else if (name.endsWith(&quot;.txt&quot;) || name.endsWith(&quot;.log&quot;))
            items.addElement(new DefaultNode(list[i].getName(),
              textIcon, list[i], textView));
          else items.addElement(new DefaultNode(list[i].getName(),
              fileIcon, list[i], fileView));
        }
      }
      setListData(items);
    }	
  }

  public static void main(String[] args)
  {
    PLAF.setNativeLookAndFeel(true);
    JExploreTest test = new JExploreTest();
    
    JFrame frame = new JFrame(&quot;JExplore Test&quot;);
    frame.getContentPane().setLayout(new BorderLayout());
    frame.getContentPane().add(BorderLayout.CENTER,
      new JExplore(
        test.createTreeNavigator(), JExplore.HORIZONTAL));
    frame.setBounds(50, 50, 600, 400);
    frame.setVisible(true);
  }
}