Java Source - JTree Table.

Mã nguồn sau sẽ minh họa cách xây dựng một JTree Table hiển thị danh sách các tập tin, thư mục tồn tài trong máy tính của bản nó hao hao giống với Explore ^^!, do srouce quá dài nên bạn có thể download về máy xem cho tiện.


http://www.mediafire.com/download/u29k13h8le30jcb/TreeTableExample2.zip


TreeTableExample2.java
/*
 * TreeTableExample2.java
 *
 * Copyright (c) 1998 Sun Microsystems, Inc. All Rights Reserved.
 *
 * This software is the confidential and proprietary information of Sun
 * Microsystems, Inc. ("Confidential Information").  You shall not
 * disclose such Confidential Information and shall use it only in
 * accordance with the terms of the license agreement you entered into
 * with Sun.
 *
 * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
 * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 * PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES
 * SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
 * THIS SOFTWARE OR ITS DERIVATIVES.
 *
 */

import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;
import javax.swing.table.*;
import javax.swing.tree.*;
import java.awt.*;
import java.awt.event.*;
import java.text.NumberFormat;

/**
 * Assembles the UI. The UI consists of a JTreeTable and a status label.
 * As nodes are loaded by the FileSystemModel2, in a background thread,
 * the status label updates as well as the renderer to draw the node that
 * is being loaded differently.
 *
 * @author Scott Violet
 * @author Philip Milne
 */
public class TreeTableExample2 {
    /** Number of instances of TreeTableExample2. */
    protected static int         ttCount;

    /** Model for the JTreeTable. */
    protected FileSystemModel2    model;
    /** Used to represent the model. */
    protected JTreeTable         treeTable;
    /** Row the is being reloaded. */
    protected int                reloadRow;
    /** TreePath being reloaded. */
    protected TreePath           reloadPath;
    /** A counter increment as the Timer fies and the same path is
     * being reloaded. */
    protected int                reloadCounter;
    /** Timer used to update reload state. */
    protected Timer              timer;
    /** Used to indicate status. */
    protected JLabel             statusLabel;
    /** Frame containing everything. */
    protected JFrame             frame;
    /** Path created with. */
    protected String             path;


    public TreeTableExample2(String path) {
    this.path = path;
    ttCount++;

    frame = createFrame();

    Container      cPane = frame.getContentPane();
    JMenuBar       mb = createMenuBar();

    model = createModel(path);
    treeTable = createTreeTable();
    statusLabel = createStatusLabel();
    cPane.add(new JScrollPane(treeTable));
    cPane.add(statusLabel, BorderLayout.SOUTH);

    reloadRow = -1;
    frame.setJMenuBar(mb);
    frame.pack();
    frame.show();
    SwingUtilities.invokeLater(new Runnable() {
        public void run() {
        reload(model.getRoot());
        }
    });
    }

    /**
     * Creates and return a JLabel that is used to indicate the status
     * of loading.
     */
    protected JLabel createStatusLabel() {
    JLabel         retLabel = new JLabel(" ");

    retLabel.setHorizontalAlignment(JLabel.RIGHT);
    retLabel.setBorder(new BevelBorder(BevelBorder.LOWERED));
    return retLabel;
    }

    /**
     * Creates and returns the instanceof JTreeTable that will be used.
     * This also creates, but does not start, the Timer that is used to
     * update the display as files are loaded.
     */
    protected JTreeTable createTreeTable() {
    JTreeTable       treeTable = new JTreeTable(model);

    treeTable.getColumnModel().getColumn(1).setCellRenderer
                               (new IndicatorRenderer());

    Reloader rl = new Reloader();

    timer = new Timer(700, rl);
    timer.setRepeats(true);
    treeTable.getTree().addTreeExpansionListener(rl);
    return treeTable;
    }

    /**
     * Creates the FileSystemModel2 that will be used.
     */
    protected FileSystemModel2 createModel(String path) {
    return new FileSystemModel2(path);
    }

    /**
     * Creates the JFrame that will contain everything.
     */
    protected JFrame createFrame() {
    JFrame       retFrame = new JFrame("TreeTable II");

    retFrame.addWindowListener(new WindowAdapter() {
        public void windowClosing(WindowEvent we) {
        if (--ttCount == 0) {
            System.exit(0);
        }
        }
    });
    return retFrame;
    }

    /**
     * Creates a menu bar.
     */
    protected JMenuBar createMenuBar() {
        JMenu            fileMenu = new JMenu("File");
    JMenuItem        menuItem;

    menuItem = new JMenuItem("Open");
    menuItem.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent ae) {
        JFileChooser      fc = new JFileChooser(path);

        fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);

        int               result = fc.showOpenDialog(frame);

        if (result == JFileChooser.APPROVE_OPTION) {
            String      newPath = fc.getSelectedFile().getPath();

            new TreeTableExample2(newPath);
        }
        }
    });
    fileMenu.add(menuItem);
    fileMenu.addSeparator();
   
    menuItem = new JMenuItem("Reload");
    menuItem.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent ae) {
        TreePath         path = treeTable.getTree().getSelectionPath();

        if (path != null) {
            model.stopLoading();
            reload(path.getLastPathComponent());
        }
        }
    });
    fileMenu.add(menuItem);

    menuItem = new JMenuItem("Stop");
    menuItem.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent ae) {
        model.stopLoading();
        }
    });
    fileMenu.add(menuItem);

    fileMenu.addSeparator();

    menuItem = new JMenuItem("Exit");
    menuItem.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent ae) {
        System.exit(0);
        }
    });
    fileMenu.add(menuItem);


    // Create a menu bar
    JMenuBar        menuBar = new JMenuBar();

    menuBar.add(fileMenu);

    // Menu for the look and feels (lafs).
    UIManager.LookAndFeelInfo[]        lafs = UIManager.
                                        getInstalledLookAndFeels();
    ButtonGroup                        lafGroup = new ButtonGroup();

    JMenu          optionsMenu = new JMenu("Options");

    menuBar.add(optionsMenu);

    for(int i = 0; i < lafs.length; i++) {
        JRadioButtonMenuItem rb = new JRadioButtonMenuItem(lafs[i].
                                   getName());
        optionsMenu.add(rb);
        rb.setSelected(UIManager.getLookAndFeel().getName().equals
               (lafs[i].getName()));
        rb.putClientProperty("UIKey", lafs[i]);
        rb.addItemListener(new ItemListener() {
            public void itemStateChanged(ItemEvent ae) {
                JRadioButtonMenuItem rb2 = (JRadioButtonMenuItem)ae.
                                   getSource();
                if(rb2.isSelected()) {
                UIManager.LookAndFeelInfo       info =
                                      (UIManager.LookAndFeelInfo)
                           rb2.getClientProperty("UIKey");
                try {
                    UIManager.setLookAndFeel(info.getClassName());
                    SwingUtilities.updateComponentTreeUI(frame);
            }
            catch (Exception e) {
                     System.err.println("unable to set UI " +
                        e.getMessage());
            }
                }
            }
        });
        lafGroup.add(rb);
    }
    return menuBar;
    }

    /**
     * Invoked to reload the children of a particular node. This will
     * also restart the timer.
     */
    protected void reload(Object node) {
    model.reloadChildren(node);
    if (!timer.isRunning()) {
        timer.start();
    }
    }

    /**
     * Updates the status label based on reloadRow.
     */
    protected void updateStatusLabel() {
    if (reloadPath != null) {
        statusLabel.setText("Reloading: " + model.getPath
                (reloadPath.getLastPathComponent()));
        if ((reloadCounter % 4) < 2) {
        statusLabel.setForeground(Color.red);
        }
        else {
        statusLabel.setForeground(Color.blue);
        }
    }
    else if (!model.isReloading()) {
        statusLabel.setText("Total Size: " + NumberFormat.getInstance().
                format(model.getTotalSize(model.getRoot())));
        statusLabel.setForeground(Color.black);
    }
    }


    /**
     * Reloader is the ActionListener used in the Timer. In response to
     * the timer updating it will reset the reloadRow/reloadPath and
     * generate the necessary event so that the display will update. It
     * also implements the TreeExpansionListener so that if the tree is
     * altered while loading the reloadRow is updated accordingly.
     */
    class Reloader implements ActionListener, TreeExpansionListener {
    public void actionPerformed(ActionEvent ae) {
        if (!model.isReloading()) {
        // No longer loading.
        timer.stop();
        if (reloadRow != -1) {
            generateChangeEvent(reloadRow);
        }
        reloadRow = -1;
        reloadPath = null;
        }
        else {
        // Still loading, see if paths changed.
        TreePath       newPath = model.getPathLoading();

        if (newPath == null) {
            // Hmm... Will usually indicate the reload thread
            // completed between time we asked if reloading.
            if (reloadRow != -1) {
            generateChangeEvent(reloadRow);
            }
            reloadRow = -1;
            reloadPath = null;
        }
        else {
            // Ok, valid path, see if matches last path.
            int        newRow = treeTable.getTree().getRowForPath
                                      (newPath);

            if (newPath.equals(reloadPath)) {
            reloadCounter = (reloadCounter + 1) % 8;
            if (newRow != reloadRow) {
                int             lastRow = reloadRow;

                reloadRow = newRow;
                generateChangeEvent(lastRow);
            }
            generateChangeEvent(reloadRow);
            }
            else {
            int          lastRow = reloadRow;

            reloadCounter = 0;
            reloadRow = newRow;
            reloadPath = newPath;
            if (lastRow != reloadRow) {
                generateChangeEvent(lastRow);
            }
            generateChangeEvent(reloadRow);
            }
        }
        }
        updateStatusLabel();
    }

    /**
     * Generates and update event for the specified row. FileSystemModel2
     * could do this, but it would not know when the row has changed
     * as a result of expanding/collapsing nodes in the tree.
     */
    protected void generateChangeEvent(int row) {
        if (row != -1) {
        AbstractTableModel     tModel = (AbstractTableModel)treeTable.
                                         getModel();

        tModel.fireTableChanged(new TableModelEvent
                       (tModel, row, row, 1));
        }
    }

    //
    // TreeExpansionListener
    //

    /**
     * Invoked when the tree has expanded.
     */
    public void treeExpanded(TreeExpansionEvent te) {
        updateRow();
    }

    /**
     * Invoked when the tree has collapsed.
     */
    public void treeCollapsed(TreeExpansionEvent te) {
        updateRow();
    }

    /**
     * Updates the reloadRow and path, this does not genernate a
     * change event.
     */
    protected void updateRow() {
        reloadPath = model.getPathLoading();

        if (reloadPath != null) {
        reloadRow = treeTable.getTree().getRowForPath(reloadPath);
        }
    }
    }


    /**
     * A renderer that will give an indicator when a cell is being reloaded.
     */
    class IndicatorRenderer extends DefaultTableCellRenderer {
    /** Makes sure the number of displayed in an internationalized
     * manner. */
    protected NumberFormat       formatter;
    /** Row that is currently being painted. */
    protected int                lastRow;
   

    IndicatorRenderer() {
        setHorizontalAlignment(JLabel.RIGHT);
        formatter = NumberFormat.getInstance();
    }

    /**
     * Invoked as part of DefaultTableCellRenderers implemention. Sets
     * the text of the label.
     */
    public void setValue(Object value) {
        setText((value == null) ? "---" : formatter.format(value));
    }

    /**
     * Returns this.
     */
    public Component getTableCellRendererComponent(JTable table,
                Object value, boolean isSelected, boolean hasFocus,
                int row, int column) {
        super.getTableCellRendererComponent(table, value, isSelected,
                        hasFocus, row, column);
        lastRow = row;
        return this;
    }

    /**
     * If the row being painted is also being reloaded this will draw
     * a little indicator.
     */
    public void paint(Graphics g) {
        if (lastRow == reloadRow) {
        int       width = getWidth();
        int       height = getHeight();

        g.setColor(getBackground());
        g.fillRect(0, 0, width, height);
        g.setColor(getForeground());

        int       diameter = Math.min(width, height);

        if (reloadCounter < 5) {
            g.fillArc((width - diameter) / 2, (height - diameter) / 2,
                  diameter, diameter, 90, -(reloadCounter * 90));
        }
        else {
            g.fillArc((width - diameter) / 2, (height - diameter) / 2,
                  diameter, diameter, 90,
                  (4 - reloadCounter % 4) * 90);
        }
        }
        else {
        super.paint(g);
        }
    }
    }


    public static void main(String[] args) {
    if (args.length > 0) {
        for (int counter = args.length - 1; counter >= 0; counter--) {
        new TreeTableExample2(args[counter]);
        }
    }
    else {
        String            path;

        try {
        path = System.getProperty("user.home");
        if (path != null) {
            new TreeTableExample2(path);
        }
        }
        catch (SecurityException se) {
        path = null;
        }
        if (path == null) {
        System.out.println("Could not determine home directory");
        }
    }
    }
}


AbstractCellEditor.java
import java.awt.Component;
import java.awt.event.*;
import java.awt.AWTEvent;
import javax.swing.*;
import javax.swing.event.*;
import java.util.EventObject;
import java.io.Serializable;

public class AbstractCellEditor implements CellEditor {

    protected EventListenerList listenerList = new EventListenerList();

    public Object getCellEditorValue() { return null; }
    public boolean isCellEditable(EventObject e) { return true; }
    public boolean shouldSelectCell(EventObject anEvent) { return false; }
    public boolean stopCellEditing() { return true; }
    public void cancelCellEditing() {}

    public void addCellEditorListener(CellEditorListener l) {
    listenerList.add(CellEditorListener.class, l);
    }

    public void removeCellEditorListener(CellEditorListener l) {
    listenerList.remove(CellEditorListener.class, l);
    }

    /*
     * Notify all listeners that have registered interest for
     * notification on this event type. 
     * @see EventListenerList
     */
    protected void fireEditingStopped() {
    // Guaranteed to return a non-null array
    Object[] listeners = listenerList.getListenerList();
    // Process the listeners last to first, notifying
    // those that are interested in this event
    for (int i = listeners.length-2; i>=0; i-=2) {
        if (listeners[i]==CellEditorListener.class) {
        ((CellEditorListener)listeners[i+1]).editingStopped(new ChangeEvent(this));
        }          
    }
    }

