KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > openide > explorer > view > TreeTable


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19 package org.openide.explorer.view;
20 import javax.swing.table.TableColumnModel JavaDoc;
21 import org.openide.awt.MouseUtils;
22 import org.openide.explorer.propertysheet.PropertyPanel;
23 import org.openide.nodes.Node;
24 import org.openide.nodes.Node.Property;
25 import org.openide.util.NbBundle;
26 import org.openide.util.Utilities;
27
28 import java.awt.*;
29 import java.awt.event.ActionEvent JavaDoc;
30 import java.awt.event.ActionListener JavaDoc;
31 import java.awt.event.FocusEvent JavaDoc;
32 import java.awt.event.FocusListener JavaDoc;
33 import java.awt.event.KeyEvent JavaDoc;
34 import java.awt.event.MouseEvent JavaDoc;
35
36 import java.beans.PropertyEditor JavaDoc;
37
38 import java.util.ArrayList JavaDoc;
39 import java.util.Collections JavaDoc;
40 import java.util.EventObject JavaDoc;
41 import java.util.List JavaDoc;
42 import java.util.logging.Level JavaDoc;
43 import java.util.logging.Logger JavaDoc;
44
45 import javax.swing.*;
46 import javax.swing.event.*;
47 import javax.swing.plaf.basic.BasicTableUI JavaDoc;
48 import javax.swing.plaf.basic.BasicTreeUI JavaDoc;
49 import javax.swing.table.JTableHeader JavaDoc;
50 import javax.swing.table.TableCellEditor JavaDoc;
51 import javax.swing.table.TableCellRenderer JavaDoc;
52 import javax.swing.table.TableColumn JavaDoc;
53 import javax.swing.tree.*;
54 import org.openide.util.Exceptions;
55
56
57 /**
58  * TreeTable implementation.
59  *
60  * @author Jan Rojcek
61  */

62 class TreeTable extends JTable implements Runnable JavaDoc {
63     /** Action key for up/down focus action */
64     private static final String JavaDoc ACTION_FOCUS_NEXT = "focusNext"; //NOI18N
65
private static Color unfocusedSelBg = null;
66     private static Color unfocusedSelFg = null;
67
68     /** A subclass of JTree. */
69     private TreeTableCellRenderer tree;
70     private NodeTableModel tableModel;
71     private int treeColumnIndex = -1;
72
73     /** Tree editor stuff. */
74     private int lastRow = -1;
75     private boolean canEdit;
76     private boolean ignoreScrolling = false;
77
78     /** Flag to ignore clearSelection() called from super.tableChanged(). */
79     private boolean ignoreClearSelection = false;
80
81     /** Position of tree renderer, used for horizontal scrolling. */
82     private int positionX;
83
84     /** If true, horizontal scrolling of tree column is enabled in TreeTableView */
85     private boolean treeHScrollingEnabled = true;
86     private final ListToTreeSelectionModelWrapper selectionWrapper;
87     private boolean edCreated = false;
88     boolean inSelectAll = false;
89     private boolean needCalcRowHeight = true;
90     boolean inEditRequest = false;
91     boolean inEditorChangeRequest = false;
92     int editRow = -1;
93     private boolean inRemoveRequest = false;
94
95     public TreeTable(NodeTreeModel treeModel, NodeTableModel tableModel) {
96         super();
97
98         setSurrendersFocusOnKeystroke(true);
99
100         this.tree = new TreeTableCellRenderer(treeModel);
101         this.tableModel = new TreeTableModelAdapter(tree, tableModel);
102
103         tree.setCellRenderer(new NodeRenderer());
104
105         // Install a tableModel representing the visible rows in the tree.
106
setModel(this.tableModel);
107
108         // Force the JTable and JTree to share their row selection models.
109
selectionWrapper = new ListToTreeSelectionModelWrapper();
110         tree.setSelectionModel(selectionWrapper);
111         setSelectionModel(selectionWrapper.getListSelectionModel());
112         getTableHeader().setReorderingAllowed(false);
113
114         // Install the tree editor renderer and editor.
115
setDefaultRenderer(TreeTableModelAdapter.class, tree);
116
117         // Install property renderer and editor.
118
TableSheetCell tableCell = new TableSheetCell(this.tableModel);
119         tableCell.setFlat(true);
120         setDefaultRenderer(Property.class, tableCell);
121         setDefaultEditor(Property.class, tableCell);
122         getTableHeader().setDefaultRenderer(tableCell);
123         getAccessibleContext().setAccessibleName(NbBundle.getBundle(TreeTable.class).getString("ACSN_TreeTable")); // NOI18N
124
getAccessibleContext().setAccessibleDescription( // NOI18N
125
NbBundle.getBundle(TreeTable.class).getString("ACSD_TreeTable")); // NOI18N
126

127         setFocusCycleRoot(true);
128         setFocusTraversalPolicy(new STPolicy());
129         putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
130         putClientProperty("JTable.autoStartsEdit", Boolean.FALSE);
131
132         initKeysAndActions();
133     }
134
135     private void initKeysAndActions() {
136         setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, Collections.<AWTKeyStroke>emptySet());
137         setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, Collections.<AWTKeyStroke>emptySet());
138
139         //Next two lines do not work using inputmap/actionmap, but do work
140
//using the older API. We will process ENTER to skip to next row,
141
//not next cell
142
unregisterKeyboardAction(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0));
143         unregisterKeyboardAction(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, Event.SHIFT_MASK));
144
145         InputMap imp = getInputMap(WHEN_FOCUSED);
146         InputMap imp2 = getInputMap(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
147         ActionMap am = getActionMap();
148
149         // copied from TreeView which tried to fix #18292
150
// by doing this
151
imp2.put(KeyStroke.getKeyStroke("control C"), "none"); // NOI18N
152
imp2.put(KeyStroke.getKeyStroke("control V"), "none"); // NOI18N
153
imp2.put(KeyStroke.getKeyStroke("control X"), "none"); // NOI18N
154
imp2.put(KeyStroke.getKeyStroke("COPY"), "none"); // NOI18N
155
imp2.put(KeyStroke.getKeyStroke("PASTE"), "none"); // NOI18N
156
imp2.put(KeyStroke.getKeyStroke("CUT"), "none"); // NOI18N
157

158         imp.put(
159             KeyStroke.getKeyStroke(KeyEvent.VK_TAB, KeyEvent.CTRL_MASK | KeyEvent.SHIFT_MASK, false), ACTION_FOCUS_NEXT
160         );
161         imp.put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, KeyEvent.CTRL_MASK, false), ACTION_FOCUS_NEXT);
162
163         Action ctrlTab = new CTRLTabAction();
164         am.put(ACTION_FOCUS_NEXT, ctrlTab);
165
166         getActionMap().put(
167             "selectNextColumn", // NOI18N
168
new TreeTableAction(
169                 tree.getActionMap().get("selectChild"), // NOI18N
170
getActionMap().get("selectNextColumn")
171             )
172         ); // NOI18N
173
getActionMap().put(
174             "selectPreviousColumn", // NOI18N
175
new TreeTableAction(
176                 tree.getActionMap().get("selectParent"), // NOI18N
177
getActionMap().get("selectPreviousColumn")
178             )
179         ); // NOI18N
180

181         getAccessibleContext().setAccessibleName(NbBundle.getBundle(TreeTable.class).getString("ACSN_TreeTable")); // NOI18N
182
getAccessibleContext().setAccessibleDescription( // NOI18N
183
NbBundle.getBundle(TreeTable.class).getString("ACSD_TreeTable")); // NOI18N
184

185         imp.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0, false), "beginEdit");
186         getActionMap().put("beginEdit", new EditAction());
187
188         imp2.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, false), "cancelEdit");
189         getActionMap().put("cancelEdit", new CancelEditAction());
190
191         imp.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false), "enter");
192         getActionMap().put("enter", new EnterAction());
193
194         imp.put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0), "next");
195
196         imp.put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, KeyEvent.SHIFT_DOWN_MASK), "previous");
197
198         am.put("next", new NavigationAction(true));
199         am.put("previous", new NavigationAction(false));
200     }
201
202     public TableCellEditor JavaDoc getDefaultEditor(Class JavaDoc columnClass) {
203         if (!edCreated && (columnClass == TreeTableModelAdapter.class)) {
204             //Creating this editor in the constructor can take > 100ms even
205
//on a very fast machine, so do it lazily here to improve
206
//performance of creating a TreeTable
207
setDefaultEditor(TreeTableModelAdapter.class, new TreeTableCellEditor());
208             edCreated = true;
209         }
210
211         return super.getDefaultEditor(columnClass);
212     }
213
214     public void selectAll() {
215         //#48242 - select all over 1000 nodes generates 1000 re-sorts
216
inSelectAll = true;
217
218         try {
219             super.selectAll();
220         } finally {
221             inSelectAll = false;
222             selectionWrapper.updateSelectedPathsFromSelectedRows();
223         }
224     }
225
226     /*
227      * Overridden to message super and forward the method to the tree.
228      */

