/* * 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. */ package org.openide.explorer.view; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.FocusListener; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import javax.swing.*; import javax.swing.tree.*; import javax.swing.event.*; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import javax.swing.plaf.basic.BasicTableUI; import java.util.EventObject; import org.openide.nodes.Node.Property; import org.openide.nodes.Node; import org.openide.ErrorManager; import org.openide.util.NbBundle; import org.openide.awt.MouseUtils; /** * TreeTable implementation. * * @author Jan Rojcek */ class TreeTable extends JTable { /** A subclass of JTree. */ private TreeTableCellRenderer tree; private NodeTableModel tableModel; private int treeColumnIndex = -1; /** Tree editor stuff. */ private int lastRow = -1; private boolean canEdit; private boolean ignoreScrolling = false; /** Flag to ignore clearSelection() called from super.tableChanged(). */ private boolean ignoreClearSelection = false; /** Position of tree renderer, used for horizontal scrolling. */ private int positionX; /** If true, horizontal scrolling of tree column is enabled in TreeTableView */ private boolean treeHScrollingEnabled = true; public TreeTable(NodeTreeModel treeModel, NodeTableModel tableModel) { super(); this.tree = new TreeTableCellRenderer(treeModel); this.tableModel = new TreeTableModelAdapter(tree, tableModel); NodeRenderer rend = NodeRenderer.sharedInstance (); tree.setCellRenderer(rend); // Install a tableModel representing the visible rows in the tree. setModel(this.tableModel); // Force the JTable and JTree to share their row selection models. ListToTreeSelectionModelWrapper selectionWrapper = new ListToTreeSelectionModelWrapper(); tree.setSelectionModel(selectionWrapper); setSelectionModel(selectionWrapper.getListSelectionModel()); getTableHeader().setReorderingAllowed(false); // Install the tree editor renderer and editor. setDefaultRenderer(TreeTableModelAdapter.class, tree); setDefaultEditor(TreeTableModelAdapter.class, new TreeTableCellEditor()); // Install property renderer and editor. TableSheetCell tableCell = new TableSheetCell(this.tableModel); tableCell.setFlat(true); setDefaultRenderer(Property.class, tableCell); setDefaultEditor(Property.class, tableCell); getTableHeader().setDefaultRenderer(tableCell); /* fix of #23873, then removed - davidjon request getActionMap().put("selectNextColumnExtendSelection", getActionMap().get("selectNextColumn")); getActionMap().put("selectPreviousColumnExtendSelection", getActionMap().get("selectPreviousColumn")); */ getActionMap().put("selectNextColumn", new TreeTableAction(tree.getActionMap().get("selectChild"), getActionMap().get("selectNextColumn"))); getActionMap().put("selectPreviousColumn", new TreeTableAction(tree.getActionMap().get("selectParent"), getActionMap().get("selectPreviousColumn"))); getAccessibleContext ().setAccessibleName ( NbBundle.getBundle (TreeTable.class).getString ("ACSN_TreeTable")); getAccessibleContext ().setAccessibleDescription ( NbBundle.getBundle (TreeTable.class).getString ("ACSD_TreeTable")); } /* * Overridden to message super and forward the method to the tree. */ 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"); setUI(new TreeTableUI()); needCalcRowHeight = true; } /* 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) == TreeTableModelAdapter.class) ? -1 : editingRow; } private boolean needCalcRowHeight = true; public void paint (Graphics g) { if (needCalcRowHeight) { calcRowHeight(g); } super.paint(g); } /** Calculate the height of rows based on the current font. This is * done when the first paint occurs, to ensure that a valid Graphics * object is available. * @since 1.25 */ private void calcRowHeight(Graphics g) { Font f = getFont(); FontMetrics fm = g.getFontMetrics(f); int rowHeight = fm.getHeight(); needCalcRowHeight = false; setRowHeight (rowHeight); } /* * 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. */ JTree getTree() { return tree; } /** * Returns table column index of the column displaying the tree. */ int getTreeColumnIndex() { return treeColumnIndex; } /** * Sets tree column index and fires property change. */ void setTreeColumnIndex(int index) { if (treeColumnIndex == index) return; int old = treeColumnIndex; treeColumnIndex = index; firePropertyChange("treeColumnIndex", old, treeColumnIndex); } /* Overriden to do not clear a selection upon model changes. */ public void clearSelection() { if (!ignoreClearSelection) { super.clearSelection(); } } /* Updates tree column name and sets ignoreClearSelection flag. */ public void tableChanged(TableModelEvent e) { // update tree column name int modelColumn = getTreeColumnIndex(); if (e.getFirstRow() <= 0 && modelColumn != -1 && getColumnCount() > 0) { String columnName = getModel().getColumnName(modelColumn); TableColumn aColumn = getColumnModel().getColumn(modelColumn); aColumn.setHeaderValue(columnName); } ignoreClearSelection = true; try { super.tableChanged(e); } finally { ignoreClearSelection = false; } } /* Performs horizontal scrolling of the tree when editing is started. */ public boolean editCellAt(int row, int column, EventObject e) { canEdit = (lastRow == row); boolean ret = super.editCellAt(row, column, e); if (ret && column == getTreeColumnIndex()) { ignoreScrolling = true; tree.scrollRectToVisible(tree.getRowBounds(row)); ignoreScrolling = false; } return ret; } /* */ public void valueChanged(ListSelectionEvent e) { if (getSelectedRowCount() == 1) lastRow = getSelectedRow(); else lastRow = -1; super.valueChanged(e); } /* Updates tree column index */ public void columnAdded(TableColumnModelEvent e) { super.columnAdded(e); updateTreeColumnIndex(); } /* Updates tree column index */ public void columnRemoved(TableColumnModelEvent e) { super.columnRemoved(e); updateTreeColumnIndex(); } /* Updates tree column index */ public void columnMoved(TableColumnModelEvent e) { super.columnMoved(e); updateTreeColumnIndex(); int from = e.getFromIndex(); int to = e.getToIndex(); if ( from != to ) firePropertyChange( "column_moved", from, to ); // NOI18N } /* Updates tree column index */ private void updateTreeColumnIndex() { for (int i = getColumnCount() - 1; i >= 0; i--) { if (getColumnClass(i) == TreeTableModelAdapter.class) { setTreeColumnIndex(i); return; } } setTreeColumnIndex(-1); } /** Returns x coordinate of tree renderer. */ public int getPositionX() { return positionX; } /** Sets x position. */ public void setPositionX(int x) { if (x == positionX || !treeHScrollingEnabled) return; int old = positionX; positionX = x; firePropertyChange("positionX", old, x); if (isEditing() && getEditingColumn() == getTreeColumnIndex()) { CellEditor editor = getCellEditor(); if (ignoreScrolling && editor instanceof TreeTableCellEditor) { ((TreeTableCellEditor)editor).revalidateTextField(); } else { removeEditor(); } } repaint(); } /** Enables horizontal scrolling of tree column */ void setTreeHScrollingEnabled(boolean enabled) { treeHScrollingEnabled = enabled; } /** * A TreeCellRenderer that displays a JTree. */ class TreeTableCellRenderer extends JTree implements TableCellRenderer { /** Last table/tree row asked to renderer. */ protected int visibleRow; /* Last width of the tree. */ private int oldWidth; public TreeTableCellRenderer(TreeModel model) { super(model); setRowHeight(getRowHeight()); setToggleClickCount(0); putClientProperty("JTree.lineStyle", "None"); // NOI18N } /** * 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 + positionX, TreeTable.this.getHeight()); } /* Fire width property change so that we can revalidate horizontal scrollbar in TreeTableView. */ public void reshape(int x, int y, int w, int h) { super.reshape(x, y, w, h); int newWidth = getPreferredSize().width; if (oldWidth != newWidth) { firePropertyChange("width", oldWidth, newWidth); oldWidth = newWidth; } } /** * Sublcassed to translate the graphics such that the last visible * row will be drawn at 0,0. */ public void paint(Graphics g) { g.translate(-positionX, -visibleRow * getRowHeight()); super.paint(g); } public Rectangle getVisibleRect() { Rectangle visibleRect = TreeTable.this.getVisibleRect(); visibleRect.x = positionX; visibleRect.width = TreeTable.this.getColumnModel().getColumn(getTreeColumnIndex()).getWidth(); return visibleRect; } /* Overriden to use this call for moving tree renderer. */ public void scrollRectToVisible(Rectangle aRect) { Rectangle rect = getVisibleRect(); rect.y = aRect.y; rect.height = aRect.height; TreeTable.this.scrollRectToVisible(rect); int x = rect.x; if (aRect.width > rect.width) { x = aRect.x; } else if (aRect.x < rect.x) { x = aRect.x; } else if (aRect.x + aRect.width > rect.x + rect.width) { x = aRect.x + aRect.width - rect.width; } TreeTable.this.setPositionX(x); } public String getToolTipText(MouseEvent event) { if(event != null) { Point p = event.getPoint(); p.translate(positionX, visibleRow * getRowHeight()); int selRow = getRowForLocation(p.x, p.y); if(selRow != -1) { TreePath path = getPathForRow(selRow); VisualizerNode v = (VisualizerNode)path.getLastPathComponent(); String tooltip = v.getShortDescription(); String displayName = v.getDisplayName (); if ((tooltip != null) && !tooltip.equals (displayName)) return tooltip; } } return null; } /* To make the tree think that it has focus when rendering focused cell. * It will paint focus border around node name. */ boolean hasFocus; //public boolean hasFocus() { // return hasFocus; //} /** * 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()); this.hasFocus = hasFocus; visibleRow = row; return this; } protected TreeModelListener createTreeModelListener() { return new JTree.TreeModelHandler() { public void treeNodesRemoved(TreeModelEvent e) { if (tree.getSelectionCount () == 0) { TreePath path = TreeView.findSiblingTreePath (e.getTreePath (), e.getChildIndices ()); if (path == null) return; if (path.getPathCount () > 0 || tree.isRootVisible ()) { tree.setSelectionPath (path); } } } }; } } /** * TreeTableCellEditor implementation. */ class TreeTableCellEditor extends DefaultCellEditor implements TreeSelectionListener, ActionListener, FocusListener, CellEditorListener { /** Used in editing. Indicates x position to place editingComponent. */ protected transient int offset; /** Used before starting the editing session. */ protected transient Timer timer; public TreeTableCellEditor() { super(new TreeTableTextField()); tree.addTreeSelectionListener(this); addCellEditorListener(this); super.getComponent().addFocusListener(this); } /** * Overridden to determine an offset that tree would place the * editor at. The offset is determined from the * <code>getRowBounds</code> JTree method, and additionally * from the icon DefaultTreeCellRenderer will use. * <p>The offset is then set on the TreeTableTextField component * created in the constructor, and returned. */ public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int r, int c) { Component component = super.getTableCellEditorComponent (table, value, isSelected, r, c); determineOffset(value, isSelected, r); ((TreeTableTextField)getComponent()).offset = offset; return component; } /** * This is overridden to forward the event to the tree and start editor timer. */ public boolean isCellEditable(EventObject e) { if (lastRow != -1) { org.openide.nodes.Node n = Visualizer.findNode (tree.getPathForRow(lastRow).getLastPathComponent()); if (n == null || !n.canRename ()) { //return false; canEdit = false; } } if (canEdit && e != null && ( e.getSource() instanceof Timer )) return true; if (canEdit && shouldStartEditingTimer(e)) { startEditingTimer(); } else if (shouldStopEditingTimer(e)) { timer.stop(); } if (e instanceof MouseEvent) { MouseEvent me = (MouseEvent)e; int column = getTreeColumnIndex(); if ( MouseUtils.isLeftMouseButton(me) && me.getClickCount() == 2 ) { TreePath path = tree.getPathForRow(TreeTable.this.rowAtPoint(me.getPoint())); Rectangle r = tree.getPathBounds(path); if ( me.getX() < r.x - positionX || me.getX() > r.x - positionX + r.width ) { me.translatePoint( r.x - me.getX(), 0 ); } } MouseEvent newME = new MouseEvent (TreeTable.this.tree, me.getID(), me.getWhen(), me.getModifiers(), me.getX() - getCellRect(0, column, true).x + positionX, me.getY(), me.getClickCount(), me.isPopupTrigger()); TreeTable.this.tree.dispatchEvent(newME); } return false; } /* Stop timer when selection has been changed. */ public void valueChanged(TreeSelectionEvent e) { if (timer != null) { timer.stop(); } } /* Timer performer. */ public void actionPerformed(java.awt.event.ActionEvent e) { if (lastRow != -1) { editCellAt(lastRow, getTreeColumnIndex(), new EventObject( timer )); } } /* Start editing timer only on certain conditions. */ private boolean shouldStartEditingTimer(EventObject event) { if ((event instanceof MouseEvent) && SwingUtilities.isLeftMouseButton((MouseEvent)event)) { MouseEvent me = (MouseEvent)event; return (me.getID() == me.MOUSE_PRESSED && me.getClickCount() == 1 && inHitRegion(me)); } return false; } /* Stop editing timer only on certain conditions. */ private boolean shouldStopEditingTimer(EventObject event) { if (timer == null) return false; if (event instanceof MouseEvent) { MouseEvent me = (MouseEvent)event; return (!SwingUtilities.isLeftMouseButton(me) || me.getClickCount() > 1); } return false; } /** * Starts the editing timer. */ private void startEditingTimer() { if(timer == null) { timer = new Timer(1200, this); timer.setRepeats(false); } timer.start(); } /* Does a click go into node's label? */ private boolean inHitRegion(MouseEvent me) { determineOffset(me); if (me.getX() <= offset) { return false; } return true; } /* Determines offset of node's label from left edge of the table. */ private void determineOffset(MouseEvent me) { int row = TreeTable.this.rowAtPoint(me.getPoint()); if (row == -1) { offset = 0; return; } determineOffset(tree.getPathForRow(row).getLastPathComponent(), TreeTable.this.isRowSelected(row), row); } /* Determines offset of node's label from left edge of the table. */ private void determineOffset(Object value, boolean isSelected, int row) { JTree t = getTree(); boolean rv = t.isRootVisible(); int offsetRow = row; if ( !rv && row > 0 ) offsetRow--; Rectangle bounds = t.getRowBounds(offsetRow); offset = bounds.x; TreeCellRenderer tcr = t.getCellRenderer(); Object node = t.getPathForRow(offsetRow).getLastPathComponent(); Component comp = tcr.getTreeCellRendererComponent( t, node, isSelected, t.isExpanded(offsetRow), t.getModel().isLeaf(node), offsetRow, false); if (comp instanceof JLabel) { Icon icon = ((JLabel)comp).getIcon(); if (icon != null) { offset += ((JLabel)comp).getIconTextGap() + icon.getIconWidth(); } } offset -= positionX; } /* Revalidates text field upon change of x position of renderer */ private void revalidateTextField() { int row = TreeTable.this.editingRow; if (row == -1) { offset = 0; return; } determineOffset(tree.getPathForRow(row).getLastPathComponent(), TreeTable.this.isRowSelected(row), row); ((TreeTableTextField)super.getComponent()).offset = offset; getComponent().setBounds(TreeTable.this.getCellRect(row, getTreeColumnIndex(), false)); } // Focus listener /* Cancel editing when text field loses focus */ public void focusLost (java.awt.event.FocusEvent evt) { /* to allow Escape functionality if (!stopCellEditing()) cancelCellEditing(); */ } /* Select a text in text field when it gets focus. */ public void focusGained (java.awt.event.FocusEvent evt) { ((TreeTableTextField)super.getComponent()).selectAll(); } // Cell editor listener - copied from TreeViewCellEditor /** Implements <code>CellEditorListener</code> interface method. */ public void editingStopped(ChangeEvent e) { TreePath lastP = tree.getPathForRow(lastRow); if (lastP != null) { Node n = Visualizer.findNode (lastP.getLastPathComponent()); if (n != null && n.canRename ()) { String newStr = (String) getCellEditorValue(); try { // bugfix #21589 don't update name if there is not any change if (!n.getName ().equals (newStr)) { n.setName (newStr); } } catch (IllegalArgumentException exc) { boolean needToAnnotate = true; ErrorManager em = ErrorManager.getDefault (); ErrorManager.Annotation[] ann = em.findAnnotations(exc); // determine if "new annotation" of this exception is needed if (ann!=null && ann.length>0) { for (int i=0; i<ann.length; i++) { String glm = ann[i].getLocalizedMessage(); if (glm!=null && !glm.equals("")) { // NOI18N needToAnnotate = false; } } } // annotate new localized message only if there is no localized message yet if (needToAnnotate) { String msg = NbBundle.getMessage(TreeViewCellEditor.class, "RenameFailed", n.getName (), newStr); em.annotate(exc, msg); } em.notify(exc); } } } } /** Implements <code>CellEditorListener</code> interface method. */ public void editingCanceled(ChangeEvent e) { } } /** * Component used by TreeTableCellEditor. The only thing this does * is to override the <code>reshape</code> method, and to ALWAYS * make the x location be <code>offset</code>. */ static class TreeTableTextField extends JTextField { public int offset; public void reshape(int x, int y, int w, int h) { int newX = Math.max(x, offset); super.reshape(newX, y, w - (newX - x), h); } public void addNotify() { super.addNotify(); requestFocus(); } } /** * 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(); this.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 changes. */ class ListSelectionHandler implements ListSelectionListener { public void valueChanged(ListSelectionEvent e) { updateSelectedPathsFromSelectedRows(); } } } /* This is overriden to handle mouse events especially. E.g. do not change selection * when it was clicked on tree's expand/collapse toggles. */ class TreeTableUI extends BasicTableUI { /** * Creates the mouse listener for the JTable. */ protected MouseInputListener createMouseInputListener() { return new TreeTableMouseInputHandler(); } public class TreeTableMouseInputHandler extends MouseInputHandler { // Component recieving mouse events during editing. May not be editorComponent. private Component dispatchComponent; // The Table's mouse listener methods. public void mouseClicked(MouseEvent e) { processMouseEvent(e); } public void mousePressed(MouseEvent e) { processMouseEvent(e); } public void mouseReleased(MouseEvent e) { if (shouldIgnore(e)) { return; } repostEvent(e); dispatchComponent = null; setValueIsAdjusting(false); if (!TreeTable.this.isEditing()) processMouseEvent(e); } public void mouseDragged(MouseEvent e) { return; } private void setDispatchComponent(MouseEvent e) { Component editorComponent = table.getEditorComponent(); Point p = e.getPoint(); Point p2 = SwingUtilities.convertPoint(table, p, editorComponent); dispatchComponent = SwingUtilities.getDeepestComponentAt(editorComponent, p2.x, p2.y); } private boolean repostEvent(MouseEvent e) { if (dispatchComponent == null) { return false; } MouseEvent e2 = SwingUtilities.convertMouseEvent(table, e, dispatchComponent); dispatchComponent.dispatchEvent(e2); return true; } private void setValueIsAdjusting(boolean flag) { table.getSelectionModel().setValueIsAdjusting(flag); table.getColumnModel().getSelectionModel().setValueIsAdjusting(flag); } private boolean shouldIgnore(MouseEvent e) { return !table.isEnabled(); } private boolean isTreeColumn(int column) { return TreeTable.this.getColumnClass(column) == TreeTableModelAdapter.class; } /** Forwards mouse events to a renderer (tree). */ private void processMouseEvent(MouseEvent e) { if (shouldIgnore(e)) { return; } Point p = e.getPoint(); int row = table.rowAtPoint(p); int column = table.columnAtPoint(p); // The autoscroller can generate drag events outside the Table's range. if ((column == -1) || (row == -1)) { return; } // for automatic jemmy testing purposes if ( getEditingColumn() == column && getEditingRow() == row ) { return; } boolean changeSelection = true; if (isTreeColumn(column)) { TreePath path = tree.getPathForRow(TreeTable.this.rowAtPoint(e.getPoint())); Rectangle r = tree.getPathBounds(path); if (e.getX() >= r.x - positionX && e.getX() <= r.x - positionX + r.width) { changeSelection = false; } } if ( table.getSelectionModel().isSelectedIndex( row ) && e.isPopupTrigger() ) return; if (table.editCellAt(row, column, e)) { setDispatchComponent(e); repostEvent(e); } else { table.requestFocus(); } CellEditor editor = table.getCellEditor(); if (changeSelection && (editor == null || editor.shouldSelectCell(e))) { setValueIsAdjusting(true); table.changeSelection(row, column, e.isControlDown(), e.isShiftDown()); } } } } /* When selected column is tree column then call tree's action otherwise call table's. */ class TreeTableAction extends AbstractAction { Action treeAction; Action tableAction; TreeTableAction(Action treeAction, Action tableAction) { this.treeAction = treeAction; this.tableAction = tableAction; } public void actionPerformed(ActionEvent e) { if (TreeTable.this.getSelectedColumn() == getTreeColumnIndex()) { treeAction.actionPerformed(e); } } } }