    /*
     * Notify all listeners that have registered interest for
     * notification on this event type. 
     * @see EventListenerList
     */
    protected void fireEditingCanceled() {
    // Guaranteed to return a non-null array
    Object[] listeners = listenerList.getListenerList();
    // Process the listeners last to first, notifying
    // those that are interested in this event
    for (int i = listeners.length-2; i>=0; i-=2) {
        if (listeners[i]==CellEditorListener.class) {
        ((CellEditorListener)listeners[i+1]).editingCanceled(new ChangeEvent(this));
        }          
    }
    }
}


AbstractTreeTableModel.java
/*
 * @(#)AbstractTreeTableModel.java    1.2 98/10/27
 *
 * Copyright 1997, 1998 by Sun Microsystems, Inc.,
 * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
 * All rights reserved.
 *
 * This software is the confidential and proprietary information
 * of Sun Microsystems, Inc. ("Confidential Information").  You
 * shall not disclose such Confidential Information and shall use
 * it only in accordance with the terms of the license agreement
 * you entered into with Sun.
 */

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

/**
 * @version 1.2 10/27/98
 * An abstract implementation of the TreeTableModel interface, handling the list
 * of listeners.
 * @author Philip Milne
 */

public abstract class AbstractTreeTableModel implements TreeTableModel {
    protected Object root;    
    protected EventListenerList listenerList = new EventListenerList();
 
    public AbstractTreeTableModel(Object root) {
        this.root = root;
    }

    //
    // Default implmentations for methods in the TreeModel interface.
    //

    public Object getRoot() {
        return root;
    }

    public boolean isLeaf(Object node) {
        return getChildCount(node) == 0;
    }

    public void valueForPathChanged(TreePath path, Object newValue) {}

    // This is not called in the JTree's default mode: use a naive implementation.
    public int getIndexOfChild(Object parent, Object child) {
        for (int i = 0; i < getChildCount(parent); i++) {
        if (getChild(parent, i).equals(child)) {
            return i;
        }
        }
    return -1;
    }

    public void addTreeModelListener(TreeModelListener l) {
        listenerList.add(TreeModelListener.class, l);
    }

    public void removeTreeModelListener(TreeModelListener l) {
        listenerList.remove(TreeModelListener.class, l);
    }

    /*
     * Notify all listeners that have registered interest for
     * notification on this event type.  The event instance
     * is lazily created using the parameters passed into
     * the fire method.
     * @see EventListenerList
     */
    protected void fireTreeNodesChanged(Object source, Object[] path,
                                        int[] childIndices,
                                        Object[] children) {
        // Guaranteed to return a non-null array
        Object[] listeners = listenerList.getListenerList();
        TreeModelEvent e = null;
        // Process the listeners last to first, notifying
        // those that are interested in this event
        for (int i = listeners.length-2; i>=0; i-=2) {
            if (listeners[i]==TreeModelListener.class) {
                // Lazily create the event:
                if (e == null)
                    e = new TreeModelEvent(source, path,
                                           childIndices, children);
                ((TreeModelListener)listeners[i+1]).treeNodesChanged(e);
            }         
        }
    }

    /*
     * Notify all listeners that have registered interest for
     * notification on this event type.  The event instance
     * is lazily created using the parameters passed into
     * the fire method.
     * @see EventListenerList
     */
    protected void fireTreeNodesInserted(Object source, Object[] path,
                                        int[] childIndices,
                                        Object[] children) {
        // Guaranteed to return a non-null array
        Object[] listeners = listenerList.getListenerList();
        TreeModelEvent e = null;
        // Process the listeners last to first, notifying
        // those that are interested in this event
        for (int i = listeners.length-2; i>=0; i-=2) {
            if (listeners[i]==TreeModelListener.class) {
                // Lazily create the event:
                if (e == null)
                    e = new TreeModelEvent(source, path,
                                           childIndices, children);
                ((TreeModelListener)listeners[i+1]).treeNodesInserted(e);
            }         
        }
    }

    /*
     * Notify all listeners that have registered interest for
     * notification on this event type.  The event instance
     * is lazily created using the parameters passed into
     * the fire method.
     * @see EventListenerList
     */
    protected void fireTreeNodesRemoved(Object source, Object[] path,
                                        int[] childIndices,
                                        Object[] children) {
        // Guaranteed to return a non-null array
        Object[] listeners = listenerList.getListenerList();
        TreeModelEvent e = null;
        // Process the listeners last to first, notifying
        // those that are interested in this event
        for (int i = listeners.length-2; i>=0; i-=2) {
            if (listeners[i]==TreeModelListener.class) {
                // Lazily create the event:
                if (e == null)
                    e = new TreeModelEvent(source, path,
                                           childIndices, children);
                ((TreeModelListener)listeners[i+1]).treeNodesRemoved(e);
            }         
        }
    }

    /*
     * Notify all listeners that have registered interest for
     * notification on this event type.  The event instance
     * is lazily created using the parameters passed into
     * the fire method.
     * @see EventListenerList
     */
    protected void fireTreeStructureChanged(Object source, Object[] path,
                                        int[] childIndices,
                                        Object[] children) {
        // Guaranteed to return a non-null array
        Object[] listeners = listenerList.getListenerList();
        TreeModelEvent e = null;
        // Process the listeners last to first, notifying
        // those that are interested in this event
        for (int i = listeners.length-2; i>=0; i-=2) {
            if (listeners[i]==TreeModelListener.class) {
                // Lazily create the event:
                if (e == null)
                    e = new TreeModelEvent(source, path,
                                           childIndices, children);
                ((TreeModelListener)listeners[i+1]).treeStructureChanged(e);
            }         
        }
    }

    //
    // Default impelmentations for methods in the TreeTableModel interface.
    //

    public Class getColumnClass(int column) { return Object.class; }

   /** By default, make the column with the Tree in it the only editable one.
    *  Making this column editable causes the JTable to forward mouse
    *  and keyboard events in the Tree column to the underlying JTree.
    */
    public boolean isCellEditable(Object node, int column) {
         return getColumnClass(column) == TreeTableModel.class;
    }

    public void setValueAt(Object aValue, Object node, int column) {}


    // Left to be implemented in the subclass:

    /*
     *   public Object getChild(Object parent, int index)
     *   public int getChildCount(Object parent)
     *   public int getColumnCount()
     *   public String getColumnName(Object node, int column) 
     *   public Object getValueAt(Object node, int column)
     */
}


FileSystemModel2.java
/*
 * FileSystemModel2.java
 *
 * Copyright (c) 1998 Sun Microsystems, Inc. All Rights Reserved.
 *
 * This software is the confidential and proprietary information of Sun
 * Microsystems, Inc. ("Confidential Information").  You shall not
 * disclose such Confidential Information and shall use it only in
 * accordance with the terms of the license agreement you entered into
 * with Sun.
 *
 * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
 * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 * PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES
 * SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
 * THIS SOFTWARE OR ITS DERIVATIVES.
 *
 */

import java.io.IOException;
import java.io.File;
import java.util.Date;
import java.util.Stack;
import javax.swing.SwingUtilities;
import javax.swing.tree.TreePath;