229     public void updateUI() {
230         super.updateUI();
231
232         if (tree != null) {
233             tree.updateUI();
234         }
235
236         // Use the tree's default foreground and background colors in the
237
// table.
238
LookAndFeel.installColorsAndFont(this, "Tree.background", // NOI18N
239
"Tree.foreground", "Tree.font"
240         ); // NOI18N
241

242         if (UIManager.getColor("Table.selectionBackground") == null) { // NOI18N
243
UIManager.put("Table.selectionBackground", new JTable().getSelectionBackground()); // NOI18N
244
}
245
246         if (UIManager.getColor("Table.selectionForeground") == null) { // NOI18N
247
UIManager.put("Table.selectionForeground", new JTable().getSelectionForeground()); // NOI18N
248
}
249
250         if (UIManager.getColor("Table.gridColor") == null) { // NOI18N
251
UIManager.put("Table.gridColor", new JTable().getGridColor()); // NOI18N
252
}
253
254         setUI(new TreeTableUI());
255         needCalcRowHeight = true;
256     }
257
258     /* Workaround for BasicTableUI anomaly. Make sure the UI never tries to
259      * paint the editor. The UI currently uses different techniques to
260      * paint the renderers and editors and overriding setBounds() below
261      * is not the right thing to do for an editor. Returning -1 for the
262      * editing row in this case, ensures the editor is never painted.
263      */

264     public int getEditingRow() {
265         return (getColumnClass(editingColumn) == TreeTableModelAdapter.class) ? (-1) : editingRow;
266     }
267
268     /** Overridden - JTable's implementation of the method will
269      * actually attach (and leave behind) a gratuitous border
270      * on the enclosing scroll pane. */

271     protected final void configureEnclosingScrollPane() {
272         Container p = getParent();
273
274         if (p instanceof JViewport) {
275             Container gp = p.getParent();
276
277             if (gp instanceof JScrollPane) {
278                 JScrollPane scrollPane = (JScrollPane) gp;
279                 JViewport viewport = scrollPane.getViewport();
280
281                 if ((viewport == null) || (viewport.getView() != this)) {
282                     return;
283                 }
284
285                 JTableHeader JavaDoc jth = getTableHeader();
286
287                 if (jth != null) {
288                     jth.setBorder(null);
289                 }
290
291                 scrollPane.setColumnHeaderView(jth);
292             }
293         }
294     }
295
296     public void paint(Graphics g) {
297         if (needCalcRowHeight) {
298             calcRowHeight(g);
299
300             return;
301         }
302
303         /*
304         long time = perf.highResCounter();
305          */

306         super.paint(g);
307
308         /*
309         double dur = perf.highResCounter()-time;
310
311         total += dur;
312         System.err.println("Paint time: " + total + " ticks = " + (total / perf.highResFrequency()) + " ms. ");
313          */

314     }
315
316     // private static final sun.misc.Perf perf = sun.misc.Perf.getPerf();
317
// private static double total = 0;
318

319     /** Calculate the height of rows based on the current font. This is
320      * done when the first paint occurs, to ensure that a valid Graphics
321      * object is available.
322      * @since 1.25 */

323     private void calcRowHeight(Graphics g) {
324         Font f = getFont();
325         FontMetrics fm = g.getFontMetrics(f);
326         int rowHeight = fm.getHeight() + fm.getMaxDescent();
327         needCalcRowHeight = false;
328         rowHeight = Math.max(20, rowHeight);
329         tree.setRowHeight(rowHeight);
330         setRowHeight(rowHeight);
331     }
332
333     /**
334      * Returns the tree that is being shared between the model.
335      */

336     JTree getTree() {
337         return tree;
338     }
339
340     /**
341       * Returns table column index of the column displaying the tree.
342       */

343     int getTreeColumnIndex() {
344         return treeColumnIndex;
345     }
346
347     /**
348      * Sets tree column index and fires property change.
349      */

350     void setTreeColumnIndex(int index) {
351         if (treeColumnIndex == index) {
352             return;
353         }
354
355         int old = treeColumnIndex;
356         treeColumnIndex = index;
357         firePropertyChange("treeColumnIndex", old, treeColumnIndex);
358     }
359
360     /* Overriden to do not clear a selection upon model changes.
361      */

362     public void clearSelection() {
363         if (!ignoreClearSelection) {
364             super.clearSelection();
365         }
366     }
367     
368     /* Updates tree column name and sets ignoreClearSelection flag.
369      */

370     public void tableChanged(TableModelEvent e) {
371         // update tree column name
372
int modelColumn = getTreeColumnIndex();
373
374         if ((e.getFirstRow() <= 0) && (modelColumn != -1) && (getColumnCount() > 0)) {
375             String JavaDoc columnName = getModel().getColumnName(modelColumn);
376             TableColumn JavaDoc aColumn = getColumnModel().getColumn(modelColumn);
377             aColumn.setHeaderValue(columnName);
378         }
379
380         ignoreClearSelection = true;
381
382         try {
383             super.tableChanged(e);
384             //#61728 - force update of tree's horizontal scrollbar
385
if( null != getTree() ) {
386                 firePropertyChange( "positionX", -1, getPositionX() );
387             }
388         } finally {
389             ignoreClearSelection = false;
390         }
391     }
392
393     public void processKeyEvent(KeyEvent JavaDoc e) {
394         //Manually hook in the bindings for tab - does not seem to get called
395
//automatically
396
if (isEditing() && ((e.getKeyCode() == e.VK_DOWN) || (e.getKeyCode() == e.VK_UP))) {
397             return; //XXX
398
}
399
400         //Bypass standard tab and escape handling, and use our registered
401
//actions instead
402
if (
403             !isEditing() ||
404                 (((e.getKeyCode() != e.VK_TAB) && (e.getKeyCode() != e.VK_ESCAPE)) ||
405                 ((e.getModifiers() & e.CTRL_MASK) != 0))
406         ) {
407             super.processKeyEvent(e);
408         } else {
409             processKeyBinding(
410                 KeyStroke.getKeyStroke(e.getKeyCode(), e.getModifiersEx(), e.getID() == e.KEY_RELEASED), e,
411                 JComponent.WHEN_FOCUSED, e.getID() == e.KEY_PRESSED
412             );
413         }
414     }
415
416     /* Performs horizontal scrolling of the tree when editing is started.
417      */

