001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.palette;
043:
044: import java.awt.datatransfer.Transferable;
045: import java.text.MessageFormat;
046: import java.util.Collections;
047: import java.util.List;
048: import javax.swing.Action;
049: import org.netbeans.spi.palette.PaletteActions;
050: import org.netbeans.spi.palette.PaletteController;
051: import org.netbeans.spi.palette.PaletteFilter;
052: import org.openide.DialogDisplayer;
053: import org.openide.NotifyDescriptor;
054:
055: import org.openide.filesystems.FileObject;
056: import org.openide.loaders.DataFolder;
057: import org.openide.loaders.DataObject;
058: import org.openide.loaders.DataShadow;
059: import org.openide.nodes.FilterNode;
060: import org.openide.nodes.Node;
061: import org.openide.util.HelpCtx;
062: import org.openide.util.Utilities;
063: import org.openide.util.Lookup;
064: import org.openide.util.LookupEvent;
065: import org.openide.util.datatransfer.PasteType;
066: import org.openide.util.lookup.AbstractLookup;
067: import org.openide.util.lookup.InstanceContent;
068: import org.openide.util.lookup.ProxyLookup;
069:
070: /**
071: * A node for palette category.
072: *
073: * @author S. Aubrecht
074: */
075: class CategoryNode extends FilterNode {
076:
077: static final Node.PropertySet[] NO_PROPERTIES = new Node.PropertySet[0];
078:
079: static final String CAT_NAME = "categoryName"; // NOI18N
080:
081: private Action[] actions;
082:
083: CategoryNode(Node originalNode, Lookup lkp) {
084: this (originalNode, new InstanceContent(), lkp);
085: }
086:
087: private CategoryNode(Node originalNode, InstanceContent content,
088: Lookup lkp) {
089: super (originalNode, new Children(originalNode, lkp),
090: new ProxyLookup(new Lookup[] { lkp,
091: new AbstractLookup(content),
092: originalNode.getLookup() }));
093:
094: DataFolder folder = (DataFolder) originalNode
095: .getCookie(DataFolder.class);
096: if (null != folder) {
097: content.add(new DataFolder.Index(folder, this ));
098: FileObject fob = folder.getPrimaryFile();
099: Object catName = fob.getAttribute(CAT_NAME);
100: if (catName instanceof String)
101: setDisplayName((String) catName);
102: }
103: content.add(this );
104: }
105:
106: // -------
107:
108: public String getDisplayName() {
109:
110: String retValue = null;
111: DataFolder folder = (DataFolder) getCookie(DataFolder.class);
112: if (null != folder) {
113: FileObject fob = folder.getPrimaryFile();
114: Object catName = fob.getAttribute(CAT_NAME);
115: if (catName instanceof String)
116: retValue = catName.toString();
117: }
118: if (null == retValue) {
119: retValue = super .getDisplayName();
120: }
121: // XXX poor impl; should not depend on org.openide.loaders.Bundle#FMT_shadowName:
122: if (null != retValue && retValue.indexOf("\u2192") > 0) {
123: DataShadow shadow = (DataShadow) getCookie(DataShadow.class);
124: if (null != shadow) {
125: DataObject dobj = shadow.getOriginal();
126: if (null != dobj) {
127: Node origNode = dobj.getNodeDelegate();
128: if (null != origNode
129: && null != origNode.getDisplayName()) {
130: retValue = origNode.getDisplayName();
131: }
132: }
133: }
134: }
135: return retValue;
136: }
137:
138: public void setDisplayName(String displayName) {
139: try {
140: DataFolder folder = (DataFolder) getCookie(DataFolder.class);
141: if (null != folder) {
142: FileObject fo = folder.getPrimaryFile();
143: fo.setAttribute(CAT_NAME, displayName);
144: }
145: } catch (java.io.IOException ex) {
146: RuntimeException e = new IllegalArgumentException();
147: org.openide.ErrorManager.getDefault().annotate(e, ex);
148: throw e;
149: }
150: super .setDisplayName(displayName);
151: }
152:
153: public String getShortDescription() {
154: return getDisplayName();
155: }
156:
157: public Action[] getActions(boolean context) {
158: if (actions == null) {
159: Node n = getParentNode();
160: actions = new Action[] { new Utils.PasteItemAction(this ),
161: null, new Utils.NewCategoryAction(n), null,
162: new Utils.DeleteCategoryAction(this ),
163: new Utils.RenameCategoryAction(this ), null,
164: new Utils.SortItemsAction(this ), null,
165: new Utils.SortCategoriesAction(n), null,
166: new Utils.RefreshPaletteAction() };
167: }
168: PaletteActions customActions = (PaletteActions) getParentNode()
169: .getLookup().lookup(PaletteActions.class);
170: if (null != customActions) {
171: return Utils.mergeActions(actions, customActions
172: .getCustomCategoryActions(getLookup()));
173: }
174: return actions;
175: }
176:
177: public Node.PropertySet[] getPropertySets() {
178: return NO_PROPERTIES;
179: }
180:
181: public boolean canDestroy() {
182: return !Utils.isReadonly(getOriginal());
183: }
184:
185: public HelpCtx getHelpCtx() {
186: return Utils.getHelpCtx(this , super .getHelpCtx());
187: }
188:
189: private static class Children extends FilterNode.Children {
190:
191: private Lookup lkp;
192: private PaletteFilter filter;
193:
194: public Children(Node original, Lookup lkp) {
195: super (original);
196: this .lkp = lkp;
197: this .filter = (PaletteFilter) lkp
198: .lookup(PaletteFilter.class);
199: }
200:
201: protected Node copyNode(Node node) {
202: return new ItemNode(node);
203: }
204:
205: protected Node[] createNodes(Node key) {
206: if (null == filter || filter.isValidItem(key.getLookup())) {
207: return new Node[] { copyNode(key) };
208: }
209:
210: return null;
211: }
212:
213: public void resultChanged(LookupEvent ev) {
214: Node[] nodes = original.getChildren().getNodes();
215: List<Node> empty = Collections.emptyList();
216: setKeys(empty);
217: setKeys(nodes);
218: }
219: }
220:
221: /** Checks category name if it is valid and if there's already not
222: * a category with the same name.
223: * @param name name to be checked
224: * @param namedNode node which name is checked or null if it doesn't exist yet
225: * @return true if the name is OK
226: */
227: static boolean checkCategoryName(Node parentNode, String name,
228: Node namedNode) {
229: boolean invalid = false;
230: if (name == null || "".equals(name)) // NOI18N
231: invalid = true;
232: else
233: // name should not start with . or contain only spaces
234: for (int i = 0, n = name.length(); i < n; i++) {
235: char ch = name.charAt(i);
236: if (ch == '.' || (ch == ' ' && i + 1 == n)) {
237: invalid = true;
238: break;
239: } else if (ch != ' ')
240: break;
241: }
242:
243: if (invalid) {
244: DialogDisplayer.getDefault().notify(
245: new NotifyDescriptor.Message(MessageFormat.format(
246: Utils.getBundleString("ERR_InvalidName"), // NOI18N
247: new Object[] { name }),
248: NotifyDescriptor.INFORMATION_MESSAGE));
249: return false;
250: }
251:
252: Node[] nodes = parentNode.getChildren().getNodes();
253: for (int i = 0; i < nodes.length; i++)
254: if (name.equals(nodes[i].getName())
255: && nodes[i] != namedNode) {
256: DialogDisplayer
257: .getDefault()
258: .notify(
259: new NotifyDescriptor.Message(
260: MessageFormat
261: .format(
262: Utils
263: .getBundleString("FMT_CategoryExists"), // NOI18N
264: new Object[] { name }),
265: NotifyDescriptor.INFORMATION_MESSAGE));
266: return false;
267: }
268:
269: return true;
270: }
271:
272: /** Converts category name to name that can be used as name of folder
273: * for the category (restricted even to package name).
274: */
275: static String convertCategoryToFolderName(FileObject paletteFO,
276: String name, String currentName) {
277: if (name == null || "".equals(name)) // NOI18N
278: return null;
279:
280: int i;
281: int n = name.length();
282: StringBuffer nameBuff = new StringBuffer(n);
283:
284: char ch = name.charAt(0);
285: if (Character.isJavaIdentifierStart(ch)) {
286: nameBuff.append(ch);
287: i = 1;
288: } else {
289: nameBuff.append('_');
290: i = 0;
291: }
292:
293: while (i < n) {
294: ch = name.charAt(i);
295: if (Character.isJavaIdentifierPart(ch))
296: nameBuff.append(ch);
297: i++;
298: }
299:
300: String fName = nameBuff.toString();
301: if ("_".equals(fName)) // NOI18N
302: fName = "Category"; // NOI18N
303: if (fName.equals(currentName))
304: return fName;
305:
306: // having the base name, make sure it is not used yet
307: String freeName = null;
308: boolean nameOK = false;
309:
310: for (i = 0; !nameOK; i++) {
311: freeName = i > 0 ? fName + "_" + i : fName; // NOI18N
312:
313: if (Utilities.isWindows()) {
314: nameOK = true;
315: java.util.Enumeration en = paletteFO.getChildren(false);
316: while (en.hasMoreElements()) {
317: FileObject fo = (FileObject) en.nextElement();
318: String fn = fo.getName();
319: String fe = fo.getExt();
320:
321: // case-insensitive on Windows
322: if ((fe == null || "".equals(fe))
323: && fn.equalsIgnoreCase(freeName)) { // NOI18N
324: nameOK = false;
325: break;
326: }
327: }
328: } else
329: nameOK = paletteFO.getFileObject(freeName) == null;
330: }
331: return freeName;
332: }
333:
334: public PasteType getDropType(Transferable t, int action, int index) {
335: if (t.isDataFlavorSupported(PaletteController.ITEM_DATA_FLAVOR))
336: return super.getDropType(t, action, index);
337: return null;
338: }
339: }
|