0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041: package org.openide.explorer.view;
0042:
0043: import javax.swing.table.TableColumnModel;
0044: import org.openide.awt.MouseUtils;
0045: import org.openide.explorer.propertysheet.PropertyPanel;
0046: import org.openide.nodes.Node;
0047: import org.openide.nodes.Node.Property;
0048: import org.openide.util.NbBundle;
0049: import org.openide.util.Utilities;
0050:
0051: import java.awt.*;
0052: import java.awt.event.ActionEvent;
0053: import java.awt.event.ActionListener;
0054: import java.awt.event.FocusEvent;
0055: import java.awt.event.FocusListener;
0056: import java.awt.event.KeyEvent;
0057: import java.awt.event.MouseEvent;
0058:
0059: import java.beans.PropertyEditor;
0060:
0061: import java.util.ArrayList;
0062: import java.util.Collections;
0063: import java.util.EventObject;
0064: import java.util.List;
0065: import java.util.logging.Level;
0066: import java.util.logging.Logger;
0067:
0068: import javax.swing.*;
0069: import javax.swing.event.*;
0070: import javax.swing.plaf.basic.BasicTableUI;
0071: import javax.swing.plaf.basic.BasicTreeUI;
0072: import javax.swing.table.JTableHeader;
0073: import javax.swing.table.TableCellEditor;
0074: import javax.swing.table.TableCellRenderer;
0075: import javax.swing.table.TableColumn;
0076: import javax.swing.tree.*;
0077: import org.openide.util.Exceptions;
0078:
0079: /**
0080: * TreeTable implementation.
0081: *
0082: * @author Jan Rojcek
0083: */
0084: class TreeTable extends JTable implements Runnable {
0085: /** Action key for up/down focus action */
0086: private static final String ACTION_FOCUS_NEXT = "focusNext"; //NOI18N
0087: private static Color unfocusedSelBg = null;
0088: private static Color unfocusedSelFg = null;
0089:
0090: /** A subclass of JTree. */
0091: private TreeTableCellRenderer tree;
0092: private NodeTableModel tableModel;
0093: private int treeColumnIndex = -1;
0094:
0095: /** Tree editor stuff. */
0096: private int lastRow = -1;
0097: private boolean canEdit;
0098: private boolean ignoreScrolling = false;
0099:
0100: /** Flag to ignore clearSelection() called from super.tableChanged(). */
0101: private boolean ignoreClearSelection = false;
0102:
0103: /** Position of tree renderer, used for horizontal scrolling. */
0104: private int positionX;
0105:
0106: /** If true, horizontal scrolling of tree column is enabled in TreeTableView */
0107: private boolean treeHScrollingEnabled = true;
0108: private final ListToTreeSelectionModelWrapper selectionWrapper;
0109: private boolean edCreated = false;
0110: boolean inSelectAll = false;
0111: private boolean needCalcRowHeight = true;
0112: boolean inEditRequest = false;
0113: boolean inEditorChangeRequest = false;
0114: int editRow = -1;
0115: private boolean inRemoveRequest = false;
0116:
0117: private TableSheetCell tableCell;
0118:
0119: public TreeTable(NodeTreeModel treeModel, NodeTableModel tableModel) {
0120: super ();
0121:
0122: setSurrendersFocusOnKeystroke(true);
0123:
0124: this .tree = new TreeTableCellRenderer(treeModel);
0125: this .tableModel = new TreeTableModelAdapter(tree, tableModel);
0126:
0127: tree.setCellRenderer(new NodeRenderer());
0128:
0129: // Install a tableModel representing the visible rows in the tree.
0130: setModel(this .tableModel);
0131:
0132: // Force the JTable and JTree to share their row selection models.
0133: selectionWrapper = new ListToTreeSelectionModelWrapper();
0134: tree.setSelectionModel(selectionWrapper);
0135: setSelectionModel(selectionWrapper.getListSelectionModel());
0136: getTableHeader().setReorderingAllowed(false);
0137:
0138: // Install the tree editor renderer and editor.
0139: setDefaultRenderer(TreeTableModelAdapter.class, tree);
0140:
0141: // Install property renderer and editor.
0142: tableCell = new TableSheetCell(this .tableModel);
0143: tableCell.setFlat(true);
0144: setDefaultRenderer(Property.class, tableCell);
0145: setDefaultEditor(Property.class, tableCell);
0146: getTableHeader().setDefaultRenderer(tableCell);
0147: getAccessibleContext().setAccessibleName(
0148: NbBundle.getBundle(TreeTable.class).getString(
0149: "ACSN_TreeTable")); // NOI18N
0150: getAccessibleContext().setAccessibleDescription( // NOI18N
0151: NbBundle.getBundle(TreeTable.class).getString(
0152: "ACSD_TreeTable")); // NOI18N
0153:
0154: setFocusCycleRoot(true);
0155: setFocusTraversalPolicy(new STPolicy());
0156: putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
0157: putClientProperty("JTable.autoStartsEdit", Boolean.FALSE);
0158:
0159: initKeysAndActions();
0160: }
0161:
0162: private void initKeysAndActions() {
0163: setFocusTraversalKeys(
0164: KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS,
0165: Collections.<AWTKeyStroke> emptySet());
0166: setFocusTraversalKeys(
0167: KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS,
0168: Collections.<AWTKeyStroke> emptySet());
0169:
0170: //Next two lines do not work using inputmap/actionmap, but do work
0171: //using the older API. We will process ENTER to skip to next row,
0172: //not next cell
0173: unregisterKeyboardAction(KeyStroke.getKeyStroke(
0174: KeyEvent.VK_ENTER, 0));
0175: unregisterKeyboardAction(KeyStroke.getKeyStroke(
0176: KeyEvent.VK_ENTER, Event.SHIFT_MASK));
0177:
0178: InputMap imp = getInputMap(WHEN_FOCUSED);
0179: InputMap imp2 = getInputMap(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
0180: ActionMap am = getActionMap();
0181:
0182: // copied from TreeView which tried to fix #18292
0183: // by doing this
0184: imp2.put(KeyStroke.getKeyStroke("control C"), "none"); // NOI18N
0185: imp2.put(KeyStroke.getKeyStroke("control V"), "none"); // NOI18N
0186: imp2.put(KeyStroke.getKeyStroke("control X"), "none"); // NOI18N
0187: imp2.put(KeyStroke.getKeyStroke("COPY"), "none"); // NOI18N
0188: imp2.put(KeyStroke.getKeyStroke("PASTE"), "none"); // NOI18N
0189: imp2.put(KeyStroke.getKeyStroke("CUT"), "none"); // NOI18N
0190:
0191: imp.put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB,
0192: KeyEvent.CTRL_MASK | KeyEvent.SHIFT_MASK, false),
0193: ACTION_FOCUS_NEXT);
0194: imp.put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB,
0195: KeyEvent.CTRL_MASK, false), ACTION_FOCUS_NEXT);
0196:
0197: Action ctrlTab = new CTRLTabAction();
0198: am.put(ACTION_FOCUS_NEXT, ctrlTab);
0199:
0200: getActionMap().put(
0201: "selectNextColumn", // NOI18N
0202: new TreeTableAction(tree.getActionMap().get(
0203: "selectChild"), // NOI18N
0204: getActionMap().get("selectNextColumn"))); // NOI18N
0205: getActionMap().put(
0206: "selectPreviousColumn", // NOI18N
0207: new TreeTableAction(tree.getActionMap().get(
0208: "selectParent"), // NOI18N
0209: getActionMap().get("selectPreviousColumn"))); // NOI18N
0210:
0211: getAccessibleContext().setAccessibleName(
0212: NbBundle.getBundle(TreeTable.class).getString(
0213: "ACSN_TreeTable")); // NOI18N
0214: getAccessibleContext().setAccessibleDescription( // NOI18N
0215: NbBundle.getBundle(TreeTable.class).getString(
0216: "ACSD_TreeTable")); // NOI18N
0217:
0218: imp.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0, false),
0219: "beginEdit");
0220: getActionMap().put("beginEdit", new EditAction());
0221:
0222: imp2.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, false),
0223: "cancelEdit");
0224: getActionMap().put("cancelEdit", new CancelEditAction());
0225:
0226: imp.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false),
0227: "enter");
0228: getActionMap().put("enter", new EnterAction());
0229:
0230: imp.put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0), "next");
0231:
0232: imp.put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB,
0233: KeyEvent.SHIFT_DOWN_MASK), "previous");
0234:
0235: am.put("next", new NavigationAction(true));
0236: am.put("previous", new NavigationAction(false));
0237: }
0238:
0239: public TableCellEditor getDefaultEditor(Class columnClass) {
0240: if (!edCreated && (columnClass == TreeTableModelAdapter.class)) {
0241: //Creating this editor in the constructor can take > 100ms even
0242: //on a very fast machine, so do it lazily here to improve
0243: //performance of creating a TreeTable
0244: setDefaultEditor(TreeTableModelAdapter.class,
0245: new TreeTableCellEditor());
0246: edCreated = true;
0247: }
0248:
0249: return super .getDefaultEditor(columnClass);
0250: }
0251:
0252: public void selectAll() {
0253: //#48242 - select all over 1000 nodes generates 1000 re-sorts
0254: inSelectAll = true;
0255:
0256: try {
0257: super .selectAll();
0258: } finally {
0259: inSelectAll = false;
0260: selectionWrapper.updateSelectedPathsFromSelectedRows();
0261: }
0262: }
0263:
0264: /*
0265: * Overridden to message super and forward the method to the tree.
0266: */
0267: public void updateUI() {
0268: super .updateUI();
0269:
0270: if (tree != null) {
0271: tree.updateUI();
0272: }
0273:
0274: if (null != tableCell) {
0275: tableCell.updateUI();
0276: }
0277:
0278: // Use the tree's default foreground and background colors in the
0279: // table.
0280: LookAndFeel.installColorsAndFont(this , "Tree.background", // NOI18N
0281: "Tree.foreground", "Tree.font"); // NOI18N
0282:
0283: if (UIManager.getColor("Table.selectionBackground") == null) { // NOI18N
0284: UIManager.put("Table.selectionBackground", new JTable()
0285: .getSelectionBackground()); // NOI18N
0286: }
0287:
0288: if (UIManager.getColor("Table.selectionForeground") == null) { // NOI18N
0289: UIManager.put("Table.selectionForeground", new JTable()
0290: .getSelectionForeground()); // NOI18N
0291: }
0292:
0293: if (UIManager.getColor("Table.gridColor") == null) { // NOI18N
0294: UIManager.put("Table.gridColor", new JTable()
0295: .getGridColor()); // NOI18N
0296: }
0297:
0298: setUI(new TreeTableUI());
0299: needCalcRowHeight = true;
0300: }
0301:
0302: /* Workaround for BasicTableUI anomaly. Make sure the UI never tries to
0303: * paint the editor. The UI currently uses different techniques to
0304: * paint the renderers and editors and overriding setBounds() below
0305: * is not the right thing to do for an editor. Returning -1 for the
0306: * editing row in this case, ensures the editor is never painted.
0307: */
0308: public int getEditingRow() {
0309: return (getColumnClass(editingColumn) == TreeTableModelAdapter.class) ? (-1)
0310: : editingRow;
0311: }
0312:
0313: /** Overridden - JTable's implementation of the method will
0314: * actually attach (and leave behind) a gratuitous border
0315: * on the enclosing scroll pane. */
0316: protected final void configureEnclosingScrollPane() {
0317: Container p = getParent();
0318:
0319: if (p instanceof JViewport) {
0320: Container gp = p.getParent();
0321:
0322: if (gp instanceof JScrollPane) {
0323: JScrollPane scrollPane = (JScrollPane) gp;
0324: JViewport viewport = scrollPane.getViewport();
0325:
0326: if ((viewport == null) || (viewport.getView() != this )) {
0327: return;
0328: }
0329:
0330: JTableHeader jth = getTableHeader();
0331:
0332: if (jth != null) {
0333: jth.setBorder(null);
0334: }
0335:
0336: scrollPane.setColumnHeaderView(jth);
0337: }
0338: }
0339: }
0340:
0341: public void paint(Graphics g) {
0342: if (needCalcRowHeight) {
0343: calcRowHeight(g);
0344:
0345: return;
0346: }
0347:
0348: /*
0349: long time = perf.highResCounter();
0350: */
0351: super .paint(g);
0352:
0353: /*
0354: double dur = perf.highResCounter()-time;
0355:
0356: total += dur;
0357: System.err.println("Paint time: " + total + " ticks = " + (total / perf.highResFrequency()) + " ms. ");
0358: */
0359: }
0360:
0361: // private static final sun.misc.Perf perf = sun.misc.Perf.getPerf();
0362: // private static double total = 0;
0363:
0364: /** Calculate the height of rows based on the current font. This is
0365: * done when the first paint occurs, to ensure that a valid Graphics
0366: * object is available.
0367: * @since 1.25 */
0368: private void calcRowHeight(Graphics g) {
0369: Font f = getFont();
0370: FontMetrics fm = g.getFontMetrics(f);
0371: int rowHeight = fm.getHeight() + fm.getMaxDescent();
0372: needCalcRowHeight = false;
0373: rowHeight = Math.max(20, rowHeight);
0374: tree.setRowHeight(rowHeight);
0375: setRowHeight(rowHeight);
0376: }
0377:
0378: /**
0379: * Returns the tree that is being shared between the model.
0380: */
0381: JTree getTree() {
0382: return tree;
0383: }
0384:
0385: /**
0386: * Returns table column index of the column displaying the tree.
0387: */
0388: int getTreeColumnIndex() {
0389: return treeColumnIndex;
0390: }
0391:
0392: /**
0393: * Sets tree column index and fires property change.
0394: */
0395: void setTreeColumnIndex(int index) {
0396: if (treeColumnIndex == index) {
0397: return;
0398: }
0399:
0400: int old = treeColumnIndex;
0401: treeColumnIndex = index;
0402: firePropertyChange("treeColumnIndex", old, treeColumnIndex);
0403: }
0404:
0405: /* Overriden to do not clear a selection upon model changes.
0406: */
0407: public void clearSelection() {
0408: if (!ignoreClearSelection) {
0409: super .clearSelection();
0410: }
0411: }
0412:
0413: /* Updates tree column name and sets ignoreClearSelection flag.
0414: */
0415: public void tableChanged(TableModelEvent e) {
0416: // update tree column name
0417: int modelColumn = getTreeColumnIndex();
0418:
0419: if ((e.getFirstRow() <= 0) && (modelColumn != -1)
0420: && (getColumnCount() > 0)) {
0421: String columnName = getModel().getColumnName(modelColumn);
0422: TableColumn aColumn = getColumnModel().getColumn(
0423: modelColumn);
0424: aColumn.setHeaderValue(columnName);
0425: }
0426:
0427: ignoreClearSelection = true;
0428:
0429: try {
0430: super .tableChanged(e);
0431: //#61728 - force update of tree's horizontal scrollbar
0432: if (null != getTree()) {
0433: firePropertyChange("positionX", -1, getPositionX());
0434: }
0435: } finally {
0436: ignoreClearSelection = false;
0437: }
0438: }
0439:
0440: public void processKeyEvent(KeyEvent e) {
0441: //Manually hook in the bindings for tab - does not seem to get called
0442: //automatically
0443: if (isEditing()
0444: && ((e.getKeyCode() == e.VK_DOWN) || (e.getKeyCode() == e.VK_UP))) {
0445: return; //XXX
0446: }
0447:
0448: //Bypass standard tab and escape handling, and use our registered
0449: //actions instead
0450: if (!isEditing()
0451: || (((e.getKeyCode() != e.VK_TAB) && (e.getKeyCode() != e.VK_ESCAPE)) || ((e
0452: .getModifiers() & e.CTRL_MASK) != 0))) {
0453: super .processKeyEvent(e);
0454: } else {
0455: processKeyBinding(KeyStroke.getKeyStroke(e.getKeyCode(), e
0456: .getModifiersEx(), e.getID() == e.KEY_RELEASED), e,
0457: JComponent.WHEN_FOCUSED, e.getID() == e.KEY_PRESSED);
0458: }
0459: }
0460:
0461: /* Performs horizontal scrolling of the tree when editing is started.
0462: */
0463: public boolean editCellAt(int row, int column, EventObject e) {
0464: if (e instanceof MouseEvent && (column != 0)) {
0465: MouseEvent me = (MouseEvent) e;
0466:
0467: if (!SwingUtilities.isLeftMouseButton(me)
0468: || (me.getID() != me.MOUSE_PRESSED)) {
0469: return false;
0470: }
0471: }
0472:
0473: if ((row >= getRowCount()) || (row < 0)
0474: || (column > getColumnCount()) || (column < 0)) {
0475: //I don't want to know why this happens, but it does.
0476: return false;
0477: }
0478:
0479: inEditRequest = true;
0480: editRow = row;
0481:
0482: if ((editingRow == row) && (editingColumn == column)
0483: && isEditing()) {
0484: //discard edit requests if we're already editing that cell
0485: inEditRequest = false;
0486:
0487: return false;
0488: }
0489:
0490: if (isEditing()) {
0491: inEditorChangeRequest = true;
0492:
0493: try {
0494: removeEditor();
0495: changeSelection(row, column, false, false);
0496: } finally {
0497: inEditorChangeRequest = false;
0498: }
0499: }
0500:
0501: //Treat a keyEvent request to edit on a non-editable
0502: //column as a request to edit the nearest column that is
0503: //editable
0504: boolean editable = getModel().isCellEditable(row, column);
0505:
0506: //We never want to invoke node name editing from the keyboard,
0507: //it doesn't work anyway - better to look for an editable property
0508: if (editable && ((e == null) || e instanceof KeyEvent)
0509: && (column == 0)) {
0510: editable = false;
0511: column = 1;
0512: }
0513:
0514: boolean columnShifted = false;
0515:
0516: if (!editable && (e instanceof KeyEvent || (e == null))) {
0517: for (int i = column; i < getColumnCount(); i++) {
0518: if (getModel().isCellEditable(row, i)) {
0519: columnShifted = i != column;
0520: column = i;
0521: changeSelection(row, column, false, false);
0522:
0523: break;
0524: }
0525: }
0526: }
0527:
0528: final Rectangle r = getCellRect(row, column, true);
0529:
0530: //#44226 - Provide a way to invoke the custom editor on disabled cells
0531: boolean canTryCustomEditor = (!columnShifted && e instanceof MouseEvent) ? ((((MouseEvent) e)
0532: .getX() > ((r.x + r.width) - 24)) && (((MouseEvent) e)
0533: .getX() < (r.x + r.width)))
0534: : true;
0535:
0536: try {
0537: canEdit = (lastRow == row);
0538:
0539: Object o = getValueAt(row, column);
0540:
0541: if (o instanceof Property) { // && (e == null || e instanceof KeyEvent)) {
0542:
0543: //Toggle booleans without instantiating an editor
0544: Property p = (Property) o;
0545:
0546: if (p.canWrite()
0547: && ((p.getValueType() == Boolean.class) || (p
0548: .getValueType() == Boolean.TYPE))) {
0549: try {
0550: Boolean val = (Boolean) p.getValue();
0551:
0552: if (Boolean.FALSE.equals(val)) {
0553: p.setValue(Boolean.TRUE);
0554: } else {
0555: //This covers null multi-selections too
0556: p.setValue(Boolean.FALSE);
0557: }
0558:
0559: repaint(r.x, r.y, r.width, r.height);
0560:
0561: return false;
0562: } catch (Exception e1) {
0563: Logger.getLogger(TreeTable.class.getName())
0564: .log(Level.WARNING, null, e1);
0565:
0566: return false;
0567: }
0568: } else if (canTryCustomEditor
0569: && !Boolean.TRUE.equals(p
0570: .getValue("suppressCustomEditor"))) { //NOI18N
0571:
0572: PropertyPanel panel = new PropertyPanel(p);
0573: PropertyEditor ed = panel.getPropertyEditor();
0574:
0575: if ((ed != null) && ed.supportsCustomEditor()) {
0576: Action act = panel.getActionMap().get(
0577: "invokeCustomEditor"); //NOI18N
0578:
0579: if (act != null) {
0580: SwingUtilities.invokeLater(new Runnable() {
0581: public void run() {
0582: r.x = 0;
0583: r.width = getWidth();
0584: TreeTable.this .repaint(r);
0585: }
0586: });
0587: act.actionPerformed(null);
0588:
0589: return false;
0590: }
0591: }
0592: }
0593:
0594: if (!p.canWrite()) {
0595: return false;
0596: }
0597: }
0598:
0599: boolean ret = super .editCellAt(row, column, e);
0600:
0601: if (ret) {
0602: //InvokeLater to get out of the way of anything the winsys is going to do
0603: if (column == getTreeColumnIndex()) {
0604: ignoreScrolling = true;
0605: tree.scrollRectToVisible(tree.getRowBounds(row));
0606: ignoreScrolling = false;
0607: } else {
0608: SwingUtilities.invokeLater(this );
0609: }
0610: }
0611:
0612: return ret;
0613: } finally {
0614: inEditRequest = false;
0615: }
0616: }
0617:
0618: /**
0619: *
0620: */
0621: public void run() {
0622: if ((editorComp != null) && editorComp.isShowing()) {
0623: editorComp.requestFocus();
0624: }
0625: }
0626:
0627: /*
0628: */
0629: public void valueChanged(ListSelectionEvent e) {
0630: if (getSelectedRowCount() == 1) {
0631: lastRow = getSelectedRow();
0632: } else {
0633: lastRow = -1;
0634: }
0635:
0636: super .valueChanged(e);
0637: }
0638:
0639: /* Updates tree column index
0640: */
0641: public void columnAdded(TableColumnModelEvent e) {
0642: super .columnAdded(e);
0643: updateTreeColumnIndex();
0644: }
0645:
0646: /* Updates tree column index
0647: */
0648: public void columnRemoved(TableColumnModelEvent e) {
0649: super .columnRemoved(e);
0650: updateTreeColumnIndex();
0651: }
0652:
0653: /* Updates tree column index
0654: */
0655: public void columnMoved(TableColumnModelEvent e) {
0656: super .columnMoved(e);
0657: updateTreeColumnIndex();
0658:
0659: int from = e.getFromIndex();
0660: int to = e.getToIndex();
0661:
0662: if (from != to) {
0663: firePropertyChange("column_moved", from, to); // NOI18N
0664: }
0665: }
0666:
0667: /* Updates tree column index
0668: */
0669: private void updateTreeColumnIndex() {
0670: for (int i = getColumnCount() - 1; i >= 0; i--) {
0671: if (getColumnClass(i) == TreeTableModelAdapter.class) {
0672: setTreeColumnIndex(i);
0673:
0674: return;
0675: }
0676: }
0677:
0678: setTreeColumnIndex(-1);
0679: }
0680:
0681: /** Returns x coordinate of tree renderer.
0682: */
0683: public int getPositionX() {
0684: return positionX;
0685: }
0686:
0687: /** Sets x position.
0688: */
0689: public void setPositionX(int x) {
0690: if ((x == positionX) || !treeHScrollingEnabled) {
0691: return;
0692: }
0693:
0694: int old = positionX;
0695: positionX = x;
0696:
0697: firePropertyChange("positionX", old, x);
0698:
0699: if (isEditing() && (getEditingColumn() == getTreeColumnIndex())) {
0700: CellEditor editor = getCellEditor();
0701:
0702: if (ignoreScrolling
0703: && editor instanceof TreeTableCellEditor) {
0704: ((TreeTableCellEditor) editor).revalidateTextField();
0705: } else {
0706: removeEditor();
0707: }
0708: }
0709:
0710: repaint();
0711: }
0712:
0713: /** Overridden to manually draw the focused rectangle for the tree column */
0714: public void paintComponent(Graphics g) {
0715: super .paintComponent(g);
0716:
0717: if (hasFocus() && (getSelectedColumn() == 0)
0718: && (getSelectedRow() > 0)) {
0719: Color bdr = UIManager.getColor("Tree.selectionBorderColor"); //NOI18N
0720:
0721: if (bdr == null) {
0722: //Button focus color doesn't work on win classic - better to
0723: //get the color from a value we know will work - Tim
0724: if (getForeground().equals(Color.BLACK)) { //typical
0725: bdr = getBackground().darker();
0726: } else {
0727: bdr = getForeground().darker();
0728: }
0729: }
0730:
0731: g.setColor(bdr);
0732:
0733: Rectangle r = getCellRect(getSelectedRow(),
0734: getSelectedColumn(), false);
0735: g.drawRect(r.x + 1, r.y + 1, r.width - 3, r.height - 3);
0736: }
0737: }
0738:
0739: /** Enables horizontal scrolling of tree column */
0740: void setTreeHScrollingEnabled(boolean enabled) {
0741: treeHScrollingEnabled = enabled;
0742: }
0743:
0744: boolean isKnownComponent(Component c) {
0745: if (c == null) {
0746: return false;
0747: }
0748:
0749: if (isAncestorOf(c)) {
0750: return true;
0751: }
0752:
0753: if (c == editorComp) {
0754: return true;
0755: }
0756:
0757: if ((editorComp != null) && (editorComp instanceof Container)
0758: && ((Container) editorComp).isAncestorOf(c)) {
0759: return true;
0760: }
0761:
0762: return false;
0763: }
0764:
0765: public boolean isValidationRoot() {
0766: return true;
0767: }
0768:
0769: public void paintImmediately(int x, int y, int w, int h) {
0770: //Eliminate duplicate repaints in an editor change request
0771: if (inEditorChangeRequest) {
0772: return;
0773: }
0774:
0775: super .paintImmediately(x, y, w, h);
0776: }
0777:
0778: protected void processFocusEvent(FocusEvent fe) {
0779: super .processFocusEvent(fe);
0780:
0781: //Remove the editor here if the new focus owner is not
0782: //known to the table & the focus event is not temporary
0783: if ((fe.getID() == fe.FOCUS_LOST) && !fe.isTemporary()
0784: && !inRemoveRequest && !inEditRequest) {
0785: boolean stopEditing = ((fe.getOppositeComponent() != getParent())
0786: && !isKnownComponent(fe.getOppositeComponent()) && (fe
0787: .getOppositeComponent() != null));
0788:
0789: if (stopEditing) {
0790: removeEditor();
0791: }
0792: }
0793:
0794: //The UI will only repaint the lead selection, but we need to
0795: //paint all selected rows for the color to change when focus
0796: //is lost/gained
0797: if (!inRemoveRequest && !inEditRequest) {
0798: repaintSelection(fe.getID() == fe.FOCUS_GAINED);
0799: }
0800: }
0801:
0802: public void removeEditor() {
0803: inRemoveRequest = true;
0804:
0805: try {
0806: synchronized (getTreeLock()) {
0807: super .removeEditor();
0808: }
0809: } finally {
0810: inRemoveRequest = false;
0811: }
0812: }
0813:
0814: /** Repaint the selected row */
0815: private void repaintSelection(boolean focused) {
0816: int start = getSelectionModel().getMinSelectionIndex();
0817: int end = getSelectionModel().getMaxSelectionIndex();
0818:
0819: if (end != -1) {
0820: if (end != start) {
0821: Rectangle begin = getCellRect(start, 0, false);
0822: Rectangle r = getCellRect(end, 0, false);
0823:
0824: r.y = begin.y;
0825: r.x = 0;
0826: r.width = getWidth();
0827: r.height = (r.y + r.height) - begin.y;
0828: repaint(r.x, r.y, r.width, r.height);
0829: } else {
0830: Rectangle r = getCellRect(start, 0, false);
0831: r.width = getWidth();
0832: r.x = 0;
0833: repaint(r.x, r.y, r.width, r.height);
0834: }
0835: }
0836:
0837: if (isEditing() && (editorComp != null)) {
0838: editorComp.setBackground(focused ? getSelectionBackground()
0839: : getUnfocusedSelectedBackground());
0840: editorComp.setForeground(focused ? getSelectionForeground()
0841: : getUnfocusedSelectedForeground());
0842: }
0843: }
0844:
0845: /** Get the system-wide unfocused selection background color */
0846: static Color getUnfocusedSelectedBackground() {
0847: if (unfocusedSelBg == null) {
0848: //allow theme/ui custom definition
0849: unfocusedSelBg = UIManager
0850: .getColor("nb.explorer.unfocusedSelBg"); //NOI18N
0851:
0852: if (unfocusedSelBg == null) {
0853: //try to get standard shadow color
0854: unfocusedSelBg = UIManager.getColor("controlShadow"); //NOI18N
0855:
0856: if (unfocusedSelBg == null) {
0857: //Okay, the look and feel doesn't suport it, punt
0858: unfocusedSelBg = Color.lightGray;
0859: }
0860:
0861: //Lighten it a bit because disabled text will use controlShadow/
0862: //gray
0863: unfocusedSelBg = unfocusedSelBg.brighter();
0864: }
0865: }
0866:
0867: return unfocusedSelBg;
0868: }
0869:
0870: /** Get the system-wide unfocused selection foreground color */
0871: static Color getUnfocusedSelectedForeground() {
0872: if (unfocusedSelFg == null) {
0873: //allow theme/ui custom definition
0874: unfocusedSelFg = UIManager
0875: .getColor("nb.explorer.unfocusedSelFg"); //NOI18N
0876:
0877: if (unfocusedSelFg == null) {
0878: //try to get standard shadow color
0879: unfocusedSelFg = UIManager.getColor("textText"); //NOI18N
0880:
0881: if (unfocusedSelFg == null) {
0882: //Okay, the look and feel doesn't suport it, punt
0883: unfocusedSelFg = Color.BLACK;
0884: }
0885: }
0886: }
0887:
0888: return unfocusedSelFg;
0889: }
0890:
0891: protected JTableHeader createDefaultTableHeader() {
0892: return new TreeTableHeader(getColumnModel());
0893: }
0894:
0895: /**
0896: * #53748: Default TableHeader provides wrong preferred height when the first column
0897: * uses default renderer and has no value.
0898: */
0899: private static class TreeTableHeader extends JTableHeader {
0900: public TreeTableHeader(TableColumnModel columnModel) {
0901: super (columnModel);
0902: }
0903:
0904: public Dimension getPreferredSize() {
0905:
0906: Dimension retValue = super .getPreferredSize();
0907:
0908: Component comp = getDefaultRenderer()
0909: .getTableCellRendererComponent(getTable(), "X",
0910: false, false, -1, 0);
0911: int rendererHeight = comp.getPreferredSize().height;
0912: retValue.height = Math.max(retValue.height, rendererHeight);
0913: return retValue;
0914: }
0915: }
0916:
0917: /**
0918: * A TreeCellRenderer that displays a JTree.
0919: */
0920: class TreeTableCellRenderer extends JTree implements
0921: TableCellRenderer {
0922: /** Last table/tree row asked to renderer. */
0923: protected int visibleRow;
0924:
0925: /* Last width of the tree.
0926: */
0927: private int oldWidth;
0928: private int transY = 0;
0929:
0930: public TreeTableCellRenderer(TreeModel model) {
0931: super (model);
0932: setToggleClickCount(0);
0933: putClientProperty("JTree.lineStyle", "None"); // NOI18N
0934: }
0935:
0936: public void validate() {
0937: //do nothing
0938: }
0939:
0940: public void repaint(long tm, int x, int y, int width, int height) {
0941: //do nothing
0942: }
0943:
0944: public void addHierarchyListener(
0945: java.awt.event.HierarchyListener hl) {
0946: //do nothing
0947: }
0948:
0949: public void addComponentListener(
0950: java.awt.event.ComponentListener cl) {
0951: //do nothing
0952: }
0953:
0954: /**
0955: * Accessor so NodeRenderer can check if the tree table or its child has
0956: * focus and paint with the appropriate color.
0957: *
0958: * @see NodeRenderer#configureFrom
0959: * @return The tree table
0960: */
0961: TreeTable getTreeTable() {
0962: return TreeTable.this ;
0963: }
0964:
0965: /**
0966: * Sets the row height of the tree, and forwards the row height to
0967: * the table.
0968: */
0969: public void setRowHeight(int rowHeight) {
0970: if (rowHeight > 0) {
0971: super .setRowHeight(rowHeight);
0972: TreeTable.this .setRowHeight(rowHeight);
0973: }
0974: }
0975:
0976: /**
0977: * Overridden to always set the size to the height of the TreeTable
0978: * and the width of column 0. The paint() method will translate the
0979: * coordinates to the correct position. */
0980: public void setBounds(int x, int y, int w, int h) {
0981: transY = -y;
0982: super .setBounds(0, 0, TreeTable.this .getColumnModel()
0983: .getColumn(0).getWidth(), TreeTable.this
0984: .getHeight());
0985: }
0986:
0987: /* Fire width property change so that we can revalidate horizontal scrollbar in TreeTableView.
0988: */
0989: @SuppressWarnings("deprecation")
0990: public void reshape(int x, int y, int w, int h) {
0991: int oldWidth = getWidth();
0992: super .reshape(x, y, w, h);
0993:
0994: if (oldWidth != w) {
0995: firePropertyChange("width", oldWidth, w);
0996: }
0997: }
0998:
0999: public void paint(Graphics g) {
1000: g.translate(-getPositionX(), transY);
1001: super .paint(g);
1002: }
1003:
1004: public Rectangle getVisibleRect() {
1005: Rectangle visibleRect = TreeTable.this .getVisibleRect();
1006: visibleRect.x = positionX;
1007: visibleRect.width = TreeTable.this .getColumnModel()
1008: .getColumn(getTreeColumnIndex()).getWidth();
1009:
1010: return visibleRect;
1011: }
1012:
1013: /* Overriden to use this call for moving tree renderer.
1014: */
1015: public void scrollRectToVisible(Rectangle aRect) {
1016: Rectangle rect = getVisibleRect();
1017: rect.y = aRect.y;
1018: rect.height = aRect.height;
1019:
1020: TreeTable.this .scrollRectToVisible(rect);
1021:
1022: int x = rect.x;
1023:
1024: if (aRect.width > rect.width) {
1025: x = aRect.x;
1026: } else if (aRect.x < rect.x) {
1027: x = aRect.x;
1028: } else if ((aRect.x + aRect.width) > (rect.x + rect.width)) {
1029: x = (aRect.x + aRect.width) - rect.width;
1030: }
1031:
1032: TreeTable.this .setPositionX(x);
1033: }
1034:
1035: public String getToolTipText(MouseEvent event) {
1036: if (event != null) {
1037: Point p = event.getPoint();
1038: p.translate(positionX, visibleRow * getRowHeight());
1039:
1040: int selRow = getRowForLocation(p.x, p.y);
1041:
1042: if (selRow != -1) {
1043: TreePath path = getPathForRow(selRow);
1044: VisualizerNode v = (VisualizerNode) path
1045: .getLastPathComponent();
1046: String tooltip = v.getShortDescription();
1047: String displayName = v.getDisplayName();
1048:
1049: if ((tooltip != null)
1050: && !tooltip.equals(displayName)) {
1051: return tooltip;
1052: }
1053: }
1054: }
1055:
1056: return null;
1057: }
1058:
1059: /**
1060: * TreeCellRenderer method. Overridden to update the visible row.
1061: */
1062: public Component getTableCellRendererComponent(JTable table,
1063: Object value, boolean isSelected, boolean hasFocus,
1064: int row, int column) {
1065: if (isSelected) {
1066: Component focusOwner = KeyboardFocusManager
1067: .getCurrentKeyboardFocusManager()
1068: .getFocusOwner();
1069:
1070: boolean tableHasFocus = (focusOwner == this )
1071: || (focusOwner == TreeTable.this )
1072: || TreeTable.this .isAncestorOf(focusOwner)
1073: || focusOwner instanceof JRootPane; //RootPane == popup menu
1074:
1075: setBackground(tableHasFocus ? table
1076: .getSelectionBackground()
1077: : getUnfocusedSelectedBackground());
1078: setForeground(tableHasFocus ? table
1079: .getSelectionForeground()
1080: : getUnfocusedSelectedForeground());
1081: } else {
1082: setBackground(table.getBackground());
1083: setForeground(table.getForeground());
1084: }
1085:
1086: visibleRow = row;
1087:
1088: return this ;
1089: }
1090:
1091: protected TreeModelListener createTreeModelListener() {
1092: return new JTree.TreeModelHandler() {
1093: public void treeNodesRemoved(TreeModelEvent e) {
1094: if (tree.getSelectionCount() == 0) {
1095: TreePath path = TreeView.findSiblingTreePath(e
1096: .getTreePath(), e.getChildIndices());
1097:
1098: if ((path != null) && (path.getPathCount() > 0)) {
1099: tree.setSelectionPath(path);
1100: }
1101: }
1102: }
1103: };
1104: }
1105:
1106: public void fireTreeCollapsed(TreePath path) {
1107: super .fireTreeCollapsed(path);
1108: firePropertyChange("width", -1, getWidth());
1109: }
1110:
1111: public void fireTreeExpanded(TreePath path) {
1112: super .fireTreeExpanded(path);
1113: firePropertyChange("width", -1, getWidth());
1114: }
1115: }
1116:
1117: /**
1118: * TreeTableCellEditor implementation.
1119: */
1120: class TreeTableCellEditor extends DefaultCellEditor implements
1121: TreeSelectionListener, ActionListener, FocusListener,
1122: CellEditorListener {
1123: /** Used in editing. Indicates x position to place editingComponent. */
1124: protected transient int offset;
1125:
1126: /** Used before starting the editing session. */
1127: protected transient Timer timer;
1128:
1129: public TreeTableCellEditor() {
1130: super (new TreeTableTextField());
1131:
1132: tree.addTreeSelectionListener(this );
1133: addCellEditorListener(this );
1134: super .getComponent().addFocusListener(this );
1135: }
1136:
1137: /**
1138: * Overridden to determine an offset that tree would place the
1139: * editor at. The offset is determined from the
1140: * <code>getRowBounds</code> JTree method, and additionally
1141: * from the icon DefaultTreeCellRenderer will use.
1142: * <p>The offset is then set on the TreeTableTextField component
1143: * created in the constructor, and returned.
1144: */
1145: public Component getTableCellEditorComponent(JTable table,
1146: Object value, boolean isSelected, int r, int c) {
1147: Component component = super .getTableCellEditorComponent(
1148: table, value, isSelected, r, c);
1149:
1150: determineOffset(value, isSelected, r);
1151: ((TreeTableTextField) getComponent()).offset = offset;
1152:
1153: return component;
1154: }
1155:
1156: /**
1157: * This is overridden to forward the event to the tree and start editor timer.
1158: */
1159: public boolean isCellEditable(EventObject e) {
1160: if (lastRow != -1) {
1161: org.openide.nodes.Node n = Visualizer.findNode(tree
1162: .getPathForRow(lastRow).getLastPathComponent());
1163:
1164: if ((n == null) || !n.canRename()) {
1165: //return false;
1166: canEdit = false;
1167: }
1168: }
1169:
1170: if (canEdit && (e != null)
1171: && (e.getSource() instanceof Timer)) {
1172: return true;
1173: }
1174:
1175: if (canEdit && shouldStartEditingTimer(e)) {
1176: startEditingTimer();
1177: } else if (shouldStopEditingTimer(e)) {
1178: timer.stop();
1179: }
1180:
1181: if (e instanceof MouseEvent) {
1182: MouseEvent me = (MouseEvent) e;
1183: int column = getTreeColumnIndex();
1184:
1185: if (MouseUtils.isLeftMouseButton(me)
1186: && (me.getClickCount() == 2)) {
1187: TreePath path = tree.getPathForRow(TreeTable.this
1188: .rowAtPoint(me.getPoint()));
1189: Rectangle r = tree.getPathBounds(path);
1190:
1191: if ((me.getX() < (r.x - positionX))
1192: || (me.getX() > (r.x - positionX + r.width))) {
1193: me.translatePoint(r.x - me.getX(), 0);
1194: }
1195: }
1196:
1197: MouseEvent newME = new MouseEvent(TreeTable.this .tree,
1198: me.getID(), me.getWhen(), me.getModifiers(), me
1199: .getX()
1200: - getCellRect(0, column, true).x
1201: + positionX, me.getY(), me
1202: .getClickCount(), me.isPopupTrigger());
1203: TreeTable.this .tree.dispatchEvent(newME);
1204: }
1205:
1206: return false;
1207: }
1208:
1209: /* Stop timer when selection has been changed.
1210: */
1211: public void valueChanged(TreeSelectionEvent e) {
1212: if (timer != null) {
1213: timer.stop();
1214: }
1215: }
1216:
1217: /* Timer performer.
1218: */
1219: public void actionPerformed(java.awt.event.ActionEvent e) {
1220: if (lastRow != -1) {
1221: editCellAt(lastRow, getTreeColumnIndex(),
1222: new EventObject(timer));
1223: }
1224: }
1225:
1226: /* Start editing timer only on certain conditions.
1227: */
1228: private boolean shouldStartEditingTimer(EventObject event) {
1229: if ((event instanceof MouseEvent)
1230: && SwingUtilities
1231: .isLeftMouseButton((MouseEvent) event)) {
1232: MouseEvent me = (MouseEvent) event;
1233:
1234: return ((me.getID() == me.MOUSE_PRESSED)
1235: && (me.getClickCount() == 1) && inHitRegion(me));
1236: }
1237:
1238: return false;
1239: }
1240:
1241: /* Stop editing timer only on certain conditions.
1242: */
1243: private boolean shouldStopEditingTimer(EventObject event) {
1244: if (timer == null) {
1245: return false;
1246: }
1247:
1248: if (event instanceof MouseEvent) {
1249: MouseEvent me = (MouseEvent) event;
1250:
1251: return (!SwingUtilities.isLeftMouseButton(me) || (me
1252: .getClickCount() > 1));
1253: }
1254:
1255: return false;
1256: }
1257:
1258: /**
1259: * Starts the editing timer.
1260: */
1261: private void startEditingTimer() {
1262: if (timer == null) {
1263: timer = new Timer(1200, this );
1264: timer.setRepeats(false);
1265: }
1266:
1267: timer.start();
1268: }
1269:
1270: /* Does a click go into node's label?
1271: */
1272: private boolean inHitRegion(MouseEvent me) {
1273: determineOffset(me);
1274:
1275: if (me.getX() <= offset) {
1276: return false;
1277: }
1278:
1279: return true;
1280: }
1281:
1282: /* Determines offset of node's label from left edge of the table.
1283: */
1284: private void determineOffset(MouseEvent me) {
1285: int row = TreeTable.this .rowAtPoint(me.getPoint());
1286:
1287: if (row == -1) {
1288: offset = 0;
1289:
1290: return;
1291: }
1292:
1293: determineOffset(tree.getPathForRow(row)
1294: .getLastPathComponent(), TreeTable.this
1295: .isRowSelected(row), row);
1296: }
1297:
1298: /* Determines offset of node's label from left edge of the table.
1299: */
1300: private void determineOffset(Object value, boolean isSelected,
1301: int row) {
1302: JTree t = getTree();
1303: boolean rv = t.isRootVisible();
1304: int offsetRow = row;
1305:
1306: if (!rv && (row > 0)) {
1307: offsetRow--;
1308: }
1309:
1310: Rectangle bounds = t.getRowBounds(offsetRow);
1311: offset = bounds.x;
1312:
1313: TreeCellRenderer tcr = t.getCellRenderer();
1314: Object node = t.getPathForRow(offsetRow)
1315: .getLastPathComponent();
1316: Component comp = tcr.getTreeCellRendererComponent(t, node,
1317: isSelected, t.isExpanded(offsetRow), t.getModel()
1318: .isLeaf(node), offsetRow, false);
1319:
1320: if (comp instanceof JLabel) {
1321: Icon icon = ((JLabel) comp).getIcon();
1322:
1323: if (icon != null) {
1324: offset += (((JLabel) comp).getIconTextGap() + icon
1325: .getIconWidth());
1326: }
1327: }
1328:
1329: offset -= positionX;
1330: }
1331:
1332: /* Revalidates text field upon change of x position of renderer
1333: */
1334: private void revalidateTextField() {
1335: int row = TreeTable.this .editingRow;
1336:
1337: if (row == -1) {
1338: offset = 0;
1339:
1340: return;
1341: }
1342:
1343: determineOffset(tree.getPathForRow(row)
1344: .getLastPathComponent(), TreeTable.this
1345: .isRowSelected(row), row);
1346: ((TreeTableTextField) super .getComponent()).offset = offset;
1347: getComponent().setBounds(
1348: TreeTable.this .getCellRect(row,
1349: getTreeColumnIndex(), false));
1350: }
1351:
1352: // Focus listener
1353:
1354: /* Cancel editing when text field loses focus
1355: */
1356: public void focusLost(java.awt.event.FocusEvent evt) {
1357: /* to allow Escape functionality
1358: if (!stopCellEditing())
1359: cancelCellEditing();
1360: */
1361: }
1362:
1363: /* Select a text in text field when it gets focus.
1364: */
1365: public void focusGained(java.awt.event.FocusEvent evt) {
1366: ((TreeTableTextField) super .getComponent()).selectAll();
1367: }
1368:
1369: // Cell editor listener - copied from TreeViewCellEditor
1370:
1371: /** Implements <code>CellEditorListener</code> interface method. */
1372: public void editingStopped(ChangeEvent e) {
1373: TreePath lastP = tree.getPathForRow(lastRow);
1374:
1375: if (lastP != null) {
1376: Node n = Visualizer.findNode(lastP
1377: .getLastPathComponent());
1378:
1379: if ((n != null) && n.canRename()) {
1380: String newStr = (String) getCellEditorValue();
1381:
1382: try {
1383: // bugfix #21589 don't update name if there is not any change
1384: if (!n.getName().equals(newStr)) {
1385: n.setName(newStr);
1386: }
1387: } catch (IllegalArgumentException exc) {
1388: boolean needToAnnotate = Exceptions
1389: .findLocalizedMessage(exc) == null;
1390:
1391: // annotate new localized message only if there is no localized message yet
1392: if (needToAnnotate) {
1393: String msg = NbBundle
1394: .getMessage(
1395: TreeViewCellEditor.class,
1396: "RenameFailed",
1397: n.getName(), newStr);
1398: Exceptions.attachLocalizedMessage(exc, msg);
1399: }
1400:
1401: Exceptions.printStackTrace(exc);
1402: }
1403: }
1404: }
1405: }
1406:
1407: /** Implements <code>CellEditorListener</code> interface method. */
1408: public void editingCanceled(ChangeEvent e) {
1409: }
1410: }
1411:
1412: /**
1413: * Component used by TreeTableCellEditor. The only thing this does
1414: * is to override the <code>reshape</code> method, and to ALWAYS
1415: * make the x location be <code>offset</code>.
1416: */
1417: static class TreeTableTextField extends JTextField {
1418: public int offset;
1419:
1420: @SuppressWarnings("deprecation")
1421: public void reshape(int x, int y, int w, int h) {
1422: int newX = Math.max(x, offset);
1423: super .reshape(newX, y, w - (newX - x), h);
1424: }
1425:
1426: public void addNotify() {
1427: super .addNotify();
1428:
1429: //requestFocus(); //no longer necessary, STPolicy will do it - Tim
1430: }
1431: }
1432:
1433: /**
1434: * ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel
1435: * to listen for changes in the ListSelectionModel it maintains. Once
1436: * a change in the ListSelectionModel happens, the paths are updated
1437: * in the DefaultTreeSelectionModel.
1438: */
1439: class ListToTreeSelectionModelWrapper extends
1440: DefaultTreeSelectionModel {
1441: /** Set to true when we are updating the ListSelectionModel. */
1442: protected boolean updatingListSelectionModel;
1443:
1444: public ListToTreeSelectionModelWrapper() {
1445: super ();
1446: listSelectionModel = new TreeTableSelectionModel();
1447: getListSelectionModel().addListSelectionListener(
1448: createListSelectionListener());
1449: }
1450:
1451: /**
1452: * Returns the list selection model. ListToTreeSelectionModelWrapper
1453: * listens for changes to this model and updates the selected paths
1454: * accordingly.
1455: */
1456: ListSelectionModel getListSelectionModel() {
1457: return listSelectionModel;
1458: }
1459:
1460: /**
1461: * This is overridden to set <code>updatingListSelectionModel</code>
1462: * and message super. This is the only place DefaultTreeSelectionModel
1463: * alters the ListSelectionModel.
1464: */
1465: public void resetRowSelection() {
1466: if (!updatingListSelectionModel) {
1467: updatingListSelectionModel = true;
1468:
1469: try {
1470: super .resetRowSelection();
1471: } finally {
1472: updatingListSelectionModel = false;
1473: }
1474: }
1475:
1476: // Notice how we don't message super if
1477: // updatingListSelectionModel is true. If
1478: // updatingListSelectionModel is true, it implies the
1479: // ListSelectionModel has already been updated and the
1480: // paths are the only thing that needs to be updated.
1481: }
1482:
1483: /**
1484: * Creates and returns an instance of ListSelectionHandler.
1485: */
1486: protected ListSelectionListener createListSelectionListener() {
1487: return new ListSelectionHandler();
1488: }
1489:
1490: /**
1491: * If <code>updatingListSelectionModel</code> is false, this will
1492: * reset the selected paths from the selected rows in the list
1493: * selection model.
1494: */
1495: protected void updateSelectedPathsFromSelectedRows() {
1496: if (!updatingListSelectionModel) {
1497: updatingListSelectionModel = true;
1498:
1499: try {
1500: int min = listSelectionModel.getMinSelectionIndex();
1501: int max = listSelectionModel.getMaxSelectionIndex();
1502:
1503: if ((min == 0) && (max == getRowCount())) {
1504: //#48242 - optimize the case of select all
1505: int[] rows = new int[max];
1506:
1507: for (int i = 0; i < rows.length; i++) {
1508: rows[i] = i;
1509: }
1510:
1511: tree.setSelectionRows(rows);
1512: } else {
1513: List<Integer> list = new ArrayList<Integer>(11);
1514:
1515: for (int i = min; i <= max; i++) {
1516: if (listSelectionModel.isSelectedIndex(i)) {
1517: list.add(Integer.valueOf(i));
1518: }
1519: }
1520:
1521: if (list.isEmpty()) {
1522: clearSelection();
1523: } else {
1524: int[] rows = (int[]) Utilities
1525: .toPrimitiveArray(list
1526: .toArray(new Integer[list
1527: .size()]));
1528: tree.setSelectionRows(rows);
1529: }
1530: }
1531: } finally {
1532: updatingListSelectionModel = false;
1533: }
1534: }
1535: }
1536:
1537: /**
1538: * Class responsible for calling updateSelectedPathsFromSelectedRows
1539: * when the selection of the list changes.
1540: */
1541: class ListSelectionHandler implements ListSelectionListener {
1542: public void valueChanged(ListSelectionEvent e) {
1543: if (inSelectAll || e.getValueIsAdjusting()) {
1544: return;
1545: }
1546:
1547: updateSelectedPathsFromSelectedRows();
1548: }
1549: }
1550: }
1551:
1552: /**
1553: * #63287 - in JDK1.6 the JTable makes additional changes to the ListSelectionModel
1554: * when clearing row selection. These events must be ignored the same way as
1555: * the overridden method TreeTable.clearSelection()
1556: */
1557: class TreeTableSelectionModel extends DefaultListSelectionModel {
1558: public void setAnchorSelectionIndex(int anchorIndex) {
1559: if (ignoreClearSelection)
1560: return;
1561: super .setAnchorSelectionIndex(anchorIndex);
1562: }
1563:
1564: public void setLeadSelectionIndex(int leadIndex) {
1565: if (ignoreClearSelection)
1566: return;
1567: super .setLeadSelectionIndex(leadIndex);
1568: }
1569: }
1570:
1571: /* This is overriden to handle mouse events especially. E.g. do not change selection
1572: * when it was clicked on tree's expand/collapse toggles.
1573: */
1574: class TreeTableUI extends BasicTableUI {
1575: /**
1576: * Creates the mouse listener for the JTable.
1577: */
1578: protected MouseInputListener createMouseInputListener() {
1579: return new TreeTableMouseInputHandler();
1580: }
1581:
1582: public class TreeTableMouseInputHandler extends
1583: MouseInputHandler {
1584: // Component recieving mouse events during editing. May not be editorComponent.
1585: private Component dispatchComponent;
1586:
1587: // The Table's mouse listener methods.
1588: public void mouseClicked(MouseEvent e) {
1589: processMouseEvent(e);
1590: }
1591:
1592: public void mousePressed(MouseEvent e) {
1593: processMouseEvent(e);
1594: }
1595:
1596: public void mouseReleased(MouseEvent e) {
1597: if (shouldIgnore(e)) {
1598: return;
1599: }
1600:
1601: repostEvent(e);
1602: dispatchComponent = null;
1603: setValueIsAdjusting(false);
1604:
1605: if (!TreeTable.this .isEditing()) {
1606: processMouseEvent(e);
1607: }
1608: }
1609:
1610: public void mouseDragged(MouseEvent e) {
1611: return;
1612: }
1613:
1614: private void setDispatchComponent(MouseEvent e) {
1615: Component editorComponent = table.getEditorComponent();
1616: Point p = e.getPoint();
1617: Point p2 = SwingUtilities.convertPoint(table, p,
1618: editorComponent);
1619: dispatchComponent = SwingUtilities
1620: .getDeepestComponentAt(editorComponent, p2.x,
1621: p2.y);
1622: }
1623:
1624: private boolean repostEvent(MouseEvent e) {
1625: if (dispatchComponent == null) {
1626: return false;
1627: }
1628:
1629: MouseEvent e2 = SwingUtilities.convertMouseEvent(table,
1630: e, dispatchComponent);
1631: dispatchComponent.dispatchEvent(e2);
1632:
1633: return true;
1634: }
1635:
1636: private void setValueIsAdjusting(boolean flag) {
1637: table.getSelectionModel().setValueIsAdjusting(flag);
1638: table.getColumnModel().getSelectionModel()
1639: .setValueIsAdjusting(flag);
1640: }
1641:
1642: private boolean shouldIgnore(MouseEvent e) {
1643: return !table.isEnabled()
1644: || ((e.getButton() == MouseEvent.BUTTON3)
1645: && (e.getClickCount() == 1) && !e
1646: .isPopupTrigger());
1647: }
1648:
1649: private boolean isTreeColumn(int column) {
1650: return TreeTable.this .getColumnClass(column) == TreeTableModelAdapter.class;
1651: }
1652:
1653: /** Forwards mouse events to a renderer (tree).
1654: */
1655: private void processMouseEvent(MouseEvent e) {
1656: if (shouldIgnore(e)) {
1657: return;
1658: }
1659:
1660: Point p = e.getPoint();
1661: int row = table.rowAtPoint(p);
1662: int column = table.columnAtPoint(p);
1663:
1664: // The autoscroller can generate drag events outside the Table's range.
1665: if ((column == -1) || (row == -1)) {
1666: return;
1667: }
1668:
1669: // for automatic jemmy testing purposes
1670: if ((getEditingColumn() == column)
1671: && (getEditingRow() == row)) {
1672: return;
1673: }
1674:
1675: boolean changeSelection = true;
1676:
1677: if (isTreeColumn(column)) {
1678: TreePath path = tree.getPathForRow(TreeTable.this
1679: .rowAtPoint(e.getPoint()));
1680: Rectangle r = tree.getPathBounds(path);
1681:
1682: if ((e.getX() >= (r.x - positionX))
1683: && (e.getX() <= (r.x - positionX + r.width))
1684: || isLocationInExpandControl(path, p)
1685: || e.getID() == MouseEvent.MOUSE_RELEASED
1686: || e.getID() == MouseEvent.MOUSE_CLICKED) {
1687: changeSelection = false;
1688: }
1689: }
1690:
1691: if (table.getSelectionModel().isSelectedIndex(row)
1692: && e.isPopupTrigger()) {
1693: return;
1694: }
1695:
1696: if (table.editCellAt(row, column, e)) {
1697: setDispatchComponent(e);
1698: repostEvent(e);
1699: }
1700:
1701: if (e.getID() == MouseEvent.MOUSE_PRESSED) {
1702: table.requestFocus();
1703: }
1704:
1705: CellEditor editor = table.getCellEditor();
1706:
1707: if (changeSelection
1708: && ((editor == null) || editor
1709: .shouldSelectCell(e))) {
1710: setValueIsAdjusting(true);
1711: table.changeSelection(row, column, Utilities
1712: .isMac() ? e.isMetaDown() : e
1713: .isControlDown(), //use META key on Mac to toggle selection
1714: e.isShiftDown());
1715: }
1716: }
1717:
1718: private boolean isLocationInExpandControl(TreePath path,
1719: Point location) {
1720: if (tree.getModel().isLeaf(path.getLastPathComponent()))
1721: return false;
1722:
1723: Rectangle r = tree.getPathBounds(path);
1724: int boxWidth = 8;
1725: Insets i = tree.getInsets();
1726: int indent = 0;
1727:
1728: if (tree.getUI() instanceof BasicTreeUI) {
1729: BasicTreeUI ui = (BasicTreeUI) tree.getUI();
1730: if (null != ui.getExpandedIcon())
1731: boxWidth = ui.getExpandedIcon().getIconWidth();
1732:
1733: indent = ui.getLeftChildIndent();
1734: }
1735: int boxX;
1736: if (tree.getComponentOrientation().isLeftToRight()) {
1737: boxX = r.x - positionX - indent - boxWidth;
1738: } else {
1739: boxX = r.x - positionX + indent + r.width;
1740: }
1741: return location.getX() >= boxX
1742: && location.getX() <= (boxX + boxWidth);
1743: }
1744: }
1745: }
1746:
1747: /* When selected column is tree column then call tree's action otherwise call table's.
1748: */
1749: class TreeTableAction extends AbstractAction {
1750: Action treeAction;
1751: Action tableAction;
1752:
1753: TreeTableAction(Action treeAction, Action tableAction) {
1754: this .treeAction = treeAction;
1755: this .tableAction = tableAction;
1756: }
1757:
1758: public void actionPerformed(ActionEvent e) {
1759: if (TreeTable.this .getSelectedColumn() == getTreeColumnIndex()) {
1760: //Issue 40075, on JDK 1.5, BasicTreeUI remarkably expects
1761: //that action events performed on trees actually come from
1762: //trees
1763: e.setSource(getTree());
1764: treeAction.actionPerformed(e);
1765: } else {
1766: tableAction.actionPerformed(e);
1767: }
1768: }
1769: }
1770:
1771: /** Focus transfer policy that retains focus after closing an editor.
1772: * Copied wholesale from org.openide.explorer.propertysheet.SheetTable */
1773: private class STPolicy extends ContainerOrderFocusTraversalPolicy {
1774: public Component getComponentAfter(Container focusCycleRoot,
1775: Component aComponent) {
1776: if (inRemoveRequest) {
1777: return TreeTable.this ;
1778: } else {
1779: Component result = super .getComponentAfter(
1780: focusCycleRoot, aComponent);
1781:
1782: return result;
1783: }
1784: }
1785:
1786: public Component getComponentBefore(Container focusCycleRoot,
1787: Component aComponent) {
1788: if (inRemoveRequest) {
1789: return TreeTable.this ;
1790: } else {
1791: return super .getComponentBefore(focusCycleRoot,
1792: aComponent);
1793: }
1794: }
1795:
1796: public Component getFirstComponent(Container focusCycleRoot) {
1797: if (!inRemoveRequest && isEditing()) {
1798: return editorComp;
1799: } else {
1800: return TreeTable.this ;
1801: }
1802: }
1803:
1804: public Component getDefaultComponent(Container focusCycleRoot) {
1805: if (inRemoveRequest && isEditing()
1806: && editorComp.isShowing()) {
1807: return editorComp;
1808: } else {
1809: return TreeTable.this ;
1810: }
1811: }
1812:
1813: protected boolean accept(Component aComponent) {
1814: //Do not allow focus to go to a child of the editor we're using if
1815: //we are in the process of removing the editor
1816: if (isEditing() && inEditRequest) {
1817: return isKnownComponent(aComponent);
1818: }
1819:
1820: return super .accept(aComponent) && aComponent.isShowing();
1821: }
1822: }
1823:
1824: /** Enables tab keys to navigate between rows but also exit the table
1825: * to the next focusable component in either direction */
1826: private final class NavigationAction extends AbstractAction {
1827: private boolean direction;
1828:
1829: public NavigationAction(boolean direction) {
1830: this .direction = direction;
1831: }
1832:
1833: public void actionPerformed(ActionEvent e) {
1834: if (isEditing()) {
1835: removeEditor();
1836: }
1837:
1838: int targetRow;
1839: int targetColumn;
1840:
1841: if (direction) {
1842: if (getSelectedColumn() == (getColumnCount() - 1)) {
1843: targetColumn = 0;
1844: targetRow = getSelectedRow() + 1;
1845: } else {
1846: targetColumn = getSelectedColumn() + 1;
1847: targetRow = getSelectedRow();
1848: }
1849: } else {
1850: if (getSelectedColumn() <= 0) {
1851: targetColumn = getColumnCount() - 1;
1852: targetRow = getSelectedRow() - 1;
1853: } else {
1854: targetRow = getSelectedRow();
1855: targetColumn = getSelectedColumn() - 1;
1856: }
1857: }
1858:
1859: //if we're off the end, try to find a sibling component to pass
1860: //focus to
1861: if ((targetRow >= getRowCount()) || (targetRow < 0)) {
1862: //This code is a bit ugly, but works
1863: Container ancestor = getFocusCycleRootAncestor();
1864:
1865: //Find the next component in our parent's focus cycle
1866: Component sibling = direction ? ancestor
1867: .getFocusTraversalPolicy().getComponentAfter(
1868: ancestor, TreeTable.this .getParent())
1869: : ancestor.getFocusTraversalPolicy()
1870: .getComponentBefore(ancestor,
1871: TreeTable.this );
1872:
1873: //Often LayoutFocusTranferPolicy will return ourselves if we're
1874: //the last. First try to find a parent focus cycle root that
1875: //will be a little more polite
1876: if (sibling == TreeTable.this ) {
1877: Container grandcestor = ancestor
1878: .getFocusCycleRootAncestor();
1879:
1880: if (grandcestor != null) {
1881: sibling = direction ? grandcestor
1882: .getFocusTraversalPolicy()
1883: .getComponentAfter(grandcestor,
1884: ancestor) : grandcestor
1885: .getFocusTraversalPolicy()
1886: .getComponentBefore(grandcestor,
1887: ancestor);
1888: ancestor = grandcestor;
1889: }
1890: }
1891:
1892: //Okay, we still ended up with ourselves, or there is only one focus
1893: //cycle root ancestor. Try to find the first component according to
1894: //the policy
1895: if (sibling == TreeTable.this ) {
1896: if (ancestor.getFocusTraversalPolicy()
1897: .getFirstComponent(ancestor) != null) {
1898: sibling = ancestor.getFocusTraversalPolicy()
1899: .getFirstComponent(ancestor);
1900: }
1901: }
1902:
1903: //If we're *still* getting ourselves, find the default button and punt
1904: if (sibling == TreeTable.this ) {
1905: JRootPane rp = getRootPane();
1906: JButton jb = rp.getDefaultButton();
1907:
1908: if (jb != null) {
1909: sibling = jb;
1910: }
1911: }
1912:
1913: //See if it's us, or something we know about, and if so, just
1914: //loop around to the top or bottom row - there's noplace
1915: //interesting for focus to go to
1916: if (sibling != null) {
1917: if (sibling == TreeTable.this ) {
1918: //set the selection if there's nothing else to do
1919: changeSelection(direction ? 0
1920: : (getRowCount() - 1), direction ? 0
1921: : (getColumnCount() - 1), false, false);
1922: } else {
1923: //Request focus on the sibling
1924: sibling.requestFocus();
1925: }
1926:
1927: return;
1928: }
1929: }
1930:
1931: changeSelection(targetRow, targetColumn, false, false);
1932: }
1933: }
1934:
1935: /** Used to explicitly invoke editing from the keyboard */
1936: private class EditAction extends AbstractAction {
1937: public void actionPerformed(ActionEvent e) {
1938: int row = getSelectedRow();
1939: int col = getSelectedColumn();
1940:
1941: if (col == 0) {
1942: col = 1;
1943: }
1944:
1945: editCellAt(row, col, null);
1946: }
1947:
1948: public boolean isEnabled() {
1949: return (getSelectedRow() != -1)
1950: && (getSelectedColumn() != -1) && !isEditing();
1951: }
1952: }
1953:
1954: /** Either cancels an edit, or closes the enclosing dialog if present */
1955: private class CancelEditAction extends AbstractAction {
1956: public void actionPerformed(ActionEvent e) {
1957: if (isEditing() || (editorComp != null)) {
1958: removeEditor();
1959:
1960: return;
1961: } else {
1962: Component c = KeyboardFocusManager
1963: .getCurrentKeyboardFocusManager()
1964: .getFocusOwner();
1965:
1966: InputMap imp = getRootPane().getInputMap(
1967: WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
1968: ActionMap am = getRootPane().getActionMap();
1969:
1970: KeyStroke escape = KeyStroke.getKeyStroke(
1971: KeyEvent.VK_ESCAPE, 0, false);
1972: Object key = imp.get(escape);
1973:
1974: if (key == null) {
1975: //Default for NbDialog
1976: key = "Cancel";
1977: }
1978:
1979: if (key != null) {
1980: Action a = am.get(key);
1981:
1982: if (a != null) {
1983: String commandKey = (String) a
1984: .getValue(Action.ACTION_COMMAND_KEY);
1985:
1986: if (commandKey == null) {
1987: commandKey = key.toString();
1988: }
1989:
1990: a.actionPerformed(new ActionEvent(this ,
1991: ActionEvent.ACTION_PERFORMED,
1992: commandKey)); //NOI18N
1993: }
1994: }
1995: }
1996: }
1997:
1998: public boolean isEnabled() {
1999: return isEditing();
2000: }
2001: }
2002:
2003: private class EnterAction extends AbstractAction {
2004: public void actionPerformed(ActionEvent e) {
2005: JRootPane jrp = getRootPane();
2006:
2007: if (jrp != null) {
2008: JButton b = getRootPane().getDefaultButton();
2009:
2010: if ((b != null) && b.isEnabled()) {
2011: b.doClick();
2012: }
2013: }
2014: }
2015:
2016: public boolean isEnabled() {
2017: return !isEditing() && !inRemoveRequest;
2018: }
2019: }
2020:
2021: private class CTRLTabAction extends AbstractAction {
2022: public void actionPerformed(ActionEvent e) {
2023: setFocusCycleRoot(false);
2024:
2025: try {
2026: Container con = TreeTable.this
2027: .getFocusCycleRootAncestor();
2028:
2029: if (con != null) {
2030: Component target = TreeTable.this ;
2031:
2032: if (getParent() instanceof JViewport) {
2033: target = getParent().getParent();
2034:
2035: if (target == con) {
2036: target = TreeTable.this ;
2037: }
2038: }
2039:
2040: EventObject eo = EventQueue.getCurrentEvent();
2041: boolean backward = false;
2042:
2043: if (eo instanceof KeyEvent) {
2044: backward = ((((KeyEvent) eo).getModifiers() & KeyEvent.SHIFT_MASK) != 0)
2045: && ((((KeyEvent) eo).getModifiersEx() & KeyEvent.SHIFT_DOWN_MASK) != 0);
2046: }
2047:
2048: Component to = backward ? con
2049: .getFocusTraversalPolicy()
2050: .getComponentAfter(con, TreeTable.this )
2051: : con.getFocusTraversalPolicy()
2052: .getComponentAfter(con,
2053: TreeTable.this );
2054:
2055: if (to == TreeTable.this ) {
2056: to = backward ? con.getFocusTraversalPolicy()
2057: .getFirstComponent(con) : con
2058: .getFocusTraversalPolicy()
2059: .getLastComponent(con);
2060: }
2061:
2062: to.requestFocus();
2063: }
2064: } finally {
2065: setFocusCycleRoot(true);
2066: }
2067: }
2068: }
2069: }
|