418     public boolean editCellAt(int row, int column, EventObject JavaDoc e) {
419         if (e instanceof MouseEvent JavaDoc && (column != 0)) {
420             MouseEvent JavaDoc me = (MouseEvent JavaDoc) e;
421
422             if (!SwingUtilities.isLeftMouseButton(me) || (me.getID() != me.MOUSE_PRESSED)) {
423                 return false;
424             }
425         }
426
427         if ((row >= getRowCount()) || (row < 0) || (column > getColumnCount()) || (column < 0)) {
428             //I don't want to know why this happens, but it does.
429
return false;
430         }
431
432         inEditRequest = true;
433         editRow = row;
434
435         if ((editingRow == row) && (editingColumn == column) && isEditing()) {
436             //discard edit requests if we're already editing that cell
437
inEditRequest = false;
438
439             return false;
440         }
441
442         if (isEditing()) {
443             inEditorChangeRequest = true;
444
445             try {
446                 removeEditor();
447                 changeSelection(row, column, false, false);
448             } finally {
449                 inEditorChangeRequest = false;
450             }
451         }
452
453         //Treat a keyEvent request to edit on a non-editable
454
//column as a request to edit the nearest column that is
455
//editable
456
boolean editable = getModel().isCellEditable(row, column);
457
458         //We never want to invoke node name editing from the keyboard,
459
//it doesn't work anyway - better to look for an editable property
460
if (editable && ((e == null) || e instanceof KeyEvent JavaDoc) && (column == 0)) {
461             editable = false;
462             column = 1;
463         }
464
465         boolean columnShifted = false;
466
467         if (!editable && (e instanceof KeyEvent JavaDoc || (e == null))) {
468             for (int i = column; i < getColumnCount(); i++) {
469                 if (getModel().isCellEditable(row, i)) {
470                     columnShifted = i != column;
471                     column = i;
472                     changeSelection(row, column, false, false);
473
474                     break;
475                 }
476             }
477         }
478
479         final Rectangle r = getCellRect(row, column, true);
480
481         //#44226 - Provide a way to invoke the custom editor on disabled cells
482
boolean canTryCustomEditor = (!columnShifted && e instanceof MouseEvent JavaDoc)
483             ? ((((MouseEvent JavaDoc) e).getX() > ((r.x + r.width) - 24)) && (((MouseEvent JavaDoc) e).getX() < (r.x + r.width))) : true;
484
485         try {
486             canEdit = (lastRow == row);
487
488             Object JavaDoc o = getValueAt(row, column);
489
490             if (o instanceof Property) { // && (e == null || e instanceof KeyEvent)) {
491

492                 //Toggle booleans without instantiating an editor
493
Property p = (Property) o;
494
495                 if (p.canWrite() && ((p.getValueType() == Boolean JavaDoc.class) || (p.getValueType() == Boolean.TYPE))) {
496                     try {
497                         Boolean JavaDoc val = (Boolean JavaDoc) p.getValue();
498
499                         if (Boolean.FALSE.equals(val)) {
500                             p.setValue(Boolean.TRUE);
501                         } else {
502                             //This covers null multi-selections too
503
p.setValue(Boolean.FALSE);
504                         }
505
506                         repaint(r.x, r.y, r.width, r.height);
507
508                         return false;
509                     } catch (Exception JavaDoc e1) {
510                         Logger.getLogger(TreeTable.class.getName()).log(Level.WARNING, null, e1);
511
512                         return false;
513                     }
514                 } else if (canTryCustomEditor && !Boolean.TRUE.equals(p.getValue("suppressCustomEditor"))) { //NOI18N
515

516                     PropertyPanel panel = new PropertyPanel(p);
517                     PropertyEditor JavaDoc ed = panel.getPropertyEditor();
518
519                     if ((ed != null) && ed.supportsCustomEditor()) {
520                         Action act = panel.getActionMap().get("invokeCustomEditor"); //NOI18N
521

522                         if (act != null) {
523                             SwingUtilities.invokeLater(
524                                 new Runnable JavaDoc() {
525                                     public void run() {
526                                         r.x = 0;
527                                         r.width = getWidth();
528                                         TreeTable.this.repaint(r);
529                                     }
530                                 }
531                             );
532                             act.actionPerformed(null);
533
534                             return false;
535                         }
536                     }
537                 }
538
539                 if (!p.canWrite()) {
540                     return false;
541                 }
542             }
543
544             boolean ret = super.editCellAt(row, column, e);
545
546             if (ret) {
547                 //InvokeLater to get out of the way of anything the winsys is going to do
548
if (column == getTreeColumnIndex()) {
549                     ignoreScrolling = true;
550                     tree.scrollRectToVisible(tree.getRowBounds(row));
551                     ignoreScrolling = false;
552                 } else {
553                     SwingUtilities.invokeLater(this);
554                 }
555             }
556
557             return ret;
558         } finally {
559             inEditRequest = false;
560         }
561     }
562
563     /**
564      *
565      */

566     public void run() {
567         if ((editorComp != null) && editorComp.isShowing()) {
568             editorComp.requestFocus();
569         }
570     }
571
572     /*
573      */

574     public void valueChanged(ListSelectionEvent e) {
575         if (getSelectedRowCount() == 1) {
576             lastRow = getSelectedRow();
577         } else {
578             lastRow = -1;
579         }
580
581         super.valueChanged(e);
582     }
583
584     /* Updates tree column index
585      */

586     public void columnAdded(TableColumnModelEvent e) {
587         super.columnAdded(e);
588         updateTreeColumnIndex();
589     }
590
591     /* Updates tree column index
592      */

593     public void columnRemoved(TableColumnModelEvent e) {
594         super.columnRemoved(e);
595         updateTreeColumnIndex();
596     }
597
598     /* Updates tree column index
599      */

600     public void columnMoved(TableColumnModelEvent e) {
601         super.columnMoved(e);
602         updateTreeColumnIndex();
603
604         int from = e.getFromIndex();
605         int to = e.getToIndex();
606
607         if (from != to) {
608             firePropertyChange("column_moved", from, to); // NOI18N
609
}
610     }
611
612     /* Updates tree column index
613      */

614     private void updateTreeColumnIndex() {
615         for (int i = getColumnCount() - 1; i >= 0; i--) {
616             if (getColumnClass(i) == TreeTableModelAdapter.class) {
617                 setTreeColumnIndex(i);
618
619                 return;
620             }
621         }
622
623         setTreeColumnIndex(-1);
624     }
625
626     /** Returns x coordinate of tree renderer.
627      */

628     public int getPositionX() {
629         return positionX;
630     }
631
632     /** Sets x position.
633      */

634     public void setPositionX(int x) {
635         if ((x == positionX) || !treeHScrollingEnabled) {
636             return;
637         }
638
639         int old = positionX;
640         positionX = x;
641
642         firePropertyChange("positionX", old, x);
643
644         if (isEditing() && (getEditingColumn() == getTreeColumnIndex())) {
645             CellEditor JavaDoc editor = getCellEditor();
646
647             if (ignoreScrolling && editor instanceof TreeTableCellEditor) {
648                 ((TreeTableCellEditor) editor).revalidateTextField();
649             } else {
650                 removeEditor();
651             }
652         }
653
654         repaint();
655     }
656
657     /** Overridden to manually draw the focused rectangle for the tree column */
658     public void paintComponent(Graphics g) {
659         super.paintComponent(g);
660
661         if (hasFocus() && (getSelectedColumn() == 0) && (getSelectedRow() > 0)) {
662             Color bdr = UIManager.getColor("Tree.selectionBorderColor"); //NOI18N
663

664             if (bdr == null) {
665                 //Button focus color doesn't work on win classic - better to
666
//get the color from a value we know will work - Tim
667
if (getForeground().equals(Color.BLACK)) { //typical
668
bdr = getBackground().darker();
669                 } else {
670                     bdr = getForeground().darker();
671                 }
672             }
673
674             g.setColor(bdr);
675
676             Rectangle r = getCellRect(getSelectedRow(), getSelectedColumn(), false);
677             g.drawRect(r.x + 1, r.y + 1, r.width - 3, r.height - 3);
678         }
679     }
680
681     /** Enables horizontal scrolling of tree column */
682     void setTreeHScrollingEnabled(boolean enabled) {
683         treeHScrollingEnabled = enabled;
684     }
685
686     boolean isKnownComponent(Component c) {
687         if (c == null) {
688             return false;
689         }
690
691         if (isAncestorOf(c)) {
692             return true;
693         }
694
695         if (c == editorComp) {
696             return true;
697         }
698
699         if ((editorComp != null) && (editorComp instanceof Container) && ((Container) editorComp).isAncestorOf(c)) {
700             return true;
701         }
702
703         return false;
704     }
705
706     public boolean isValidationRoot() {
707         return true;
708     }
709
710     public void paintImmediately(int x, int y, int w, int h) {
711         //Eliminate duplicate repaints in an editor change request
712
if (inEditorChangeRequest) {
713             return;
714         }
715
716         super.paintImmediately(x, y, w, h);
717     }
718
719     protected void processFocusEvent(FocusEvent JavaDoc fe) {
720         super.processFocusEvent(fe);
721
722         //Remove the editor here if the new focus owner is not
723
//known to the table & the focus event is not temporary
724
if ((fe.getID() == fe.FOCUS_LOST) && !fe.isTemporary() && !inRemoveRequest && !inEditRequest) {
725             boolean stopEditing = ((fe.getOppositeComponent() != getParent()) &&
726                 !isKnownComponent(fe.getOppositeComponent()) && (fe.getOppositeComponent() != null));
727
728             if (stopEditing) {
729                 removeEditor();
730             }
731         }
732
733         //The UI will only repaint the lead selection, but we need to
734
//paint all selected rows for the color to change when focus
735
//is lost/gained
736
if (!inRemoveRequest && !inEditRequest) {
737             repaintSelection(fe.getID() == fe.FOCUS_GAINED);
738         }
739     }
740
741     public void removeEditor() {
742         inRemoveRequest = true;
743
744         try {
745             synchronized (getTreeLock()) {
746                 super.removeEditor();
747             }
748         } finally {
749             inRemoveRequest = false;
750         }
751     }
752
753     /** Repaint the selected row */
754     private void repaintSelection(boolean focused) {
755         int start = getSelectionModel().getMinSelectionIndex();
756         int end = getSelectionModel().getMaxSelectionIndex();
757
758         if (end != -1) {
759             if (end != start) {
760                 Rectangle begin = getCellRect(start, 0, false);
761                 Rectangle r = getCellRect(end, 0, false);
762
763                 r.y = begin.y;
764                 r.x = 0;
765                 r.width = getWidth();
766                 r.height = (r.y + r.height) - begin.y;
767                 repaint(r.x, r.y, r.width, r.height);
768             } else {
769                 Rectangle r = getCellRect(start, 0, false);
770                 r.width = getWidth();
771                 r.x = 0;
772                 repaint(r.x, r.y, r.width, r.height);
773             }
774         }
775
776         if (isEditing() && (editorComp != null)) {
777             editorComp.setBackground(focused ? getSelectionBackground() : getUnfocusedSelectedBackground());
778             editorComp.setForeground(focused ? getSelectionForeground() : getUnfocusedSelectedForeground());
779         }
780     }
781
782     /** Get the system-wide unfocused selection background color */
783     static Color getUnfocusedSelectedBackground() {
784         if (unfocusedSelBg == null) {
785             //allow theme/ui custom definition
786
unfocusedSelBg = UIManager.getColor("nb.explorer.unfocusedSelBg"); //NOI18N
787

788             if (unfocusedSelBg == null) {
789                 //try to get standard shadow color
790
unfocusedSelBg = UIManager.getColor("controlShadow"); //NOI18N
791

792                 if (unfocusedSelBg == null) {
793                     //Okay, the look and feel doesn't suport it, punt
794
unfocusedSelBg = Color.lightGray;
795                 }
796
797                 //Lighten it a bit because disabled text will use controlShadow/
798
//gray
799
unfocusedSelBg = unfocusedSelBg.brighter();
800             }
801         }
802
803         return unfocusedSelBg;
804     }
805
806     /** Get the system-wide unfocused selection foreground color */
807     static Color getUnfocusedSelectedForeground() {
808         if (unfocusedSelFg == null) {
809             //allow theme/ui custom definition
810
unfocusedSelFg = UIManager.getColor("nb.explorer.unfocusedSelFg"); //NOI18N
811

812             if (unfocusedSelFg == null) {
813                 //try to get standard shadow color
814
unfocusedSelFg = UIManager.getColor("textText"); //NOI18N
815

816                 if (unfocusedSelFg == null) {
817                     //Okay, the look and feel doesn't suport it, punt
818
unfocusedSelFg = Color.BLACK;
819                 }
820             }
821         }
822
823         return unfocusedSelFg;
824     }
825
826     protected JTableHeader JavaDoc createDefaultTableHeader() {
827         return new TreeTableHeader( getColumnModel() );
828     }
829     
830     /**
831      * #53748: Default TableHeader provides wrong preferred height when the first column
832      * uses default renderer and has no value.
833      */

834     private static class TreeTableHeader extends JTableHeader JavaDoc {
835         public TreeTableHeader( TableColumnModel JavaDoc columnModel ) {
836             super( columnModel );
837         }
838
839         public Dimension getPreferredSize() {
840
841             Dimension retValue = super.getPreferredSize();
842             
843             Component comp = getDefaultRenderer().getTableCellRendererComponent( getTable(),
844                         "X", false, false, -1, 0 );
845             int rendererHeight = comp.getPreferredSize().height;
846             retValue.height = Math.max(retValue.height, rendererHeight);
847             return retValue;
848         }
849     }
850
851     /**
852      * A TreeCellRenderer that displays a JTree.
853      */

854     class TreeTableCellRenderer extends JTree implements TableCellRenderer JavaDoc {
855         /** Last table/tree row asked to renderer. */
856         protected int visibleRow;
857
858         /* Last width of the tree.
859          */

860         private int oldWidth;
861         private int transY = 0;
862
863         public TreeTableCellRenderer(TreeModel model) {
864             super(model);
865             setToggleClickCount(0);
866             putClientProperty("JTree.lineStyle", "None"); // NOI18N
867
}
868
869         public void validate() {
870             //do nothing
871
}
872
873         public void repaint(long tm, int x, int y, int width, int height) {
874             //do nothing
875
}
876
877         public void addHierarchyListener(java.awt.event.HierarchyListener JavaDoc hl) {
878             //do nothing
879
}
880
881         public void addComponentListener(java.awt.event.ComponentListener JavaDoc cl) {
882             //do nothing
883
}
884
885         /**
886          * Accessor so NodeRenderer can check if the tree table or its child has
887          * focus and paint with the appropriate color.
888          *
889          * @see NodeRenderer#configureFrom
890          * @return The tree table
891          */

892         TreeTable getTreeTable() {
893             return TreeTable.this;
894         }
895
896         /**
897          * Sets the row height of the tree, and forwards the row height to
898          * the table.
899          */

900         public void setRowHeight(int rowHeight) {
901             if (rowHeight > 0) {
902                 super.setRowHeight(rowHeight);
903                 TreeTable.this.setRowHeight(rowHeight);
904             }
905         }
906
907         /**
908          * Overridden to always set the size to the height of the TreeTable
909          * and the width of column 0. The paint() method will translate the
910          * coordinates to the correct position. */

911         public void setBounds(int x, int y, int w, int h) {
912             transY = -y;
913             super.setBounds(0, 0, TreeTable.this.getColumnModel().getColumn(0).getWidth(), TreeTable.this.getHeight());
914         }
915
916         /* Fire width property change so that we can revalidate horizontal scrollbar in TreeTableView.
917          */

918         @SuppressWarnings JavaDoc("deprecation")
919         public void reshape(int x, int y, int w, int h) {
920             int oldWidth = getWidth();
921             super.reshape(x, y, w, h);
922
923             if (oldWidth != w) {
924                 firePropertyChange("width", oldWidth, w);
925             }
926         }
927
928         public void paint(Graphics g) {
929             g.translate(-getPositionX(), transY);
930             super.paint(g);
931         }
932
933         public Rectangle getVisibleRect() {
934             Rectangle visibleRect = TreeTable.this.getVisibleRect();
935             visibleRect.x = positionX;
936             visibleRect.width = TreeTable.this.getColumnModel().getColumn(getTreeColumnIndex()).getWidth();
937
938             return visibleRect;
939         }
940
941         /* Overriden to use this call for moving tree renderer.
942          */

943         public void scrollRectToVisible(Rectangle aRect) {
944             Rectangle rect = getVisibleRect();
945             rect.y = aRect.y;
946             rect.height = aRect.height;
947
948             TreeTable.this.scrollRectToVisible(rect);
949
950             int x = rect.x;
951
952             if (aRect.width > rect.width) {
953                 x = aRect.x;
954             } else if (aRect.x < rect.x) {
955                 x = aRect.x;
956             } else if ((aRect.x + aRect.width) > (rect.x + rect.width)) {
957                 x = (aRect.x + aRect.width) - rect.width;
958             }
959
960             TreeTable.this.setPositionX(x);
961         }
962
963         public String JavaDoc getToolTipText(MouseEvent JavaDoc event) {
964             if (event != null) {
965                 Point p = event.getPoint();
966                 p.translate(positionX, visibleRow * getRowHeight());
967
968                 int selRow = getRowForLocation(p.x, p.y);
969
970                 if (selRow != -1) {
971                     TreePath path = getPathForRow(selRow);
972                     VisualizerNode v = (VisualizerNode) path.getLastPathComponent();
973                     String JavaDoc tooltip = v.getShortDescription();
974                     String JavaDoc displayName = v.getDisplayName();
975
976                     if ((tooltip != null) && !tooltip.equals(displayName)) {
977                         return tooltip;
978                     }
979                 }
980             }
981
982             return null;
983         }
984
985         /**
986          * TreeCellRenderer method. Overridden to update the visible row.
987          */

988         public Component getTableCellRendererComponent(
989             JTable table, Object JavaDoc value, boolean isSelected, boolean hasFocus, int row, int column
990         ) {
991             if (isSelected) {
992                 Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
993
994                 boolean tableHasFocus = (focusOwner == this) || (focusOwner == TreeTable.this) ||
995                     TreeTable.this.isAncestorOf(focusOwner) || focusOwner instanceof JRootPane; //RootPane == popup menu
996

997                 setBackground(tableHasFocus ? table.getSelectionBackground() : getUnfocusedSelectedBackground());
998                 setForeground(tableHasFocus ? table.getSelectionForeground() : getUnfocusedSelectedForeground());
999             } else {
1000                setBackground(table.getBackground());
1001                setForeground(table.getForeground());
1002            }
1003
1004            visibleRow = row;
1005
1006            return this;
1007        }
1008
1009        protected TreeModelListener createTreeModelListener() {
1010            return new JTree.TreeModelHandler() {
1011                    public void treeNodesRemoved(TreeModelEvent e) {
1012                        if (tree.getSelectionCount() == 0) {
1013                            TreePath path = TreeView.findSiblingTreePath(e.getTreePath(), e.getChildIndices());
1014
1015                            if ((path != null) && (path.getPathCount() > 0)) {
1016                                tree.setSelectionPath(path);
1017                            }
1018                        }
1019                    }
1020                };
1021        }
1022
1023        public void fireTreeCollapsed(TreePath path) {
1024            super.fireTreeCollapsed(path);
1025            firePropertyChange("width", -1, getWidth());
1026        }
1027
1028        public void fireTreeExpanded(TreePath path) {
1029            super.fireTreeExpanded(path);
1030            firePropertyChange("width", -1, getWidth());
1031        }
1032    }
1033
1034    /**
1035     * TreeTableCellEditor implementation.
1036     */

1037    class TreeTableCellEditor extends DefaultCellEditor implements TreeSelectionListener, ActionListener JavaDoc, FocusListener JavaDoc,
1038        CellEditorListener {
1039        /** Used in editing. Indicates x position to place editingComponent. */
1040        protected transient int offset;
1041
1042        /** Used before starting the editing session. */
1043        protected transient Timer timer;
1044
1045        public TreeTableCellEditor() {
1046            super(new TreeTableTextField());
1047
1048            tree.addTreeSelectionListener(this);
1049            addCellEditorListener(this);
1050            super.getComponent().addFocusListener(this);
1051        }
1052
1053        /**
1054         * Overridden to determine an offset that tree would place the
1055         * editor at. The offset is determined from the
1056         * <code>getRowBounds</code> JTree method, and additionally
1057         * from the icon DefaultTreeCellRenderer will use.
1058         * <p>The offset is then set on the TreeTableTextField component
1059         * created in the constructor, and returned.
1060         */

1061        public Component getTableCellEditorComponent(JTable table, Object JavaDoc value, boolean isSelected, int r, int c) {
1062            Component component = super.getTableCellEditorComponent(table, value, isSelected, r, c);
1063
1064            determineOffset(value, isSelected, r);
1065            ((TreeTableTextField) getComponent()).offset = offset;
1066
1067            return component;
1068        }
1069
1070        /**
1071         * This is overridden to forward the event to the tree and start editor timer.
1072         */

1073        public boolean isCellEditable(EventObject JavaDoc e) {
1074            if (lastRow != -1) {
1075                org.openide.nodes.Node n = Visualizer.findNode(tree.getPathForRow(lastRow).getLastPathComponent());
1076
1077                if ((n == null) || !n.canRename()) {
1078                    //return false;
1079
canEdit = false;
1080                }
1081            }
1082
1083            if (canEdit && (e != null) && (e.getSource() instanceof Timer)) {
1084                return true;
1085            }
1086
1087            if (canEdit && shouldStartEditingTimer(e)) {
1088                startEditingTimer();
1089            } else if (shouldStopEditingTimer(e)) {
1090                timer.stop();
1091            }
1092
1093            if (e instanceof MouseEvent JavaDoc) {
1094                MouseEvent JavaDoc me = (MouseEvent JavaDoc) e;
1095                int column = getTreeColumnIndex();
1096
1097                if (MouseUtils.isLeftMouseButton(me) && (me.getClickCount() == 2)) {
1098                    TreePath path = tree.getPathForRow(TreeTable.this.rowAtPoint(me.getPoint()));
1099                    Rectangle r = tree.getPathBounds(path);
1100
1101                    if ((me.getX() < (r.x - positionX)) || (me.getX() > (r.x - positionX + r.width))) {
1102                        me.translatePoint(r.x - me.getX(), 0);
1103                    }
1104                }
1105
1106                MouseEvent JavaDoc newME = new MouseEvent JavaDoc(
1107                        TreeTable.this.tree, me.getID(), me.getWhen(), me.getModifiers(),
1108                        me.getX() - getCellRect(0, column, true).x + positionX, me.getY(), me.getClickCount(),
1109                        me.isPopupTrigger()
1110                    );
1111                TreeTable.this.tree.dispatchEvent(newME);
1112            }
1113
1114            return false;
1115        }
1116
1117        /* Stop timer when selection has been changed.
1118         */

1119        public void valueChanged(TreeSelectionEvent e) {
1120            if (timer != null) {
1121                timer.stop();
1122            }
1123        }
1124
1125        /* Timer performer.
1126         */

1127        public void actionPerformed(java.awt.event.ActionEvent JavaDoc e) {
1128            if (lastRow != -1) {
1129                editCellAt(lastRow, getTreeColumnIndex(), new EventObject JavaDoc(timer));
1130            }
1131        }
1132
1133        /* Start editing timer only on certain conditions.
1134         */

1135        private boolean shouldStartEditingTimer(EventObject JavaDoc event) {
1136            if ((event instanceof MouseEvent JavaDoc) && SwingUtilities.isLeftMouseButton((MouseEvent JavaDoc) event)) {
1137                MouseEvent JavaDoc me = (MouseEvent JavaDoc) event;
1138
1139                return ((me.getID() == me.MOUSE_PRESSED) && (me.getClickCount() == 1) && inHitRegion(me));
1140            }
1141
1142            return false;
1143        }
1144
1145        /* Stop editing timer only on certain conditions.
1146         */

1147        private boolean shouldStopEditingTimer(EventObject JavaDoc event) {
1148            if (timer == null) {
1149                return false;
1150            }
1151
1152            if (event instanceof MouseEvent JavaDoc) {
1153                MouseEvent JavaDoc me = (MouseEvent JavaDoc) event;
1154
1155                return (!SwingUtilities.isLeftMouseButton(me) || (me.getClickCount() > 1));
1156            }
1157
1158            return false;
1159        }
1160
1161        /**
1162         * Starts the editing timer.
1163         */

1164        private void startEditingTimer() {
1165            if (timer == null) {
1166                timer = new Timer(1200, this);
1167                timer.setRepeats(false);
1168            }
1169
1170            timer.start();
1171        }
1172
1173        /* Does a click go into node's label?
1174         */

1175        private boolean inHitRegion(MouseEvent JavaDoc me) {
1176            determineOffset(me);
1177
1178            if (me.getX() <= offset) {
1179                return false;
1180            }
1181
1182            return true;
1183        }
1184
1185        /* Determines offset of node's label from left edge of the table.
1186         */

1187        private void determineOffset(MouseEvent JavaDoc me) {
1188            int row = TreeTable.this.rowAtPoint(me.getPoint());
1189
1190            if (row == -1) {
1191                offset = 0;
1192
1193                return;
1194            }
1195
1196            determineOffset(tree.getPathForRow(row).getLastPathComponent(), TreeTable.this.isRowSelected(row), row);
1197        }
1198
1199        /* Determines offset of node's label from left edge of the table.
1200         */

1201        private void determineOffset(Object JavaDoc value, boolean isSelected, int row) {
1202            JTree t = getTree();
1203            boolean rv = t.isRootVisible();
1204            int offsetRow = row;
1205
1206            if (!rv && (row > 0)) {
1207                offsetRow--;
1208            }
1209
1210            Rectangle bounds = t.getRowBounds(offsetRow);
1211            offset = bounds.x;
1212
1213            TreeCellRenderer tcr = t.getCellRenderer();
1214            Object JavaDoc node = t.getPathForRow(offsetRow).getLastPathComponent();
1215            Component comp = tcr.getTreeCellRendererComponent(
1216                    t, node, isSelected, t.isExpanded(offsetRow), t.getModel().isLeaf(node), offsetRow, false
1217                );
1218
1219            if (comp instanceof JLabel) {
1220                Icon icon = ((JLabel) comp).getIcon();
1221
1222                if (icon != null) {
1223                    offset += (((JLabel) comp).getIconTextGap() + icon.getIconWidth());
1224                }
1225            }
1226
1227            offset -= positionX;
1228        }
1229
1230        /* Revalidates text field upon change of x position of renderer
1231         */

1232        private void revalidateTextField() {
1233            int row = TreeTable.this.editingRow;
1234
1235            if (row == -1) {
1236                offset = 0;
1237
1238                return;
1239            }
1240
1241            determineOffset(tree.getPathForRow(row).getLastPathComponent(), TreeTable.this.isRowSelected(row), row);
1242            ((TreeTableTextField) super.getComponent()).offset = offset;
1243            getComponent().setBounds(TreeTable.this.getCellRect(row, getTreeColumnIndex(), false));
1244        }
1245
1246        // Focus listener
1247

1248        /* Cancel editing when text field loses focus
1249         */

1250        public void focusLost(java.awt.event.FocusEvent JavaDoc evt) {
1251            /* to allow Escape functionality
1252            if (!stopCellEditing())
1253              cancelCellEditing();
1254             */

1255        }
1256
1257        /* Select a text in text field when it gets focus.
1258         */

1259        public void focusGained(java.awt.event.FocusEvent JavaDoc evt) {
1260            ((TreeTableTextField) super.getComponent()).selectAll();
1261        }
1262
1263        // Cell editor listener - copied from TreeViewCellEditor
1264

1265        /** Implements <code>CellEditorListener</code> interface method. */
1266        public void editingStopped(ChangeEvent e) {
1267            TreePath lastP = tree.getPathForRow(lastRow);
1268
1269            if (lastP != null) {
1270                Node n = Visualizer.findNode(lastP.getLastPathComponent());
1271
1272                if ((n != null) && n.canRename()) {
1273                    String JavaDoc newStr = (String JavaDoc) getCellEditorValue();
1274
1275                    try {
1276                        // bugfix #21589 don't update name if there is not any change
1277
if (!n.getName().equals(newStr)) {
1278                            n.setName(newStr);
1279                        }
1280                    } catch (IllegalArgumentException JavaDoc exc) {
1281                        boolean needToAnnotate = Exceptions.findLocalizedMessage(exc) == null;
1282
1283                        // annotate new localized message only if there is no localized message yet
1284
if (needToAnnotate) {
1285                            String JavaDoc msg = NbBundle.getMessage(
1286                                    TreeViewCellEditor.class, "RenameFailed", n.getName(), newStr
1287                                );
1288                            Exceptions.attachLocalizedMessage(exc, msg);
1289                        }
1290
1291                        Exceptions.printStackTrace(exc);
1292                    }
1293                }
1294            }
1295        }
1296
1297        /** Implements <code>CellEditorListener</code> interface method. */
1298        public void editingCanceled(ChangeEvent e) {
1299        }
1300    }
1301
1302    /**
1303     * Component used by TreeTableCellEditor. The only thing this does
1304     * is to override the <code>reshape</code> method, and to ALWAYS
1305     * make the x location be <code>offset</code>.
1306     */