/**
 * FileSystemModel2 is a TreeTableModel representing a hierarchical file
 * system.<p>
 * This will recursively load all the children from the path it is
 * created with. The loading is done with the method reloadChildren, and
 * happens in another thread. The method isReloading can be invoked to check
 * if there are active threads. The total size of all the files are also
 * accumulated.
 * <p>
 * By default links are not descended. java.io.File does not have a way
 * to distinguish links, so a file is assumed to be a link if its canonical
 * path does not start with its parent path. This may not cover all cases,
 * but works for the time being.
 * <p>Reloading happens such that all the files of the directory are
 * loaded and immediately available. The background thread then recursively
 * loads all the files of each of those directories. When each directory has
 * finished loading all its sub files they are attached and an event is
 * generated in the event dispatching thread. A more ambitious approach
 * would be to attach each set of directories as they are loaded and generate
 * an event. Then, once all the direct descendants of the directory being
 * reloaded have finished loading, it is resorted based on total size.
 * <p>
 * While you can invoke reloadChildren as many times as you want, care
 * should be taken in doing this. You should not invoke reloadChildren
 * on a node that is already being reloaded, or going to be reloaded
 * (meaning its parent is reloading but it hasn't started reloading
 * that directory yet). If this is done odd results may
 * happen. FileSystemModel2 does not enforce any policy in this manner,
 * and it is up to the user of FileSystemModel2 to ensure it doesn't
 * happen.
 *
 * @version 1.12 05/12/98
 * @author Philip Milne
 * @author Scott Violet
 */

public class FileSystemModel2 extends AbstractTreeTableModel {

    // Names of the columns.
    static protected String[]  cNames = {"Name", "Size", "Type", "Modified"};

    // Types of the columns.
    static protected Class[]  cTypes = { TreeTableModel.class,
                     Integer.class, String.class,
                     Date.class};

    // The the returned file length for directories.
    public static final Integer      ZERO = new Integer(0);

    /** An array of MergeSorter sorters, that will sort based on size. */
    static Stack      sorters = new Stack();


    /** True if the receiver is valid, once set to false all Threads
     * loading files will stop. */
    protected boolean                isValid;

    /** Node currently being reloaded, this becomes somewhat muddy if
     * reloading is happening in multiple threads. */
    protected FileNode               reloadNode;

    /** > 0 indicates reloading some nodes. */
    int                              reloadCount;

    /** Returns true if links are to be descended. */
    protected boolean                descendLinks;


    /**
     * Returns a MergeSort that can sort on the totalSize of a FileNode.
     */
    protected static MergeSort getSizeSorter() {
    synchronized(sorters) {
        if (sorters.size() == 0) {
        return new SizeSorter();
        }
        return (MergeSort)sorters.pop();
    }
    }

    /**
     * Should be invoked when a MergeSort is no longer needed.
     */
    protected static void recycleSorter(MergeSort sorter) {
    synchronized(sorters) {
        sorters.push(sorter);
    }
    }


    /**
     * Creates a FileSystemModel2 rooted at File.separator, which is usually
     * the root of the file system. This does not load it, you should invoke
     * <code>reloadChildren</code> with the root to start loading.
     */
    public FileSystemModel2() {
    this(File.separator);
    }

    /**
     * Creates a FileSystemModel2 with the root being <code>rootPath</code>.
     * This does not load it, you should invoke
     * <code>reloadChildren</code> with the root to start loading.
     */
    public FileSystemModel2(String rootPath) {
    super(null);
    isValid = true;
    root = new FileNode(new File(rootPath));
    }

    //
    // The TreeModel interface
    //

    /**
     * Returns the number of children of <code>node</code>.
     */
    public int getChildCount(Object node) {
    Object[] children = getChildren(node);
    return (children == null) ? 0 : children.length;
    }

    /**
     * Returns the child of <code>node</code> at index <code>i</code>.
     */
    public Object getChild(Object node, int i) {
    return getChildren(node)[i];
    }

    /**
     * Returns true if the passed in object represents a leaf, false
     * otherwise.
     */
    public boolean isLeaf(Object node) {
    return ((FileNode)node).isLeaf();
    }

    //
    //  The TreeTableNode interface.
    //

    /**
     * Returns the number of columns.
     */
    public int getColumnCount() {
    return cNames.length;
    }

    /**
     * Returns the name for a particular column.
     */
    public String getColumnName(int column) {
    return cNames[column];
    }

    /**
     * Returns the class for the particular column.
     */
    public Class getColumnClass(int column) {
    return cTypes[column];
    }

    /**
     * Returns the value of the particular column.
     */
    public Object getValueAt(Object node, int column) {
    FileNode     fn = (FileNode)node;

    try {
        switch(column) {
        case 0:
        return fn.getFile().getName();
        case 1:
        if (fn.isTotalSizeValid()) {
            return new Integer((int)((FileNode)node).totalSize());
        }
        return null;
        case 2:
        return fn.isLeaf() ?  "File" : "Directory";
        case 3:
        return fn.lastModified();
        }
    }
    catch  (SecurityException se) { }
  
    return null;
    }

    //
    // Some convenience methods.
    //

    /**
     * Reloads the children of the specified node.
     */
    public void reloadChildren(Object node) {
    FileNode         fn = (FileNode)node;

    synchronized(this) {
        reloadCount++;
    }
    fn.resetSize();
    new Thread(new FileNodeLoader((FileNode)node)).start();
    }

    /**
     * Stops and waits for all threads to finish loading.
     */
    public void stopLoading() {
    isValid = false;
    synchronized(this) {
        while (reloadCount > 0) {
        try {
            wait();
        } catch (InterruptedException ie) {}
        }
    }
    isValid = true;
    }

    /**
     * If <code>newValue</code> is true, links are descended. Odd results
     * may happen if you set this while other threads are loading.
     */
    public void setDescendsLinks(boolean newValue) {
    descendLinks = newValue;
    }

    /**
     * Returns true if links are to be automatically descended.
     */
    public boolean getDescendsLinks() {
    return descendLinks;
    }

    /**
     * Returns the path <code>node</code> represents.
     */
    public String getPath(Object node) {
    return ((FileNode)node).getFile().getPath();
    }

    /**
     * Returns the total size of the receiver.
     */
    public long getTotalSize(Object node) {
    return ((FileNode)node).totalSize();
    }

    /**
     * Returns true if the receiver is loading any children.
     */
    public boolean isReloading() {
    return (reloadCount > 0);
    }

    /**
     * Returns the path to the node that is being loaded.
     */
    public TreePath getPathLoading() {
    FileNode      rn = reloadNode;

    if (rn != null) {
        return new TreePath(rn.getPath());
    }
    return null;
    }

    /**
     * Returns the node being loaded.
     */
    public Object getNodeLoading() {
    return reloadNode;
    }

    protected File getFile(Object node) {
    FileNode fileNode = ((FileNode)node);
    return fileNode.getFile();      
    }

    protected Object[] getChildren(Object node) {
    FileNode fileNode = ((FileNode)node);
    return fileNode.getChildren();
    }


    protected static FileNode[] EMPTY_CHILDREN = new FileNode[0];

