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:
0042: package org.openide.explorer.propertysheet;
0043:
0044: import java.awt.Color;
0045: import java.awt.Component;
0046: import java.awt.Container;
0047: import java.awt.ContainerOrderFocusTraversalPolicy;
0048: import java.awt.Dimension;
0049: import java.awt.Graphics;
0050: import java.awt.Insets;
0051: import java.awt.KeyboardFocusManager;
0052: import java.awt.Point;
0053: import java.awt.Rectangle;
0054: import java.awt.Toolkit;
0055: import java.awt.datatransfer.DataFlavor;
0056: import java.awt.datatransfer.Transferable;
0057: import java.awt.datatransfer.UnsupportedFlavorException;
0058: import java.awt.event.ActionEvent;
0059: import java.awt.event.FocusEvent;
0060: import java.awt.event.KeyEvent;
0061: import java.awt.event.MouseEvent;
0062: import java.beans.FeatureDescriptor;
0063: import java.beans.PropertyEditor;
0064: import java.io.IOException;
0065: import java.io.InputStream;
0066: import java.io.Reader;
0067: import java.io.StringBufferInputStream;
0068: import java.io.StringReader;
0069: import java.util.EventObject;
0070: import java.util.logging.Level;
0071: import java.util.logging.Logger;
0072: import javax.swing.AbstractAction;
0073: import javax.swing.Action;
0074: import javax.swing.ActionMap;
0075: import javax.swing.BorderFactory;
0076: import javax.swing.DefaultListSelectionModel;
0077: import javax.swing.InputMap;
0078: import javax.swing.JComponent;
0079: import javax.swing.JDialog;
0080: import javax.swing.JFrame;
0081: import javax.swing.JTable;
0082: import javax.swing.KeyStroke;
0083: import javax.swing.ListSelectionModel;
0084: import javax.swing.SwingUtilities;
0085: import javax.swing.TransferHandler;
0086: import javax.swing.UIManager;
0087: import javax.swing.event.ChangeEvent;
0088: import javax.swing.event.TableModelEvent;
0089: import javax.swing.plaf.TableUI;
0090: import javax.swing.table.JTableHeader;
0091: import javax.swing.table.TableCellEditor;
0092: import javax.swing.table.TableCellRenderer;
0093: import javax.swing.table.TableColumnModel;
0094: import javax.swing.table.TableModel;
0095: import org.openide.awt.HtmlRenderer;
0096: import org.openide.nodes.Node;
0097: import org.openide.nodes.Node.Property;
0098: import org.openide.nodes.Node.PropertySet;
0099: import org.openide.util.Exceptions;
0100: import org.openide.util.NbBundle;
0101:
0102: /**
0103: * A JTable subclass that displays node properties. To set the properties,
0104: * call <code>getPropertySetModel().setPropertySets()</code>. This class
0105: * uses instance counts to track shared resources. Do NOT un-final this class.
0106: * <p>
0107: * This class implements only property-specific functionality; the row-selection
0108: * painting logic, etc, are in the superclass BaseTable.
0109: *
0110: * @author Tim Boudreau
0111: */
0112: final class SheetTable extends BaseTable implements
0113: PropertySetModelListener, CustomEditorAction.Invoker {
0114: /** Action key for right-arrow expansion of property sets */
0115: private static final String ACTION_EXPAND = "expandSet"; //NOI18N
0116:
0117: /** Action key for left-arrow closing of property sets */
0118: private static final String ACTION_COLLAPSE = "collapseSet"; //NOI18N
0119:
0120: /** Action key for invoking the custom editor */
0121: private static final String ACTION_CUSTOM_EDITOR = "invokeCustomEditor"; //NOI18N
0122:
0123: /** Action key for action to log the curent property editor class*/
0124: private static final String ACTION_EDCLASS = "edclass"; //NOI18N
0125:
0126: /** A reference count so the finalizer can release the shared editor and
0127: * renderer instances (which hold onto some fairly heavy GUI components
0128: * when no more instances are active */
0129: private static int instanceCount = 0;
0130:
0131: /** Flag to block calls to setModel, etc., after initialization */
0132: private transient boolean initialized = false;
0133:
0134: /** Field to hold last edited feature descriptor if state was stored */
0135: private FeatureDescriptor storedFd = null;
0136:
0137: /** Field to hold last editing state if state was stored */
0138: private boolean wasEditing = false;
0139:
0140: /** Field to hold partial user input if state was stored while editing */
0141: private Object partialValue = null;
0142:
0143: /** Fallback field storing the last selected row, in the case that the
0144: * table changes and selection should be restored */
0145: private int lastSelectedRow = -1;
0146:
0147: /** Static sheetCellRenderer which will be shared by all instances of
0148: * SheetTable */
0149: private SheetCellRenderer renderer = null;
0150:
0151: /** Static sheetCellEditor which will be shared by all instances of
0152: * SheetTable */
0153: private SheetCellEditor cellEditor = null;
0154:
0155: /** Custom editor action used to invoke the custom editor from keyboard
0156: * or button */
0157: private Action customEditorAction = null;
0158:
0159: /** Display name of the current node, for passing to the custom editor
0160: * dialog for setting the title */
0161: /** Action to collapse or expand a set when the user presses the left or right arrow */
0162: private Action expandAction;
0163:
0164: /** Action to collapse or expand a set when the user presses the left or right arrow */
0165: private Action collapseAction;
0166:
0167: /** For debugging, an action that prints the current selection's property editor class
0168: * to the standard out */
0169: private Action edClassAction;
0170:
0171: /** Name used for the custom editor dialog, set by PropertySheet */
0172: private String beanName;
0173:
0174: /** Flag set when a custom editor is opening until it closes. This is
0175: * used to shut off tooltips to avoid a Windows bug that when a tooltip
0176: * appears, the window containing it will be fronted, moving the modal
0177: * custom editor behind it. */
0178: private boolean customEditorIsOpen = false;
0179: private ReusablePropertyEnv reusableEnv = new ReusablePropertyEnv();
0180: private ReusablePropertyModel reusableModel = new ReusablePropertyModel(
0181: reusableEnv);
0182: boolean lastIncludeMargin = false;
0183: private HtmlRenderer.Renderer htmlrenderer = null;
0184:
0185: //***************Implementation of issue 9691 - restore editing state after failed edit (dlg shown) *********
0186:
0187: /** field to keep a count of focus events - there will be two following a
0188: * failed edit. EditingStopped will set this value to 2. FocusGained will
0189: * decrement it. Doing almost anything else that touches the property sheet
0190: * will reset it to -1. If it is 0 when a focusGained event occurs, editing
0191: * will be restarted. This is less than ideal, but the code that shows the
0192: * dialog will not start blocking the AWT queue until after focus has
0193: * returned and the editor has been removed. We get one focusGained event as a
0194: * result of the inplace editor being removed; the second is the user closing
0195: * the dialog. */
0196: int countDown = -1;
0197: boolean lastFailed = false;
0198:
0199: /** Creates a new instance of SheetTable */
0200: public SheetTable() {
0201: super (new SheetTableModel(), new SheetColumnModel(),
0202: new DefaultListSelectionModel());
0203: setPropertySetModel(new PropertySetModelImpl());
0204:
0205: //Set a default row height
0206: setRowHeight(16);
0207:
0208: //Show grid lines if no alternating color defined
0209: setShowGrid(PropUtils.noAltBg());
0210: setShowVerticalLines(PropUtils.noAltBg());
0211: setShowHorizontalLines(PropUtils.noAltBg());
0212: setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN);
0213:
0214: if (!PropUtils.noAltBg()) {
0215: setIntercellSpacing(new Dimension(0, 0));
0216: }
0217:
0218: setGridColor(PropUtils.getSetRendererColor());
0219:
0220: Color c = UIManager.getColor("PropSheet.selectionBackground"); //NOI18N
0221:
0222: if (c != null) {
0223: setSelectionBackground(c);
0224: }
0225:
0226: c = UIManager.getColor("PropSheet.selectionForeground"); //NOI18N
0227:
0228: if (c != null) {
0229: setSelectionForeground(c);
0230: }
0231:
0232: getAccessibleContext().setAccessibleName(
0233: NbBundle.getMessage(SheetTable.class,
0234: "ACSN_SHEET_TABLE")); //NOI18N
0235:
0236: getAccessibleContext().setAccessibleDescription(
0237: NbBundle.getMessage(SheetTable.class,
0238: "ACSD_SHEET_TABLE")); //NOI18N
0239:
0240: setTransferHandler(new SheetTableTransferHandler());
0241:
0242: Color col = UIManager.getColor("netbeans.ps.background"); //NOI18N
0243:
0244: if (col != null) {
0245: setBackground(col);
0246: }
0247:
0248: setFocusTraversalPolicy(new STPolicy());
0249: instanceCount++;
0250: }
0251:
0252: //************Shared infrastructure*****************************
0253: protected void finalize() {
0254: instanceCount--;
0255:
0256: if (instanceCount == 0) {
0257: renderer = null;
0258: cellEditor = null;
0259: cleanup();
0260: }
0261: }
0262:
0263: /** Fetch the static render instance shared among tables */
0264: SheetCellRenderer getRenderer() {
0265: if (renderer == null) {
0266: renderer = new SheetCellRenderer(true, reusableEnv,
0267: reusableModel);
0268: }
0269:
0270: return renderer;
0271: }
0272:
0273: /** Fetch the static editor instance shared among tables */
0274: SheetCellEditor getEditor() {
0275: if (cellEditor == null) {
0276: cellEditor = new SheetCellEditor(getReusablePropertyEnv());
0277: }
0278:
0279: return cellEditor;
0280: }
0281:
0282: /****************Bean getters/setters*****************************************
0283:
0284: /** Implement's Rochelle's suggestion of including the display name
0285: * of the edited bean in the custom editor dlg title. SheetTable doesn't
0286: * know what node it's displaying, so property sheet code sets this
0287: * when it changes */
0288: void setBeanName(String name) {
0289: this .beanName = name;
0290: }
0291:
0292: /** Fetch the name of the currently displayed JavaBean */
0293: public String getBeanName() {
0294: return beanName;
0295: }
0296:
0297: /** Returns a reference to the static editor shared among all instances
0298: * of SheetTable */
0299: public TableCellEditor getCellEditor(int row, int column) {
0300: return getEditor();
0301: }
0302:
0303: /** Returns a reference to the static renderer shared among all instances
0304: * of SheetTable */
0305: public TableCellRenderer getCellRenderer(int row, int column) {
0306: return getRenderer();
0307: }
0308:
0309: //**********Overrides of model setters to disable changes that would break the impl******
0310:
0311: /** Throws an UnsupportedOperationException when called by user code. Replacing
0312: * the data model of property sheets is unsupported. You can change the model
0313: * that determines what properties are shown - see <code>setPropertySetModel()</code>. */
0314: public void setModel(TableModel model) {
0315: if (initialized) {
0316: throw new UnsupportedOperationException(
0317: "Changing the model of a property sheet table is not supported. If you want to change the set of properties, ordering or other characteristings, see setPropertySetModel()."); //NOI18N
0318: }
0319:
0320: super .setModel(model);
0321: }
0322:
0323: /** Throws an UnsupportedOperationException when called by user code. Replacing
0324: * the column model of property sheets is unsupported.*/
0325: public void setColumnModel(TableColumnModel model) {
0326: if (initialized) {
0327: throw new UnsupportedOperationException(
0328: "Changing the column model of a property sheet table is not supported. If you want to change the set of properties, ordering or other characteristings, see setPropertySetModel()."); //NOI18N
0329: }
0330:
0331: super .setColumnModel(model);
0332: }
0333:
0334: /** Throws an UnsupportedOperationException when called by user code. Replacing
0335: * the selection model of property sheets not supported.*/
0336: public void setSelectionModel(ListSelectionModel model) {
0337: if (initialized) {
0338: throw new UnsupportedOperationException(
0339: "Changing the selection model of a property sheet table is not supported. If you want to change the set of properties, ordering or other characteristings, see setPropertySetModel()."); //NOI18N
0340: }
0341:
0342: super .setSelectionModel(model);
0343: }
0344:
0345: /** Set the model which determines the ordering of properties and expansion
0346: * state of embedded property sets. */
0347: public void setPropertySetModel(PropertySetModel psm) {
0348: PropertySetModel old = getSheetModel().getPropertySetModel();
0349:
0350: if (old == psm) {
0351: return;
0352: }
0353:
0354: if (old != null) {
0355: old.removePropertySetModelListener(this );
0356: }
0357:
0358: getSheetModel().setPropertySetModel(psm);
0359: psm.addPropertySetModelListener(this );
0360: }
0361:
0362: /** Convenience getter for the property set model. Delegates to the SheetModel. */
0363: PropertySetModel getPropertySetModel() {
0364: return getSheetModel().getPropertySetModel();
0365: }
0366:
0367: /** Convenience getter for the model as an instance of SheetTableModel. */
0368: SheetTableModel getSheetModel() {
0369: return (SheetTableModel) this .getModel();
0370: }
0371:
0372: /** Overridden to return null - some look and feels will want to create
0373: * an empty header, and we don't want them to do that */
0374: public JTableHeader getTableHeader() {
0375: return null;
0376: }
0377:
0378: //******************Keyboard/mouse mgmt***********************************
0379: protected void initKeysAndActions() {
0380: super .initKeysAndActions();
0381: unregisterKeyboardAction(KeyStroke.getKeyStroke(
0382: KeyEvent.VK_RIGHT, 0));
0383: unregisterKeyboardAction(KeyStroke.getKeyStroke(
0384: KeyEvent.VK_LEFT, 0));
0385:
0386: expandAction = new ExpandAction();
0387: collapseAction = new CollapseAction();
0388: edClassAction = new EditorClassAction();
0389:
0390: InputMap imp = getInputMap();
0391: InputMap impAncestor = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
0392: ActionMap am = getActionMap();
0393: KeyStroke ks = KeyStroke.getKeyStroke(KeyEvent.VK_C,
0394: KeyEvent.CTRL_MASK);
0395: imp.put(ks, null);
0396:
0397: imp.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0),
0398: ACTION_EXPAND);
0399:
0400: imp.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0),
0401: ACTION_COLLAPSE);
0402:
0403: imp.put(KeyStroke.getKeyStroke(KeyEvent.VK_HOME,
0404: KeyEvent.SHIFT_DOWN_MASK
0405: | Toolkit.getDefaultToolkit()
0406: .getMenuShortcutKeyMask()),
0407: ACTION_EDCLASS);
0408:
0409: imp
0410: .put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0),
0411: ACTION_NEXT);
0412:
0413: imp.put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB,
0414: KeyEvent.SHIFT_DOWN_MASK), ACTION_PREV);
0415:
0416: impAncestor.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE,
0417: KeyEvent.CTRL_DOWN_MASK), ACTION_CUSTOM_EDITOR);
0418:
0419: impAncestor
0420: .remove(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0));
0421: impAncestor.remove(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0));
0422:
0423: am.put(ACTION_EXPAND, expandAction);
0424: am.put(ACTION_COLLAPSE, collapseAction);
0425:
0426: am.put(ACTION_CUSTOM_EDITOR, getCustomEditorAction());
0427: am.put(ACTION_EDCLASS, edClassAction);
0428: }
0429:
0430: Action getCustomEditorAction() {
0431: if (customEditorAction == null) {
0432: customEditorAction = new CustomEditorAction(this );
0433: }
0434:
0435: return customEditorAction;
0436: }
0437:
0438: /** Overridden to cast value to FeatureDescriptor and return true if the
0439: * text matches its display name. The popup search field uses this method
0440: * to check matches. */
0441: protected boolean matchText(Object value, String text) {
0442: if (value instanceof FeatureDescriptor) {
0443: return ((FeatureDescriptor) value).getDisplayName()
0444: .toUpperCase().startsWith(text.toUpperCase());
0445: } else {
0446: return false;
0447: }
0448: }
0449:
0450: //******************Painting logic **********************************
0451:
0452: /** Paint the table. After the super.paint() call, calls paintMargin() to fill
0453: * in the left edge with the appropriate color, and then calls paintExpandableSets()
0454: * to paint the property sets, which are not painted by the default painting
0455: * methods because they need to be painted across two rows. */
0456: public void paintComponent(Graphics g) {
0457: boolean includeMargin = PropUtils
0458: .shouldDrawMargin(getPropertySetModel());
0459:
0460: getRenderer().setIncludeMargin(includeMargin);
0461: super .paintComponent(g);
0462:
0463: if (!PropUtils.noAltBg()) {
0464: paintCenterLine(g);
0465: }
0466:
0467: if (includeMargin) {
0468: paintMargin(g);
0469: }
0470:
0471: paintExpandableSets(g);
0472:
0473: lastIncludeMargin = includeMargin;
0474: }
0475:
0476: /** Workaround for excessive paints by SwingUtilities.paintComponent() */
0477: private void paintComponent(Graphics g, Component c, int x, int y,
0478: int w, int h) {
0479: c.setBounds(x, y, w, h);
0480: g.translate(x, y);
0481: c.paint(g);
0482: g.translate(-x, -y);
0483: c.setBounds(-w, -h, 0, 0);
0484: }
0485:
0486: /** Paints the center line in the property sheet if an alternate
0487: * color has been specified, so the divider is visible */
0488: private void paintCenterLine(Graphics g) {
0489: Color c = PropUtils.getAltBg();
0490: g.setColor(c);
0491:
0492: int xpos = getColumn(SheetColumnModel.NAMES_IDENTIFIER)
0493: .getWidth() - 1;
0494: g.drawLine(xpos, 0, xpos, getHeight());
0495: }
0496:
0497: /** We only use a single listener on the selected node, PropertySheet.SheetPCListener,
0498: * to centralize things. It will call this method if a property change is detected
0499: * so that it can be repainted. */
0500: void repaintProperty(String name) {
0501: if (!isShowing()) {
0502: return;
0503: }
0504:
0505: if (PropUtils.isLoggable(SheetTable.class)) {
0506: PropUtils.log(SheetTable.class, "RepaintProperty: " + name);
0507: }
0508:
0509: PropertySetModel psm = getPropertySetModel();
0510: int min = getFirstVisibleRow();
0511:
0512: if (min == -1) {
0513: return;
0514: }
0515:
0516: int max = min + getVisibleRowCount();
0517:
0518: for (int i = min; i < max; i++) {
0519: FeatureDescriptor fd = psm.getFeatureDescriptor(i);
0520:
0521: if (null != fd && fd.getName().equals(name)) {
0522: Rectangle r = getCellRect(i, 1, true);
0523:
0524: if (PropUtils.isLoggable(SheetTable.class)) {
0525: PropUtils.log(SheetTable.class, "Repainting " + r
0526: + " for property " + name);
0527: }
0528:
0529: repaint(r.x, r.y, r.width, r.height);
0530:
0531: return;
0532: }
0533: }
0534:
0535: if (PropUtils.isLoggable(SheetTable.class)) {
0536: PropUtils.log(SheetTable.class,
0537: "Property is either scrolled offscreen or property name is bogus: "
0538: + name);
0539: }
0540: }
0541:
0542: /** Paint the outside margin where the spinners for expandable
0543: * sets are. This should be derived from the standard control
0544: * color. This method will overpaint the grid lines in this
0545: * area. */
0546: private void paintMargin(Graphics g) {
0547: //Don't paint the margin for sorted modes
0548: //fill the outer column with the set renderer color, per UI spec
0549: g.setColor(PropUtils.getSetRendererColor());
0550:
0551: int w = PropUtils.getMarginWidth();
0552: int h = getHeight();
0553:
0554: if (g.hitClip(0, 0, w, h)) {
0555: g.fillRect(0, 0, w, h);
0556: }
0557: }
0558:
0559: public Component prepareRenderer(TableCellRenderer renderer,
0560: int row, int col) {
0561: Component result = super .prepareRenderer(renderer, row, col);
0562:
0563: if ((row < 0) || (row >= getRowCount())) {
0564: return result;
0565: }
0566:
0567: Object value = getValueAt(row, col);
0568:
0569: if ((result != null) && value instanceof Property && (col == 1)) {
0570: result.setEnabled(((Property) value).canWrite());
0571: }
0572:
0573: return result;
0574: }
0575:
0576: /** Paint the expandable sets. These are painted double width,
0577: * across the entire width of the table. */
0578: private void paintExpandableSets(Graphics g) {
0579: int start = 0;
0580: int end = getRowCount();
0581:
0582: Insets ins = getInsets();
0583:
0584: boolean canBeSelected = isKnownComponent(KeyboardFocusManager
0585: .getCurrentKeyboardFocusManager()
0586: .getPermanentFocusOwner());
0587:
0588: for (int i = 0; i < end; i++) {
0589: int idx = start + i;
0590: Object value = getValueAt(idx, 0);
0591:
0592: if (value instanceof PropertySet) {
0593: Rectangle r = getCellRect(idx, 0, false);
0594: r.x = ins.left;
0595: r.width = getWidth() - (ins.left + ins.right);
0596:
0597: if (g.hitClip(r.x, r.y, r.width, r.height)) {
0598: PropertySet ps = (PropertySet) value;
0599:
0600: String txt = ps.getHtmlDisplayName();
0601: boolean isHtml = txt != null;
0602:
0603: if (!isHtml) {
0604: txt = ps.getDisplayName();
0605: }
0606:
0607: if (htmlrenderer == null) {
0608: htmlrenderer = HtmlRenderer.createRenderer();
0609: }
0610:
0611: JComponent painter = (JComponent) htmlrenderer
0612: .getTableCellRendererComponent(this , txt,
0613: false, false, idx, 0);
0614:
0615: htmlrenderer.setHtml(isHtml);
0616: htmlrenderer.setParentFocused(true);
0617:
0618: htmlrenderer.setIconTextGap(2);
0619:
0620: htmlrenderer.setIcon(getPropertySetModel()
0621: .isExpanded(ps) ? PropUtils
0622: .getExpandedIcon() : PropUtils
0623: .getCollapsedIcon());
0624:
0625: boolean selected = canBeSelected
0626: && (getSelectedRow() == idx);
0627:
0628: if (!selected) {
0629: painter.setBackground(PropUtils
0630: .getSetRendererColor());
0631: painter.setForeground(PropUtils
0632: .getSetForegroundColor());
0633: } else {
0634: painter.setBackground(PropUtils
0635: .getSelectedSetRendererColor());
0636: painter.setForeground(PropUtils
0637: .getSelectedSetForegroundColor());
0638: }
0639:
0640: painter.setOpaque(true);
0641:
0642: paintComponent(g, painter, r.x, r.y, r.width,
0643: r.height);
0644: }
0645: }
0646: }
0647: }
0648:
0649: /** Overridden to check if the edit failed, and if so, set a focus event
0650: * countdown for re-initiating editing */
0651: public void editingStopped(ChangeEvent e) {
0652: super .editingStopped(e);
0653:
0654: //Po Ting's request for Rave - if commit on focus loss is on, all
0655: //edits look like failures and trigger a new call to editCellAt()
0656: if (!PropUtils.psCommitOnFocusLoss
0657: && !getEditor().isLastUpdateSuccessful()) {
0658: //The last update failed, we're two focus events away from really
0659: //having focus again - we'll get one, then the error dialog will
0660: //steal focus. On the second one we've got focus back.
0661: countDown = 2;
0662: }
0663: }
0664:
0665: /** Initiate editing automatically - triggered by the focus event countdown */
0666: private void autoEdit() {
0667: editCellAt(getSelectedRow(), getSelectedColumn(), null);
0668:
0669: if (editorComp != null) {
0670: editorComp.requestFocus();
0671: }
0672:
0673: countDown = -1;
0674: }
0675:
0676: /** Overridden to clear the focus event countdown */
0677: public void changeSelection(int row, int col, boolean a, boolean b) {
0678: countDown = -1;
0679: super .changeSelection(row, col, a, b);
0680: }
0681:
0682: /** Overridden to check the focus event countdown and initiate editing on
0683: * the second focus event following a failed edit (dialog was shown) */
0684: public void processFocusEvent(FocusEvent fe) {
0685: super .processFocusEvent(fe);
0686:
0687: if (fe.getID() == fe.FOCUS_GAINED) {
0688: countDown--;
0689:
0690: if (countDown == 0) {
0691: autoEdit();
0692: }
0693: }
0694:
0695: if ((fe.getID() == fe.FOCUS_GAINED)
0696: || ((fe.getOppositeComponent() != null)
0697: && (fe.getID() == fe.FOCUS_LOST) && !isAncestorOf(fe
0698: .getOppositeComponent()))) {
0699: //Ensure the description goes back to the node description if
0700: //we lose focus
0701: fireChange();
0702: }
0703: }
0704:
0705: protected void focusLostCancel() {
0706: if (PropUtils.psCommitOnFocusLoss && isEditing()) {
0707: getEditor().stopCellEditing();
0708: } else {
0709: super .focusLostCancel();
0710: }
0711: }
0712:
0713: //**********************Miscellaneous**************************
0714:
0715: /** Overridden to catch a mouse pressed event over the custom editor
0716: * button and invoke the custom editor even if we do not have focus;
0717: * otherwise, for example, in the options dialog, clicking from the
0718: * tree to the table over the custom editor button will just set focus
0719: * to the table, but will not initiate the custom editor dialog */
0720: public void processMouseEvent(MouseEvent me) {
0721: if (me.getID() == me.MOUSE_PRESSED
0722: && SwingUtilities.isLeftMouseButton(me)
0723: && onCustomEditorButton(me) && !hasFocus()) {
0724: if (PropUtils.psCommitOnFocusLoss && isEditing()) {
0725: getEditor().stopCellEditing();
0726:
0727: // #54211: it can happen that PropertySheet window is closed
0728: // when previous property editing is finished (e.g. Form
0729: // event properties) If this is the case don't try to edit
0730: // newly selected property.
0731: if (isGoingToBeClosed()) {
0732: return;
0733: }
0734: }
0735:
0736: int row = rowAtPoint(me.getPoint());
0737: int col = columnAtPoint(me.getPoint());
0738:
0739: if ((row != -1) && (col != -1)) {
0740: changeSelection(row, col, false, false);
0741: getCustomEditorAction().actionPerformed(
0742: new ActionEvent(this ,
0743: ActionEvent.ACTION_PERFORMED,
0744: ACTION_CUSTOM_EDITOR));
0745: me.consume();
0746:
0747: return;
0748: }
0749: }
0750:
0751: super .processMouseEvent(me);
0752: }
0753:
0754: /** Overridden to do nothing, the editor will take care of updating
0755: * the value */
0756: public void setValueAt(Object o, int row, int column) {
0757: //do nothing
0758: }
0759:
0760: /** See if a component is one we know about or one the current editor
0761: * knows about. This affects whether we paint as if focused or not, and
0762: * is used to determine what kind of focus changes mean we should stop
0763: * editing, and what kind are ok */
0764: protected boolean isKnownComponent(Component c) {
0765: boolean result = super .isKnownComponent(c);
0766:
0767: if (result) {
0768: return result;
0769: }
0770:
0771: if (c == null) {
0772: return false;
0773: }
0774:
0775: if (c instanceof ButtonPanel) {
0776: return true;
0777: }
0778:
0779: InplaceEditor ie = getEditor().getInplaceEditor();
0780:
0781: if (ie != null) {
0782: JComponent comp = ie.getComponent();
0783:
0784: if (comp == c) {
0785: return true;
0786: }
0787:
0788: if (comp.isAncestorOf(c)) {
0789: return true;
0790: }
0791: }
0792:
0793: if (c.getParent() instanceof ButtonPanel) {
0794: return true;
0795: }
0796:
0797: if ((getParent() != null) && (getParent().isAncestorOf(c))) {
0798: return true;
0799: }
0800:
0801: Container par = getParent();
0802:
0803: if ((par != null) && par.isAncestorOf(c)) {
0804: return true;
0805: }
0806:
0807: if (c instanceof InplaceEditor) {
0808: return true;
0809: }
0810:
0811: InplaceEditor ine = getEditor().getInplaceEditor();
0812:
0813: if (ine != null) {
0814: return ine.isKnownComponent(c);
0815: }
0816:
0817: return false;
0818: }
0819:
0820: /** Returns true if a mouse event occured over the custom editor button.
0821: * This is used to supply button specific tooltips and launch the custom
0822: * editor without needing to instantiate a real button */
0823: private boolean onCustomEditorButton(MouseEvent e) {
0824: //see if we're in the approximate bounds of the custom editor button
0825: Point pt = e.getPoint();
0826: int row = rowAtPoint(pt);
0827: int col = columnAtPoint(pt);
0828: FeatureDescriptor fd = getSheetModel().getPropertySetModel()
0829: .getFeatureDescriptor(row);
0830: if (null == fd) {
0831: //prevent NPE when the activated Node has been destroyed and a new one hasn't been set yet
0832: return false;
0833: }
0834:
0835: //see if the event happened over the custom editor button
0836: boolean success;
0837:
0838: if (PropUtils.noCustomButtons) {
0839: //#41412 - impossible to invoke custom editor on props w/ no inline
0840: //edit mode if the no custom buttons switch is set
0841: success = false;
0842: } else {
0843: success = e.getX() > (getWidth() - PropUtils
0844: .getCustomButtonWidth());
0845: }
0846:
0847: //if it's a mouse button event, then we're not showing a tooltip, we're
0848: //deciding if we should display a custom editor. For read-only props that
0849: //support one, we should return true, since clicking the non-editable cell
0850: //is not terribly useful.
0851: if ((e.getID() == MouseEvent.MOUSE_PRESSED)
0852: || (e.getID() == MouseEvent.MOUSE_RELEASED)
0853: || (e.getID() == MouseEvent.MOUSE_CLICKED)) {
0854: //We will show the custom editor for any click on the text value
0855: //of a property that looks editable but sets canEditAsText to false -
0856: //the click means the user is trying to edit something, so to just
0857: //swallow the gesture is confusing
0858: success |= Boolean.FALSE.equals(fd
0859: .getValue("canEditAsText"));
0860:
0861: if (!success && fd instanceof Property) {
0862: PropertyEditor pe = PropUtils
0863: .getPropertyEditor((Property) fd);
0864:
0865: if ((pe != null) && pe.supportsCustomEditor()) {
0866: //Undocumented but used in Studio - in NB 3.5 and earlier, returning null from getAsText()
0867: //was a way to make a property non-editable
0868: success |= (pe.isPaintable()
0869: && (pe.getAsText() == null) && (pe
0870: .getTags() == null));
0871: }
0872: }
0873: }
0874:
0875: try {
0876: if (success) { //NOI18N
0877:
0878: if (fd instanceof Property && (col == 1)) {
0879: boolean supp = PropUtils.getPropertyEditor(
0880: (Property) fd).supportsCustomEditor();
0881:
0882: return (supp);
0883: }
0884: }
0885: } catch (IllegalStateException ise) {
0886: //See bugtraq 4941073 - if a property accessed via Reflection throws
0887: //an unexpected exception (try customize bean on a vanilla GenericServlet
0888: //to produce this) when the getter is accessed, then we are already
0889: //displaying "Error fetching property value" in the value area of
0890: //the propertysheet. No point in distracting the user with a
0891: //stack trace - it's not our bug.
0892: Logger.getLogger(SheetTable.class.getName()).log(
0893: Level.WARNING, null, ise);
0894: }
0895:
0896: return false;
0897: }
0898:
0899: /** Overridden to supply different tooltips depending on mouse position (name,
0900: * value, custom editor button). Will HTML-ize long tooltips*/
0901: public String getToolTipText(MouseEvent e) {
0902: if (customEditorIsOpen) {
0903: return null;
0904: }
0905:
0906: String result;
0907: Point pt = e.getPoint();
0908: int row = rowAtPoint(pt);
0909: int col = columnAtPoint(pt);
0910:
0911: if ((col == 1) && onCustomEditorButton(e)) {
0912: result = NbBundle.getMessage(SheetTable.class,
0913: "CTL_EDBUTTON_TIP"); // NOI18N
0914: } else {
0915: result = getSheetModel().getDescriptionFor(row, col);
0916:
0917: if ((col == 1) && (result != null)
0918: && (result.length() > 100)) {
0919: //e.g. Jesse's new file list property gives massive
0920: //tooltips; break them up
0921: result = PropUtils.createHtmlTooltip(
0922: getPropertySetModel().getFeatureDescriptor(row)
0923: .getDisplayName(), result);
0924: }
0925: }
0926:
0927: if ((result != null) && "".equals(result.trim())) {
0928: result = null; // prevents 2x2 dot as a tooltip
0929: }
0930:
0931: return result;
0932: }
0933:
0934: /** Convenience method to get the currently selected property. Equivalent to calling
0935: * <code>getSheetModel().getPropertySetModel().getFeatureDescriptor(getSelectedRow())
0936: * </code>. This method will return null if the table does not have focus or editing
0937: * is not in progress. */
0938: public final FeatureDescriptor getSelection() {
0939: return _getSelection();
0940: }
0941:
0942: /** Internal implementation of getSelection() which returns the selected feature
0943: * descriptor whether or not the component has focus. */
0944: public final FeatureDescriptor _getSelection() {
0945: int i = getSelectedRow();
0946: FeatureDescriptor result;
0947:
0948: //Check bounds - a change can be fired after the model has been changed, but
0949: //before the table has received the event and updated itself, in which case
0950: //you get an AIOOBE
0951: if (i < getPropertySetModel().getCount()) {
0952: result = getSheetModel().getPropertySetModel()
0953: .getFeatureDescriptor(getSelectedRow());
0954: } else {
0955: result = null;
0956: }
0957:
0958: return result;
0959: }
0960:
0961: //*********Implementation of editing*************************************
0962:
0963: /**
0964: * Overridden to do a bunch of property related things: cancel editing
0965: * if the name cell was clicked; ignore duplicate requests; ignore edit
0966: * requests if the user is currently dragging the center line; launch
0967: * the custom editor dialog without entering edit mode if a click is
0968: * over the custom editor button; expand/close property sets; directly
0969: * toggle boolean values rather than rapidly instantiate and hide a
0970: * checkbox editor
0971: */
0972: public boolean editCellAt(int row, int column, EventObject e) {
0973: assert SwingUtilities.isEventDispatchThread();
0974: enterEditRequest();
0975:
0976: if ((editingRow == row) && isEditing()) {
0977: if (0 == column) {
0978: //click on name cell should stop editing
0979: getEditor().stopCellEditing();
0980: removeEditor();
0981: }
0982: //discard edit requests if we're already editing that cell
0983: exitEditRequest();
0984:
0985: return false;
0986: }
0987:
0988: //issue 37584, there are some requests for commit on focus loss,
0989: //so we'll try experimental support for this. Not sure it's a great
0990: //idea, but might as well keep an open mind
0991: if (PropUtils.psCommitOnFocusLoss && isEditing()) {
0992: getEditor().stopCellEditing();
0993:
0994: // #53870: it can happen that PropertySheet window is closed when
0995: // previous property editing is finished (e.g. Form event properties)
0996: // If this is the case don't try to edit newly selected property.
0997: if (isGoingToBeClosed()) {
0998: return false;
0999: }
1000: }
1001:
1002: if ((e instanceof MouseEvent) && (onCenterLine((MouseEvent) e))) {
1003: //If it's a drag request, other code will handle it
1004: exitEditRequest();
1005:
1006: return false;
1007: }
1008:
1009: if ((e instanceof MouseEvent)
1010: && (onCustomEditorButton((MouseEvent) e))) {
1011: if (PropUtils.isLoggable(SheetTable.class)) {
1012: PropUtils.log(SheetTable.class,
1013: "Got a mouse click on the "
1014: + "custom editor button"); //NOI18N
1015: }
1016:
1017: if (isEditing() && (editingRow != row)) {
1018: removeEditor();
1019: }
1020:
1021: //If it's a click on the custom editor button, just display the
1022: //dialog, don't open an inplace editor
1023: int prevSel = getSelectedRow();
1024: changeSelection(row, column, false, false);
1025:
1026: if (prevSel != -1) {
1027: paintRow(prevSel);
1028: }
1029:
1030: paintSelectionRow();
1031: getCustomEditorAction().actionPerformed(
1032: new ActionEvent(this , 0, null));
1033: exitEditRequest();
1034:
1035: return false;
1036: }
1037:
1038: //Get the selected item
1039: FeatureDescriptor fd = getPropertySetModel()
1040: .getFeatureDescriptor(row);
1041:
1042: //See if we got an edit trigger for a property set - if so,
1043: //toggle its expanded state
1044: if (fd instanceof PropertySet) {
1045: //It was a legitimate click, so do stop editing and set the
1046: //selection (otherwise selection will change but editor will remain)
1047: if (isEditing()) {
1048: removeEditor();
1049: changeSelection(row, column, false, false);
1050: }
1051:
1052: maybeToggleExpanded(row, e);
1053: exitEditRequest();
1054:
1055: return false;
1056: }
1057:
1058: //Set the flag indicating we're starting to edit - affects paint
1059: //and focus requests
1060:
1061: /* boolean useRadioButtons = PropUtils.forceRadioButtons ||
1062: (fd.getValue ("stringValues") != null);
1063: */
1064: boolean useRadioButtons = (e instanceof MouseEvent && PropUtils.forceRadioButtons)
1065: || ((fd != null) && (fd.getValue("stringValues") != null));
1066:
1067: //Special handling for boolean if checkbox - no need to create an
1068: //editor that will be removed immediately, just toggles the value
1069: //programmatically
1070: if (!useRadioButtons
1071: && (((column == 1) || e instanceof KeyEvent) && checkEditBoolean(row))) {
1072: //if checkEditBoolean returned true, then the value was toggled -
1073: //set the flag off and return
1074: exitEditRequest();
1075:
1076: return false;
1077: }
1078:
1079: boolean result = false;
1080:
1081: try {
1082: //Try to start an actual edit
1083: result = super .editCellAt(row, column, e);
1084: } finally {
1085: exitEditRequest();
1086: }
1087:
1088: return result;
1089: }
1090:
1091: public void removeEditor() {
1092: enterEditorRemoveRequest();
1093:
1094: try {
1095: // synchronized(getTreeLock()) {
1096: super .removeEditor();
1097:
1098: //Make the editor detach its listeners and clear values in the
1099: //inplace editor since we're done with it
1100: getEditor().setInplaceEditor(null);
1101:
1102: // }
1103: //Order of removal can cause the custom editor button to get focus even
1104: //though it's no longer onscreen, when the custom editor is removed
1105: // Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
1106: } finally {
1107: exitEditorRemoveRequest();
1108: }
1109: }
1110:
1111: /**Overridden to do the assorted black magic by which one determines if
1112: * a property is editable */
1113: public boolean isCellEditable(int row, int column) {
1114: if (column == 0) {
1115: return false;
1116: }
1117:
1118: FeatureDescriptor fd = getPropertySetModel()
1119: .getFeatureDescriptor(row);
1120: boolean result;
1121:
1122: if (fd instanceof PropertySet) {
1123: result = false;
1124: } else {
1125: Property p = (Property) fd;
1126: result = p.canWrite();
1127:
1128: if (result) {
1129: Object val = p.getValue("canEditAsText"); //NOI18N
1130:
1131: if (val != null) {
1132: result &= Boolean.TRUE.equals(val);
1133: }
1134: }
1135: }
1136:
1137: return result;
1138: }
1139:
1140: /** Toggle the expanded state of a property set if either the event
1141: * was a double click in the title area, a single click in the spinner
1142: * area, or a keyboard event. */
1143: private void maybeToggleExpanded(int row, EventObject e) {
1144: boolean doExpand = true;
1145:
1146: //If it's a mouse event, we need to check if it's a double click.
1147: if (e instanceof MouseEvent) {
1148: MouseEvent me = (MouseEvent) e;
1149: doExpand = me.getClickCount() > 1;
1150:
1151: //If not a double click, allow single click in the spinner margin
1152: if (!doExpand) {
1153: //marginWidth will definitely be initialized, you can't
1154: //click something that isn't on the screen
1155: doExpand = me.getPoint().x <= PropUtils
1156: .getMarginWidth();
1157: }
1158: }
1159:
1160: if (doExpand) {
1161: toggleExpanded(row);
1162: }
1163: }
1164:
1165: /** Toggle the expanded state of a property set. If editing, the edit is
1166: * cancelled. */
1167: private void toggleExpanded(int index) {
1168: if (isEditing()) {
1169: getEditor().cancelCellEditing();
1170: }
1171:
1172: PropertySetModel psm = getSheetModel().getPropertySetModel();
1173: psm.toggleExpanded(index);
1174: }
1175:
1176: /** In the case that an edit request is made on a boolean checkbox property, an
1177: * edit request should simply toggle its state without instantiating a custom
1178: * editor component. Returns true if the state was toggled, in which case the
1179: * editor instantiation portion of editCellAt() should be aborted */
1180: boolean checkEditBoolean(int row) {
1181: FeatureDescriptor fd = getSheetModel().getPropertySetModel()
1182: .getFeatureDescriptor(row);
1183:
1184: if (fd.getValue("stringValues") != null) {
1185: return false; //NOI18N
1186: }
1187:
1188: Property p = (fd instanceof Property) ? (Property) fd : null;
1189:
1190: if (p != null) {
1191: Class c = p.getValueType();
1192:
1193: //only do this if the property is supplying no special values for
1194: //the tags - if it is, we are using the radio button renderer
1195: if ((c == Boolean.class) || (c == boolean.class)) {
1196: if (!isCellEditable(row, 1)) {
1197: return true;
1198: }
1199:
1200: //Okay, try to toggle it
1201: try {
1202: Boolean b = null;
1203:
1204: //get the current value
1205: try {
1206: b = (Boolean) p.getValue();
1207: } catch (ProxyNode.DifferentValuesException dve) {
1208: //If we're represeting conflicting multi-selected
1209: //properties, we'll make them both true when we toggle
1210: b = Boolean.FALSE;
1211: }
1212:
1213: if (isEditing()) {
1214: removeEditor();
1215: }
1216:
1217: changeSelection(row, 1, false, false);
1218:
1219: //Toggle the value
1220: Boolean newValue = ((b == null) || Boolean.FALSE
1221: .equals(b)) ? Boolean.TRUE : Boolean.FALSE;
1222: p.setValue(newValue);
1223:
1224: //Force an event so we'll repaint
1225: /*
1226: tableChanged(new TableModelEvent (getSheetModel(), row,
1227: row, 1, TableModelEvent.UPDATE));
1228: */
1229: paintRow(row);
1230:
1231: return true;
1232: } catch (Exception ex) {
1233: //Something wrong, log it
1234: Exceptions.printStackTrace(ex);
1235: }
1236: }
1237: }
1238:
1239: return false;
1240: }
1241:
1242: /** Overridden to set the colors apropriately - we always want the editor
1243: * to appear selected */
1244: public Component prepareEditor(TableCellEditor editor, int row,
1245: int col) {
1246: if (editor == null) {
1247: return null;
1248: }
1249:
1250: Component result = super .prepareEditor(editor, row, col);
1251:
1252: if (result == null) {
1253: return null;
1254: }
1255:
1256: //Usually result == ine, but custom impls may not be
1257: InplaceEditor ine = getEditor().getInplaceEditor();
1258:
1259: if (ine.supportsTextEntry()) {
1260: result.setBackground(PropUtils.getTextFieldBackground());
1261: result.setForeground(PropUtils.getTextFieldForeground());
1262: }
1263:
1264: if (result instanceof JComponent) {
1265: //unlikely that it won't be
1266: ((JComponent) result).setBorder(BorderFactory
1267: .createEmptyBorder(0, PropUtils.getTextMargin(), 0,
1268: 0));
1269: }
1270:
1271: return result;
1272: }
1273:
1274: //***********Methods for storing state if a recoverable change is happening****
1275:
1276: /** Overridden to store some data in the event of a recoverable change,
1277: * such as the row currently being edited */
1278: public void tableChanged(TableModelEvent e) {
1279: boolean ed = isEditing();
1280: lastSelectedRow = ed ? getEditingRow() : getSelectionModel()
1281: .getAnchorSelectionIndex();
1282:
1283: if (ed) {
1284: getEditor().stopCellEditing();
1285: }
1286:
1287: super .tableChanged(e);
1288: restoreEditingState();
1289: }
1290:
1291: /** Temporarily store the currently edited feature descriptor and partial
1292: * value from the editor. This info is used to restore the editing state
1293: * after temporary losses of focus and recoverable changes like reordering
1294: * the model, or changes that derive from the underlying node */
1295: void saveEditingState() {
1296: storedFd = _getSelection();
1297:
1298: if (isEditing()) {
1299: InplaceEditor ine = getEditor().getInplaceEditor();
1300:
1301: if (ine != null) {
1302: partialValue = ine.getValue();
1303: }
1304: }
1305: }
1306:
1307: /** Restore the previous editing state, if the previously edited
1308: * FeatureDescriptor is still available for editing */
1309: void restoreEditingState() {
1310: int idx = indexOfLastSelected();
1311: boolean canResumeEditing = idx != -1;
1312:
1313: if (!canResumeEditing) {
1314: idx = lastSelectedRow;
1315: }
1316:
1317: if (idx == -1) {
1318: clearSavedEditingState();
1319:
1320: return;
1321: }
1322:
1323: if (idx < getRowCount()) {
1324: changeSelection(idx, 1, false, false);
1325:
1326: if ((canResumeEditing) && wasEditing) {
1327: editCellAt(idx, 1);
1328:
1329: InplaceEditor ine = getEditor().getInplaceEditor();
1330:
1331: if ((ine != null) && (partialValue != null)) {
1332: ine.setValue(partialValue);
1333: }
1334: }
1335: }
1336:
1337: clearSavedEditingState();
1338: }
1339:
1340: /** Clear saved editing data, so no memory leaks can occur */
1341: private void clearSavedEditingState() {
1342: storedFd = null;
1343: wasEditing = false;
1344: partialValue = null;
1345: }
1346:
1347: /** Find the current index of the last edited FeatureDescriptor, to
1348: * figure out in which cell to restore the editing state */
1349: private int indexOfLastSelected() {
1350: if (storedFd == null) {
1351: return -1;
1352: }
1353:
1354: PropertySetModel mdl = getPropertySetModel();
1355: int idx = mdl.indexOf(storedFd);
1356: storedFd = null;
1357:
1358: return idx;
1359: }
1360:
1361: //*************PropertySetModelListener implementation ******************
1362:
1363: /** If we know a change is going to happen, try to store the current
1364: * state to restore after the change is completed. Reordering of
1365: * properties and addition of properties by the underlying node can
1366: * trigger this. Since the PropertySetModel has a cache of current
1367: * properties, it can call this while its internal state is still
1368: * intact */
1369: public void pendingChange(PropertySetModelEvent e) {
1370: if (e.isReordering()) {
1371: wasEditing = isEditing();
1372: saveEditingState();
1373: } else {
1374: storedFd = null;
1375: wasEditing = false;
1376: partialValue = null;
1377: }
1378: }
1379:
1380: public void boundedChange(PropertySetModelEvent e) {
1381: //Do nothing, we'll get notification from the TableModel
1382: }
1383:
1384: public void wholesaleChange(PropertySetModelEvent e) {
1385: //Do nothing, we'll get notification from the TableModel
1386: }
1387:
1388: //*************CustomEditorAction.Invoker implementation ******************
1389: //Generally, EditablePropertyDisplayer does a lot more with this
1390: //interface than SheetTable needs to
1391:
1392: /** Returns the content pane of our owner, so as to display the wait
1393: * cursor while the dialog is being invoked */
1394: public Component getCursorChangeComponent() {
1395: Container cont = SheetTable.this .getTopLevelAncestor();
1396:
1397: return (cont instanceof JFrame) ? ((JFrame) cont)
1398: .getContentPane()
1399: : ((cont instanceof JDialog) ? ((JDialog) cont)
1400: .getContentPane() : cont);
1401: }
1402:
1403: /** If we have been editing and the user has typed something, fetch this
1404: * value to use in the custom editor */
1405: public Object getPartialValue() {
1406: Object partialValue = null;
1407:
1408: if (isEditing() && (editingRow == getSelectedRow())) {
1409: InplaceEditor ine = getEditor().getInplaceEditor();
1410:
1411: if (ine != null) {
1412: partialValue = ine.getValue();
1413:
1414: //reset the inplace editor so the value is not taken when the editor
1415: //is closed
1416: ine.reset();
1417: getEditor().cancelCellEditing();
1418: }
1419: } else {
1420: partialValue = null;
1421:
1422: if (isEditing()) {
1423: removeEditor();
1424: }
1425: }
1426:
1427: return partialValue;
1428: }
1429:
1430: /** Restarts inline edit mode if the the preceding custom edit failed */
1431: public void editorClosed() {
1432: if (lastFailed) {
1433: editCellAt(getSelectedRow(), 1, null);
1434: }
1435:
1436: repaint();
1437: customEditorIsOpen = false;
1438: }
1439:
1440: public void editorOpened() {
1441: //Make sure it's painted as non-focused
1442: paintSelectionRow();
1443: customEditorIsOpen = true;
1444: }
1445:
1446: public void editorOpening() {
1447: lastFailed = false;
1448: customEditorIsOpen = true;
1449: }
1450:
1451: public void valueChanged(java.beans.PropertyEditor editor) {
1452: lastFailed = false;
1453: }
1454:
1455: public boolean allowInvoke() {
1456: return true;
1457: }
1458:
1459: public void failed() {
1460: lastFailed = true;
1461: }
1462:
1463: public boolean wantAllChanges() {
1464: return false;
1465: }
1466:
1467: public ReusablePropertyEnv getReusablePropertyEnv() {
1468: return reusableEnv;
1469: }
1470:
1471: public ReusablePropertyModel getReusablePropertyModel() {
1472: return reusableModel;
1473: }
1474:
1475: private boolean isGoingToBeClosed() {
1476: // TODO mkrauskopf: try to find better way for the case that
1477: // PropertySheet is going to be removed (note that isShowing, isVisible,
1478: // ... methods return still true when this method is called)
1479: return getRowCount() <= 0;
1480: }
1481:
1482: @Override
1483: public void setUI(TableUI ui) {
1484: super .setUI(ui);
1485: renderer = null;
1486: cellEditor = null;
1487: }
1488:
1489: //*************Actions bound to the keyboard ******************
1490: private class ExpandAction extends AbstractAction {
1491: public ExpandAction() {
1492: super (ACTION_EXPAND);
1493: }
1494:
1495: public void actionPerformed(ActionEvent ae) {
1496: FeatureDescriptor fd = _getSelection();
1497:
1498: if (fd instanceof PropertySet) {
1499: int row = SheetTable.this .getSelectedRow();
1500: boolean b = getPropertySetModel().isExpanded(fd);
1501:
1502: if (b) {
1503: toggleExpanded(row);
1504: }
1505: }
1506: }
1507:
1508: public boolean isEnabled() {
1509: return _getSelection() instanceof PropertySet;
1510: }
1511: }
1512:
1513: private class CollapseAction extends AbstractAction {
1514: public CollapseAction() {
1515: super (ACTION_COLLAPSE);
1516: }
1517:
1518: public void actionPerformed(ActionEvent ae) {
1519: FeatureDescriptor fd = _getSelection();
1520:
1521: if (fd instanceof PropertySet) {
1522: int row = SheetTable.this .getSelectedRow();
1523: boolean b = getPropertySetModel().isExpanded(fd);
1524:
1525: if (!b) {
1526: toggleExpanded(row);
1527: }
1528: }
1529: }
1530:
1531: public boolean isEnabled() {
1532: boolean result = _getSelection() instanceof PropertySet;
1533:
1534: return result;
1535: }
1536: }
1537:
1538: private class EditorClassAction extends AbstractAction {
1539: public EditorClassAction() {
1540: super (ACTION_EDCLASS);
1541: }
1542:
1543: public void actionPerformed(ActionEvent ae) {
1544: int i = getSelectedRow();
1545:
1546: if (i != -1) {
1547: FeatureDescriptor fd = getPropertySetModel()
1548: .getFeatureDescriptor(i);
1549:
1550: if (fd instanceof Property) {
1551: java.beans.PropertyEditor ped = PropUtils
1552: .getPropertyEditor((Property) fd);
1553: System.err.println(ped.getClass().getName());
1554: } else {
1555: System.err.println("PropertySets - no editor"); //NOI18N
1556: }
1557: } else {
1558: System.err.println("No selection"); //NOI18N
1559: }
1560: }
1561:
1562: public boolean isEnabled() {
1563: return getSelectedRow() != -1;
1564: }
1565: }
1566:
1567: private class STPolicy extends ContainerOrderFocusTraversalPolicy {
1568: public Component getComponentAfter(Container focusCycleRoot,
1569: Component aComponent) {
1570: if (inEditorRemoveRequest()) {
1571: return SheetTable.this ;
1572: } else {
1573: Component result = super .getComponentAfter(
1574: focusCycleRoot, aComponent);
1575:
1576: return result;
1577: }
1578: }
1579:
1580: public Component getComponentBefore(Container focusCycleRoot,
1581: Component aComponent) {
1582: if (inEditorRemoveRequest()) {
1583: return SheetTable.this ;
1584: } else {
1585: return super .getComponentBefore(focusCycleRoot,
1586: aComponent);
1587: }
1588: }
1589:
1590: public Component getFirstComponent(Container focusCycleRoot) {
1591: if (!inEditorRemoveRequest() && isEditing()) {
1592: return editorComp;
1593: } else {
1594: return SheetTable.this ;
1595: }
1596: }
1597:
1598: public Component getDefaultComponent(Container focusCycleRoot) {
1599: if (!inEditorRemoveRequest() && isEditing()
1600: && editorComp.isShowing()) {
1601: return editorComp;
1602: } else {
1603: return SheetTable.this ;
1604: }
1605: }
1606:
1607: protected boolean accept(Component aComponent) {
1608: //Do not allow focus to go to a child of the editor we're using if
1609: //we are in the process of removing the editor
1610: if (isEditing() && inEditorRemoveRequest()) {
1611: InplaceEditor ine = getEditor().getInplaceEditor();
1612:
1613: if (ine != null) {
1614: if ((aComponent == ine.getComponent())
1615: || ine.isKnownComponent(aComponent)) {
1616: return false;
1617: }
1618: }
1619: }
1620:
1621: return super .accept(aComponent) && aComponent.isShowing();
1622: }
1623: }
1624:
1625: private static class SheetTableTransferHandler extends
1626: TransferHandler {
1627: protected Transferable createTransferable(JComponent c) {
1628: if (c instanceof SheetTable) {
1629: SheetTable table = (SheetTable) c;
1630: FeatureDescriptor fd = table.getSelection();
1631:
1632: if (fd == null) {
1633: return null;
1634: }
1635:
1636: String res = fd.getDisplayName();
1637:
1638: if (fd instanceof Node.Property) {
1639: Node.Property prop = (Node.Property) fd;
1640: res += ("\t" + PropUtils.getPropertyEditor(prop)
1641: .getAsText());
1642: }
1643:
1644: return new SheetTableTransferable(res);
1645: }
1646:
1647: return null;
1648: }
1649:
1650: public int getSourceActions(JComponent c) {
1651: return COPY;
1652: }
1653: }
1654:
1655: /**
1656: * Transferable implementation for SheetTable.
1657: */
1658: private static class SheetTableTransferable implements Transferable {
1659: private static DataFlavor[] stringFlavors;
1660: private static DataFlavor[] plainFlavors;
1661:
1662: static {
1663: try {
1664: plainFlavors = new DataFlavor[3];
1665: plainFlavors[0] = new DataFlavor(
1666: "text/plain;class=java.lang.String"); // NOI18N
1667: plainFlavors[1] = new DataFlavor(
1668: "text/plain;class=java.io.Reader"); // NOI18N
1669: // XXX isn't this just DataFlavor.plainTextFlavor?
1670: plainFlavors[2] = new DataFlavor(
1671: "text/plain;charset=unicode;class=java.io.InputStream"); // NOI18N
1672:
1673: stringFlavors = new DataFlavor[2];
1674: stringFlavors[0] = new DataFlavor(
1675: DataFlavor.javaJVMLocalObjectMimeType
1676: + ";class=java.lang.String"); // NOI18N
1677: stringFlavors[1] = DataFlavor.stringFlavor;
1678: } catch (ClassNotFoundException cle) {
1679: assert false : cle;
1680: }
1681: }
1682:
1683: protected String plainData;
1684:
1685: public SheetTableTransferable(String plainData) {
1686: this .plainData = plainData;
1687: }
1688:
1689: public DataFlavor[] getTransferDataFlavors() {
1690: int nPlain = (isPlainSupported()) ? plainFlavors.length : 0;
1691: int nString = (isPlainSupported()) ? stringFlavors.length
1692: : 0;
1693: int nFlavors = nPlain + nString;
1694: DataFlavor[] flavors = new DataFlavor[nFlavors];
1695:
1696: // fill in the array
1697: int nDone = 0;
1698:
1699: if (nPlain > 0) {
1700: System.arraycopy(plainFlavors, 0, flavors, nDone,
1701: nPlain);
1702: nDone += nPlain;
1703: }
1704:
1705: if (nString > 0) {
1706: System.arraycopy(stringFlavors, 0, flavors, nDone,
1707: nString);
1708: nDone += nString;
1709: }
1710:
1711: return flavors;
1712: }
1713:
1714: public boolean isDataFlavorSupported(DataFlavor flavor) {
1715: DataFlavor[] flavors = getTransferDataFlavors();
1716:
1717: for (int i = 0; i < flavors.length; i++) {
1718: if (flavors[i].equals(flavor)) {
1719: return true;
1720: }
1721: }
1722:
1723: return false;
1724: }
1725:
1726: public Object getTransferData(DataFlavor flavor)
1727: throws UnsupportedFlavorException, IOException {
1728: if (isPlainFlavor(flavor)) {
1729: String data = getPlainData();
1730: data = (data == null) ? "" : data;
1731:
1732: if (String.class
1733: .equals(flavor.getRepresentationClass())) {
1734: return data;
1735: } else if (Reader.class.equals(flavor
1736: .getRepresentationClass())) {
1737: return new StringReader(data);
1738: } else if (InputStream.class.equals(flavor
1739: .getRepresentationClass())) {
1740: // XXX should this enforce UTF-8 encoding?
1741: return new StringBufferInputStream(data);
1742: }
1743:
1744: // fall through to unsupported
1745: } else if (isStringFlavor(flavor)) {
1746: String data = getPlainData();
1747: data = (data == null) ? "" : data;
1748:
1749: return data;
1750: }
1751:
1752: throw new UnsupportedFlavorException(flavor);
1753: }
1754:
1755: // --- plain text flavors ----------------------------------------------
1756:
1757: /**
1758: * Returns whether or not the specified data flavor is an plain flavor
1759: * that is supported.
1760: *
1761: * @param flavor the requested flavor for the data
1762: * @return boolean indicating whether or not the data flavor is supported
1763: */
1764: protected boolean isPlainFlavor(DataFlavor flavor) {
1765: DataFlavor[] flavors = plainFlavors;
1766:
1767: for (int i = 0; i < flavors.length; i++) {
1768: if (flavors[i].equals(flavor)) {
1769: return true;
1770: }
1771: }
1772:
1773: return false;
1774: }
1775:
1776: /**
1777: * Should the plain text flavors be offered? If so, the method
1778: * getPlainData should be implemented to provide something reasonable.
1779: */
1780: protected boolean isPlainSupported() {
1781: return plainData != null;
1782: }
1783:
1784: /**
1785: * Fetch the data in a text/plain format.
1786: */
1787: protected String getPlainData() {
1788: return plainData;
1789: }
1790:
1791: // --- string flavors --------------------------------------------------
1792:
1793: /**
1794: * Returns whether or not the specified data flavor is a String flavor
1795: * that is supported.
1796: *
1797: * @param flavor the requested flavor for the data
1798: * @return boolean indicating whether or not the data flavor is supported
1799: */
1800: protected boolean isStringFlavor(DataFlavor flavor) {
1801: DataFlavor[] flavors = stringFlavors;
1802:
1803: for (int i = 0; i < flavors.length; i++) {
1804: if (flavors[i].equals(flavor)) {
1805: return true;
1806: }
1807: }
1808:
1809: return false;
1810: }
1811: }
1812: }
|