All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openide.nodes.AbstractNode Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.openide.nodes;

import org.openide.util.HelpCtx;
import org.openide.util.ImageUtilities;
import org.openide.util.Lookup;
import org.openide.util.actions.SystemAction;
import org.openide.util.datatransfer.*;

import java.awt.Image;
import java.awt.datatransfer.Transferable;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import java.io.IOException;

import java.text.MessageFormat;

import java.util.*;

import javax.swing.Action;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.openide.util.Exceptions;


/** A basic implementation of a node.
*
* 

It simplifies creation of the display name, based on a message * format and the system name. It also simplifies working with icons: * one need only specify the base name and all icons will be loaded * when needed. Other common requirements are handled as well. * * @author Jaroslav Tulach */ public class AbstractNode extends Node { /** messages to create a resource identification for each type of * icon from the base name for the icon. */ private static final String[] icons = { // color 16x16 "", // NOI18N // color 32x32 "32", // NOI18N // mono 16x16 "", // NOI18N // mono 32x32 "32", // NOI18N // opened color 16x16 "Open", // NOI18N // opened color 32x32 "Open32", // NOI18N // opened mono 16x16 "Open", // NOI18N // opened mono 32x32 "Open32" // NOI18N }; /** To index normal icon from previous array use * + ICON_BASE. */ private static final int ICON_BASE = -1; /** for indexing opened icons */ private static final int OPENED_ICON_BASE = 3; /** empty array of paste types */ private static final PasteType[] NO_PASTE_TYPES = { }; /** empty array of new types */ private static final NewType[] NO_NEW_TYPES = { }; /** default icon base for all nodes */ private static final String DEFAULT_ICON_BASE = "org/openide/nodes/defaultNode"; // NOI18N private static final String DEFAULT_ICON_EXTENSION = ".gif"; // NOI18N private static final String DEFAULT_ICON = DEFAULT_ICON_BASE + ".png"; // NOI18N // maps class either to Boolean or to this private static final WeakHashMap overridesGetDefaultAction = new WeakHashMap(32); /** Message format to use for creation of the display name. * It permits conversion of text from * {@link #getName} to the one sent to {@link #setDisplayName}. The format can take * one parameter, {0}, which will be filled by a value from getName(). * *

The default format just uses the simple name; subclasses may * change it, though it will not take effect until the next {@link #setName} call. * *

Can be set to null. Then there is no connection between the * name and display name; they may be independently modified. */ protected MessageFormat displayFormat; /** Preferred action */ private Action preferredAction; /** Resource base for icons (without suffix denoting right icon) */ private String iconBase = DEFAULT_ICON_BASE; /** Resource extension for icons */ private String iconExtension = ".png"; // NOI18N /** array of cookies for this node */ private Object lookup; /** set of properties to use */ private Sheet sheet; /** Actions for the node. They are used only for the pop-up menus * of this node. * @deprecated Override {@link #getActions(boolean)} instead of using * this field. */ @Deprecated protected SystemAction[] systemActions; private SheetAndCookieListener sheetCookieL = null; /** Create a new abstract node with a given child set. * @param children the children to use for this node */ public AbstractNode(Children children) { this(children, null); } /** Create a new abstract node with a given child set and associated * lookup. If you use this constructor, please do not call methods * {@link #getCookieSet} and {@link #setCookieSet} they will throw an * exception. *

* More info on the correct usage of constructor with Lookup can be found * in the {@link Node#Node(org.openide.nodes.Children, org.openide.util.Lookup)} * javadoc. * * @param children the children to use for this node * @param lookup the lookup to provide content of {@link #getLookup} * and also {@link #getCookie} * @since 3.11 */ public AbstractNode(Children children, Lookup lookup) { super(children, lookup); // Setting the name to non-null value for the node // to return "reasonable" name and displayName // not using this.setName since the descendants // can override it and might assume that it is // not called from constructor (see e.g. DataNode) super.setName(""); // NOI18N } /** Fake node constructor with given CookieSet */ AbstractNode(CookieSet set) { super(Children.LEAF); lookup = set; } /** Clone the node. If the object implements {@link Cloneable}, * that is used; otherwise a {@link FilterNode filter node} * is created. * * @return copy of this node */ public Node cloneNode() { try { if (this instanceof Cloneable) { return (Node) clone(); } } catch (CloneNotSupportedException ex) { } return new FilterNode(this); } /** Set the system name. Fires a property change event. * Also may change the display name according to {@link #displayFormat}. * * @param s the new name */ public void setName(String s) { super.setName(s); MessageFormat mf = displayFormat; if (mf != null) { setDisplayName(mf.format(new Object[] { s })); } else { // additional hack, because if no display name is set, then it // is taken from the getName, that means calling setName can // also change display name // fix of 10665 fireDisplayNameChange(null, null); } } /** Change the icon. * One need only specify the base resource name without extension; * the real name of the icon is obtained by the applying icon message * formats. * * The method effectively behaves as if it was just delegating * to {@link #setIconBaseWithExtension(java.lang.String)} * using base + ".gif" as parameter. * * @param base base resouce name (no initial slash) * @deprecated Use {@link #setIconBaseWithExtension(java.lang.String)} */ @Deprecated public void setIconBase(String base) { setIconBaseWithExtension(base, DEFAULT_ICON_EXTENSION); } /** Change the icon. * One need only specify the base name of the icon resource, * including the resource extension; the real name of the icon is obtained * by inserting proper infixes into the resource name. * *

For example, for the base org/foo/resource/MyIcon.png * the following images may be used according to the icon * state and {@link java.beans.BeanInfo#getIcon presentation type}: * *

    *
  • org/foo/resource/MyIcon.png *
  • org/foo/resource/MyIconOpen.png *
  • org/foo/resource/MyIcon32.png *
  • org/foo/resource/MyIconOpen32.png
* *

* This method may be used to dynamically switch between different sets * of icons for different configurations. If the set is changed, * an icon property change event is fired. * * @param baseExt base resouce name with extension (no initial slash) * @since org.openide.nodes 6.5 */ public final void setIconBaseWithExtension(String baseExt) { int lastDot = baseExt.lastIndexOf('.'); int lastSlash = baseExt.lastIndexOf('/'); if ((lastSlash > lastDot) || (lastDot == -1)) { // no .extension setIconBaseWithExtension(baseExt, ""); } else { String base = baseExt.substring(0, lastDot); String ext = baseExt.substring(lastDot); setIconBaseWithExtension(base, ext); } } /** Change the icon. */ private final void setIconBaseWithExtension(String base, String extension) { if (base.equals(iconBase) && extension.equals(iconExtension)) { return; } this.iconBase = base; this.iconExtension = extension; fireIconChange(); fireOpenedIconChange(); } /** Find an icon for this node. Uses an {@link #setIconBase icon set}. * * @param type constants from {@link java.beans.BeanInfo} * * @return icon to use to represent the bean */ public Image getIcon(int type) { return findIcon(type, ICON_BASE); } /** Finds an icon for this node when opened. This icon should represent the node * only when it is opened (when it can have children). * * @param type as in {@link #getIcon} * @return icon to use to represent the bean when opened */ public Image getOpenedIcon(int type) { return findIcon(type, OPENED_ICON_BASE); } public HelpCtx getHelpCtx() { return HelpCtx.DEFAULT_HELP; } /** Tries to find the right icon for the iconbase. * @param type type of icon (from BeanInfo constants) * @param ib base where to scan in the array */ private Image findIcon(int type, int ib) { String res = iconBase + icons[type + ib] + iconExtension; Image im = ImageUtilities.loadImage(res, true); if (im != null) { return im; } // try the first icon res = iconBase + icons[java.beans.BeanInfo.ICON_COLOR_16x16 + ib] + iconExtension; im = ImageUtilities.loadImage(res, true); if (im != null) { return im; } if (ib == OPENED_ICON_BASE) { // try closed icon also return findIcon(type, ICON_BASE); } // if still not found return default icon return getDefaultIcon(); } Image getDefaultIcon() { Image i = ImageUtilities.loadImage(DEFAULT_ICON, true); if (i == null) { throw new MissingResourceException("No default icon", "", DEFAULT_ICON); // NOI18N } return i; } /** Can this node be renamed? * @return false */ public boolean canRename() { return false; } /** Can this node be destroyed? * @return false */ public boolean canDestroy() { return false; } /** Set the set of properties. * A listener is attached to the provided sheet * and any change of the sheet is propagated to the node by * firing a {@link #PROP_PROPERTY_SETS} change event. * * @param s the sheet to use */ protected final synchronized void setSheet(Sheet s) { setSheetImpl(s); firePropertySetsChange(null, null); } private synchronized void setSheetImpl(Sheet s) { if (sheetCookieL == null) { sheetCookieL = new SheetAndCookieListener(); } if (sheet != null) { sheet.removePropertyChangeListener(sheetCookieL); } s.addPropertyChangeListener(sheetCookieL); sheet = s; } /** Initialize a default * property sheet; commonly overridden. If {@link #getSheet} * is called and there is not yet a sheet, * this method is called to allow a subclass * to specify its properties. *

* Warning: Do not call getSheet in this method. *

* The default implementation returns an empty sheet. * * @return the sheet with initialized values (never null) */ protected Sheet createSheet() { return new Sheet(); } /** Get the current property sheet. If the sheet has been * previously set by a call to {@link #setSheet}, that sheet * is returned. Otherwise {@link #createSheet} is called. * * @return the sheet (never null) */ protected final synchronized Sheet getSheet() { if (sheet != null) { return sheet; } Sheet s = createSheet(); if (s == null) { // #150503 throw new IllegalStateException("createSheet returns null in " + this.getClass().getName()); // NOI18N } setSheetImpl(s); return s; } /** Get a list of property sets. * * @return the property sets for this node * @see #getSheet */ public PropertySet[] getPropertySets() { Sheet s = getSheet(); return s.toArray(); } boolean propertySetsAreKnown() { return (sheet != null); } /** Copy this node to the clipboard. * * @return {@link org.openide.util.datatransfer.ExTransferable.Single} with one copy flavor * @throws IOException if it could not copy * @see NodeTransfer */ public Transferable clipboardCopy() throws IOException { return NodeTransfer.transferable(this, NodeTransfer.CLIPBOARD_COPY); } /** Cut this node to the clipboard. * * @return {@link org.openide.util.datatransfer.ExTransferable.Single} with one cut flavor * @throws IOException if it could not cut * @see NodeTransfer */ public Transferable clipboardCut() throws IOException { return NodeTransfer.transferable(this, NodeTransfer.CLIPBOARD_CUT); } /** * This implementation only calls clipboardCopy supposing that * copy to clipboard and copy by d'n'd are similar. * * @return transferable to represent this node during a drag * @exception IOException when the * cut cannot be performed */ public Transferable drag() throws IOException { return clipboardCopy(); } /** Can this node be copied? * @return true */ public boolean canCopy() { return true; } /** Can this node be cut? * @return false */ public boolean canCut() { return false; } /** Accumulate the paste types that this node can handle * for a given transferable. *

* The default implementation simply tests whether the transferable supports * intelligent pasting via {@link NodeTransfer#findPaste}, and if so, it obtains the paste types * from the {@link NodeTransfer.Paste transfer data} and inserts them into the set. *

Subclass implementations should typically call super (first or last) so that they * add to, rather than replace, a superclass's available paste types; especially as the * default implementation in AbstractNode is generally desirable to retain. * * @param t a transferable containing clipboard data * @param s a list of {@link PasteType}s that will have added to it all types * valid for this node (ordered as they will be presented to the user) */ protected void createPasteTypes(Transferable t, List s) { NodeTransfer.Paste p = NodeTransfer.findPaste(t); if (p != null) { // adds all its types into the set s.addAll(Arrays.asList(p.types(this))); } } /** Determine which paste operations are allowed when a given transferable is in the clipboard. * Subclasses should override {@link #createPasteTypes}. * * @param t the transferable in the clipboard * @return array of operations that are allowed */ public final PasteType[] getPasteTypes(Transferable t) { List s = new LinkedList(); createPasteTypes(t, s); return s.toArray(NO_PASTE_TYPES); } /** Default implementation that tries to delegate the implementation * to the createPasteTypes method. Simply calls the method and * tries to take the first provided argument. Ignores the action * argument and index. * * @param t the transferable * @param action the drag'n'drop action to do DnDConstants.ACTION_MOVE, ACTION_COPY, ACTION_LINK * @param index index between children the drop occured at or -1 if not specified * @return null if the transferable cannot be accepted or the paste type * to execute when the drop occures */ public PasteType getDropType(Transferable t, int action, int index) { java.util.List s = new LinkedList(); createPasteTypes(t, s); return s.isEmpty() ? null : s.get(0); } /* List new types that can be created in this node. * @return new types */ public NewType[] getNewTypes() { return NO_NEW_TYPES; } /** Checks whether subclass overrides a method */ private boolean overridesAMethod(String name, Class[] arguments) { // we are subclass of AbstractNode try { java.lang.reflect.Method m = getClass().getMethod(name, arguments); if (m.getDeclaringClass() != AbstractNode.class) { // ok somebody overriden the method return true; } } catch (NoSuchMethodException ex) { Exceptions.printStackTrace(ex); } return false; } /** Gets preferred action. * By default, null. * @return preferred action * @see Node#getPreferredAction * @since 3.29 */ public Action getPreferredAction() { boolean delegate = false; Class c = getClass(); if (c != AbstractNode.class) { synchronized (overridesGetDefaultAction) { Object in = overridesGetDefaultAction.get(c); if (in == this) { // catched in a loop of overriding getDefaultAction and // calling super.getDefaultAction // pretend that we do not override overridesGetDefaultAction.put(c, Boolean.FALSE); return preferredAction; } Boolean b; if (in == null) { b = overridesAMethod("getDefaultAction", new Class[0]) ? Boolean.TRUE : Boolean.FALSE; // NOI18N if (b.booleanValue()) { // check whether it is safe to call the getDefaultAction overridesGetDefaultAction.put(c, this); getDefaultAction(); if (overridesGetDefaultAction.get(c) == this) { // value unchanged, we have not been cought in a loop overridesGetDefaultAction.put(c, b); } } else { overridesGetDefaultAction.put(c, b); } } else { b = (Boolean) in; } delegate = b.booleanValue(); } } return delegate ? getDefaultAction() : preferredAction; } /** Gets the default action. Overrides superclass method. * @return if there is a default action set, then returns it * @deprecated Use {@link #getPreferredAction} instead. */ @Deprecated public SystemAction getDefaultAction() { Action a = getPreferredAction(); if (a instanceof SystemAction) { return (SystemAction) a; } return null; } /** Set a default action for the node. * @param action the new default action, or null for none * @deprecated Override {@link #getPreferredAction} instead. */ @Deprecated public void setDefaultAction(SystemAction action) { preferredAction = action; } /** Get all actions for the node. * Initialized with {@link #createActions}, or with the superclass's list. * * @return actions for the node * @deprecated Override {@link #getActions(boolean)} instead. */ @Deprecated public SystemAction[] getActions() { if (systemActions == null) { systemActions = createActions(); if (systemActions == null) { systemActions = super.getActions(); } } return systemActions; } /** Lazily initialize set of node's actions (overridable). * The default implementation returns null. *

Warning: do not call {@link #getActions} within this method. * @return array of actions for this node, or null to use the default node actions * @deprecated Override {@link #getActions(boolean)} instead. */ @Deprecated protected SystemAction[] createActions() { return null; } /** Does this node have a customizer? * @return false */ public boolean hasCustomizer() { return false; } /** Get the customizer. * @return null in the default implementation */ public java.awt.Component getCustomizer() { return null; } /** Set the cookie set. * A listener is attached to the provided cookie set, * and any change of the sheet is propagated to the node by * firing {@link #PROP_COOKIE} change events. * * @param s the cookie set to use * @deprecated just use getCookieSet().add(...) instead * @exception IllegalStateException If you pass a Lookup instance into the constructor, this * method cannot be called. */ @Deprecated protected final synchronized void setCookieSet(CookieSet s) { if (internalLookup(false) != null) { throw new IllegalStateException("CookieSet cannot be used when lookup is associated with the node"); // NOI18N } if (sheetCookieL == null) { sheetCookieL = new SheetAndCookieListener(); } CookieSet cookieSet = (CookieSet) lookup; if (cookieSet != null) { cookieSet.removeChangeListener(sheetCookieL); } s.addChangeListener(sheetCookieL); lookup = s; fireCookieChange(); } /** Get the cookie set. * * @return the cookie set created by {@link #setCookieSet}, or an empty set (never null) * @exception IllegalStateException If you pass a Lookup instance into the constructor, this * method cannot be called. */ protected final CookieSet getCookieSet() { if (internalLookup(false) != null) { throw new IllegalStateException("CookieSet cannot be used when lookup is associated with the node"); // NOI18N } CookieSet s = (CookieSet) lookup; if (s != null) { return s; } synchronized (this) { if (lookup != null) { return (CookieSet) lookup; } // sets empty sheet and adds a listener to it setCookieSet(new CookieSet()); return (CookieSet) lookup; } } /** Get a cookie from the node. * Uses the cookie set as determined by {@link #getCookieSet}. * * @param type the representation class * @return the cookie or null */ @Override public T getCookie(Class type) { if (lookup instanceof CookieSet) { CookieSet c = (CookieSet) lookup; return c.getCookie(type); } else { return super.getCookie(type); } } /** Get a serializable handle for this node. * @return a {@link DefaultHandle} in the default implementation */ public Handle getHandle() { return DefaultHandle.createHandle(this); } /** Listener for changes in the sheet and the cookie set. */ private final class SheetAndCookieListener implements PropertyChangeListener, ChangeListener { SheetAndCookieListener() { } public void propertyChange(PropertyChangeEvent ev) { AbstractNode.this.firePropertySetsChange(null, null); } public void stateChanged(ChangeEvent ev) { AbstractNode.this.fireCookieChange(); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy