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.form.palette;
043:
044: import java.awt.Image;
045: import java.beans.PropertyChangeEvent;
046: import java.beans.PropertyChangeListener;
047: import java.lang.ref.WeakReference;
048: import java.util.*;
049: import java.io.IOException;
050:
051: import org.netbeans.spi.palette.*;
052: import org.openide.ErrorManager;
053: import org.openide.nodes.*;
054: import org.openide.loaders.DataFolder;
055: import org.openide.loaders.DataObject;
056: import org.openide.filesystems.*;
057: import org.openide.util.*;
058: import org.openide.util.lookup.*;
059:
060: import org.netbeans.api.java.classpath.ClassPath;
061: import org.netbeans.api.project.Project;
062: import org.netbeans.api.project.FileOwnerQuery;
063:
064: import org.netbeans.modules.form.project.ClassSource;
065:
066: /**
067: * Class providing various useful methods for palette classes.
068: *
069: * @author Tomas Pavek, Jan Stola
070: */
071:
072: public final class PaletteUtils {
073:
074: private static FileObject paletteFolder;
075: private static DataFolder paletteDataFolder;
076:
077: private static FileObject context;
078: private static Map<Project, ProjectPaletteInfo> palettes = new WeakHashMap<Project, ProjectPaletteInfo>();
079:
080: private static class ProjectPaletteInfo {
081: PaletteLookup paletteLookup;
082: ClassPathFilter paletteFilter;
083: List<PropertyChangeListener> paletteListeners;
084:
085: PaletteController getPalette() {
086: return paletteLookup.lookup(PaletteController.class);
087: }
088: }
089:
090: private PaletteUtils() {
091: }
092:
093: static String getItemComponentDescription(PaletteItem item) {
094: ClassSource classSource = item.getComponentClassSource();
095:
096: if (classSource == null || !classSource.hasEntries()) {
097: String className = classSource.getClassName();
098: if (className != null) {
099: if (className.startsWith("javax.") // NOI18N
100: || className.startsWith("java.")) // NOI18N
101: return getBundleString("MSG_StandardJDKComponent"); // NOI18N
102: if (className.startsWith("org.netbeans.")) // NOI18N
103: return getBundleString("MSG_NetBeansComponent"); // NOI18N
104: }
105: return getBundleString("MSG_UnspecifiedComponent"); // NOI18N
106: } else {
107: return NbBundle.getMessage(PaletteUtils.class,
108: "FMT_ComponentFrom", classSource.getEntries()
109: .iterator().next().getDisplayName());
110: }
111: }
112:
113: public static FileObject getPaletteFolder() {
114: if (paletteFolder != null)
115: return paletteFolder;
116:
117: try {
118: paletteFolder = Repository.getDefault()
119: .getDefaultFileSystem().findResource(
120: "FormDesignerPalette"); // NOI18N
121: if (paletteFolder == null) // not found, create new folder
122: paletteFolder = Repository.getDefault()
123: .getDefaultFileSystem().getRoot().createFolder(
124: "FormDesignerPalette"); // NOI18N
125: } catch (java.io.IOException ex) {
126: throw new IllegalStateException(
127: "Palette folder not found and cannot be created."); // NOI18N
128: }
129: return paletteFolder;
130: }
131:
132: public static Node getPaletteNode() {
133: return getPaletteDataFolder().getNodeDelegate();
134: }
135:
136: public static void showPaletteManager() {
137: try {
138: PaletteFactory.createPalette(
139: "FormDesignerPalette", // NOI18N
140: new FormPaletteActions(),
141: new ClassPathFilter(null), // filters out only invisible Layouts category
142: null).showCustomizer();
143: } catch (IOException ex) {
144: ErrorManager.getDefault().notify(ex);
145: }
146: }
147:
148: public static void setContext(FileObject fileInProject) {
149: context = fileInProject;
150: }
151:
152: public static synchronized void addPaletteListener(
153: PropertyChangeListener listener, FileObject context) {
154: ProjectPaletteInfo pInfo = preparePalette(context);
155: if (pInfo != null) {
156: if (pInfo.paletteListeners == null) {
157: pInfo.paletteListeners = new LinkedList<PropertyChangeListener>();
158: }
159: pInfo.paletteListeners.add(listener);
160: pInfo.getPalette().addPropertyChangeListener(listener);
161: }
162: }
163:
164: public static synchronized void removePaletteListener(
165: PropertyChangeListener listener, FileObject context) {
166: Project project = FileOwnerQuery.getOwner(context);
167: if (project != null) {
168: ProjectPaletteInfo pInfo = palettes.get(project);
169: if (pInfo != null && pInfo.paletteListeners != null) {
170: pInfo.paletteListeners.remove(listener);
171: pInfo.getPalette().removePropertyChangeListener(
172: listener);
173: }
174: }
175: }
176:
177: public static Lookup getPaletteLookup(FileObject context) {
178: ProjectPaletteInfo pInfo = preparePalette(context);
179: return pInfo != null ? pInfo.paletteLookup : Lookups
180: .fixed(new Object[0]);
181: }
182:
183: private static PaletteController getPalette() {
184: ProjectPaletteInfo pInfo = preparePalette(context);
185: return pInfo != null ? pInfo.getPalette() : null;
186: }
187:
188: private static ClassPathFilter getPaletteFilter() {
189: if (context != null) {
190: Project project = FileOwnerQuery.getOwner(context);
191: if (project != null) {
192: ProjectPaletteInfo pInfo = palettes.get(project);
193: if (pInfo != null)
194: return pInfo.paletteFilter;
195: }
196: }
197: return null;
198: }
199:
200: /**
201: * Gets the registered palette and related data for given context (project
202: * of given file). Creates new palette if does not exist yet.
203: */
204: private static ProjectPaletteInfo preparePalette(FileObject context) {
205: if (context == null)
206: return null;
207:
208: Project project = FileOwnerQuery.getOwner(context);
209: if (project == null)
210: return null;
211:
212: ProjectPaletteInfo pInfo = palettes.get(project);
213: if (pInfo == null) {
214: ClassPath classPath = ClassPath.getClassPath(context,
215: ClassPath.BOOT);
216: classPath.addPropertyChangeListener(new ClassPathListener(
217: classPath, project));
218:
219: PaletteLookup lookup = new PaletteLookup();
220: ClassPathFilter filter = new ClassPathFilter(classPath);
221: lookup.setPalette(createPalette(filter));
222:
223: pInfo = new ProjectPaletteInfo();
224: pInfo.paletteLookup = lookup;
225: pInfo.paletteFilter = filter;
226: palettes.put(project, pInfo);
227: }
228: return pInfo;
229: }
230:
231: /**
232: * Creates a new palette with filter for given ClassPath.
233: */
234: private static PaletteController createPalette(
235: ClassPathFilter filter) {
236: try {
237: return PaletteFactory.createPalette("FormDesignerPalette", // NOI18N
238: new FormPaletteActions(), filter, null);
239: } catch (IOException ex) {
240: ErrorManager.getDefault().notify(
241: ErrorManager.INFORMATIONAL, ex);
242: return null;
243: }
244: }
245:
246: /**
247: * Called when the project's boot classpath changes (typically means that
248: * the project's platform has changed). This method creates a new palette
249: * with a filter based on the new classpath, updating the lookup providing
250: * the palette. Palette listeners are transferred automatically.
251: */
252: private static synchronized void bootClassPathChanged(Project p,
253: ClassPath cp) {
254: ProjectPaletteInfo pInfo = palettes.get(p);
255: if (pInfo != null) {
256: PaletteLookup lookup = pInfo.paletteLookup;
257: PaletteController oldPalette = pInfo.getPalette();
258: oldPalette.clearSelection();
259: ClassPathFilter newFilter = new ClassPathFilter(cp);
260: PaletteController newPalette = createPalette(newFilter);
261: if (pInfo.paletteListeners != null) {
262: for (PropertyChangeListener l : pInfo.paletteListeners) {
263: oldPalette.removePropertyChangeListener(l);
264: newPalette.addPropertyChangeListener(l);
265: }
266: }
267: lookup.setPalette(newPalette);
268: pInfo.paletteFilter = newFilter;
269: }
270: }
271:
272: static DataFolder getPaletteDataFolder() {
273: if (paletteDataFolder == null)
274: paletteDataFolder = DataFolder
275: .findFolder(getPaletteFolder());
276: return paletteDataFolder;
277: }
278:
279: public static void clearPaletteSelection() {
280: PaletteController palette = getPalette();
281: if (palette != null) {
282: palette.clearSelection();
283: }
284: }
285:
286: public static PaletteItem getSelectedItem() {
287: PaletteController palette = getPalette();
288: if (palette == null) {
289: return null;
290: }
291: Lookup lkp = palette.getSelectedItem();
292:
293: return lkp.lookup(PaletteItem.class);
294: }
295:
296: public static void selectItem(PaletteItem item) {
297: if (null == item) {
298: clearPaletteSelection();
299: } else {
300: // This is not the node returned by getPaletteNode()!
301: Node paletteNode = getPalette().getRoot()
302: .lookup(Node.class);
303: Node[] categories = getCategoryNodes(paletteNode, true,
304: true, true, true);
305: for (int i = 0; i < categories.length; i++) {
306: Node[] items = getItemNodes(categories[i], true);
307: for (int j = 0; j < items.length; j++) {
308: PaletteItem formItem = items[j].getLookup().lookup(
309: PaletteItem.class);
310: if (item.equals(formItem)) {
311: getPalette().setSelectedItem(
312: categories[i].getLookup(),
313: items[j].getLookup());
314: }
315: }
316: }
317: }
318: }
319:
320: public static Image getIconForClass(String className, int type,
321: boolean optimalResult) {
322: Image img = null;
323: for (PaletteItem item : getAllItems(optimalResult)) {
324: if (PaletteItem.TYPE_CHOOSE_BEAN.equals(item
325: .getExplicitComponentType())) {
326: continue;
327: }
328: if (className.equals(item.getComponentClassName())) {
329: Node node = item.getNode();
330: if (node != null) {
331: img = node.getIcon(type);
332: } else {
333: img = item.getIcon(type);
334: }
335: }
336: }
337: return img;
338: }
339:
340: public static PaletteItem[] getAllItems() {
341: return getAllItems(true);
342: }
343:
344: public static PaletteItem[] getAllItems(boolean optimalResult) {
345: HashSet<PaletteItem> uniqueItems = null;
346: // collect valid items from all categories (including invisible)
347: Node[] categories = getCategoryNodes(getPaletteNode(), false,
348: true, false, optimalResult);
349: for (int i = 0; i < categories.length; i++) {
350: Node[] items = getItemNodes(categories[i], true,
351: optimalResult);
352: for (int j = 0; j < items.length; j++) {
353: PaletteItem formItem = items[j].getLookup().lookup(
354: PaletteItem.class);
355: if (null != formItem) {
356: if (null == uniqueItems) {
357: uniqueItems = new HashSet<PaletteItem>();
358: }
359: if (!PaletteItem.TYPE_CHOOSE_BEAN.equals(formItem
360: .getExplicitComponentType())) {
361: uniqueItems.add(formItem);
362: }
363: }
364: }
365: }
366: PaletteItem[] res;
367: if (null != uniqueItems) {
368: res = uniqueItems.toArray(new PaletteItem[uniqueItems
369: .size()]);
370: } else {
371: res = new PaletteItem[0];
372: }
373: return res;
374: }
375:
376: static String getBundleString(String key) {
377: return NbBundle.getBundle(PaletteUtils.class).getString(key);
378: }
379:
380: public static Node[] getItemNodes(Node categoryNode,
381: boolean mustBeValid) {
382: return getItemNodes(categoryNode, mustBeValid, true);
383: }
384:
385: /**
386: * Get an array of Node for the given category.
387: *
388: * @param categoryNode Category node.
389: * @param mustBeValid True if all the nodes returned must be valid palette items.
390: * @return An array of Nodes for the given category.
391: */
392: private static Node[] getItemNodes(Node categoryNode,
393: boolean mustBeValid, boolean optimalResult) {
394: Node[] nodes = categoryNode.getChildren().getNodes(
395: optimalResult);
396: if (!mustBeValid)
397: return nodes;
398:
399: ClassPathFilter filter = getPaletteFilter();
400: if (filter == null)
401: return nodes;
402:
403: List<Node> validList = null;
404: for (int i = 0; i < nodes.length; i++) {
405: PaletteItem item = nodes[i].getCookie(PaletteItem.class);
406: if (filter.isValidItem(item)) {
407: if (validList != null)
408: validList.add(nodes[i]);
409: } else if (validList == null) {
410: validList = new ArrayList<Node>(nodes.length);
411: for (int j = 0; j < i; j++) {
412: validList.add(nodes[j]);
413: }
414: }
415: }
416: if (validList != null)
417: nodes = validList.toArray(new Node[validList.size()]);
418:
419: return nodes;
420: }
421:
422: /**
423: * Get an array of all categories in the given palette.
424: *
425: * @param paletteNode Palette's root node.
426: * @param mustBeVisible True to return only visible categories, false to return also
427: * categories with Hidden flag.
428: * @return An array of categories in the given palette.
429: */
430: public static Node[] getCategoryNodes(Node paletteNode,
431: boolean mustBeVisible) {
432: return getCategoryNodes(paletteNode, mustBeVisible,
433: mustBeVisible, true, true);
434: }
435:
436: /**
437: * Get an array of all categories in the given palette.
438: *
439: * @param paletteNode Palette's root node.
440: * @param mustBeVisible True to return only visible categories, false to return also
441: * categories with Hidden flag (user can setup what's visibile in palette manager).
442: * @param mustBeValid True to return only categories containing some
443: * classpath-valid items, false to don't care about platform classpath.
444: * @param mustBePaletteCategory True to return only categories not tagged as
445: * 'isNoPaletteCategory' (marks a never visible category like Layouts)
446: * @return An array of category nodes in the given palette.
447: */
448: private static Node[] getCategoryNodes(Node paletteNode,
449: boolean mustBeVisible, boolean mustBeValid,
450: boolean mustBePaletteCategory, boolean optimalResult) {
451: if (mustBeVisible)
452: mustBeValid = mustBePaletteCategory = true;
453:
454: Node[] nodes = paletteNode.getChildren()
455: .getNodes(optimalResult);
456:
457: ClassPathFilter filter = mustBeValid ? getPaletteFilter()
458: : null;
459: java.util.List<Node> list = null; // don't create until needed
460: for (int i = 0; i < nodes.length; i++) {
461: if ((!mustBeVisible || isVisibleCategoryNode(nodes[i]))
462: && (!mustBeValid || filter == null || filter
463: .isValidCategory(nodes[i]))
464: && (!mustBePaletteCategory || representsShowableCategory(nodes[i]))) { // this is a relevant category
465: if (list != null) {
466: list.add(nodes[i]);
467: }
468: } else if (list == null) {
469: list = new ArrayList<Node>(nodes.length);
470: for (int j = 0; j < i; j++) {
471: list.add(nodes[j]);
472: }
473: }
474: }
475: if (list != null) {
476: nodes = new Node[list.size()];
477: list.toArray(nodes);
478: }
479: return nodes;
480: }
481:
482: /**
483: * @return True if the given node is a DataFolder and does not have Hidden flag set.
484: */
485: private static boolean isVisibleCategoryNode(Node node) {
486: DataFolder df = node.getCookie(DataFolder.class);
487: if (df != null) {
488: Object value = node.getValue("psa_"
489: + PaletteController.ATTR_IS_VISIBLE); // NOI18N
490: if (null == value || "null".equals(value)) { // NOI18N
491: value = df.getPrimaryFile().getAttribute(
492: PaletteController.ATTR_IS_VISIBLE);
493: }
494: if (value == null) {
495: value = Boolean.TRUE;
496: }
497: return Boolean.valueOf(value.toString()).booleanValue();
498: }
499: return false;
500: }
501:
502: private static boolean representsShowableCategory(Node node) {
503: DataFolder df = node.getCookie(DataFolder.class);
504: return (df != null)
505: && !Boolean.TRUE.equals(df.getPrimaryFile()
506: .getAttribute("isNoPaletteCategory")); // NOI18N
507: }
508:
509: // -----
510:
511: /**
512: * Filter for PaletteController. Filters items from platform (i.e. not user
513: * beans) based on given classpath. If classpath is null, all items pass.
514: * Also filters out categories containing only unavailable items.
515: * Always filters out permanently invisible categories (e.g. Layouts).
516: */
517: private static class ClassPathFilter extends PaletteFilter {
518: private ClassPath classPath;
519: private Set<PaletteItem> validItems;
520: private Set<PaletteItem> invalidItems;
521:
522: ClassPathFilter(ClassPath cp) {
523: if (cp != null) {
524: validItems = new WeakSet<PaletteItem>();
525: invalidItems = new WeakSet<PaletteItem>();
526: }
527: classPath = cp;
528: }
529:
530: public boolean isValidCategory(Lookup lkp) {
531: Node categoryNode = lkp.lookup(Node.class);
532: if (!representsShowableCategory(categoryNode))
533: return false; // filter out categories that should never be visible (e.g. Layouts)
534:
535: return isValidCategory(categoryNode);
536: }
537:
538: boolean isValidCategory(Node node) {
539: if (classPath == null)
540: return true;
541:
542: // check if there is some valid item in this category
543: // [ideally we should listen on the category for adding/removing items,
544: // practically we just need to hide Swing categories on some mobile platforms]
545: DataFolder folder = node.getCookie(DataFolder.class);
546: if (folder == null)
547: return false;
548:
549: DataObject[] dobjs = folder.getChildren();
550: for (int i = 0; i < dobjs.length; i++) {
551: PaletteItem item = dobjs[i]
552: .getCookie(PaletteItem.class);
553: if (item == null || isValidItem(item))
554: return true;
555: }
556: return dobjs.length == 0;
557: }
558:
559: public boolean isValidItem(Lookup lkp) {
560: return isValidItem(lkp.lookup(PaletteItem.class));
561: }
562:
563: boolean isValidItem(PaletteItem item) {
564: if (classPath == null)
565: return true;
566:
567: if (item == null) // Issue 81506
568: return false;
569:
570: if (item.getComponentClassSource().hasEntries()
571: || PaletteItem.TYPE_CHOOSE_BEAN.equals(item
572: .getExplicitComponentType())
573: || "org.netbeans.modules.form.layoutsupport.delegates.NullLayoutSupport"
574: .equals(item.getComponentClassName())) // NOI18N
575: return true; // this is not a platform component
576:
577: if (validItems.contains(item)) {
578: return true;
579: } else if (invalidItems.contains(item)) {
580: return false;
581: }
582:
583: // check if the class is available on platform classpath
584: String resName = item.getComponentClassName().replace('.',
585: '/').concat(".class"); // NOI18N
586: if (classPath.findResource(resName) != null) {
587: validItems.add(item);
588: return true;
589: } else {
590: invalidItems.add(item);
591: return false;
592: }
593: }
594: }
595:
596: /**
597: * Reacts on classpath changes and updates the palette for given project
598: * accordingly (in lookup).
599: */
600: private static class ClassPathListener implements
601: PropertyChangeListener {
602: private ClassPath classPath;
603: private WeakReference<Project> projRef;
604:
605: ClassPathListener(ClassPath cp, Project p) {
606: classPath = cp;
607: projRef = new WeakReference<Project>(p);
608: }
609:
610: public void propertyChange(PropertyChangeEvent evt) {
611: if (ClassPath.PROP_ROOTS.equals(evt.getPropertyName())) {
612: Project p = projRef.get();
613: if (p != null)
614: PaletteUtils.bootClassPathChanged(p, classPath);
615: else
616: classPath.removePropertyChangeListener(this );
617: }
618: }
619: }
620:
621: /**
622: * Lookup providing a PaletteController. Can be updated with a new instance.
623: */
624: private static class PaletteLookup extends AbstractLookup {
625: private InstanceContent content;
626:
627: PaletteLookup() {
628: this (new InstanceContent());
629: }
630:
631: private PaletteLookup(InstanceContent content) {
632: super (content);
633: this .content = content;
634: }
635:
636: void setPalette(PaletteController palette) {
637: content.set(Arrays
638: .asList(new PaletteController[] { palette }), null);
639: }
640: }
641: }
|