    // Used to sort the file names.
    static private MergeSort  fileMS = new MergeSort() {
    public int compareElementsAt(int beginLoc, int endLoc) {
        return ((String)toSort[beginLoc]).compareTo
                    ((String)toSort[endLoc]);
    }
    };


    /**
     * A FileNode is a derivative of the File class - though we delegate to
     * the File object rather than subclassing it. It is used to maintain a
     * cache of a directory's children and therefore avoid repeated access
     * to the underlying file system during rendering.
     */
    class FileNode {
    /** java.io.File the receiver represents. */
    protected File              file;
    /** Parent FileNode of the receiver. */
    private FileNode            parent;
    /** Children of the receiver. */
    protected FileNode[]        children;
    /** Size of the receiver and all its children. */
    protected long              totalSize;
    /** Valid if the totalSize has finished being calced. */
    protected boolean           totalSizeValid;
    /** Path of the receiver. */
    protected String            canonicalPath;
    /** True if the canonicalPath of this instance does not start with
     * the canonical path of the parent. */
    protected boolean           isLink;
    /** Date last modified. */
    protected Date              lastModified;


    protected FileNode(File file) {
        this(null, file);
    }

    protected FileNode(FileNode parent, File file) {
        this.parent = parent;
        this.file = file;
        try {
        canonicalPath = file.getCanonicalPath();
        }
        catch (IOException ioe) {
        canonicalPath = "";
        }
        if (parent != null) {
        isLink = !canonicalPath.startsWith(parent.getCanonicalPath());
        }
        else {
        isLink = false;
        }
        if (isLeaf()) {
        totalSize = file.length();
        totalSizeValid = true;
        }
    }

    /**
     * Returns the date the receiver was last modified.
     */
    public Date lastModified() {
        if (lastModified == null && file != null) {
        lastModified = new Date(file.lastModified());
        }
        return lastModified;
    }

    /**
     * Returns the the string to be used to display this leaf in the JTree.
     */
    public String toString() {
        return file.getName();
    }

    /**
     * Returns the java.io.File the receiver represents.
     */
    public File getFile() {
        return file;
    }

    /**
     * Returns size of the receiver and all its children.
     */
    public long totalSize() {
        return totalSize;
    }

    /**
     * Returns the parent of the receiver.
     */
    public FileNode getParent() {
        return parent;
    }

    /**
     * Returns true if the receiver represents a leaf, that is it is
     * isn't a directory.
     */
    public boolean isLeaf() {
        return file.isFile();
    }

    /**
     * Returns true if the total size is valid.
     */
    public boolean isTotalSizeValid() {
        return totalSizeValid;
    }

    /**
     * Clears the date.
     */
    protected void resetLastModified() {
        lastModified = null;
    }

    /**
     * Sets the size of the receiver to be 0.
     */
    protected void resetSize() {
        alterTotalSize(-totalSize);
    }

    /**
     * Loads the children, caching the results in the children
     * instance variable.
     */
    protected FileNode[] getChildren() {
        return children;
    }

    /**
     * Recursively loads all the children of the receiver.
     */
    protected void loadChildren(MergeSort sorter) {
        totalSize = file.length();
        children = createChildren(null);
        for (int counter = children.length - 1; counter >= 0; counter--) {
        Thread.yield(); // Give the GUI CPU time to draw itself.
        if (!children[counter].isLeaf() &&
            (descendLinks || !children[counter].isLink())) {
            children[counter].loadChildren(sorter);
        }
        totalSize += children[counter].totalSize();
        if (!isValid) {
            counter = 0;
        }
        }
        if (isValid) {
        if (sorter != null) {
            sorter.sort(children);
        }
        totalSizeValid = true;
        }
    }

    /**
     * Loads the children of of the receiver.
     */
    protected FileNode[] createChildren(MergeSort sorter) {
        FileNode[]        retArray = null;

        try {
        String[] files = file.list();
        if(files != null) {
            if (sorter != null) {
            sorter.sort(files);
            }
            retArray = new FileNode[files.length];
            String path = file.getPath();
            for(int i = 0; i < files.length; i++) {
            File childFile = new File(path, files[i]);
            retArray[i] = new FileNode(this, childFile);
            }
        }
        } catch (SecurityException se) {}
        if (retArray == null) {
        retArray = EMPTY_CHILDREN;
        }
        return retArray;
    }

    /**
     * Returns true if the children have been loaded.
     */
    protected boolean loadedChildren() {
        return (file.isFile() || (children != null));
    }

    /**
     * Gets the path from the root to the receiver.
     */
    public FileNode[] getPath() {
        return getPathToRoot(this, 0);
    }

    /**
     * Returns the canonical path for the receiver.
     */
    public String getCanonicalPath() {
        return canonicalPath;
    }

    /**
     * Returns true if the receiver's path does not begin with the
     * parent's canonical path.
     */
    public boolean isLink() {
        return isLink;
    }

    protected FileNode[] getPathToRoot(FileNode aNode, int depth) {
        FileNode[]              retNodes;

        if(aNode == null) {
        if(depth == 0)
            return null;
        else
            retNodes = new FileNode[depth];
        }
        else {
        depth++;
        retNodes = getPathToRoot(aNode.getParent(), depth);
        retNodes[retNodes.length - depth] = aNode;
        }
        return retNodes;
    }

    /**
     * Sets the children of the receiver, updates the total size,
     * and if generateEvent is true a tree structure changed event
     * is created.
     */
    protected void setChildren(FileNode[] newChildren,
                   boolean generateEvent) {
        long           oldSize = totalSize;

        totalSize = file.length();
        children = newChildren;
        for (int counter = children.length - 1; counter >= 0;
         counter--) {
        totalSize += children[counter].totalSize();
        }

        if (generateEvent) {
        FileNode[]   path = getPath();

        fireTreeStructureChanged(FileSystemModel2.this, path, null,
                     null);

        FileNode             parent = getParent();

        if (parent != null) {
            parent.alterTotalSize(totalSize - oldSize);
        }
        }
    }

    protected synchronized void alterTotalSize(long sizeDelta) {
        if (sizeDelta != 0 && (parent = getParent()) != null) {
        totalSize += sizeDelta;
        nodeChanged();
        parent.alterTotalSize(sizeDelta);
        }
        else {
        // Need a way to specify the root.
        totalSize += sizeDelta;
        }
    }

    /**
     * This should only be invoked on the event dispatching thread.
     */
    protected synchronized void setTotalSizeValid(boolean newValue) {
        if (totalSizeValid != newValue) {
        nodeChanged();
        totalSizeValid = newValue;

        FileNode          parent = getParent();

        if (parent != null) {
            parent.childTotalSizeChanged(this);
        }
        }
    }

    /**
     * Marks the receivers total size as valid, but does not invoke
     * node changed, nor message the parent.
     */
    protected synchronized void forceTotalSizeValid() {
        totalSizeValid = true;
    }

    /**
     * Invoked when a childs total size has changed.
     */
    protected synchronized void childTotalSizeChanged(FileNode child) {
        if (totalSizeValid != child.isTotalSizeValid()) {
        if (totalSizeValid) {
            setTotalSizeValid(false);
        }
        else {
            FileNode[]    children = getChildren();

            for (int counter = children.length - 1; counter >= 0;
             counter--) {
            if (!children[counter].isTotalSizeValid()) {
                return;
            }
            }
            setTotalSizeValid(true);
        }
        }
       
    }

