001 package ui.treetable; 002 003 import ui.model.TreeTableModel; 004 import ui.model.TreeTableModelAdapter; 005 import ui.editor.AbstractCellEditor; 006 007 import javax.swing.*; 008 import javax.swing.event.*; 009 import javax.swing.tree.*; 010 import javax.swing.table.*; 011 012 import java.awt.Dimension; 013 import java.awt.Component; 014 import java.awt.Graphics; 015 import java.awt.Rectangle; 016 017 import java.awt.event.MouseEvent; 018 019 import java.util.EventObject; 020 021 /** 022 * This example shows how to create a simple JTreeTable component, 023 * by using a JTree as a renderer (and editor) for the cells in a 024 * particular column in the JTable. 025 * 026 * @version 1.2 10/27/98 027 * 028 * @author Philip Milne 029 * @author Scott Violet 030 */ 031 public class JTreeTable extends JTable { 032 /** A subclass of JTree. */ 033 protected TreeTableCellRenderer tree; 034 035 public JTreeTable(TreeTableModel treeTableModel) { 036 super(); 037 038 // Create the tree. It will be used as a renderer and editor. 039 tree = new TreeTableCellRenderer(treeTableModel); 040 041 // Install a tableModel representing the visible rows in the tree. 042 super.setModel(new TreeTableModelAdapter(treeTableModel, tree)); 043 044 // Force the JTable and JTree to share their row selection models. 045 ListToTreeSelectionModelWrapper selectionWrapper = new ListToTreeSelectionModelWrapper(); 046 tree.setSelectionModel(selectionWrapper); 047 setSelectionModel(selectionWrapper.getListSelectionModel()); 048 049 // Install the tree editor renderer and editor. 050 setDefaultRenderer(TreeTableModel.class, tree); 051 setDefaultEditor(TreeTableModel.class, new TreeTableCellEditor()); 052 053 // No grid. 054 setShowGrid(false); 055 056 // No intercell spacing 057 setIntercellSpacing(new Dimension(0, 0)); 058 059 // Update the height of the trees row to match that of the table. Metal looks better like this. 060 if (tree.getRowHeight() < 1) setRowHeight(18); 061 062 } 063 064 /** 065 * Overridden to message super and forward the method to the tree. 066 * Since the tree is not actually in the component hieachy it will 067 * never receive this unless we forward it in this manner. 068 */ 069 public void updateUI() { 070 super.updateUI(); 071 if(tree != null) tree.updateUI(); 072 // Use the tree's default foreground and background colors in the table. 073 LookAndFeel.installColorsAndFont(this, "Tree.background", "Tree.foreground", "Tree.font"); 074 } 075 076 /* Workaround for BasicTableUI anomaly. Make sure the UI never tries to 077 * paint the editor. The UI currently uses different techniques to 078 * paint the renderers and editors and overriding setBounds() below 079 * is not the right thing to do for an editor. Returning -1 for the 080 * editing row in this case, ensures the editor is never painted. 081 */ 082 public int getEditingRow() { 083 return (getColumnClass(editingColumn) == TreeTableModel.class) ? -1 : editingRow; 084 } 085 086 /** 087 * Overridden to pass the new rowHeight to the tree. 088 */ 089 public void setRowHeight(int rowHeight) { 090 super.setRowHeight(rowHeight); 091 if (tree != null && tree.getRowHeight() != rowHeight) { 092 tree.setRowHeight(getRowHeight()); 093 } 094 } 095 096 /** 097 * Returns the tree that is being shared between the model. 098 */ 099 public JTree getTree() { 100 return tree; 101 } 102 103 /** 104 * A TreeCellRenderer that displays a JTree. 105 */ 106 public class TreeTableCellRenderer extends JTree implements TableCellRenderer { 107 /** Last table/tree row asked to renderer. */ 108 protected int visibleRow; 109 110 public TreeTableCellRenderer(TreeModel model) { 111 super(model); 112 } 113 114 /** 115 * updateUI is overridden to set the colors of the Tree's renderer 116 * to match that of the table. 117 */ 118 public void updateUI() { 119 super.updateUI(); 120 // Make the tree's cell renderer use the table's cell selection colors. 121 TreeCellRenderer tcr = getCellRenderer(); 122 if (tcr instanceof DefaultTreeCellRenderer) { 123 DefaultTreeCellRenderer dtcr = ((DefaultTreeCellRenderer)tcr); 124 // For 1.1 uncomment this, 1.2 has a bug that will cause an 125 // exception to be thrown if the border selection color is null. 126 // dtcr.setBorderSelectionColor(null); 127 dtcr.setTextSelectionColor(UIManager.getColor("Table.selectionForeground")); 128 dtcr.setBackgroundSelectionColor(UIManager.getColor("Table.selectionBackground")); 129 } 130 } 131 132 /** 133 * Sets the row height of the tree, and forwards the row height to 134 * the table. 135 */ 136 public void setRowHeight(int rowHeight) { 137 if (rowHeight > 0) { 138 super.setRowHeight(rowHeight); 139 if (JTreeTable.this != null && 140 JTreeTable.this.getRowHeight() != rowHeight) { 141 JTreeTable.this.setRowHeight(getRowHeight()); 142 } 143 } 144 } 145 146 /** 147 * This is overridden to set the height to match that of the JTable. 148 */ 149 public void setBounds(int x, int y, int w, int h) { 150 super.setBounds(x, 0, w, JTreeTable.this.getHeight()); 151 } 152 153 /** 154 * Sublcassed to translate the graphics such that the last visible 155 * row will be drawn at 0,0. 156 */ 157 public void paint(Graphics g) { 158 g.translate(0, -visibleRow * getRowHeight()); 159 super.paint(g); 160 } 161 162 /** 163 * TreeCellRenderer method. Overridden to update the visible row. 164 */ 165 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { 166 if(isSelected) 167 setBackground(table.getSelectionBackground()); 168 else 169 setBackground(table.getBackground()); 170 171 visibleRow = row; 172 return this; 173 } 174 } 175 176 /** 177 * TreeTableCellEditor implementation. Component returned is the 178 * JTree. 179 */ 180 public class TreeTableCellEditor extends AbstractCellEditor implements TableCellEditor { 181 public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { 182 return tree; 183 } 184 185 /** 186 * Overridden to return false, and if the event is a mouse event 187 * it is forwarded to the tree.<p> 188 * The behavior for this is debatable, and should really be offered 189 * as a property. By returning false, all keyboard actions are 190 * implemented in terms of the table. By returning true, the 191 * tree would get a chance to do something with the keyboard 192 * events. For the most part this is ok. But for certain keys, 193 * such as left/right, the tree will expand/collapse where as 194 * the table focus should really move to a different column. Page 195 * up/down should also be implemented in terms of the table. 196 * By returning false this also has the added benefit that clicking 197 * outside of the bounds of the tree node, but still in the tree 198 * column will select the row, whereas if this returned true 199 * that wouldn't be the case. 200 * <p>By returning false we are also enforcing the policy that 201 * the tree will never be editable (at least by a key sequence). 202 */ 203 public boolean isCellEditable(EventObject e) { 204 if (e instanceof MouseEvent) { 205 for (int counter = getColumnCount() - 1; counter >= 0; counter--) { 206 if (getColumnClass(counter) == TreeTableModel.class) { 207 MouseEvent me = (MouseEvent)e; 208 MouseEvent newME = new MouseEvent(tree, me.getID(), 209 me.getWhen(), me.getModifiers(), 210 me.getX() - getCellRect(0, counter, true).x, 211 me.getY(), me.getClickCount(), 212 me.isPopupTrigger()); 213 tree.dispatchEvent(newME); 214 break; 215 } 216 } 217 } 218 return false; 219 } 220 } 221 222 223 /** 224 * ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel 225 * to listen for changes in the ListSelectionModel it maintains. Once 226 * a change in the ListSelectionModel happens, the paths are updated 227 * in the DefaultTreeSelectionModel. 228 */ 229 class ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel { 230 /** Set to true when we are updating the ListSelectionModel. */ 231 protected boolean updatingListSelectionModel; 232 233 public ListToTreeSelectionModelWrapper() { 234 super(); 235 getListSelectionModel().addListSelectionListener(createListSelectionListener()); 236 } 237 238 /** 239 * Returns the list selection model. ListToTreeSelectionModelWrapper 240 * listens for changes to this model and updates the selected paths 241 * accordingly. 242 */ 243 ListSelectionModel getListSelectionModel() { 244 return listSelectionModel; 245 } 246 247 /** 248 * This is overridden to set <code>updatingListSelectionModel</code> 249 * and message super. This is the only place DefaultTreeSelectionModel 250 * alters the ListSelectionModel. 251 */ 252 public void resetRowSelection() { 253 if(!updatingListSelectionModel) { 254 updatingListSelectionModel = true; 255 try { 256 super.resetRowSelection(); 257 } 258 finally { 259 updatingListSelectionModel = false; 260 } 261 } 262 // Notice how we don't message super if 263 // updatingListSelectionModel is true. If 264 // updatingListSelectionModel is true, it implies the 265 // ListSelectionModel has already been updated and the 266 // paths are the only thing that needs to be updated. 267 } 268 269 /** 270 * Creates and returns an instance of ListSelectionHandler. 271 */ 272 protected ListSelectionListener createListSelectionListener() { 273 return new ListSelectionHandler(); 274 } 275 276 /** 277 * If <code>updatingListSelectionModel</code> is false, this will 278 * reset the selected paths from the selected rows in the list 279 * selection model. 280 */ 281 protected void updateSelectedPathsFromSelectedRows() { 282 if(!updatingListSelectionModel) { 283 updatingListSelectionModel = true; 284 try { 285 // This is way expensive, ListSelectionModel needs an enumerator for iterating. 286 int min = listSelectionModel.getMinSelectionIndex(); 287 int max = listSelectionModel.getMaxSelectionIndex(); 288 289 clearSelection(); 290 if(min != -1 && max != -1) { 291 for(int counter = min; counter <= max; counter++) { 292 if(listSelectionModel.isSelectedIndex(counter)) { 293 TreePath selPath = tree.getPathForRow(counter); 294 if(selPath != null) addSelectionPath(selPath); 295 } 296 } 297 } 298 } finally { 299 updatingListSelectionModel = false; 300 } 301 } 302 } 303 304 /** 305 * Class responsible for calling updateSelectedPathsFromSelectedRows 306 * when the selection of the list changse. 307 */ 308 class ListSelectionHandler implements ListSelectionListener { 309 public void valueChanged(ListSelectionEvent e) { 310 updateSelectedPathsFromSelectedRows(); 311 } 312 } 313 } 314 }