1307    static class TreeTableTextField extends JTextField {
1308        public int offset;
1309
1310        @SuppressWarnings JavaDoc("deprecation")
1311        public void reshape(int x, int y, int w, int h) {
1312            int newX = Math.max(x, offset);
1313            super.reshape(newX, y, w - (newX - x), h);
1314        }
1315
1316        public void addNotify() {
1317            super.addNotify();
1318
1319            //requestFocus(); //no longer necessary, STPolicy will do it - Tim
1320
}
1321    }
1322    /**
1323     * ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel
1324     * to listen for changes in the ListSelectionModel it maintains. Once
1325     * a change in the ListSelectionModel happens, the paths are updated
1326     * in the DefaultTreeSelectionModel.
1327     */

1328    class ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel {
1329        /** Set to true when we are updating the ListSelectionModel. */
1330        protected boolean updatingListSelectionModel;
1331
1332        public ListToTreeSelectionModelWrapper() {
1333            super();
1334            listSelectionModel = new TreeTableSelectionModel();
1335            getListSelectionModel().addListSelectionListener(createListSelectionListener());
1336        }
1337
1338        /**
1339         * Returns the list selection model. ListToTreeSelectionModelWrapper
1340         * listens for changes to this model and updates the selected paths
1341         * accordingly.
1342         */

1343        ListSelectionModel getListSelectionModel() {
1344            return listSelectionModel;
1345        }
1346
1347        /**
1348         * This is overridden to set <code>updatingListSelectionModel</code>
1349         * and message super. This is the only place DefaultTreeSelectionModel
1350         * alters the ListSelectionModel.
1351         */

1352        public void resetRowSelection() {
1353            if (!updatingListSelectionModel) {
1354                updatingListSelectionModel = true;
1355
1356                try {
1357                    super.resetRowSelection();
1358                } finally {
1359                    updatingListSelectionModel = false;
1360                }
1361            }
1362
1363            // Notice how we don't message super if
1364
// updatingListSelectionModel is true. If
1365
// updatingListSelectionModel is true, it implies the
1366
// ListSelectionModel has already been updated and the
1367
// paths are the only thing that needs to be updated.
1368
}
1369
1370        /**
1371         * Creates and returns an instance of ListSelectionHandler.
1372         */

1373        protected ListSelectionListener createListSelectionListener() {
1374            return new ListSelectionHandler();
1375        }
1376
1377        /**
1378         * If <code>updatingListSelectionModel</code> is false, this will
1379         * reset the selected paths from the selected rows in the list
1380         * selection model.
1381         */

1382        protected void updateSelectedPathsFromSelectedRows() {
1383            if (!updatingListSelectionModel) {
1384                updatingListSelectionModel = true;
1385
1386                try {
1387                    int min = listSelectionModel.getMinSelectionIndex();
1388                    int max = listSelectionModel.getMaxSelectionIndex();
1389
1390                    if ((min == 0) && (max == getRowCount())) {
1391                        //#48242 - optimize the case of select all
1392
int[] rows = new int[max];
1393
1394                        for (int i = 0; i < rows.length; i++) {
1395                            rows[i] = i;
1396                        }
1397
1398                        tree.setSelectionRows(rows);
1399                    } else {
1400                        List JavaDoc<Integer JavaDoc> list = new ArrayList JavaDoc<Integer JavaDoc>(11);
1401
1402                        for (int i = min; i <= max; i++) {
1403                            if (listSelectionModel.isSelectedIndex(i)) {
1404                                list.add(Integer.valueOf(i));
1405                            }
1406                        }
1407
1408                        if (list.isEmpty()) {
1409                            clearSelection();
1410                        } else {
1411                            int[] rows = (int[]) Utilities.toPrimitiveArray(
1412                                    list.toArray(new Integer JavaDoc[list.size()])
1413                                );
1414                            tree.setSelectionRows(rows);
1415                        }
1416                    }
1417                } finally {
1418                    updatingListSelectionModel = false;
1419                }
1420            }
1421        }
1422
1423        /**
1424         * Class responsible for calling updateSelectedPathsFromSelectedRows
1425         * when the selection of the list changes.
1426         */

1427        class ListSelectionHandler implements ListSelectionListener {
1428            public void valueChanged(ListSelectionEvent e) {
1429                if( inSelectAll || e.getValueIsAdjusting() ) {
1430                    return;
1431                }
1432                
1433                updateSelectedPathsFromSelectedRows();
1434            }
1435        }
1436    }
1437
1438    /**
1439     * #63287 - in JDK1.6 the JTable makes additional changes to the ListSelectionModel
1440     * when clearing row selection. These events must be ignored the same way as
1441     * the overridden method TreeTable.clearSelection()
1442     */