    /**
     * Can be invoked when a node has changed, will create the
     * appropriate event.
     */
    protected void nodeChanged() {
        FileNode        parent = getParent();

        if (parent != null) {
        FileNode[]   path = parent.getPath();
        int[]        index = { getIndexOfChild(parent, this) };
        Object[]     children = { this };

        fireTreeNodesChanged(FileSystemModel2.this, path,  index,
                     children);
        }
    }
    }


    /**
     * FileNodeLoader can be used to reload all the children of a
     * particular node. It first resets the children of the FileNode
     * it is created with, and in its run method will reload all of
     * that nodes children. FileNodeLoader may not be running in the event
     * dispatching thread. As swing is not thread safe it is important
     * that we don't generate events in this thread. SwingUtilities.invokeLater
     * is used so that events are generated in the event dispatching thread.
     */
    class FileNodeLoader implements Runnable {
    /** Node creating children for. */
    FileNode          node;
    /** Sorter. */
    MergeSort         sizeMS;

    FileNodeLoader(FileNode node) {
        this.node = node;
        node.resetLastModified();
        node.setChildren(node.createChildren(fileMS), true);
        node.setTotalSizeValid(false);
    }

    public void run() {
        FileNode[]      children = node.getChildren();

        sizeMS = getSizeSorter();
        for (int counter = children.length - 1; counter >= 0; counter--) {
        if (!children[counter].isLeaf()) {
            reloadNode = children[counter];
            loadChildren(children[counter]);
            reloadNode = null;
        }
        if (!isValid) {
            counter = 0;
        }
        }
        recycleSorter(sizeMS);
        if (isValid) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
            MergeSort    sorter = getSizeSorter();

            sorter.sort(node.getChildren());
            recycleSorter(sorter);
            node.setChildren(node.getChildren(), true);
            synchronized(FileSystemModel2.this) {
                reloadCount--;
                FileSystemModel2.this.notifyAll();
            }
            }
        });
        }
        else {
        synchronized(FileSystemModel2.this) {
            reloadCount--;
            FileSystemModel2.this.notifyAll();
        }
        }
    }

    protected void loadChildren(FileNode node) {
        if (!node.isLeaf() && (descendLinks || !node.isLink())) {
        final FileNode[]      children = node.createChildren(null);

        for (int counter = children.length - 1; counter >= 0;
             counter--) {
            if (!children[counter].isLeaf()) {
            if (descendLinks || !children[counter].isLink()) {
                children[counter].loadChildren(sizeMS);
            }
            else {
                children[counter].forceTotalSizeValid();
            }
            }
            if (!isValid) {
            counter = 0;
            }
        }
        if (isValid) {
            final FileNode       fn = node;

            // Reset the children
            SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                MergeSort    sorter = getSizeSorter();

                sorter.sort(children);
                recycleSorter(sorter);
                fn.setChildren(children, true);
                fn.setTotalSizeValid(true);
                fn.nodeChanged();
            }
            });
        }
        }
        else {
        node.forceTotalSizeValid();
        }
    }
    }


    /**
     * Sorts the contents, which must be instances of FileNode based on
     * totalSize.
     */
    static class SizeSorter extends MergeSort {
    public int compareElementsAt(int beginLoc, int endLoc) {
        long    firstSize = ((FileNode)toSort[beginLoc]).totalSize();
        long    secondSize = ((FileNode)toSort[endLoc]).totalSize();

        if (firstSize != secondSize) {
        return (int)(secondSize - firstSize);
        }
        return ((FileNode)toSort[beginLoc]).toString().compareTo
            (((FileNode)toSort[endLoc]).toString());
    }
    }
}


JTreeTable.java
/*
 * @(#)JTreeTable.java    1.2 98/10/27
 *
 * Copyright 1997, 1998 by Sun Microsystems, Inc.,
 * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
 * All rights reserved.
 *
 * This software is the confidential and proprietary information
 * of Sun Microsystems, Inc. ("Confidential Information").  You
 * shall not disclose such Confidential Information and shall use
 * it only in accordance with the terms of the license agreement
 * you entered into with Sun.
 */

import javax.swing.*;
import javax.swing.event.*;
import javax.swing.tree.*;
import javax.swing.table.*;

import java.awt.Dimension;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Rectangle;

import java.awt.event.MouseEvent;

import java.util.EventObject;

/**
 * This example shows how to create a simple JTreeTable component,
 * by using a JTree as a renderer (and editor) for the cells in a
 * particular column in the JTable. 
 *
 * @version 1.2 10/27/98
 *
 * @author Philip Milne
 * @author Scott Violet
 */
public class JTreeTable extends JTable {
    /** A subclass of JTree. */
    protected TreeTableCellRenderer tree;

    public JTreeTable(TreeTableModel treeTableModel) {
    super();

    // Create the tree. It will be used as a renderer and editor.
    tree = new TreeTableCellRenderer(treeTableModel);

    // Install a tableModel representing the visible rows in the tree.
    super.setModel(new TreeTableModelAdapter(treeTableModel, tree));

    // Force the JTable and JTree to share their row selection models.
    ListToTreeSelectionModelWrapper selectionWrapper = new
                            ListToTreeSelectionModelWrapper();
    tree.setSelectionModel(selectionWrapper);
    setSelectionModel(selectionWrapper.getListSelectionModel());

    // Install the tree editor renderer and editor.
    setDefaultRenderer(TreeTableModel.class, tree);
    setDefaultEditor(TreeTableModel.class, new TreeTableCellEditor());

    // No grid.
    setShowGrid(false);

    // No intercell spacing
    setIntercellSpacing(new Dimension(0, 0));   

    // And update the height of the trees row to match that of
    // the table.
    if (tree.getRowHeight() < 1) {
        // Metal looks better like this.
        setRowHeight(18);
    }
    }

    /**
     * Overridden to message super and forward the method to the tree.
     * Since the tree is not actually in the component hieachy it will
     * never receive this unless we forward it in this manner.
     */
    public void updateUI() {
    super.updateUI();
    if(tree != null) {
        tree.updateUI();
    }
    // Use the tree's default foreground and background colors in the
    // table.
        LookAndFeel.installColorsAndFont(this, "Tree.background",
                                         "Tree.foreground", "Tree.font");
    }

    /* Workaround for BasicTableUI anomaly. Make sure the UI never tries to
     * paint the editor. The UI currently uses different techniques to
     * paint the renderers and editors and overriding setBounds() below
     * is not the right thing to do for an editor. Returning -1 for the
     * editing row in this case, ensures the editor is never painted.
     */
    public int getEditingRow() {
        return (getColumnClass(editingColumn) == TreeTableModel.class) ? -1 :
            editingRow; 
    }

    /**
     * Overridden to pass the new rowHeight to the tree.
     */
    public void setRowHeight(int rowHeight) {
        super.setRowHeight(rowHeight);
    if (tree != null && tree.getRowHeight() != rowHeight) {
            tree.setRowHeight(getRowHeight());
    }
    }

    /**
     * Returns the tree that is being shared between the model.
     */
    public JTree getTree() {
    return tree;
    }

