Mega Code Archive

 
Categories / Java / Swing Components
 

JTreeTable component

/*  * The contents of this file are subject to the Sapient Public License  * Version 1.0 (the "License"); you may not use this file except in compliance  * with the License. You may obtain a copy of the License at  * http://carbon.sf.net/License.html.  *  * Software distributed under the License is distributed on an "AS IS" basis,  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for  * the specific language governing rights and limitations under the License.  *  * The Original Code is The Carbon Component Framework.  *  * The Initial Developer of the Original Code is Sapient Corporation  *  * Copyright (C) 2003 Sapient Corporation. All Rights Reserved.  */ /*  * @(#)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 java.awt.Component; import java.awt.Dimension; import java.awt.Graphics; import java.awt.event.MouseEvent; import java.util.EventObject; import javax.swing.AbstractCellEditor; import javax.swing.JTable; import javax.swing.JTree; import javax.swing.ListSelectionModel; import javax.swing.LookAndFeel; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.event.TreeExpansionEvent; import javax.swing.event.TreeExpansionListener; import javax.swing.event.TreeModelEvent; import javax.swing.event.TreeModelListener; import javax.swing.table.AbstractTableModel; import javax.swing.table.TableCellEditor; import javax.swing.table.TableCellRenderer; import javax.swing.tree.DefaultTreeCellRenderer; import javax.swing.tree.DefaultTreeSelectionModel; import javax.swing.tree.TreeCellRenderer; import javax.swing.tree.TreeModel; import javax.swing.tree.TreePath; /**  * 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.      */      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;     }     public Object getCellEditorValue() {       // TODO Auto-generated method stub       return null;     }     }     /**      * 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();         }     }     } } /*  * The contents of this file are subject to the Sapient Public License  * Version 1.0 (the "License"); you may not use this file except in compliance  * with the License. You may obtain a copy of the License at  * http://carbon.sf.net/License.html.  *  * Software distributed under the License is distributed on an "AS IS" basis,  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for  * the specific language governing rights and limitations under the License.  *  * The Original Code is The Carbon Component Framework.  *  * The Initial Developer of the Original Code is Sapient Corporation  *  * Copyright (C) 2003 Sapient Corporation. All Rights Reserved.  */ /*  * 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.  *  */ /**  * 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  */  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); }  /*   * The contents of this file are subject to the Sapient Public License   * Version 1.0 (the "License"); you may not use this file except in compliance   * with the License. You may obtain a copy of the License at   * http://carbon.sf.net/License.html.   *   * Software distributed under the License is distributed on an "AS IS" basis,   * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for   * the specific language governing rights and limitations under the License.   *   * The Original Code is The Carbon Component Framework.   *   * The Initial Developer of the Original Code is Sapient Corporation   *   * Copyright (C) 2003 Sapient Corporation. All Rights Reserved.   */  /*   * @(#)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.   */  /**   * 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   */   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();          }      });      }  } /*  * The contents of this file are subject to the Sapient Public License  * Version 1.0 (the "License"); you may not use this file except in compliance  * with the License. You may obtain a copy of the License at  * http://carbon.sf.net/License.html.  *  * Software distributed under the License is distributed on an "AS IS" basis,  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for  * the specific language governing rights and limitations under the License.  *  * The Original Code is The Carbon Component Framework.  *  * The Initial Developer of the Original Code is Sapient Corporation  *  * Copyright (C) 2003 Sapient Corporation. All Rights Reserved.  */ package org.sape.carbon.services.swing.treetable; /*  * @(#)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.event.EventListenerList; import javax.swing.event.TreeModelEvent; import javax.swing.event.TreeModelListener; import javax.swing.tree.TreePath; /**  * @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)      */ } /*  * The contents of this file are subject to the Sapient Public License  * Version 1.0 (the "License"); you may not use this file except in compliance  * with the License. You may obtain a copy of the License at  * http://carbon.sf.net/License.html.  *  * Software distributed under the License is distributed on an "AS IS" basis,  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for  * the specific language governing rights and limitations under the License.  *  * The Original Code is The Carbon Component Framework.  *  * The Initial Developer of the Original Code is Sapient Corporation  *  * Copyright (C) 2003 Sapient Corporation. All Rights Reserved.  */ import java.util.EventObject; import javax.swing.CellEditor; import javax.swing.event.CellEditorListener; import javax.swing.event.ChangeEvent; import javax.swing.event.EventListenerList; 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));         }     }     } }