![JAR search and dependency download from the Maven repository](/logo.png)
ca.odell.glazedlists.swing.TreeTableCellEditor Maven / Gradle / Ivy
package ca.odell.glazedlists.swing;
import ca.odell.glazedlists.TreeList;
import javax.swing.*;
import javax.swing.event.CellEditorListener;
import javax.swing.event.ChangeEvent;
import javax.swing.table.TableCellEditor;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.util.EventObject;
/**
* This editor removes some of the burden of producing an appropriate looking
* component for the hierarchy column of a tree table. Specifically, it takes
* care of adding components to a panel that render a node location within the
* tree, but leaves the rendering of the data of the node to
* a delegate {@link TableCellEditor} that is supplied in the constructor.
*
* For example, in the following tree representation, the spacing and +/-
* icons would be added to each tree node by this editor, while the data text
* would be added by the component returned from the delegate
* {@link TableCellEditor}.
*
*
* - Cars
* + BMW
* - Ford
* Taurus
* Focus
* - Lexus
* ES 300
* LS 600h L
*
*
* @author James Lemieux
*/
public class TreeTableCellEditor extends AbstractCellEditor implements TableCellEditor {
/** The panel capable of laying out a indenter component, expander button, spacer component, and data Component to produce the entire tree node display. */
private final TreeTableCellPanel component = new TreeTableCellPanel();
/** The data structure that answers questions about the tree node. */
private TreeList treeList;
/** The user-supplied editor that produces the look of the tree node's data. */
private TableCellEditor delegate;
/** true indicates the expander button should be visible even if the parent has no children. */
private boolean showExpanderForEmptyParent;
/** Respond to editing changes in the delegate TableCellEditor. */
private final CellEditorListener delegateListener = new DelegateTableCellEditorListener();
/** Data describing the hierarchy information of the tree node being edited. */
private final TreeNodeData treeNodeData = new TreeNodeData();
/**
* Decorate the component returned from the delegate
with
* extra components that display the tree nodes location within the tree.
* If delegate
is null then a
* {@link DefaultCellEditor} using a {@link JTextField} will be used as
* the delegate.
*
* @param delegate the editor that produces the data for the tree node
* @param treeList the data structure that answers questions about the tree
* node and the tree that contains it
*/
public TreeTableCellEditor(TableCellEditor delegate, TreeList treeList) {
this.delegate = delegate == null ? createDelegateEditor() : delegate;
this.delegate.addCellEditorListener(delegateListener);
this.treeList = treeList;
}
/**
* Build the delegate TableCellEditor that handles editing the data of
* each tree node.
*/
protected TableCellEditor createDelegateEditor() {
final JTextField textField = new JTextField();
textField.setBorder(BorderFactory.createEmptyBorder(0, 1, 0, 0));
return new DefaultCellEditor(textField);
}
/**
* Return a decorated form of the component returned by the data
* {@link TableCellEditor}.
*/
@Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
treeList.getReadWriteLock().readLock().lock();
try {
// read information about the tree node from the TreeList
treeNodeData.setDepth(treeList.depth(row));
treeNodeData.setExpanded(treeList.isExpanded(row));
treeNodeData.setHasChildren(treeList.hasChildren(row));
treeNodeData.setAllowsChildren(treeList.getAllowsChildren(row));
} finally {
treeList.getReadWriteLock().readLock().unlock();
}
// if the delegate editor accepts TreeNodeData, give it
if (delegate instanceof TreeTableNodeDataEditor)
((TreeTableNodeDataEditor) delegate).setTreeNodeData(treeNodeData);
// ask the delegate editor to produce the data component
final Component c = delegate.getTableCellEditorComponent(table, value, isSelected, row, column);
// fetch the number of pixels to indent
final int indent = getIndent(treeNodeData, showExpanderForEmptyParent);
// fetch the number of pixels to space over
final int spacer = getSpacer(treeNodeData, showExpanderForEmptyParent);
// ask our special component to configure itself for this tree node
component.configure(treeNodeData, showExpanderForEmptyParent, c, false, indent, spacer);
return component;
}
/**
* Returns the number of pixels to indent the contents of the editor.
*
* @param treeNodeData hierarhical information about the node within the tree
* @param showExpanderForEmptyParent true indicates the expander
* button should always be present, even when no children yet exist
*/
protected int getIndent(TreeNodeData treeNodeData, boolean showExpanderForEmptyParent) {
return UIManager.getIcon("Tree.expandedIcon").getIconWidth() * treeNodeData.getDepth();
}
/**
* Returns the number of pixels of space between the expand/collapse button
* and the node component.
*
* @param treeNodeData hierarhical information about the node within the tree
* @param showExpanderForEmptyParent true indicates the expander
* button should always be present, even when no children yet exist
*/
protected int getSpacer(TreeNodeData treeNodeData, boolean showExpanderForEmptyParent) {
return 2;
}
/**
* This method checks if the event
is a MouseEvent
* and determines if it occurred overtop of the component created by the
* delegate TableCellEditor. If so, it translates the coordinates of the
* event
so they are relative to that component and then asks
* the delegate TableCellEditor if it believes supports cell editing.
*
* Effectively, this implies that clicks overtop of other areas of the
* editor component are ignored. In truth, they are handled elsewhere (a
* MouseListener on the JTable installed within TreeTableSupport).
*
* @param event the event attempting to begin a cell edit
* @return true if cell is ready for editing, false otherwise
*/
@Override
public boolean isCellEditable(EventObject event) {
if (event instanceof MouseEvent) {
final MouseEvent me = (MouseEvent) event;
// we're going to check if the MouseEvent was overtop of the node component
// and if it was, ask the node component if it wants a cell edit
// extract information about the location of the click
final JTable table = (JTable) me.getSource();
final Point clickPoint = me.getPoint();
final int row = table.rowAtPoint(clickPoint);
final int column = table.columnAtPoint(clickPoint);
// translate the clickPoint to be relative to the rendered component
final Rectangle cellRect = table.getCellRect(row, column, true);
clickPoint.translate(-cellRect.x, -cellRect.y);
// get the rendered component which we will query about the MouseEvent
final TreeTableCellPanel renderedPanel = TreeTableUtilities.prepareRenderer(me);
// if the click occurred over the node component
if (renderedPanel != null && renderedPanel.isPointOverNodeComponent(clickPoint)) {
// create a new MouseEvent that is translated over the node component
final Rectangle delegateRendererBounds = renderedPanel.getNodeComponent().getBounds();
final MouseEvent translatedMouseEvent = new MouseEvent(me.getComponent(), me.getID(), me.getWhen(), me.getModifiers(), me.getX() - delegateRendererBounds.x, me.getY() - delegateRendererBounds.y, me.getClickCount(), me.isPopupTrigger(), me.getButton());
// allow the actual node editor to decide
return delegate.isCellEditable(translatedMouseEvent);
}
return false;
}
// otherwise, we're here because of an unrecognized EventObject
return super.isCellEditable(event);
}
/** @inheritDoc */
@Override
public boolean shouldSelectCell(EventObject anEvent) {
return delegate.shouldSelectCell(anEvent);
}
/** @inheritDoc */
@Override
public Object getCellEditorValue() {
return delegate.getCellEditorValue();
}
/**
* If b
is true then the expand/collapse button must
* be displayed for nodes which allow children but do not currently have
* children. This implies that empty tree nodes with the
* potential for children may be displayed differently
* than pure leaf nodes which are guaranteed to never have children.
*/
void setShowExpanderForEmptyParent(boolean b) {
showExpanderForEmptyParent = b;
}
/**
* Use the given delegate
to edit the data associated with
* each tree node.
*/
void setDelegate(TableCellEditor delegate) {
this.delegate = delegate;
}
/**
* Returns the delegate TableCellEditor being decorated.
*/
public TableCellEditor getDelegate() {
return delegate;
}
/**
* Cleanup the data within this editor as it is no longer being used and
* must be prepared for garbage collection.
*/
public void dispose() {
delegate.removeCellEditorListener(delegateListener);
delegate = null;
treeList = null;
}
/**
* When the delegate TableCellEditor changes its editing state, we follow suit.
*/
private class DelegateTableCellEditorListener implements CellEditorListener {
@Override
public void editingCanceled(ChangeEvent e) {
cancelCellEditing();
}
@Override
public void editingStopped(ChangeEvent e) {
stopCellEditing();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy