1 | package genius.core.jtreetable;
|
---|
2 |
|
---|
3 | /*
|
---|
4 | * @(#)JTreeTable.java 1.2 98/10/27
|
---|
5 | *
|
---|
6 | * Copyright 1997, 1998 by Sun Microsystems, Inc.,
|
---|
7 | * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
|
---|
8 | * All rights reserved.
|
---|
9 | *
|
---|
10 | * This software is the confidential and proprietary information
|
---|
11 | * of Sun Microsystems, Inc. ("Confidential Information"). You
|
---|
12 | * shall not disclose such Confidential Information and shall use
|
---|
13 | * it only in accordance with the terms of the license agreement
|
---|
14 | * you entered into with Sun.
|
---|
15 | */
|
---|
16 |
|
---|
17 | import javax.swing.*;
|
---|
18 | import javax.swing.event.*;
|
---|
19 | import javax.swing.tree.*;
|
---|
20 | import javax.swing.table.*;
|
---|
21 |
|
---|
22 | import java.awt.Dimension;
|
---|
23 | import java.awt.Component;
|
---|
24 | import java.awt.Graphics;
|
---|
25 | import java.awt.Rectangle;
|
---|
26 |
|
---|
27 | import java.awt.event.MouseEvent;
|
---|
28 |
|
---|
29 | import java.util.EventObject;
|
---|
30 |
|
---|
31 | /**
|
---|
32 | * This example shows how to create a simple JTreeTable component,
|
---|
33 | * by using a JTree as a renderer (and editor) for the cells in a
|
---|
34 | * particular column in the JTable.
|
---|
35 | *
|
---|
36 | * @version 1.2 10/27/98
|
---|
37 | *
|
---|
38 | * @author Philip Milne
|
---|
39 | * @author Scott Violet
|
---|
40 | */
|
---|
41 | public class JTreeTable extends JTable {
|
---|
42 | /** A subclass of JTree. */
|
---|
43 | protected TreeTableCellRenderer tree;
|
---|
44 |
|
---|
45 | public JTreeTable(TreeTableModel treeTableModel) {
|
---|
46 | super();
|
---|
47 |
|
---|
48 | // Create the tree. It will be used as a renderer and editor.
|
---|
49 | tree = new TreeTableCellRenderer(treeTableModel);
|
---|
50 |
|
---|
51 | // Install a tableModel representing the visible rows in the tree.
|
---|
52 | super.setModel(new TreeTableModelAdapter(treeTableModel, tree));
|
---|
53 |
|
---|
54 | // Force the JTable and JTree to share their row selection models.
|
---|
55 | ListToTreeSelectionModelWrapper selectionWrapper = new
|
---|
56 | ListToTreeSelectionModelWrapper();
|
---|
57 | tree.setSelectionModel(selectionWrapper);
|
---|
58 | setSelectionModel(selectionWrapper.getListSelectionModel());
|
---|
59 |
|
---|
60 | // Install the tree editor renderer and editor.
|
---|
61 | setDefaultRenderer(TreeTableModel.class, tree);
|
---|
62 | setDefaultEditor(TreeTableModel.class, new TreeTableCellEditor());
|
---|
63 |
|
---|
64 | // No grid.
|
---|
65 | setShowGrid(false);
|
---|
66 |
|
---|
67 | // No intercell spacing
|
---|
68 | setIntercellSpacing(new Dimension(0, 0));
|
---|
69 |
|
---|
70 | // And update the height of the trees row to match that of
|
---|
71 | // the table.
|
---|
72 | if (tree.getRowHeight() < 1) {
|
---|
73 | // Metal looks better like this.
|
---|
74 | setRowHeight(18);
|
---|
75 | }
|
---|
76 | }
|
---|
77 |
|
---|
78 | /**
|
---|
79 | * Overridden to message super and forward the method to the tree.
|
---|
80 | * Since the tree is not actually in the component hieachy it will
|
---|
81 | * never receive this unless we forward it in this manner.
|
---|
82 | */
|
---|
83 | public void updateUI() {
|
---|
84 | super.updateUI();
|
---|
85 | if(tree != null) {
|
---|
86 | tree.updateUI();
|
---|
87 | }
|
---|
88 | // Use the tree's default foreground and background colors in the
|
---|
89 | // table.
|
---|
90 | LookAndFeel.installColorsAndFont(this, "Tree.background",
|
---|
91 | "Tree.foreground", "Tree.font");
|
---|
92 | }
|
---|
93 |
|
---|
94 | /* Workaround for BasicTableUI anomaly. Make sure the UI never tries to
|
---|
95 | * paint the editor. The UI currently uses different techniques to
|
---|
96 | * paint the renderers and editors and overriding setBounds() below
|
---|
97 | * is not the right thing to do for an editor. Returning -1 for the
|
---|
98 | * editing row in this case, ensures the editor is never painted.
|
---|
99 | */
|
---|
100 | public int getEditingRow() {
|
---|
101 | return (getColumnClass(editingColumn) == TreeTableModel.class) ? -1 :
|
---|
102 | editingRow;
|
---|
103 | }
|
---|
104 |
|
---|
105 | /**
|
---|
106 | * Overridden to pass the new rowHeight to the tree.
|
---|
107 | */
|
---|
108 | public void setRowHeight(int rowHeight) {
|
---|
109 | super.setRowHeight(rowHeight);
|
---|
110 | if (tree != null && tree.getRowHeight() != rowHeight) {
|
---|
111 | tree.setRowHeight(getRowHeight());
|
---|
112 | }
|
---|
113 | }
|
---|
114 |
|
---|
115 | /**
|
---|
116 | * Returns the tree that is being shared between the model.
|
---|
117 | */
|
---|
118 | public JTree getTree() {
|
---|
119 | return tree;
|
---|
120 | }
|
---|
121 |
|
---|
122 | /**
|
---|
123 | * A TreeCellRenderer that displays a JTree.
|
---|
124 | */
|
---|
125 | public class TreeTableCellRenderer extends JTree implements
|
---|
126 | TableCellRenderer {
|
---|
127 | /** Last table/tree row asked to renderer. */
|
---|
128 | protected int visibleRow;
|
---|
129 |
|
---|
130 | public TreeTableCellRenderer(TreeModel model) {
|
---|
131 | super(model);
|
---|
132 | }
|
---|
133 |
|
---|
134 | /**
|
---|
135 | * updateUI is overridden to set the colors of the Tree's renderer
|
---|
136 | * to match that of the table.
|
---|
137 | */
|
---|
138 | public void updateUI() {
|
---|
139 | super.updateUI();
|
---|
140 | // Make the tree's cell renderer use the table's cell selection
|
---|
141 | // colors.
|
---|
142 | TreeCellRenderer tcr = getCellRenderer();
|
---|
143 | if (tcr instanceof DefaultTreeCellRenderer) {
|
---|
144 | DefaultTreeCellRenderer dtcr = ((DefaultTreeCellRenderer)tcr);
|
---|
145 | // For 1.1 uncomment this, 1.2 has a bug that will cause an
|
---|
146 | // exception to be thrown if the border selection color is
|
---|
147 | // null.
|
---|
148 | // dtcr.setBorderSelectionColor(null);
|
---|
149 | dtcr.setTextSelectionColor(UIManager.getColor
|
---|
150 | ("Table.selectionForeground"));
|
---|
151 | dtcr.setBackgroundSelectionColor(UIManager.getColor
|
---|
152 | ("Table.selectionBackground"));
|
---|
153 | }
|
---|
154 | }
|
---|
155 |
|
---|
156 | /**
|
---|
157 | * Sets the row height of the tree, and forwards the row height to
|
---|
158 | * the table.
|
---|
159 | */
|
---|
160 | public void setRowHeight(int rowHeight) {
|
---|
161 | if (rowHeight > 0) {
|
---|
162 | super.setRowHeight(rowHeight);
|
---|
163 | if (JTreeTable.this != null &&
|
---|
164 | JTreeTable.this.getRowHeight() != rowHeight) {
|
---|
165 | JTreeTable.this.setRowHeight(getRowHeight());
|
---|
166 | }
|
---|
167 | }
|
---|
168 | }
|
---|
169 |
|
---|
170 | /**
|
---|
171 | * This is overridden to set the height to match that of the JTable.
|
---|
172 | */
|
---|
173 | public void setBounds(int x, int y, int w, int h) {
|
---|
174 | super.setBounds(x, 0, w, JTreeTable.this.getHeight());
|
---|
175 | }
|
---|
176 |
|
---|
177 | /**
|
---|
178 | * Sublcassed to translate the graphics such that the last visible
|
---|
179 | * row will be drawn at 0,0.
|
---|
180 | */
|
---|
181 | public void paint(Graphics g) {
|
---|
182 | g.translate(0, -visibleRow * getRowHeight());
|
---|
183 | super.paint(g);
|
---|
184 | }
|
---|
185 |
|
---|
186 | /**
|
---|
187 | * TreeCellRenderer method. Overridden to update the visible row.
|
---|
188 | */
|
---|
189 | public Component getTableCellRendererComponent(JTable table,
|
---|
190 | Object value,
|
---|
191 | boolean isSelected,
|
---|
192 | boolean hasFocus,
|
---|
193 | int row, int column) {
|
---|
194 | if(isSelected)
|
---|
195 | setBackground(table.getSelectionBackground());
|
---|
196 | else
|
---|
197 | setBackground(table.getBackground());
|
---|
198 |
|
---|
199 | visibleRow = row;
|
---|
200 | return this;
|
---|
201 | }
|
---|
202 | }
|
---|
203 |
|
---|
204 |
|
---|
205 | /**
|
---|
206 | * TreeTableCellEditor implementation. Component returned is the
|
---|
207 | * JTree.
|
---|
208 | */
|
---|
209 | public class TreeTableCellEditor extends AbstractCellEditor implements
|
---|
210 | TableCellEditor {
|
---|
211 | public Component getTableCellEditorComponent(JTable table,
|
---|
212 | Object value,
|
---|
213 | boolean isSelected,
|
---|
214 | int r, int c) {
|
---|
215 | return tree;
|
---|
216 | }
|
---|
217 |
|
---|
218 | /**
|
---|
219 | * Overridden to return false, and if the event is a mouse event
|
---|
220 | * it is forwarded to the tree.<p>
|
---|
221 | * The behavior for this is debatable, and should really be offered
|
---|
222 | * as a property. By returning false, all keyboard actions are
|
---|
223 | * implemented in terms of the table. By returning true, the
|
---|
224 | * tree would get a chance to do something with the keyboard
|
---|
225 | * events. For the most part this is ok. But for certain keys,
|
---|
226 | * such as left/right, the tree will expand/collapse where as
|
---|
227 | * the table focus should really move to a different column. Page
|
---|
228 | * up/down should also be implemented in terms of the table.
|
---|
229 | * By returning false this also has the added benefit that clicking
|
---|
230 | * outside of the bounds of the tree node, but still in the tree
|
---|
231 | * column will select the row, whereas if this returned true
|
---|
232 | * that wouldn't be the case.
|
---|
233 | * <p>By returning false we are also enforcing the policy that
|
---|
234 | * the tree will never be editable (at least by a key sequence).
|
---|
235 | */
|
---|
236 | public boolean isCellEditable(EventObject e) {
|
---|
237 | if (e instanceof MouseEvent) {
|
---|
238 | for (int counter = getColumnCount() - 1; counter >= 0;
|
---|
239 | counter--) {
|
---|
240 | if (getColumnClass(counter) == TreeTableModel.class) {
|
---|
241 | MouseEvent me = (MouseEvent)e;
|
---|
242 | MouseEvent newME = new MouseEvent(tree, me.getID(),
|
---|
243 | me.getWhen(), me.getModifiers(),
|
---|
244 | me.getX() - getCellRect(0, counter, true).x,
|
---|
245 | me.getY(), me.getClickCount(),
|
---|
246 | me.isPopupTrigger());
|
---|
247 | tree.dispatchEvent(newME);
|
---|
248 | break;
|
---|
249 | }
|
---|
250 | }
|
---|
251 | }
|
---|
252 | return false;
|
---|
253 | }
|
---|
254 | }
|
---|
255 |
|
---|
256 |
|
---|
257 | /**
|
---|
258 | * ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel
|
---|
259 | * to listen for changes in the ListSelectionModel it maintains. Once
|
---|
260 | * a change in the ListSelectionModel happens, the paths are updated
|
---|
261 | * in the DefaultTreeSelectionModel.
|
---|
262 | */
|
---|
263 | class ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel {
|
---|
264 | /** Set to true when we are updating the ListSelectionModel. */
|
---|
265 | protected boolean updatingListSelectionModel;
|
---|
266 |
|
---|
267 | public ListToTreeSelectionModelWrapper() {
|
---|
268 | super();
|
---|
269 | getListSelectionModel().addListSelectionListener
|
---|
270 | (createListSelectionListener());
|
---|
271 | }
|
---|
272 |
|
---|
273 | /**
|
---|
274 | * Returns the list selection model. ListToTreeSelectionModelWrapper
|
---|
275 | * listens for changes to this model and updates the selected paths
|
---|
276 | * accordingly.
|
---|
277 | */
|
---|
278 | ListSelectionModel getListSelectionModel() {
|
---|
279 | return listSelectionModel;
|
---|
280 | }
|
---|
281 |
|
---|
282 | /**
|
---|
283 | * This is overridden to set <code>updatingListSelectionModel</code>
|
---|
284 | * and message super. This is the only place DefaultTreeSelectionModel
|
---|
285 | * alters the ListSelectionModel.
|
---|
286 | */
|
---|
287 | public void resetRowSelection() {
|
---|
288 | if(!updatingListSelectionModel) {
|
---|
289 | updatingListSelectionModel = true;
|
---|
290 | try {
|
---|
291 | super.resetRowSelection();
|
---|
292 | }
|
---|
293 | finally {
|
---|
294 | updatingListSelectionModel = false;
|
---|
295 | }
|
---|
296 | }
|
---|
297 | // Notice how we don't message super if
|
---|
298 | // updatingListSelectionModel is true. If
|
---|
299 | // updatingListSelectionModel is true, it implies the
|
---|
300 | // ListSelectionModel has already been updated and the
|
---|
301 | // paths are the only thing that needs to be updated.
|
---|
302 | }
|
---|
303 |
|
---|
304 | /**
|
---|
305 | * Creates and returns an instance of ListSelectionHandler.
|
---|
306 | */
|
---|
307 | protected ListSelectionListener createListSelectionListener() {
|
---|
308 | return new ListSelectionHandler();
|
---|
309 | }
|
---|
310 |
|
---|
311 | /**
|
---|
312 | * If <code>updatingListSelectionModel</code> is false, this will
|
---|
313 | * reset the selected paths from the selected rows in the list
|
---|
314 | * selection model.
|
---|
315 | */
|
---|
316 | protected void updateSelectedPathsFromSelectedRows() {
|
---|
317 | if(!updatingListSelectionModel) {
|
---|
318 | updatingListSelectionModel = true;
|
---|
319 | try {
|
---|
320 | // This is way expensive, ListSelectionModel needs an
|
---|
321 | // enumerator for iterating.
|
---|
322 | int min = listSelectionModel.getMinSelectionIndex();
|
---|
323 | int max = listSelectionModel.getMaxSelectionIndex();
|
---|
324 |
|
---|
325 | clearSelection();
|
---|
326 | if(min != -1 && max != -1) {
|
---|
327 | for(int counter = min; counter <= max; counter++) {
|
---|
328 | if(listSelectionModel.isSelectedIndex(counter)) {
|
---|
329 | TreePath selPath = tree.getPathForRow
|
---|
330 | (counter);
|
---|
331 |
|
---|
332 | if(selPath != null) {
|
---|
333 | addSelectionPath(selPath);
|
---|
334 | }
|
---|
335 | }
|
---|
336 | }
|
---|
337 | }
|
---|
338 | }
|
---|
339 | finally {
|
---|
340 | updatingListSelectionModel = false;
|
---|
341 | }
|
---|
342 | }
|
---|
343 | }
|
---|
344 |
|
---|
345 | /**
|
---|
346 | * Class responsible for calling updateSelectedPathsFromSelectedRows
|
---|
347 | * when the selection of the list changse.
|
---|
348 | */
|
---|
349 | class ListSelectionHandler implements ListSelectionListener {
|
---|
350 | public void valueChanged(ListSelectionEvent e) {
|
---|
351 | updateSelectedPathsFromSelectedRows();
|
---|
352 | }
|
---|
353 | }
|
---|
354 | }
|
---|
355 | }
|
---|