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.propertysheet;
0042:
0043: import java.awt.BorderLayout;
0044: import java.awt.Color;
0045: import java.awt.Component;
0046: import java.awt.Dimension;
0047: import java.awt.Font;
0048: import java.awt.KeyboardFocusManager;
0049: import java.awt.Point;
0050: import java.awt.Toolkit;
0051: import java.awt.event.ActionEvent;
0052: import java.awt.event.FocusEvent;
0053: import java.awt.event.FocusListener;
0054: import java.awt.event.KeyEvent;
0055: import java.beans.FeatureDescriptor;
0056: import java.beans.PropertyChangeEvent;
0057: import java.beans.PropertyChangeListener;
0058: import java.beans.PropertyVetoException;
0059: import java.lang.ref.Reference;
0060: import java.lang.ref.WeakReference;
0061: import java.lang.reflect.InvocationTargetException;
0062: import java.lang.reflect.Method;
0063: import java.util.ArrayList;
0064: import java.util.Arrays;
0065: import java.util.HashMap;
0066: import java.util.HashSet;
0067: import java.util.Iterator;
0068: import java.util.List;
0069: import java.util.Map;
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.BorderFactory;
0075: import javax.swing.Icon;
0076: import javax.swing.JCheckBoxMenuItem;
0077: import javax.swing.JComponent;
0078: import javax.swing.JMenuItem;
0079: import javax.swing.JPanel;
0080: import javax.swing.JPopupMenu;
0081: import javax.swing.JRadioButtonMenuItem;
0082: import javax.swing.JSeparator;
0083: import javax.swing.KeyStroke;
0084: import javax.swing.SwingUtilities;
0085: import javax.swing.UIManager;
0086: import javax.swing.event.ChangeEvent;
0087: import javax.swing.event.ChangeListener;
0088: import org.openide.nodes.Node;
0089: import org.openide.nodes.Node.PropertySet;
0090: import org.openide.nodes.NodeAdapter;
0091: import org.openide.util.Exceptions;
0092: import org.openide.util.HelpCtx;
0093: import org.openide.util.Lookup;
0094: import org.openide.util.Mutex;
0095: import org.openide.util.NbBundle;
0096: import org.openide.util.RequestProcessor;
0097:
0098: /**
0099: * Implements a property sheet for a set of nodes. Can be used as a
0100: * standalone component (e.g. without a connection to {@link org.openide.explorer.ExplorerManager}).
0101: * For example to display properties of a JavaBean one could use:
0102: * <pre>
0103: * Object bean = ...;
0104: * JPanel container = ...;
0105: * PropertySheet ps = new PropertySheet();
0106: * ps.setNodes(new Node[] { new {@link org.openide.nodes.BeanNode}(bean) });
0107: * container.add(ps);
0108: * </pre>
0109: *
0110: * <strong>Note that this class should be final, but for backward compatibility,
0111: * cannot be. Subclassing this class is strongly discouraged</strong>
0112: *
0113: * @author Tim Boudreau, Jan Jancura, Jaroslav Tulach
0114: */
0115: public class PropertySheet extends JPanel {
0116: /** generated Serialized Version UID */
0117: static final long serialVersionUID = -7698351033045864945L;
0118:
0119: // public constants ........................................................
0120:
0121: /** Deprecated - no code outside the property sheet should be interested
0122: * in how items are sorted.
0123: *@deprecated Relic of the original property sheet implementation, will never be fired. */
0124: public @Deprecated
0125: static final String PROPERTY_SORTING_MODE = "sortingMode"; // NOI18N
0126:
0127: /** Property giving current value color.
0128: *@deprecated Relic of the original property sheet implementation, will never be fired. */
0129: public @Deprecated
0130: static final String PROPERTY_VALUE_COLOR = "valueColor"; // NOI18N
0131:
0132: /** Property giving current disabled property color.
0133: *@deprecated Relic of the original property sheet implementation, , will never be fired. */
0134: public @Deprecated
0135: static final String PROPERTY_DISABLED_PROPERTY_COLOR = "disabledPropertyColor"; // NOI18N
0136:
0137: /** Property with the current page index.
0138: *@deprecated Relic of the original property sheet implementation, , will never be fired.*/
0139: public @Deprecated
0140: static final String PROPERTY_CURRENT_PAGE = "currentPage"; // NOI18N
0141:
0142: /** Property for plastic mode.
0143: *@deprecated Relic of the original property sheet implementation, , will never be fired. */
0144: public @Deprecated
0145: static final String PROPERTY_PLASTIC = "plastic"; // NOI18N
0146:
0147: /** Property for the painting style.
0148: *@deprecated Relic of the original property sheet implementation, will never be fired. */
0149: public @Deprecated
0150: static final String PROPERTY_PROPERTY_PAINTING_STYLE = "propertyPaintingStyle"; // NOI18N
0151:
0152: /** Property for whether only writable properties should be displayed.
0153: *@deprecated Relic of the original property sheet implementation, will never be fired.*/
0154: public @Deprecated
0155: static final String PROPERTY_DISPLAY_WRITABLE_ONLY = "displayWritableOnly"; // NOI18N
0156:
0157: /** Constant for showing properties as a string always.
0158: *@deprecated Relic of the original property sheet implementation, useless. */
0159: public @Deprecated
0160: static final int ALWAYS_AS_STRING = 1;
0161:
0162: /** Constant for preferably showing properties as string.
0163: *@deprecated Relic of the original property sheet implementation, does useless. */
0164: public @Deprecated
0165: static final int STRING_PREFERRED = 2;
0166:
0167: /** Constant for preferably painting property values.
0168: *@deprecated Relic of the original property sheet implementation, does useless. */
0169: public @Deprecated
0170: static final int PAINTING_PREFERRED = 3;
0171:
0172: /** Constant for unsorted sorting mode. */
0173: public static final int UNSORTED = 0;
0174:
0175: /** Constant for by-name sorting mode. */
0176: public static final int SORTED_BY_NAMES = 1;
0177:
0178: /** Constant for by-type sorting mode.
0179: * @deprecated Not supported since NetBeans 3.6
0180: **/
0181: public @Deprecated
0182: static final int SORTED_BY_TYPES = 2;
0183:
0184: /** Icon for the toolbar.
0185: * @deprecated Presumably noone uses this variable. If you want to customize
0186: * the property sheet look you can change the image files directly (or use your
0187: * own).
0188: */
0189: static @Deprecated
0190: protected Icon iNoSort;
0191:
0192: /** Icon for the toolbar.
0193: * @deprecated Presumably noone uses this variable. If you want to customize
0194: * the property sheet look you can change the image files directly (or use your
0195: * own).
0196: */
0197: static @Deprecated
0198: protected Icon iAlphaSort;
0199:
0200: /** Icon for the toolbar.
0201: * @deprecated Presumably noone uses this variable. If you want to customize
0202: * the property sheet look you can change the image files directly (or use your
0203: * own).
0204: */
0205: static @Deprecated
0206: protected Icon iTypeSort;
0207:
0208: /** Icon for the toolbar.
0209: * @deprecated Presumably noone uses this variable. If you want to customize
0210: * the property sheet look you can change the image files directly (or use your
0211: * own).
0212: */
0213: static @Deprecated
0214: protected Icon iDisplayWritableOnly;
0215:
0216: /** Icon for the toolbar.
0217: * @deprecated Presumably noone uses this variable. If you want to customize
0218: * the property sheet look you can change the image files directly (or use your
0219: * own).
0220: */
0221: static @Deprecated
0222: protected Icon iCustomize;
0223:
0224: /** Action command/input map key for popup menu invocation action */
0225: private static final String ACTION_INVOKE_POPUP = "invokePopup"; //NOI18N
0226:
0227: /** Action command/input map key for help invocation action */
0228: private static final String ACTION_INVOKE_HELP = "invokeHelp"; //NOI18N
0229:
0230: /** Init delay for second change of the selected nodes. */
0231: private static final int INIT_DELAY = 70;
0232:
0233: /** Maximum delay for repeated change of the selected nodes. */
0234: private static final int MAX_DELAY = 150;
0235:
0236: /**Debugging option to suppress all use of tabs */
0237: private static final boolean neverTabs = Boolean
0238: .getBoolean("netbeans.ps.nevertabs"); //NOI18N
0239: static final boolean forceTabs = Boolean
0240: .getBoolean("nb.ps.forcetabs");
0241:
0242: /** Holds the sort mode for the property sheet */
0243: private int sortingMode = UNSORTED;
0244:
0245: /**Tracks whether the description area should be shown */
0246: private boolean showDesc;
0247:
0248: /** Temporary storage for the last selected node in the case the property
0249: * sheet was removed temporarily from a container (winsys DnD) */
0250: private Reference<Node> storedNode;
0251:
0252: //Package private for unit tests
0253: SheetTable table = new SheetTable();
0254: PSheet psheet = new PSheet();
0255: HelpAction helpAction = new HelpAction();
0256:
0257: // delayed setting nodes (partly impl issue 27781)
0258: //package private for unit testing
0259: transient Node[] helperNodes;
0260: private transient RequestProcessor.Task scheduleTask;
0261: private transient RequestProcessor.Task initTask;
0262: SheetPCListener pclistener = new SheetPCListener();
0263:
0264: /** Create a new property sheet */
0265: public PropertySheet() {
0266: init();
0267: initActions();
0268: }
0269:
0270: /** Install actions the property sheet will need */
0271: private void initActions() {
0272: Action invokePopupAction = new MutableAction(
0273: MutableAction.INVOKE_POPUP, this );
0274:
0275: table.getInputMap().put(
0276: KeyStroke.getKeyStroke(KeyEvent.VK_F10,
0277: KeyEvent.SHIFT_MASK), ACTION_INVOKE_POPUP);
0278: table.getActionMap()
0279: .put(ACTION_INVOKE_POPUP, invokePopupAction);
0280:
0281: getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
0282: KeyStroke.getKeyStroke(KeyEvent.VK_F10,
0283: KeyEvent.SHIFT_MASK), ACTION_INVOKE_POPUP);
0284: getActionMap().put(ACTION_INVOKE_POPUP, invokePopupAction);
0285:
0286: getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
0287: KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0),
0288: ACTION_INVOKE_HELP);
0289: getActionMap().put(ACTION_INVOKE_HELP, helpAction);
0290: }
0291:
0292: @Override
0293: public void addNotify() {
0294: super .addNotify();
0295:
0296: Node oldSelection = null;
0297:
0298: if (storedNode != null) {
0299: oldSelection = storedNode.get();
0300: }
0301:
0302: if (oldSelection != null) {
0303: setCurrentNode(oldSelection);
0304: }
0305: }
0306:
0307: @Override
0308: public void updateUI() {
0309: UIManager.get("nb.propertysheet"); //Causes default colors for the property sheet to be bootstrapped into
0310: //UIDefaults - see core/swing/plaf
0311:
0312: super .updateUI();
0313: }
0314:
0315: @Override
0316: public void removeNotify() {
0317: Node lastSel = null;
0318:
0319: if (pclistener != null) {
0320: //Save the last selection - if we're being transiently removed,
0321: //i.e. because of drag and drop, we'll want to reset it on the
0322: //next addNotify if it hasn't disappeared
0323: lastSel = pclistener.detach();
0324: }
0325:
0326: doSetNodes(null);
0327:
0328: if (lastSel != null) {
0329: //Save the selected node in case we're re-added to a container
0330: storedNode = new WeakReference<Node>(lastSel);
0331: }
0332:
0333: super .removeNotify();
0334: table.getReusablePropertyEnv().setBeans(null);
0335: table.getReusablePropertyEnv().setNode(null);
0336: table.getReusablePropertyModel().setProperty(null);
0337:
0338: //don't hold anything when not in component hierarchy
0339: helperNodes = null;
0340: }
0341:
0342: /** Prepare the initial state of the property sheet */
0343: private void init() {
0344: Font f = UIManager.getFont("controlFont"); //NOI18N
0345:
0346: if (f == null) {
0347: //Aqua
0348: f = UIManager.getFont("Tree.font"); //NOI18N
0349: }
0350:
0351: if (f != null) {
0352: table.setFont(f);
0353: }
0354:
0355: showDesc = PropUtils.shouldShowDescription();
0356: setLayout(new BorderLayout());
0357: psheet.setBackground(table.getBackground());
0358: setBackground(table.getBackground());
0359: psheet.setMarginColor(PropUtils.getSetRendererColor());
0360:
0361: psheet.add(table);
0362: add(psheet, BorderLayout.CENTER);
0363:
0364: table.setBorder(BorderFactory.createEmptyBorder());
0365:
0366: setDescriptionVisible(showDesc);
0367: setMinimumSize(new Dimension(100, 50));
0368: psheet.setEmptyString(NbBundle.getMessage(PropertySheet.class,
0369: "CTL_NoProperties")); //NOI18N
0370:
0371: TabSelectionListener listener = new TabSelectionListener();
0372:
0373: psheet.addSelectionChangeListener(listener);
0374:
0375: table.addChangeListener(listener);
0376:
0377: try {
0378: setSortingMode(PropUtils.getSavedSortOrder());
0379: } catch (PropertyVetoException e) {
0380: //Should never happen unless someone manually modifies
0381: //backing storage
0382: Exceptions.printStackTrace(e);
0383: }
0384: }
0385:
0386: private boolean popupEnabled = true;
0387:
0388: /**
0389: * Set whether or not the popup menu should be available on
0390: * right-click.
0391: * @param val If true, right-clicking the property sheet will show a popup
0392: * offering sorting options, show/hide description area, etc.
0393: * @since 6.9
0394: */
0395: public final void setPopupEnabled(boolean val) {
0396: this .popupEnabled = val;
0397: }
0398:
0399: /**
0400: * Set the visibility of the description area.
0401: *
0402: * @param val Whether or not it should be visible
0403: * @since 6.9
0404: */
0405: public final void setDescriptionAreaVisible(boolean val) {
0406: if (isDescriptionVisible() != val) {
0407: int state = psheet.getState();
0408:
0409: if (!val) {
0410: int newState = ((state & PSheet.STATE_HAS_TABS) != 0) ? PSheet.STATE_HAS_TABS
0411: : 0;
0412:
0413: psheet.setState(newState);
0414: } else {
0415: int newState = ((state & PSheet.STATE_HAS_TABS) != 0) ? (PSheet.STATE_HAS_TABS | PSheet.STATE_HAS_DESCRIPTION)
0416: : PSheet.STATE_HAS_DESCRIPTION;
0417:
0418: psheet.setState(newState);
0419: }
0420: }
0421: }
0422:
0423: /** Enable/disable display of the description area */
0424: void setDescriptionVisible(boolean val) {
0425: setDescriptionAreaVisible(val);
0426: PropUtils.saveShowDescription(val);
0427: }
0428:
0429: boolean isDescriptionVisible() {
0430: return (psheet.getState() & PSheet.STATE_HAS_DESCRIPTION) != 0;
0431: }
0432:
0433: /** Overridden to route focus requests to the table */
0434: @Override
0435: public void requestFocus() {
0436: if (table.getParent() != null) {
0437: table.requestFocus();
0438: } else {
0439: super .requestFocus();
0440: }
0441: }
0442:
0443: /** Overridden to route focus requests to the table */
0444: @Override
0445: public boolean requestFocusInWindow() {
0446: if (table.getParent() != null) {
0447: return table.requestFocusInWindow();
0448: } else {
0449: return super .requestFocusInWindow();
0450: }
0451: }
0452:
0453: /**
0454: * Set the nodes explored by this property sheet.
0455: *
0456: * @param nodes nodes to be explored
0457: */
0458: private void doSetNodes(Node[] nodes) {
0459: if ((nodes == null) || (nodes.length == 0)) {
0460: table.getPropertySetModel().setPropertySets(null);
0461: table.getReusablePropertyEnv().clear();
0462: return;
0463: }
0464:
0465: final Node n = (nodes.length == 1) ? nodes[0] : new ProxyNode(
0466: nodes);
0467: setCurrentNode(n);
0468: }
0469:
0470: /**Set the nodes explored by this property sheet.
0471: * @param nodes nodes to be explored or null to clear the sheet
0472: */
0473: public synchronized void setNodes(Node[] nodes) {
0474: final boolean loggable = PropUtils
0475: .isLoggable(PropertySheet.class);
0476:
0477: if (loggable) {
0478: PropUtils.log(PropertySheet.class, "SetNodes "
0479: + Arrays.asList(nodes));
0480: }
0481:
0482: //Performance - check equality and avoid some extra repaints - repainting
0483: //the property sheet can be expensive
0484: if ((nodes != null) && (nodes.length > 0)
0485: && (pclistener != null)) {
0486: if ((nodes.length == 1)
0487: && (nodes[0] == pclistener.getNode())) {
0488: if (loggable) {
0489: PropUtils
0490: .log(PropertySheet.class,
0491: " Same node selected as before; no redisplay needed");
0492: }
0493:
0494: return;
0495: } else if (pclistener.getNode() instanceof ProxyNode) {
0496: if (loggable) {
0497: PropUtils
0498: .log(PropertySheet.class,
0499: " Selected node is a proxy node - comparing contents.");
0500: }
0501:
0502: Node[] currNodes = ((ProxyNode) pclistener.getNode())
0503: .getOriginalNodes();
0504:
0505: if (Arrays.asList(nodes).equals(
0506: Arrays.asList(currNodes))) {
0507: if (loggable) {
0508: PropUtils
0509: .log(
0510: PropertySheet.class,
0511: " Proxy node represents the same "
0512: + "nodes already showing. Showing: "
0513: + Arrays
0514: .asList(currNodes)
0515: + " requested "
0516: + Arrays.asList(nodes));
0517:
0518: HashSet<Node> currs = new HashSet<Node>(Arrays
0519: .asList(currNodes));
0520: HashSet<Node> reqs = new HashSet<Node>(Arrays
0521: .asList(nodes));
0522:
0523: if (currs.size() != currNodes.length) {
0524: PropUtils
0525: .log(
0526: PropertySheet.class,
0527: " A hashSet of the current nodes does NOT have the same number "
0528: + " of elements as the array of current nodes! Check "
0529: + "your hashCode()/equals() contract. One or more nodes in "
0530: + "the array are claiming to be the same node.");
0531: }
0532:
0533: if (reqs.size() != nodes.length) {
0534: PropUtils
0535: .log(
0536: PropertySheet.class,
0537: " A hashSet of the requested selected nodes does NOT have the same number "
0538: + " of elements as the array of current nodes! Check your hashCode()/equals() contract"
0539: + " One or more nodes in the array are claiming to be the same node.");
0540: }
0541: }
0542:
0543: return;
0544: }
0545: }
0546: } else if ((nodes == null) || (nodes.length == 0)) {
0547: if (pclistener != null) {
0548: pclistener.detach();
0549: }
0550:
0551: if (SwingUtilities.isEventDispatchThread()) {
0552: if (loggable) {
0553: PropUtils
0554: .log(PropertySheet.class,
0555: " Nodes cleared on event queue. Emptying model.");
0556: }
0557:
0558: table.getPropertySetModel().setPropertySets(null);
0559: table.getReusablePropertyEnv().clear();
0560: helperNodes = null;
0561: } else {
0562: SwingUtilities.invokeLater(new Runnable() {
0563: public void run() {
0564: if (loggable) {
0565: PropUtils
0566: .log(
0567: PropertySheet.class,
0568: " Nodes "
0569: + "cleared off event queue. Empty model later on EQ.");
0570: }
0571:
0572: table.getPropertySetModel().setPropertySets(
0573: null);
0574: table.getReusablePropertyEnv().clear();
0575: helperNodes = null;
0576: }
0577: });
0578: }
0579:
0580: return;
0581: }
0582:
0583: RequestProcessor.Task task = getScheduleTask();
0584: helperNodes = nodes;
0585:
0586: //Clear any saved node if setNodes is called while we're offscreen
0587: storedNode = null;
0588:
0589: if (task.equals(initTask)) {
0590: //if task is only init task then set nodes immediatelly
0591: scheduleTask.schedule(0);
0592: task.schedule(INIT_DELAY);
0593: } else {
0594: // in a task run then increase delay and reschedule task
0595: int delay = task.getDelay() * 2;
0596:
0597: if (delay > MAX_DELAY) {
0598: delay = MAX_DELAY;
0599: }
0600:
0601: if (delay < INIT_DELAY) {
0602: delay = INIT_DELAY;
0603: }
0604:
0605: if (loggable) {
0606: PropUtils
0607: .log(PropertySheet.class,
0608: " Scheduling delayed update of selected nodes.");
0609: }
0610:
0611: task.schedule(delay);
0612: }
0613: }
0614:
0615: private synchronized RequestProcessor.Task getScheduleTask() {
0616: if (scheduleTask == null) {
0617: scheduleTask = RequestProcessor.getDefault().post(
0618: new Runnable() {
0619: public void run() {
0620: final Node[] nodes = helperNodes;
0621: SwingUtilities.invokeLater(new Runnable() {
0622: public void run() {
0623: final boolean loggable = PropUtils
0624: .isLoggable(PropertySheet.class);
0625:
0626: if (loggable) {
0627: PropUtils
0628: .log(
0629: PropertySheet.class,
0630: "Delayed "
0631: + "updater setting nodes to "
0632: + Arrays
0633: .asList(nodes));
0634: }
0635:
0636: doSetNodes(nodes);
0637: }
0638: });
0639: }
0640: });
0641: initTask = RequestProcessor.getDefault().post(
0642: new Runnable() {
0643: public void run() {
0644: }
0645: });
0646: }
0647:
0648: // if none task runs then return initTask to wait for next changes
0649: if (initTask.isFinished() && scheduleTask.isFinished()) {
0650: return initTask;
0651: }
0652:
0653: // if some task runs then return schedule task which will set nodes
0654: return scheduleTask;
0655: }
0656:
0657: // end of delayed
0658:
0659: /** This has to be called from the AWT thread. */
0660: void setCurrentNode(Node node) {
0661: Node old = pclistener.getNode();
0662:
0663: if (old != node) {
0664: psheet.storeScrollAndTabInfo();
0665: }
0666:
0667: final boolean loggable = PropUtils
0668: .isLoggable(PropertySheet.class);
0669:
0670: if (loggable) {
0671: PropUtils
0672: .log(PropertySheet.class, "SetCurrentNode:" + node);
0673: }
0674:
0675: // table.setNode (node);
0676: PropertySetModel psm = table.getPropertySetModel();
0677: Node.PropertySet[] ps = node.getPropertySets();
0678:
0679: //bloc below copied from original impl - is this common/needed?
0680: if (ps == null) {
0681: // illegal node behavior => log warning about it
0682: Logger.getAnonymousLogger().warning(
0683: "Node " + node
0684: + ": getPropertySets() returns null!"); // NOI18N
0685: ps = new Node.PropertySet[] {};
0686:
0687: //Prepare the reusable model/env's node
0688: }
0689:
0690: table.getReusablePropertyEnv().setNode(node);
0691:
0692: assert noNullPropertyLists(ps) : "Node " + node
0693: + " returns null from getProperties() for one or "
0694: + "more of its property sets"; //NOI18N
0695:
0696: if (table.isEditing()) {
0697: table.removeEditor();
0698: }
0699:
0700: boolean usingTabs = needTabs(node);
0701:
0702: if (usingTabs) {
0703: psheet.setState(psheet.getState() | PSheet.STATE_HAS_TABS);
0704:
0705: TabInfo info = getTabItems(node);
0706:
0707: psheet.setTabbedContainerItems(info.sets, info.titles);
0708: psheet.manager().setCurrentNodeName(node.getName());
0709: psm.setPropertySets(info.getSets(0));
0710: } else {
0711: psm.setPropertySets(ps);
0712: psheet
0713: .setState(((psheet.getState() & PSheet.STATE_HAS_DESCRIPTION) != 0) ? PSheet.STATE_HAS_DESCRIPTION
0714: : 0);
0715: psheet
0716: .setTabbedContainerItems(new Object[0],
0717: new String[0]);
0718: }
0719:
0720: psheet.adjustForName(node.getName());
0721:
0722: table.setBeanName(node.getDisplayName());
0723:
0724: String description = (String) node.getValue("nodeDescription"); //NOI18N
0725:
0726: psheet.setDescription(node.getDisplayName(),
0727: (description == null) ? node.getShortDescription()
0728: : description);
0729:
0730: pclistener.attach(node);
0731:
0732: if (isDescriptionVisible()) {
0733: helpAction.checkContext();
0734: }
0735: }
0736:
0737: private boolean noNullPropertyLists(PropertySet[] ps) {
0738: boolean result = true;
0739:
0740: for (int i = 0; i < ps.length; i++) {
0741: result &= (ps[i].getProperties() != null);
0742:
0743: if (!result) {
0744: break;
0745: }
0746: }
0747:
0748: return result;
0749: }
0750:
0751: /**Deprecated, does nothing.
0752: * @param style Irrelevant
0753: * @deprecated Relic of the original property sheet implementation. Does nothing.*/
0754: public @Deprecated
0755: void setPropertyPaintingStyle(int style) {
0756: }
0757:
0758: /**Deprecated, returns no meaningful value.
0759: * @return the mode
0760: * @see #setPropertyPaintingStyle
0761: * @deprecated Relic of the original property sheet implementation. Does nothing. */
0762: public @Deprecated
0763: int getPropertyPaintingStyle() {
0764: return 0;
0765: }
0766:
0767: /**
0768: * Set the sorting mode.
0769: * @param sortingMode one of {@link #UNSORTED} or {@link #SORTED_BY_NAMES}. {@link #SORTED_BY_TYPES} is
0770: * no longer supported.
0771: * @throws PropertyVetoException if a value other than one of the defined sorting modes is set
0772: */
0773: public void setSortingMode(int sortingMode)
0774: throws PropertyVetoException {
0775: try {
0776: table.getPropertySetModel().setComparator(
0777: PropUtils.getComparator(sortingMode));
0778: this .sortingMode = sortingMode;
0779: psheet.setMarginPainted(!PropUtils.neverMargin
0780: && (getSortingMode() == UNSORTED));
0781: PropUtils.putSortOrder(sortingMode);
0782: } catch (IllegalArgumentException iae) {
0783: throw new PropertyVetoException(NbBundle.getMessage(
0784: PropertySheet.class, "EXC_Unknown_sorting_mode"),
0785: new PropertyChangeEvent(this ,
0786: PROPERTY_SORTING_MODE, new Integer(0),
0787: new Integer(sortingMode))); //NOI18N
0788: }
0789: }
0790:
0791: /**Get the sorting mode.
0792: * @return the mode
0793: * @see #setSortingMode */
0794: public int getSortingMode() {
0795: return sortingMode;
0796: }
0797:
0798: /** Deprecated. Does nothing.
0799: * @param index index of the page to select
0800: * @deprecated Relic of the original property sheet implementation. Does nothing.
0801: */
0802: public @Deprecated
0803: void setCurrentPage(int index) {
0804: }
0805:
0806: /**
0807: * Deprecated. Does nothing.
0808: * @deprecated Relic of the original property sheet implementation. Does nothing.
0809: * @param str name of the tab to select
0810: * @return always returns false
0811: */
0812: public @Deprecated
0813: boolean setCurrentPage(String str) {
0814: return false;
0815: }
0816:
0817: /**Deprecated. Does nothing.
0818: * @return index of currently selected page
0819: * @deprecated Relic of the original property sheet implementation. Does nothing. */
0820: public @Deprecated
0821: int getCurrentPage() {
0822: // return pages.getSelectedIndex ();
0823: return 0;
0824: }
0825:
0826: /**Deprecated. Does nothing.
0827: * @param plastic true if so
0828: * @deprecated Relic of the original property sheet implementation. Display of properties
0829: * is handled by the look and feel.
0830: */
0831: public @Deprecated
0832: void setPlastic(boolean plastic) {
0833: }
0834:
0835: /**Test whether buttons in sheet are plastic.
0836: * @return <code>true</code> if so
0837: * @deprecated Relic of the original property sheet implementation. Does nothing.*/
0838: public @Deprecated
0839: boolean getPlastic() {
0840: return false;
0841: }
0842:
0843: /**Deprecated. Does nothing.
0844: * @param color the new color
0845: * @deprecated Relic of the original property sheet implementation. Display of properties
0846: * is handled by the look and feel. */
0847: public @Deprecated
0848: void setValueColor(Color color) {
0849: }
0850:
0851: /**Deprecated. Does nothing.
0852: * @deprecated Relic of the original property sheet implementation. Display of properties
0853: * is handled by the look and feel.
0854: * @return the color */
0855: public @Deprecated
0856: Color getValueColor() {
0857: return Color.BLACK;
0858: }
0859:
0860: /**Deprecated. Does nothing.
0861: * @deprecated Relic of the original property sheet implementation. Does nothing.
0862: * @param color the new color */
0863: public @Deprecated
0864: void setDisabledPropertyColor(Color color) {
0865: }
0866:
0867: /**Deprecated. Does not return a meaningful value.
0868: * @deprecated Relic of the original property sheet implementation. Display of properties
0869: * is handled by the look and feel.
0870: * @return the color */
0871: public @Deprecated
0872: Color getDisabledPropertyColor() {
0873: return Color.GRAY;
0874: }
0875:
0876: /**Deprecated. Does nothing.
0877: * @param b <code>true</code> if this is desired
0878: * @deprecated Relic of the original property sheet implementation. Does nothing.*/
0879: public @Deprecated
0880: void setDisplayWritableOnly(boolean b) {
0881: }
0882:
0883: /**Deprecated. Does not return a meaningful value.
0884: * @deprecated Relic of the original property sheet implementation. Does nothing.
0885: * @return <code>true</code> if so */
0886: public @Deprecated
0887: boolean getDisplayWritableOnly() {
0888: return false;
0889: }
0890:
0891: final void showPopup(Point p) {
0892: if (!popupEnabled)
0893: return;
0894: JMenuItem helpItem = new JMenuItem();
0895: JRadioButtonMenuItem sortNamesItem = new JRadioButtonMenuItem();
0896: JRadioButtonMenuItem unsortedItem = new JRadioButtonMenuItem();
0897: JCheckBoxMenuItem descriptionItem = new JCheckBoxMenuItem();
0898: JMenuItem defaultValueItem = new JMenuItem();
0899: JPopupMenu popup = new JPopupMenu();
0900:
0901: unsortedItem.setSelected(getSortingMode() == UNSORTED);
0902: sortNamesItem.setSelected(getSortingMode() == SORTED_BY_NAMES);
0903: helpAction.checkContext();
0904: helpItem.setAction(helpAction);
0905: sortNamesItem.setAction(new MutableAction(
0906: MutableAction.SORT_NAMES, this ));
0907: unsortedItem.setAction(new MutableAction(MutableAction.UNSORT,
0908: this ));
0909: descriptionItem.setAction(new MutableAction(
0910: MutableAction.SHOW_DESCRIPTION, this ));
0911: descriptionItem.setSelected(isDescriptionVisible());
0912: defaultValueItem.setAction(new MutableAction(
0913: MutableAction.RESTORE_DEFAULT, this ));
0914:
0915: FeatureDescriptor fd = table.getSelection();
0916: defaultValueItem.setEnabled(PropUtils.shallBeRDVEnabled(fd));
0917:
0918: popup.add(unsortedItem);
0919: popup.add(sortNamesItem);
0920: popup.add(new JSeparator());
0921: popup.add(descriptionItem);
0922: popup.add(new JSeparator());
0923: popup.add(defaultValueItem);
0924: popup.add(new JSeparator());
0925: popup.add(helpItem);
0926: popup.show(psheet, p.x, p.y);
0927: }
0928:
0929: Node[] getCurrentNodes() {
0930: Node n = pclistener.getNode();
0931:
0932: if (n != null) {
0933: if (n instanceof ProxyNode) {
0934: return ((ProxyNode) n).getOriginalNodes();
0935: } else {
0936: return new Node[] { n };
0937: }
0938: }
0939:
0940: return new Node[0];
0941: }
0942:
0943: private static final boolean needTabs(Node n) {
0944: boolean needTabs = true;
0945:
0946: if (forceTabs) {
0947: return true;
0948: }
0949:
0950: if (n instanceof ProxyNode) {
0951: Node[] nodes = ((ProxyNode) n).getOriginalNodes();
0952:
0953: for (int i = 0; i < nodes.length; i++) {
0954: assert nodes[i] != n : "Proxy node recursively references itself"; //NOI18N
0955: needTabs &= needTabs(nodes[i]);
0956:
0957: if (!needTabs) {
0958: break;
0959: }
0960: }
0961: } else {
0962: PropertySet[] ps = n.getPropertySets();
0963: needTabs = forceTabs ? (ps.length > 1) : (neverTabs ? false
0964: : false);
0965:
0966: //neverTabs is a debugging option to force tab use one tab per property set
0967: if (!neverTabs) {
0968: for (int i = 0; (i < ps.length) && !needTabs; i++) {
0969: needTabs |= (ps[i].getValue("tabName") != null); //NOI18N
0970: }
0971: }
0972: }
0973:
0974: return needTabs;
0975: }
0976:
0977: private static final TabInfo getTabItems(Node n) {
0978: Map<String, List<PropertySet>> titlesToContents = new HashMap<String, List<PropertySet>>();
0979: ArrayList<String> order = new ArrayList<String>();
0980:
0981: PropertySet[] sets = n.getPropertySets();
0982:
0983: for (int i = 0; i < sets.length; i++) {
0984: String currTab = (String) sets[i].getValue("tabName"); //NOI18N
0985:
0986: if (currTab == null) {
0987: currTab = PropUtils.basicPropsTabName();
0988: }
0989:
0990: List<PropertySet> l = titlesToContents.get(currTab);
0991:
0992: if (l == null) {
0993: l = new ArrayList<PropertySet>();
0994: l.add(sets[i]);
0995: titlesToContents.put(currTab, l);
0996: } else {
0997: l.add(sets[i]);
0998: }
0999:
1000: if (!order.contains(currTab)) {
1001: order.add(currTab);
1002: }
1003: }
1004:
1005: String[] titles = new String[order.size()];
1006: Object[] setSets = new Object[order.size()];
1007: int count = 0;
1008:
1009: for (Iterator<String> i = order.iterator(); i.hasNext();) {
1010: titles[count] = i.next();
1011:
1012: List<PropertySet> currSets = titlesToContents
1013: .get(titles[count]);
1014: setSets[count] = new PropertySet[currSets.size()];
1015: setSets[count] = currSets
1016: .toArray((PropertySet[]) setSets[count]);
1017: count++;
1018: }
1019:
1020: return new TabInfo(titles, setSets);
1021: }
1022:
1023: @Override
1024: public void firePropertyChange(String propertyName,
1025: boolean oldValue, boolean newValue) {
1026: super .firePropertyChange(propertyName, oldValue, newValue);
1027: // on macos we get this hint about focus lost..
1028: if ("MACOSX".equals(propertyName)) {
1029: this .table.focusLostCancel();
1030: }
1031: }
1032:
1033: private class TabSelectionListener implements ChangeListener,
1034: FocusListener {
1035: public void stateChanged(ChangeEvent e) {
1036: helpAction.checkContext();
1037:
1038: if (e.getSource() instanceof SheetTable) {
1039: SheetTable tbl = (SheetTable) e.getSource();
1040: FeatureDescriptor fd = tbl.getSelection();
1041: Component focusOwner = KeyboardFocusManager
1042: .getCurrentKeyboardFocusManager()
1043: .getPermanentFocusOwner();
1044:
1045: if ((focusOwner != tbl)
1046: && !tbl.isKnownComponent(focusOwner)
1047: && !isAncestorOf(focusOwner)) {
1048: fd = null;
1049: }
1050:
1051: if (fd != null) {
1052: String ttl = fd.getDisplayName();
1053: String desc = fd.getShortDescription();
1054: psheet.setDescription(ttl, desc);
1055: } else {
1056: Node n = pclistener.getNode();
1057:
1058: if (n != null) {
1059: String ttl = n.getDisplayName();
1060: String desc = (String) n
1061: .getValue("nodeDescription"); //NOI18N
1062:
1063: if (desc == null) {
1064: desc = n.getShortDescription();
1065: }
1066:
1067: psheet.setDescription(ttl, desc);
1068: } else {
1069: psheet.setDescription(null, null);
1070: }
1071: }
1072: } else {
1073: if (!psheet.isAdjusting()) {
1074: psheet.storeScrollAndTabInfo();
1075: }
1076:
1077: PropertySet[] sets = (PropertySet[]) psheet
1078: .getTabbedContainerSelection();
1079:
1080: if (sets != null) {
1081: table.getPropertySetModel().setPropertySets(sets);
1082:
1083: if ((sets.length > 0) && !psheet.isAdjusting()) {
1084: String tab = (String) sets[0]
1085: .getValue("tabName"); //NOI18N
1086: tab = (tab == null) ? PropUtils
1087: .basicPropsTabName() : tab;
1088: psheet.manager().storeLastSelectedGroup(tab);
1089: psheet.adjustForName(tab);
1090: }
1091: }
1092: }
1093: }
1094:
1095: public void focusGained(FocusEvent e) {
1096: ChangeEvent ce = new ChangeEvent(table);
1097: stateChanged(ce);
1098: }
1099:
1100: public void focusLost(FocusEvent e) {
1101: focusGained(e);
1102: }
1103: }
1104:
1105: final class HelpAction extends AbstractAction {
1106: HelpCtx.Provider provider = null;
1107:
1108: //XXX MERGE THIS CLASS WITH PROXYHELPPROVIDER
1109: private boolean wasEnabled = false;
1110:
1111: public HelpAction() {
1112: super (NbBundle.getMessage(PropertySheet.class, "CTL_Help")); //NOI18N
1113: checkContext();
1114: }
1115:
1116: public void checkContext() {
1117: HelpCtx ctx = getContext();
1118: boolean isEnabled = ctx != null;
1119:
1120: if (isEnabled != wasEnabled) {
1121: firePropertyChange("enabled", isEnabled ? Boolean.FALSE
1122: : Boolean.TRUE, isEnabled ? Boolean.TRUE
1123: : Boolean.FALSE); //NOI18N
1124: }
1125:
1126: wasEnabled = isEnabled;
1127: psheet.setHelpEnabled(isEnabled);
1128: }
1129:
1130: @Override
1131: public boolean isEnabled() {
1132: return getContext() != null;
1133: }
1134:
1135: public void actionPerformed(ActionEvent e) {
1136: HelpCtx ctx = getContext();
1137:
1138: if (ctx == null) {
1139: Toolkit.getDefaultToolkit().beep();
1140:
1141: return;
1142: }
1143:
1144: try {
1145: //Copied from original property sheet implementation
1146: Class<?> c = Lookup.getDefault().lookup(
1147: ClassLoader.class).loadClass(
1148: "org.netbeans.api.javahelp.Help"); // NOI18N
1149:
1150: Object o = Lookup.getDefault().lookup(c);
1151:
1152: if (o != null) {
1153: Method m = c.getMethod("showHelp", // NOI18N
1154: new Class[] { HelpCtx.class });
1155:
1156: if (m != null) { //Unit tests
1157: m.invoke(o, new Object[] { ctx });
1158: }
1159:
1160: return;
1161: }
1162: } catch (ClassNotFoundException cnfe) {
1163: // ignore - maybe javahelp module is not installed, not so strange
1164: } catch (Exception ee) {
1165: // potentially more serious
1166: Logger.getLogger(PropertySheet.class.getName()).log(
1167: Level.WARNING, null, ee);
1168: }
1169:
1170: // Did not work.
1171: Toolkit.getDefaultToolkit().beep();
1172: }
1173:
1174: public HelpCtx getContext() {
1175: FeatureDescriptor fd = table.getSelection();
1176: String id = null;
1177:
1178: //First look on the individual property
1179: if ((fd != null) && fd instanceof Node.Property) {
1180: id = (String) fd.getValue("helpID"); //NOI18N
1181: }
1182:
1183: if (id == null) {
1184: if ((psheet.getState() & PSheet.STATE_HAS_TABS) != 0) {
1185: //If we're in a tabbed pane, we want the first visible
1186: //property set's help id
1187: Node.PropertySet[] ps = (Node.PropertySet[]) psheet
1188: .getTabbedContainerSelection();
1189:
1190: if ((ps != null) && (ps.length > 0)) {
1191: id = (String) ps[0].getValue("helpID"); //NOI18N
1192: }
1193: } else if ((id == null) && (pclistener != null)) {
1194: //Otherwise, look for the first property set on the node
1195: Node n = pclistener.getNode();
1196:
1197: if (n == null) {
1198: return null;
1199: }
1200:
1201: Node.PropertySet[] ps = n.getPropertySets();
1202:
1203: if ((fd != null) && (ps != null) && (ps.length > 0)) {
1204: for (int i = 0; i < ps.length; i++) {
1205: if ((ps[i] == fd)
1206: || Arrays.asList(
1207: ps[i].getProperties())
1208: .contains(fd)) {
1209: id = (String) ps[i].getValue("helpID"); //NOI18N
1210:
1211: break;
1212: }
1213: }
1214: }
1215: }
1216:
1217: //Then look on the first property set
1218: if ((id == null) && (pclistener != null)) {
1219: Node[] nodes = getCurrentNodes();
1220:
1221: if ((nodes != null) && (nodes.length > 0)) {
1222: for (int i = 0; i < nodes.length; i++) {
1223: // Then try to find a property-sheet specific id on
1224: // the Node
1225: id = (String) nodes[i]
1226: .getValue("propertiesHelpID"); //NOI18N
1227:
1228: if (id != null) {
1229: break;
1230: }
1231:
1232: // Then try to find if node doesn't return help
1233: // context directly
1234: HelpCtx ctx = nodes[i].getHelpCtx();
1235:
1236: if ((ctx != null)
1237: && (ctx != HelpCtx.DEFAULT_HELP)) {
1238: return ctx;
1239: }
1240: }
1241: }
1242: }
1243: }
1244:
1245: if ((id != null)
1246: && !HelpCtx.DEFAULT_HELP.getHelpID().equals(id)) {
1247: return new HelpCtx(id);
1248: } else {
1249: return null;
1250: }
1251: }
1252: }
1253:
1254: /**
1255: * Convenience action class to eliminate a few action subclasses.
1256: */
1257: private static class MutableAction extends AbstractAction {
1258: private static final int SORT_NAMES = 0;
1259: private static final int UNSORT = 1;
1260: private static final int INVOKE_POPUP = 2;
1261: private static final int SHOW_DESCRIPTION = 3;
1262: private static final int SHOW_HELP = 4;
1263: private static final int RESTORE_DEFAULT = 5;
1264: private final int id;
1265: private final PropertySheet sheet;
1266:
1267: public MutableAction(int id, PropertySheet sheet) {
1268: this .id = id;
1269: this .sheet = sheet;
1270:
1271: String nameKey = null;
1272:
1273: switch (id) {
1274: case SORT_NAMES:
1275: nameKey = "CTL_AlphaSort"; //NOI18N
1276:
1277: break;
1278:
1279: case UNSORT:
1280: nameKey = "CTL_NoSort"; //NOI18N
1281:
1282: break;
1283:
1284: case INVOKE_POPUP:
1285: break;
1286:
1287: case SHOW_DESCRIPTION:
1288: nameKey = "CTL_ShowDescription"; //NOI18N
1289:
1290: break;
1291:
1292: case SHOW_HELP:
1293: break;
1294:
1295: case RESTORE_DEFAULT:
1296: nameKey = "CTL_RestoreDefaultValue"; //NOI18N
1297:
1298: break;
1299:
1300: default:
1301: throw new IllegalArgumentException(Integer.toString(id));
1302: }
1303:
1304: if (nameKey != null) {
1305: putValue(Action.NAME, NbBundle.getMessage(
1306: PropertySheet.class, nameKey));
1307: }
1308: }
1309:
1310: public void actionPerformed(ActionEvent ae) {
1311: switch (id) {
1312: case SORT_NAMES:
1313:
1314: try {
1315: sheet.setSortingMode(SORTED_BY_NAMES);
1316: } catch (PropertyVetoException pve) {
1317: //can't happen
1318: }
1319:
1320: break;
1321:
1322: case UNSORT:
1323:
1324: try {
1325: sheet.setSortingMode(UNSORTED);
1326: } catch (PropertyVetoException pve) {
1327: //can't happen
1328: }
1329:
1330: break;
1331:
1332: case INVOKE_POPUP:
1333: sheet.showPopup(new Point(0, 0));
1334:
1335: break;
1336:
1337: case SHOW_DESCRIPTION:
1338: sheet.setDescriptionVisible(!sheet
1339: .isDescriptionVisible());
1340:
1341: break;
1342:
1343: case SHOW_HELP:
1344: break;
1345:
1346: case RESTORE_DEFAULT:
1347:
1348: try {
1349: // no need to use instanceof check since this action is
1350: // not accessible if a selection is not a Node.Property
1351: // instance
1352: //#122308 - prevent NPE in an exotic scenario
1353: if (null != sheet && null != sheet.table
1354: && null != sheet.table.getSelection()) {
1355: ((Node.Property) sheet.table.getSelection())
1356: .restoreDefaultValue();
1357: }
1358: } catch (IllegalAccessException iae) {
1359: throw (IllegalStateException) new IllegalStateException(
1360: "Error restoring default value")
1361: .initCause(iae);
1362: } catch (InvocationTargetException ite) {
1363: throw (IllegalStateException) new IllegalStateException(
1364: "Error restoring defaul value")
1365: .initCause(ite);
1366: }
1367:
1368: break;
1369:
1370: default:
1371: throw new IllegalArgumentException(Integer.toString(id));
1372: }
1373: }
1374:
1375: @Override
1376: public boolean isEnabled() {
1377: if ((id == INVOKE_POPUP)
1378: && Boolean.TRUE.equals(sheet
1379: .getClientProperty("disablePopup"))) {
1380: return false;
1381: }
1382:
1383: return super .isEnabled();
1384: }
1385: }
1386:
1387: private final class SheetPCListener extends NodeAdapter {
1388: private PropertyChangeListener inner;
1389:
1390: /** Cache the current node locally only in the listener */
1391: private Node currNode;
1392:
1393: public SheetPCListener() {
1394: inner = new PCL();
1395: }
1396:
1397: /** Attach to a node, detaching from the last one if non-null. */
1398: public void attach(Node n) {
1399: if (currNode != n) {
1400: if (currNode != null) {
1401: detach();
1402: }
1403:
1404: if (n != null) {
1405: n.addPropertyChangeListener(inner);
1406: n.addNodeListener(this );
1407:
1408: if (PropUtils.isLoggable(PropertySheet.class)) {
1409: PropUtils.log(PropertySheet.class,
1410: "Now listening for changes on " + n);
1411: }
1412: }
1413:
1414: currNode = n;
1415: }
1416: }
1417:
1418: public Node getNode() {
1419: return currNode;
1420: }
1421:
1422: public Node detach() {
1423: Node n = currNode;
1424:
1425: if (n != null) {
1426: if (PropUtils.isLoggable(PropertySheet.class)) {
1427: PropUtils.log(PropertySheet.class,
1428: "Detaching listeners from " + n);
1429: }
1430:
1431: n.removePropertyChangeListener(inner);
1432: n.removeNodeListener(this );
1433:
1434: //clear the reference
1435: currNode = null;
1436: }
1437:
1438: return n;
1439: }
1440:
1441: /** Receives property change events directed to the NodeListener */
1442: @Override
1443: public void propertyChange(final PropertyChangeEvent evt) {
1444: final String nm = evt.getPropertyName();
1445:
1446: if (Node.PROP_PROPERTY_SETS.equals(nm)) {
1447: final Node n = (Node) evt.getSource();
1448: Mutex.EVENT.readAccess(new Runnable() {
1449: public void run() {
1450: attach(n);
1451: setCurrentNode(n);
1452: }
1453: });
1454: } else if (Node.PROP_COOKIE.equals(nm)
1455: || //weed out uninteresting property changes
1456: Node.PROP_ICON.equals(nm)
1457: || Node.PROP_PARENT_NODE.equals(nm)
1458: || Node.PROP_OPENED_ICON.equals(nm)
1459: || Node.PROP_LEAF.equals(nm)) {
1460: return;
1461: } else {
1462: Runnable runnable = new Runnable() {
1463: public void run() {
1464: //the following must run in EDT to avoid deadlocks, see #91371
1465: if (isDescriptionVisible()
1466: && (Node.PROP_DISPLAY_NAME.equals(nm) || Node.PROP_SHORT_DESCRIPTION
1467: .equals(nm))) {
1468:
1469: //XXX SHOULD NOT BE FIRED TO NODELISTENERS
1470: Node n = (Node) evt.getSource();
1471: String description = (String) n
1472: .getValue("nodeDescription"); //NOI18N
1473: psheet.setDescription(n.getDisplayName(),
1474: (description == null) ? n
1475: .getShortDescription()
1476: : description);
1477: table.setBeanName(n.getDisplayName());
1478: }
1479: }
1480: };
1481: if (SwingUtilities.isEventDispatchThread()) {
1482: runnable.run();
1483: } else {
1484: SwingUtilities.invokeLater(runnable);
1485: }
1486: }
1487: /*else {
1488: if (evt.getPropertyName() == null) {
1489: //Trigger rebuilding the entire list of properties, probably
1490: //one has been added or removed
1491: setCurrentNode(currNode);
1492: }
1493: }
1494: */
1495: }
1496:
1497: @Override
1498: public void nodeDestroyed(org.openide.nodes.NodeEvent ev) {
1499: detach();
1500: Mutex.EVENT.readAccess(new Runnable() {
1501: public void run() {
1502: doSetNodes(null);
1503: }
1504: });
1505: }
1506:
1507: private final class PCL implements PropertyChangeListener {
1508: /** Receives property change events directed to PropertyChangeListeners,
1509: * not NodeListeners */
1510: public void propertyChange(final PropertyChangeEvent evt) {
1511: SwingUtilities.invokeLater(new Runnable() {
1512: public void run() {
1513: String nm = evt.getPropertyName();
1514: /*
1515: if (Node.PROP_COOKIE.equals(nm) || // weed out frequently abused property changes
1516: Node.PROP_ICON.equals(nm) || Node.PROP_PARENT_NODE.equals(nm) ||
1517: Node.PROP_OPENED_ICON.equals(nm) || Node.PROP_LEAF.equals(nm)) {
1518: ErrorManager.getDefault().log(
1519: ErrorManager.WARNING,
1520: "Recived bogus property change " + nm + " from " + evt.getSource() + // NOI18N
1521: ". This should ony be fired to" + // NOI18N
1522: "NodeListeners, not general property change listeners"); // NOI18N
1523: } else if (isDescriptionVisible() &&
1524: (Node.PROP_DISPLAY_NAME.equals(nm) || Node.PROP_SHORT_DESCRIPTION.equals(nm))) {
1525: Node n = (Node) evt.getSource();
1526: /*
1527: fallbackTitle = n.getDisplayName();
1528: fallbackDescription = n.getShortDescription();
1529: if (infoPanel != null) {
1530: table.fireChange();
1531: infoPanel.getBottomComponent().repaint();
1532: }
1533:
1534: } else
1535: */
1536: if (nm == null) {
1537: if (currNode != null) {
1538: setCurrentNode(currNode);
1539: }
1540: } else {
1541: table.repaintProperty(nm);
1542: }
1543: }
1544: });
1545: }
1546: }
1547: }
1548:
1549: private static final class TabInfo {
1550: public String[] titles;
1551: public Object[] sets;
1552:
1553: public TabInfo(String[] titles, Object[] sets) {
1554: this .titles = titles;
1555: this .sets = sets;
1556: }
1557:
1558: public PropertySet[] getSets(int i) {
1559: return (PropertySet[]) sets[i];
1560: }
1561: }
1562: }
|