    /**
     * A TreeCellRenderer that displays a JTree.
     */
    public class TreeTableCellRenderer extends JTree implements
             TableCellRenderer {
    /** Last table/tree row asked to renderer. */
    protected int visibleRow;

    public TreeTableCellRenderer(TreeModel model) {
        super(model);
    }

    /**
     * updateUI is overridden to set the colors of the Tree's renderer
     * to match that of the table.
     */
    public void updateUI() {
        super.updateUI();
        // Make the tree's cell renderer use the table's cell selection
        // colors.
        TreeCellRenderer tcr = getCellRenderer();
        if (tcr instanceof DefaultTreeCellRenderer) {
        DefaultTreeCellRenderer dtcr = ((DefaultTreeCellRenderer)tcr);
        // For 1.1 uncomment this, 1.2 has a bug that will cause an
        // exception to be thrown if the border selection color is
        // null.
        // dtcr.setBorderSelectionColor(null);
        dtcr.setTextSelectionColor(UIManager.getColor
                       ("Table.selectionForeground"));
        dtcr.setBackgroundSelectionColor(UIManager.getColor
                        ("Table.selectionBackground"));
        }
    }

    /**
     * Sets the row height of the tree, and forwards the row height to
     * the table.
     */
    public void setRowHeight(int rowHeight) {
        if (rowHeight > 0) {
        super.setRowHeight(rowHeight);
        if (JTreeTable.this != null &&
            JTreeTable.this.getRowHeight() != rowHeight) {
            JTreeTable.this.setRowHeight(getRowHeight());
        }
        }
    }

    /**
     * This is overridden to set the height to match that of the JTable.
     */
    public void setBounds(int x, int y, int w, int h) {
        super.setBounds(x, 0, w, JTreeTable.this.getHeight());
    }

    /**
     * Sublcassed to translate the graphics such that the last visible
     * row will be drawn at 0,0.
     */
    public void paint(Graphics g) {
        g.translate(0, -visibleRow * getRowHeight());
        super.paint(g);
    }

    /**
     * TreeCellRenderer method. Overridden to update the visible row.
     */
    public Component getTableCellRendererComponent(JTable table,
                               Object value,
                               boolean isSelected,
                               boolean hasFocus,
                               int row, int column) {
        if(isSelected)
        setBackground(table.getSelectionBackground());
        else
        setBackground(table.getBackground());

        visibleRow = row;
        return this;
    }
    }


    /**
     * TreeTableCellEditor implementation. Component returned is the
     * JTree.
     */
    public class TreeTableCellEditor extends AbstractCellEditor implements
             TableCellEditor {
    public Component getTableCellEditorComponent(JTable table,
                             Object value,
                             boolean isSelected,
                             int r, int c) {
        return tree;
    }

    /**
     * Overridden to return false, and if the event is a mouse event
     * it is forwarded to the tree.<p>
     * The behavior for this is debatable, and should really be offered
     * as a property. By returning false, all keyboard actions are
     * implemented in terms of the table. By returning true, the
     * tree would get a chance to do something with the keyboard
     * events. For the most part this is ok. But for certain keys,
     * such as left/right, the tree will expand/collapse where as
     * the table focus should really move to a different column. Page
     * up/down should also be implemented in terms of the table.
     * By returning false this also has the added benefit that clicking
     * outside of the bounds of the tree node, but still in the tree
     * column will select the row, whereas if this returned true
     * that wouldn't be the case.
     * <p>By returning false we are also enforcing the policy that
     * the tree will never be editable (at least by a key sequence).
     */
    public boolean isCellEditable(EventObject e) {
        if (e instanceof MouseEvent) {
        for (int counter = getColumnCount() - 1; counter >= 0;
             counter--) {
            if (getColumnClass(counter) == TreeTableModel.class) {
            MouseEvent me = (MouseEvent)e;
            MouseEvent newME = new MouseEvent(tree, me.getID(),
                   me.getWhen(), me.getModifiers(),
                   me.getX() - getCellRect(0, counter, true).x,
                   me.getY(), me.getClickCount(),
                                   me.isPopupTrigger());
            tree.dispatchEvent(newME);
            break;
            }
        }
        }
        return false;
    }
    }


    /**
     * ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel
     * to listen for changes in the ListSelectionModel it maintains. Once
     * a change in the ListSelectionModel happens, the paths are updated
     * in the DefaultTreeSelectionModel.
     */
    class ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel {
    /** Set to true when we are updating the ListSelectionModel. */
    protected boolean         updatingListSelectionModel;

    public ListToTreeSelectionModelWrapper() {
        super();
        getListSelectionModel().addListSelectionListener
                                (createListSelectionListener());
    }

    /**
     * Returns the list selection model. ListToTreeSelectionModelWrapper
     * listens for changes to this model and updates the selected paths
     * accordingly.
     */
    ListSelectionModel getListSelectionModel() {
        return listSelectionModel;
    }

    /**
     * This is overridden to set <code>updatingListSelectionModel</code>
     * and message super. This is the only place DefaultTreeSelectionModel
     * alters the ListSelectionModel.
     */
    public void resetRowSelection() {
        if(!updatingListSelectionModel) {
        updatingListSelectionModel = true;
        try {
            super.resetRowSelection();
        }
        finally {
            updatingListSelectionModel = false;
        }
        }
        // Notice how we don't message super if
        // updatingListSelectionModel is true. If
        // updatingListSelectionModel is true, it implies the
        // ListSelectionModel has already been updated and the
        // paths are the only thing that needs to be updated.
    }

    /**
     * Creates and returns an instance of ListSelectionHandler.
     */
    protected ListSelectionListener createListSelectionListener() {
        return new ListSelectionHandler();
    }

    /**
     * If <code>updatingListSelectionModel</code> is false, this will
     * reset the selected paths from the selected rows in the list
     * selection model.
     */
    protected void updateSelectedPathsFromSelectedRows() {
        if(!updatingListSelectionModel) {
        updatingListSelectionModel = true;
        try {
            // This is way expensive, ListSelectionModel needs an
            // enumerator for iterating.
            int        min = listSelectionModel.getMinSelectionIndex();
            int        max = listSelectionModel.getMaxSelectionIndex();

            clearSelection();
            if(min != -1 && max != -1) {
            for(int counter = min; counter <= max; counter++) {
                if(listSelectionModel.isSelectedIndex(counter)) {
                TreePath     selPath = tree.getPathForRow
                                            (counter);

                if(selPath != null) {
                    addSelectionPath(selPath);
                }
                }
            }
            }
        }
        finally {
            updatingListSelectionModel = false;
        }
        }
    }

    /**
     * Class responsible for calling updateSelectedPathsFromSelectedRows
     * when the selection of the list changse.
     */
    class ListSelectionHandler implements ListSelectionListener {
        public void valueChanged(ListSelectionEvent e) {
        updateSelectedPathsFromSelectedRows();
        }
    }
    }
}

MergeSort.java
/*
 * MergeSort.java
 *
 * Copyright (c) 1998 Sun Microsystems, Inc. All Rights Reserved.
 *
 * This software is the confidential and proprietary information of Sun
 * Microsystems, Inc. ("Confidential Information").  You shall not
 * disclose such Confidential Information and shall use it only in
 * accordance with the terms of the license agreement you entered into
 * with Sun.
 *
 * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
 * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 * PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES
 * SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
 * THIS SOFTWARE OR ITS DERIVATIVES.
 *
 */

