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 }