1443    class TreeTableSelectionModel extends DefaultListSelectionModel {
1444        public void setAnchorSelectionIndex(int anchorIndex) {
1445            if( ignoreClearSelection )
1446                return;
1447            super.setAnchorSelectionIndex(anchorIndex);
1448        }
1449
1450        public void setLeadSelectionIndex(int leadIndex) {
1451            if( ignoreClearSelection )
1452                return;
1453            super.setLeadSelectionIndex(leadIndex);
1454        }
1455    }
1456    
1457    /* This is overriden to handle mouse events especially. E.g. do not change selection
1458     * when it was clicked on tree's expand/collapse toggles.
1459     */

1460    class TreeTableUI extends BasicTableUI JavaDoc {
1461        /**
1462         * Creates the mouse listener for the JTable.
1463         */

1464        protected MouseInputListener createMouseInputListener() {
1465            return new TreeTableMouseInputHandler();
1466        }
1467
1468        public class TreeTableMouseInputHandler extends MouseInputHandler {
1469            // Component recieving mouse events during editing. May not be editorComponent.
1470
private Component dispatchComponent;
1471
1472            // The Table's mouse listener methods.
1473
public void mouseClicked(MouseEvent JavaDoc e) {
1474                processMouseEvent(e);
1475            }
1476
1477            public void mousePressed(MouseEvent JavaDoc e) {
1478                processMouseEvent(e);
1479            }
1480
1481            public void mouseReleased(MouseEvent JavaDoc e) {
1482                if (shouldIgnore(e)) {
1483                    return;
1484                }
1485
1486                repostEvent(e);
1487                dispatchComponent = null;
1488                setValueIsAdjusting(false);
1489
1490                if (!TreeTable.this.isEditing()) {
1491                    processMouseEvent(e);
1492                }
1493            }
1494
1495            public void mouseDragged(MouseEvent JavaDoc e) {
1496                return;
1497            }
1498
1499            private void setDispatchComponent(MouseEvent JavaDoc e) {
1500                Component editorComponent = table.getEditorComponent();
1501                Point p = e.getPoint();
1502                Point p2 = SwingUtilities.convertPoint(table, p, editorComponent);
1503                dispatchComponent = SwingUtilities.getDeepestComponentAt(editorComponent, p2.x, p2.y);
1504            }
1505
1506            private boolean repostEvent(MouseEvent JavaDoc e) {
1507                if (dispatchComponent == null) {
1508                    return false;
1509                }
1510
1511                MouseEvent JavaDoc e2 = SwingUtilities.convertMouseEvent(table, e, dispatchComponent);
1512                dispatchComponent.dispatchEvent(e2);
1513
1514                return true;
1515            }
1516
1517            private void setValueIsAdjusting(boolean flag) {
1518                table.getSelectionModel().setValueIsAdjusting(flag);
1519                table.getColumnModel().getSelectionModel().setValueIsAdjusting(flag);
1520            }
1521
1522            private boolean shouldIgnore(MouseEvent JavaDoc e) {
1523                return !table.isEnabled() ||
1524                ((e.getButton() == MouseEvent.BUTTON3) && (e.getClickCount() == 1) && !e.isPopupTrigger());
1525            }
1526
1527            private boolean isTreeColumn(int column) {
1528                return TreeTable.this.getColumnClass(column) == TreeTableModelAdapter.class;
1529            }
1530
1531            /** Forwards mouse events to a renderer (tree).
1532             */

1533            private void processMouseEvent(MouseEvent JavaDoc e) {
1534                if (shouldIgnore(e)) {
1535                    return;
1536                }
1537
1538                Point p = e.getPoint();
1539                int row = table.rowAtPoint(p);
1540                int column = table.columnAtPoint(p);
1541
1542                // The autoscroller can generate drag events outside the Table's range.
1543
if ((column == -1) || (row == -1)) {
1544                    return;
1545                }
1546
1547                // for automatic jemmy testing purposes
1548
if ((getEditingColumn() == column) && (getEditingRow() == row)) {
1549                    return;
1550                }
1551
1552                boolean changeSelection = true;
1553
1554                if (isTreeColumn(column)) {
1555                    TreePath path = tree.getPathForRow(TreeTable.this.rowAtPoint(e.getPoint()));
1556                    Rectangle r = tree.getPathBounds(path);
1557 
1558                    if ((e.getX() >= (r.x - positionX)) && (e.getX() <= (r.x - positionX + r.width))
1559                        || isLocationInExpandControl( path, p ) ) {
1560                        changeSelection = false;
1561                    }
1562                }
1563
1564                if (table.getSelectionModel().isSelectedIndex(row) && e.isPopupTrigger()) {
1565                    return;
1566                }
1567
1568                if (table.editCellAt(row, column, e)) {
1569                    setDispatchComponent(e);
1570                    repostEvent(e);
1571                }
1572
1573                if (e.getID() == MouseEvent.MOUSE_PRESSED) {
1574                    table.requestFocus();
1575                }
1576
1577                CellEditor JavaDoc editor = table.getCellEditor();
1578
1579                if (changeSelection && ((editor == null) || editor.shouldSelectCell(e))) {
1580                    setValueIsAdjusting(true);
1581                    table.changeSelection(row, column, e.isControlDown(), e.isShiftDown());
1582                }
1583            }
1584            
1585            private boolean isLocationInExpandControl( TreePath path, Point location ) {
1586                if( tree.getModel().isLeaf( path.getLastPathComponent() ) )
1587                    return false;
1588                
1589                Rectangle r = tree.getPathBounds(path);
1590                int boxWidth = 8;
1591                Insets i = tree.getInsets();
1592                int indent = 0;
1593                
1594                if( tree.getUI() instanceof BasicTreeUI JavaDoc ) {
1595                    BasicTreeUI JavaDoc ui = (BasicTreeUI JavaDoc)tree.getUI();
1596                    if( null != ui.getExpandedIcon() )
1597                        boxWidth = ui.getExpandedIcon().getIconWidth();
1598                    
1599                    indent = ui.getLeftChildIndent();
1600                }
1601                int boxX;
1602                if( tree.getComponentOrientation().isLeftToRight() ) {
1603                    boxX = r.x - positionX - indent - boxWidth;
1604                } else {
1605                    boxX = r.x - positionX + indent + r.width;
1606                }
1607                return location.getX() >= boxX && location.getX() <= (boxX + boxWidth);
1608             }
1609        }
1610    }
1611
1612    /* When selected column is tree column then call tree's action otherwise call table's.
1613     */

1614    class TreeTableAction extends AbstractAction {
1615        Action treeAction;
1616        Action tableAction;
1617
1618        TreeTableAction(Action treeAction, Action tableAction) {
1619            this.treeAction = treeAction;
1620            this.tableAction = tableAction;
1621        }
1622
1623        public void actionPerformed(ActionEvent JavaDoc e) {
1624            if (TreeTable.this.getSelectedColumn() == getTreeColumnIndex()) {
1625                //Issue 40075, on JDK 1.5, BasicTreeUI remarkably expects
1626
//that action events performed on trees actually come from
1627
//trees
1628
e.setSource(getTree());
1629                treeAction.actionPerformed(e);
1630            } else {
1631                tableAction.actionPerformed(e);
1632            }
1633        }
1634    }
1635
1636    /** Focus transfer policy that retains focus after closing an editor.
1637     * Copied wholesale from org.openide.explorer.propertysheet.SheetTable */

1638    private class STPolicy extends ContainerOrderFocusTraversalPolicy {
1639        public Component getComponentAfter(Container focusCycleRoot, Component aComponent) {
1640            if (inRemoveRequest) {
1641                return TreeTable.this;
1642            } else {
1643                Component result = super.getComponentAfter(focusCycleRoot, aComponent);
1644
1645                return result;
1646            }
1647        }
1648
1649        public Component getComponentBefore(Container focusCycleRoot, Component aComponent) {
1650            if (inRemoveRequest) {
1651                return TreeTable.this;
1652            } else {
1653                return super.getComponentBefore(focusCycleRoot, aComponent);
1654            }
1655        }
1656
1657        public Component getFirstComponent(Container focusCycleRoot) {
1658            if (!inRemoveRequest && isEditing()) {
1659                return editorComp;
1660            } else {
1661                return TreeTable.this;
1662            }
1663        }
1664
1665        public Component getDefaultComponent(Container focusCycleRoot) {
1666            if (inRemoveRequest && isEditing() && editorComp.isShowing()) {
1667                return editorComp;
1668            } else {
1669                return TreeTable.this;
1670            }
1671        }
1672
1673        protected boolean accept(Component aComponent) {
1674            //Do not allow focus to go to a child of the editor we're using if
1675
//we are in the process of removing the editor
1676
if (isEditing() && inEditRequest) {
1677                return isKnownComponent(aComponent);
1678            }
1679
1680            return super.accept(aComponent) && aComponent.isShowing();
1681        }
1682    }
1683
1684    /** Enables tab keys to navigate between rows but also exit the table
1685     * to the next focusable component in either direction */