/**
 * An implementation of MergeSort, needs to be subclassed to
 * compare the terms.
 *
 * @author Scott Violet
 */
public abstract class MergeSort extends Object {
    protected Object           toSort[];
    protected Object           swapSpace[];

    public void sort(Object array[]) {
    if(array != null && array.length > 1)
    {
        int             maxLength;
 
        maxLength = array.length;
        swapSpace = new Object[maxLength];
        toSort = array;
        this.mergeSort(0, maxLength - 1);
        swapSpace = null;
        toSort = null;
    }
    }

    public abstract int compareElementsAt(int beginLoc, int endLoc);

    protected void mergeSort(int begin, int end) {
    if(begin != end)
    {
        int           mid;

        mid = (begin + end) / 2;
        this.mergeSort(begin, mid);
        this.mergeSort(mid + 1, end);
        this.merge(begin, mid, end);
    }
    }

    protected void merge(int begin, int middle, int end) {
    int           firstHalf, secondHalf, count;

    firstHalf = count = begin;
    secondHalf = middle + 1;
    while((firstHalf <= middle) && (secondHalf <= end))
    {
        if(this.compareElementsAt(secondHalf, firstHalf) < 0)
        swapSpace[count++] = toSort[secondHalf++];
        else
        swapSpace[count++] = toSort[firstHalf++];
    }
    if(firstHalf <= middle)
    {
        while(firstHalf <= middle)
        swapSpace[count++] = toSort[firstHalf++];
    }
    else
    {
        while(secondHalf <= end)
        swapSpace[count++] = toSort[secondHalf++];
    }
    for(count = begin;count <= end;count++)
        toSort[count] = swapSpace[count];
    }
}

TreeTableModel.java
/*
 * TreeTableModel.java
 *
 * Copyright (c) 1998 Sun Microsystems, Inc. All Rights Reserved.
 *
 * This software is the confidential and proprietary information of Sun
 * Microsystems, Inc. ("Confidential Information").  You shall not
 * disclose such Confidential Information and shall use it only in
 * accordance with the terms of the license agreement you entered into
 * with Sun.
 *
 * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
 * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 * PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES
 * SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
 * THIS SOFTWARE OR ITS DERIVATIVES.
 *
 */

import javax.swing.tree.TreeModel;

/**
 * TreeTableModel is the model used by a JTreeTable. It extends TreeModel
 * to add methods for getting inforamtion about the set of columns each
 * node in the TreeTableModel may have. Each column, like a column in
 * a TableModel, has a name and a type associated with it. Each node in
 * the TreeTableModel can return a value for each of the columns and
 * set that value if isCellEditable() returns true.
 *
 * @author Philip Milne
 * @author Scott Violet
 */
public interface TreeTableModel extends TreeModel
{
    /**
     * Returns the number ofs availible column.
     */
    public int getColumnCount();

    /**
     * Returns the name for column number <code>column</code>.
     */
    public String getColumnName(int column);

    /**
     * Returns the type for column number <code>column</code>.
     */
    public Class getColumnClass(int column);

    /**
     * Returns the value to be displayed for node <code>node</code>,
     * at column number <code>column</code>.
     */
    public Object getValueAt(Object node, int column);

    /**
     * Indicates whether the the value for node <code>node</code>,
     * at column number <code>column</code> is editable.
     */
    public boolean isCellEditable(Object node, int column);

    /**
     * Sets the value for node <code>node</code>,
     * at column number <code>column</code>.
     */
    public void setValueAt(Object aValue, Object node, int column);
}

TreeTableModelAdapter.java
/*
 * @(#)TreeTableModelAdapter.java    1.2 98/10/27
 *
 * Copyright 1997, 1998 by Sun Microsystems, Inc.,
 * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
 * All rights reserved.
 *
 * This software is the confidential and proprietary information
 * of Sun Microsystems, Inc. ("Confidential Information").  You
 * shall not disclose such Confidential Information and shall use
 * it only in accordance with the terms of the license agreement
 * you entered into with Sun.
 */

import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel;
import javax.swing.tree.TreePath;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;

/**
 * This is a wrapper class takes a TreeTableModel and implements
 * the table model interface. The implementation is trivial, with
 * all of the event dispatching support provided by the superclass:
 * the AbstractTableModel.
 *
 * @version 1.2 10/27/98
 *
 * @author Philip Milne
 * @author Scott Violet
 */
public class TreeTableModelAdapter extends AbstractTableModel
{
    JTree tree;
    TreeTableModel treeTableModel;

    public TreeTableModelAdapter(TreeTableModel treeTableModel, JTree tree) {
        this.tree = tree;
        this.treeTableModel = treeTableModel;

    tree.addTreeExpansionListener(new TreeExpansionListener() {
        // Don't use fireTableRowsInserted() here; the selection model
        // would get updated twice.
        public void treeExpanded(TreeExpansionEvent event) { 
          fireTableDataChanged();
        }
            public void treeCollapsed(TreeExpansionEvent event) { 
          fireTableDataChanged();
        }
    });

    // Install a TreeModelListener that can update the table when
    // tree changes. We use delayedFireTableDataChanged as we can
    // not be guaranteed the tree will have finished processing
    // the event before us.
    treeTableModel.addTreeModelListener(new TreeModelListener() {
        public void treeNodesChanged(TreeModelEvent e) {
        delayedFireTableDataChanged();
        }

        public void treeNodesInserted(TreeModelEvent e) {
        delayedFireTableDataChanged();
        }

        public void treeNodesRemoved(TreeModelEvent e) {
        delayedFireTableDataChanged();
        }

        public void treeStructureChanged(TreeModelEvent e) {
        delayedFireTableDataChanged();
        }
    });
    }

    // Wrappers, implementing TableModel interface.

    public int getColumnCount() {
    return treeTableModel.getColumnCount();
    }

    public String getColumnName(int column) {
    return treeTableModel.getColumnName(column);
    }

    public Class getColumnClass(int column) {
    return treeTableModel.getColumnClass(column);
    }

    public int getRowCount() {
    return tree.getRowCount();
    }

    protected Object nodeForRow(int row) {
    TreePath treePath = tree.getPathForRow(row);
    return treePath.getLastPathComponent();        
    }

    public Object getValueAt(int row, int column) {
    return treeTableModel.getValueAt(nodeForRow(row), column);
    }

    public boolean isCellEditable(int row, int column) {
         return treeTableModel.isCellEditable(nodeForRow(row), column);
    }

    public void setValueAt(Object value, int row, int column) {
    treeTableModel.setValueAt(value, nodeForRow(row), column);
    }

    /**
     * Invokes fireTableDataChanged after all the pending events have been
     * processed. SwingUtilities.invokeLater is used to handle this.
     */
    protected void delayedFireTableDataChanged() {
    SwingUtilities.invokeLater(new Runnable() {
        public void run() {
        fireTableDataChanged();
        }
    });
    }
}


Kết quả - Result





Source: kidslovepc








No comments:

Post a Comment