wangting
2024-09-27 a3e87f78ee262ca9bb7d9b0c997639d5f3295890
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
package com.vci.client.ui.treeTable;
 
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Rectangle;
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.UIManager;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableCellRenderer;
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;
 
import com.vci.client.ui.table.HorizontalAlignTableCellRender;
 
public class JTreeTable extends JTable {
 
    /**
     * 
     */
    private static final long serialVersionUID = -3912783890684226553L;
    protected TreeTableCellRenderer renderer;
 
    public JTreeTable(InterfaceTreeTableModel treeTableModel) {
        super();
        // Create the tree. It will be used as a renderer and editor.
        renderer = new TreeTableCellRenderer(treeTableModel);
 
        super.setModel(new TreeTableModelAdapter(treeTableModel, renderer));
 
        // Force the JTable and JTree to share their row selection models.
        ListToTreeSelectionModelWrapper selectionWrapper = new ListToTreeSelectionModelWrapper();
        renderer.setSelectionModel(selectionWrapper);
        setSelectionModel(selectionWrapper.getListSelectionModel());
 
        // Install the tree editor renderer and editor.
        setDefaultRenderer(InterfaceTreeTableModel.class, renderer);
        setDefaultEditor(InterfaceTreeTableModel.class, new TreeTableCellEditor());
 
        // No grid.
        setShowGrid(true);
 
        // No intercell spacing
        setIntercellSpacing(new Dimension(0, 0));
 
        // And update the height of the trees row to match that of
        // the table.
        if (renderer.getRowHeight() < 1) {
            // Metal looks better like this.
            setRowHeight(18);
        }
 
        setCellAlignToCenter(this);
    }
 
    /**
     * 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 (renderer != null) {
            renderer.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) == InterfaceTreeTableModel.class) ? -1 : editingRow;
    }
 
    /**
     * Overridden to pass the new rowHeight to the tree.
     */
    public void setRowHeight(int rowHeight) {
        super.setRowHeight(rowHeight);
        if (renderer != null && renderer.getRowHeight() != rowHeight) {
            renderer.setRowHeight(getRowHeight());
        }
    }
 
    /**
     * Returns the tree that is being shared between the model.
     */
    public JTree getTree() {
        return renderer;
    }
 
    public class TreeTableCellRenderer extends JTree implements TableCellRenderer {
 
        /**
         * 
         */
        private static final long serialVersionUID = -5786712212850260154L;
 
        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());
                }
            }
        }
 
        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;
        }
 
        /**
         * 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);
            
            if (!JTreeTable.this.isCellSelected(visibleRow, 0)) {
                Rectangle rcBounds = this.getBounds();
                Rectangle rc = this.getRowBounds(visibleRow);
                
                Color c = g.getColor();
                g.setColor(JTreeTable.this.gridColor);
                g.drawLine(0, rc.y + rc.height - 1, rcBounds.width, rc.y + rc.height - 1);
                g.drawLine(rcBounds.width - 1, rc.y, rcBounds.width - 1, rc.y + rc.height - 1);
                
                g.setColor(c);
            }
        }
 
    }
 
    /**
     * TreeTableCellEditor implementation. Component returned is the JTree.
     */
    public class TreeTableCellEditor extends AbstractCellEditor implements TableCellEditor {
 
        public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row,
                int column) {
            return renderer;
        }
 
        /**
         * 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) == InterfaceTreeTableModel.class) {
                        MouseEvent me = (MouseEvent) e;
                        MouseEvent newME = new MouseEvent(renderer, me.getID(), me.getWhen(), me.getModifiers(),
                                me.getX() - getCellRect(0, counter, true).x, me.getY(), me.getClickCount(),
                                me.isPopupTrigger());
                        renderer.dispatchEvent(newME);
                        break;
                    }
                }
            }
            return false;
        }
 
        public Object getCellEditorValue() {
            return null;
        }
    }
 
    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 = renderer.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();
            }
        }
    }
 
    private void setCellAlignToCenter(JTable table) {
        HorizontalAlignTableCellRender cellRenderer = new HorizontalAlignTableCellRender(
                DefaultTableCellRenderer.CENTER);
        for (int i = 1; i < table.getColumnCount(); i++) {
            this.getColumnModel().getColumn(i).setCellRenderer(cellRenderer);
        }
    }
}