1686    private final class NavigationAction extends AbstractAction {
1687        private boolean direction;
1688
1689        public NavigationAction(boolean direction) {
1690            this.direction = direction;
1691        }
1692
1693        public void actionPerformed(ActionEvent JavaDoc e) {
1694            if (isEditing()) {
1695                removeEditor();
1696            }
1697
1698            int targetRow;
1699            int targetColumn;
1700
1701            if (direction) {
1702                if (getSelectedColumn() == (getColumnCount() - 1)) {
1703                    targetColumn = 0;
1704                    targetRow = getSelectedRow() + 1;
1705                } else {
1706                    targetColumn = getSelectedColumn() + 1;
1707                    targetRow = getSelectedRow();
1708                }
1709            } else {
1710                if (getSelectedColumn() <= 0) {
1711                    targetColumn = getColumnCount() - 1;
1712                    targetRow = getSelectedRow() - 1;
1713                } else {
1714                    targetRow = getSelectedRow();
1715                    targetColumn = getSelectedColumn() - 1;
1716                }
1717            }
1718
1719            //if we're off the end, try to find a sibling component to pass
1720
//focus to
1721
if ((targetRow >= getRowCount()) || (targetRow < 0)) {
1722                //This code is a bit ugly, but works
1723
Container ancestor = getFocusCycleRootAncestor();
1724
1725                //Find the next component in our parent's focus cycle
1726
Component sibling = direction
1727                    ? ancestor.getFocusTraversalPolicy().getComponentAfter(ancestor, TreeTable.this.getParent())
1728                    : ancestor.getFocusTraversalPolicy().getComponentBefore(ancestor, TreeTable.this);
1729
1730                //Often LayoutFocusTranferPolicy will return ourselves if we're
1731
//the last. First try to find a parent focus cycle root that
1732
//will be a little more polite
1733
if (sibling == TreeTable.this) {
1734                    Container grandcestor = ancestor.getFocusCycleRootAncestor();
1735
1736                    if (grandcestor != null) {
1737                        sibling = direction
1738                            ? grandcestor.getFocusTraversalPolicy().getComponentAfter(grandcestor, ancestor)
1739                            : grandcestor.getFocusTraversalPolicy().getComponentBefore(grandcestor, ancestor);
1740                        ancestor = grandcestor;
1741                    }
1742                }
1743
1744                //Okay, we still ended up with ourselves, or there is only one focus
1745
//cycle root ancestor. Try to find the first component according to
1746
//the policy
1747
if (sibling == TreeTable.this) {
1748                    if (ancestor.getFocusTraversalPolicy().getFirstComponent(ancestor) != null) {
1749                        sibling = ancestor.getFocusTraversalPolicy().getFirstComponent(ancestor);
1750                    }
1751                }
1752
1753                //If we're *still* getting ourselves, find the default button and punt
1754
if (sibling == TreeTable.this) {
1755                    JRootPane rp = getRootPane();
1756                    JButton jb = rp.getDefaultButton();
1757
1758                    if (jb != null) {
1759                        sibling = jb;
1760                    }
1761                }
1762
1763                //See if it's us, or something we know about, and if so, just
1764
//loop around to the top or bottom row - there's noplace
1765
//interesting for focus to go to
1766
if (sibling != null) {
1767                    if (sibling == TreeTable.this) {
1768                        //set the selection if there's nothing else to do
1769
changeSelection(
1770                            direction ? 0 : (getRowCount() - 1), direction ? 0 : (getColumnCount() - 1), false, false
1771                        );
1772                    } else {
1773                        //Request focus on the sibling
1774
sibling.requestFocus();
1775                    }
1776
1777                    return;
1778                }
1779            }
1780
1781            changeSelection(targetRow, targetColumn, false, false);
1782        }
1783    }
1784
1785    /** Used to explicitly invoke editing from the keyboard */
1786    private class EditAction extends AbstractAction {
1787        public void actionPerformed(ActionEvent JavaDoc e) {
1788            int row = getSelectedRow();
1789            int col = getSelectedColumn();
1790
1791            if (col == 0) {
1792                col = 1;
1793            }
1794
1795            editCellAt(row, col, null);
1796        }
1797
1798        public boolean isEnabled() {
1799            return (getSelectedRow() != -1) && (getSelectedColumn() != -1) && !isEditing();
1800        }
1801    }
1802
1803    /** Either cancels an edit, or closes the enclosing dialog if present */
1804    private class CancelEditAction extends AbstractAction {
1805        public void actionPerformed(ActionEvent JavaDoc e) {
1806            if (isEditing() || (editorComp != null)) {
1807                removeEditor();
1808
1809                return;
1810            } else {
1811                Component c = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
1812
1813                InputMap imp = getRootPane().getInputMap(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
1814                ActionMap am = getRootPane().getActionMap();
1815
1816                KeyStroke escape = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, false);
1817                Object JavaDoc key = imp.get(escape);
1818
1819                if (key == null) {
1820                    //Default for NbDialog
1821
key = "Cancel";
1822                }
1823
1824                if (key != null) {
1825                    Action a = am.get(key);
1826
1827                    if (a != null) {
1828                        String JavaDoc commandKey = (String JavaDoc) a.getValue(Action.ACTION_COMMAND_KEY);
1829
1830                        if (commandKey == null) {
1831                            commandKey = key.toString();
1832                        }
1833
1834                        a.actionPerformed(new ActionEvent JavaDoc(this, ActionEvent.ACTION_PERFORMED, commandKey)); //NOI18N
1835
}
1836                }
1837            }
1838        }
1839
1840        public boolean isEnabled() {
1841            return isEditing();
1842        }
1843    }
1844
1845    private class EnterAction extends AbstractAction {
1846        public void actionPerformed(ActionEvent JavaDoc e) {
1847            JRootPane jrp = getRootPane();
1848
1849            if (jrp != null) {
1850                JButton b = getRootPane().getDefaultButton();
1851
1852                if ((b != null) && b.isEnabled()) {
1853                    b.doClick();
1854                }
1855            }
1856        }
1857
1858        public boolean isEnabled() {
1859            return !isEditing() && !inRemoveRequest;
1860        }
1861    }
1862
1863    private class CTRLTabAction extends AbstractAction {
1864        public void actionPerformed(ActionEvent JavaDoc e) {
1865            setFocusCycleRoot(false);
1866
1867            try {
1868                Container con = TreeTable.this.getFocusCycleRootAncestor();
1869
1870                if (con != null) {
1871                    Component target = TreeTable.this;
1872
1873                    if (getParent() instanceof JViewport) {
1874                        target = getParent().getParent();
1875
1876                        if (target == con) {
1877                            target = TreeTable.this;
1878                        }
1879                    }
1880
1881                    EventObject JavaDoc eo = EventQueue.getCurrentEvent();
1882                    boolean backward = false;
1883
1884                    if (eo instanceof KeyEvent JavaDoc) {
1885                        backward = ((((KeyEvent JavaDoc) eo).getModifiers() & KeyEvent.SHIFT_MASK) != 0) &&
1886                            ((((KeyEvent JavaDoc) eo).getModifiersEx() & KeyEvent.SHIFT_DOWN_MASK) != 0);
1887                    }
1888
1889                    Component to = backward ? con.getFocusTraversalPolicy().getComponentAfter(con, TreeTable.this)
1890                                            : con.getFocusTraversalPolicy().getComponentAfter(con, TreeTable.this);
1891
1892                    if (to == TreeTable.this) {
1893                        to = backward ? con.getFocusTraversalPolicy().getFirstComponent(con)
1894                                      : con.getFocusTraversalPolicy().getLastComponent(con);
1895                    }
1896
1897                    to.requestFocus();
1898                }
1899            } finally {
1900                setFocusCycleRoot(true);
1901            }
1902        }
1903    }
1904}
1905
Popular Tags