alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

What this is

This file is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Other links

The source code

/*
 *                 Sun Public License Notice
 *
 * The contents of this file are subject to the Sun Public License
 * Version 1.0 (the "License"). You may not use this file except in
 * compliance with the License. A copy of the License is available at
 * http://www.sun.com/
 *
 * The Original Code is NetBeans. The Initial Developer of the Original
 * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun
 * Microsystems, Inc. All Rights Reserved.
 */

/*
 * this file is derived from the "Creating TreeTable" article at
 * http://java.sun.com/products/jfc/tsc/articles/treetable2/index.html
 */
package org.netbeans.modules.tasklist.compiler.treetable;

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;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;

/**
 * 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 TreeTable extends JTable {

    private static final long serialVersionUID = 1;

    /** A subclass of JTree. */
    protected TreeTableCellRenderer tree;
    private TreeTableModel treeTableModel;

    public TreeTable(TreeTableModel treeTableModel) {
	super();

	// Create the tree. It will be used as a renderer and editor. 
        // First we create a dummy model for the tree and set later the
        // real model with setModel(). This way JTree's TreeModelListener
        // will be called first and we can update our table.
	tree = new TreeTableCellRenderer(
            new DefaultTreeModel(new DefaultMutableTreeNode()));

	// Install a tableModel representing the visible rows in the tree. 
	setTreeTableModel(treeTableModel);

	// 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);
	}
    }

    /**
     * Sets new TreeTableModel
     *
     * @param treeTableModel a model
     */
    public void setTreeTableModel(TreeTableModel treeTableModel) {
        this.treeTableModel = treeTableModel;
        setModel(new TreeTableModelAdapter(treeTableModel, tree));
        tree.setModel(treeTableModel);
    }
    
    /**
     * Returns the current model
     *
     * @return model
     */
    public TreeTableModel getTreeTableModel() {
        return treeTableModel;
    }
    
    /**
     * Returns the object for the specified row
     *
     * @param row row number
     */
    public Object getNodeForRow(int row) {
        TreePath tp = tree.getPathForRow(row);
        return tp.getLastPathComponent();
    }
    
    /**
     * 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 {

        private static final long serialVersionUID = 1;

	/** Last table/tree row asked to renderer. */
	protected int visibleRow;
        private Border border;

	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 (TreeTable.this != null &&
		    TreeTable.this.getRowHeight() != rowHeight) {
		    TreeTable.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, TreeTable.this.getHeight());
	}

	/**
	 * Sublcassed to translate the graphics such that the last visible
	 * row will be drawn at 0,0.
	 */
	public void paint(Graphics g) {
            Rectangle oldClip = g.getClipBounds();
            Rectangle clip;
            if (border == null)
                clip = oldClip.intersection(
                    new Rectangle(0, 0, getWidth() - 1, getRowHeight() - 1));
            else 
                clip = oldClip.intersection(
                    new Rectangle(1, 1, getWidth() - 2, getRowHeight() - 2));
            g.setClip(clip);
	    g.translate(0, -visibleRow * getRowHeight());
	    super.paint(g);
            g.translate(0, visibleRow * getRowHeight());
            g.setClip(oldClip);
            if (border != null)
                border.paintBorder(this, g, 0, 0, getWidth(), getRowHeight());
	}

	/**
	 * TreeCellRenderer method. Overridden to update the visible row.
	 */
	public Component getTableCellRendererComponent(JTable table,
						       Object value,
						       boolean isSelected,
						       boolean hasFocus,
						       int row, int column) {
            if (hasFocus) {
                border = UIManager.getBorder("Table.focusCellHighlightBorder");
                if (table.isCellEditable(row, column)) {
                    super.setForeground( UIManager.getColor("Table.focusCellForeground") );
                    super.setBackground( UIManager.getColor("Table.focusCellBackground") );
                }
            } else {
                border = null;
            }
            
	    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.

* 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. *

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 { private static final long serialVersionUID = 1; /** 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 updatingListSelectionModel * 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 updatingListSelectionModel 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(); } } } }

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.