001: /*
002: * The contents of this file are subject to the terms of the Common Development
003: * and Distribution License (the License). You may not use this file except in
004: * compliance with the License.
005: *
006: * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
007: * or http://www.netbeans.org/cddl.txt.
008: *
009: * When distributing Covered Code, include this CDDL Header Notice in each file
010: * and include the License file at http://www.netbeans.org/cddl.txt.
011: * If applicable, add the following below the CDDL Header, with the fields
012: * enclosed by brackets [] replaced by your own identifying information:
013: * "Portions Copyrighted [year] [name of copyright owner]"
014: *
015: * The Original Software is NetBeans. The Initial Developer of the Original
016: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
017: * Microsystems, Inc. All Rights Reserved.
018: */
019:
020: package org.netbeans.modules.sql.project.ui;
021:
022: import java.awt.Component;
023: import java.awt.Image;
024: import java.awt.Panel;
025: import java.awt.datatransfer.Transferable;
026: import java.awt.datatransfer.DataFlavor;
027: import java.awt.datatransfer.UnsupportedFlavorException;
028: import java.awt.image.BufferedImage;
029: import java.io.IOException;
030: import java.util.Arrays;
031: import java.util.Collections;
032: import java.util.Iterator;
033: import java.util.List;
034: import java.util.ArrayList;
035: import java.util.Set;
036: import javax.swing.Action;
037: import javax.swing.Icon;
038: import javax.swing.ImageIcon;
039: import org.netbeans.api.project.SourceGroup;
040: import org.netbeans.spi.project.ui.support.CommonProjectActions;
041: import org.openide.ErrorManager;
042: import org.openide.filesystems.FileObject;
043: import org.openide.filesystems.FileStateInvalidException;
044: import org.openide.filesystems.FileStatusEvent;
045: import org.openide.filesystems.FileStatusListener;
046: import org.openide.filesystems.FileSystem;
047: import org.openide.filesystems.FileUtil;
048: import org.openide.loaders.DataFolder;
049: import org.openide.loaders.DataObject;
050: import org.openide.nodes.AbstractNode;
051: import org.openide.nodes.Node;
052: import org.openide.nodes.NodeNotFoundException;
053: import org.openide.nodes.NodeOp;
054: import org.openide.nodes.PropertySupport;
055: import org.openide.nodes.Sheet;
056: import org.openide.util.Lookup;
057: import org.openide.util.NbBundle;
058: import org.openide.util.RequestProcessor;
059: import org.openide.util.Utilities;
060: import org.openide.util.datatransfer.ExTransferable;
061: import org.openide.util.datatransfer.MultiTransferObject;
062: import org.openide.util.datatransfer.PasteType;
063: import org.openide.util.lookup.AbstractLookup;
064: import org.openide.util.lookup.InstanceContent;
065: import org.openide.util.lookup.Lookups;
066: import org.openide.util.lookup.ProxyLookup;
067: import org.openidex.search.SearchInfo;
068: import org.openidex.search.SearchInfoFactory;
069:
070: /** Node displaying a packages in given SourceGroup
071: * @author Petr Hrebejk
072: */
073: final class PackageRootNode extends AbstractNode implements Runnable,
074: FileStatusListener {
075:
076: static Image PACKAGE_BADGE = Utilities
077: .loadImage("org/netbeans/spi/java/project/support/ui/packageBadge.gif"); // NOI18N
078:
079: private static Action actions[];
080:
081: private SourceGroup group;
082:
083: private final FileObject file;
084: private final Set files;
085: private FileStatusListener fileSystemListener;
086: private RequestProcessor.Task task;
087: private volatile boolean iconChange;
088: private volatile boolean nameChange;
089:
090: PackageRootNode(SourceGroup group) {
091: this (group, new InstanceContent());
092: }
093:
094: private PackageRootNode(SourceGroup group, InstanceContent ic) {
095: super (new PackageViewChildren(group.getRootFolder()),
096: new ProxyLookup(new Lookup[] { createLookup(group),
097: new AbstractLookup(ic) }));
098: ic.add(alwaysSearchableSearchInfo(SearchInfoFactory
099: .createSearchInfoBySubnodes(this )));
100: this .group = group;
101: file = group.getRootFolder();
102: files = Collections.singleton(file);
103: try {
104: FileSystem fs = file.getFileSystem();
105: fileSystemListener = FileUtil.weakFileStatusListener(this ,
106: fs);
107: fs.addFileStatusListener(fileSystemListener);
108: } catch (FileStateInvalidException e) {
109: ErrorManager err = ErrorManager.getDefault();
110: err.annotate(e, "Can not get " + file
111: + " filesystem, ignoring..."); // NO18N
112: err.notify(ErrorManager.INFORMATIONAL, e);
113: }
114: setName(group.getName());
115: setDisplayName(group.getDisplayName());
116: // setIconBase("org/netbeans/modules/java/j2seproject/ui/resources/packageRoot");
117: }
118:
119: public Image getIcon(int type) {
120: return computeIcon(false, type);
121: }
122:
123: public Image getOpenedIcon(int type) {
124: return computeIcon(true, type);
125: }
126:
127: public String getDisplayName() {
128: String s = super .getDisplayName();
129:
130: try {
131: s = file.getFileSystem().getStatus().annotateName(s, files);
132: } catch (FileStateInvalidException e) {
133: ErrorManager.getDefault().notify(
134: ErrorManager.INFORMATIONAL, e);
135: }
136:
137: return s;
138: }
139:
140: public String getHtmlDisplayName() {
141: try {
142: FileSystem.Status stat = file.getFileSystem().getStatus();
143: if (stat instanceof FileSystem.HtmlStatus) {
144: FileSystem.HtmlStatus hstat = (FileSystem.HtmlStatus) stat;
145:
146: String result = hstat.annotateNameHtml(super
147: .getDisplayName(), files);
148:
149: //Make sure the super string was really modified
150: if (!super .getDisplayName().equals(result)) {
151: return result;
152: }
153: }
154: } catch (FileStateInvalidException e) {
155: ErrorManager.getDefault().notify(
156: ErrorManager.INFORMATIONAL, e);
157: }
158: return super .getHtmlDisplayName();
159: }
160:
161: public void run() {
162: if (iconChange) {
163: fireIconChange();
164: fireOpenedIconChange();
165: iconChange = false;
166: }
167: if (nameChange) {
168: fireDisplayNameChange(null, null);
169: nameChange = false;
170: }
171: }
172:
173: public void annotationChanged(FileStatusEvent event) {
174: if (task == null) {
175: task = RequestProcessor.getDefault().create(this );
176: }
177:
178: if ((iconChange == false && event.isIconChange())
179: || (nameChange == false && event.isNameChange())) {
180: if (event.hasChanged(file)) {
181: iconChange |= event.isIconChange();
182: nameChange |= event.isNameChange();
183: }
184: }
185:
186: task.schedule(50); // batch by 50 ms
187: }
188:
189: public Action[] getActions(boolean context) {
190:
191: if (actions == null) {
192: actions = new Action[] {
193: CommonProjectActions.newFileAction(),
194: null,
195: org.openide.util.actions.SystemAction
196: .get(org.openide.actions.FileSystemAction.class),
197: null,
198: org.openide.util.actions.SystemAction
199: .get(org.openide.actions.FindAction.class),
200: null,
201: org.openide.util.actions.SystemAction
202: .get(org.openide.actions.PasteAction.class),
203: null,
204: org.openide.util.actions.SystemAction
205: .get(org.openide.actions.ToolsAction.class), };
206: }
207: return actions;
208: }
209:
210: // Show reasonable properties of the DataFolder,
211: //it shows the sorting names as rw property, the name as ro property and the path to root as ro property
212: public PropertySet[] getPropertySets() {
213: PropertySet[] properties = getDataFolderNodeDelegate()
214: .getPropertySets();
215: for (int i = 0; i < properties.length; i++) {
216: if (Sheet.PROPERTIES.equals(properties[i].getName())) {
217: //Replace the Sheet.PROPERTIES by the new one
218: //having the ro name property and ro path property
219: properties[i] = Sheet.createPropertiesSet();
220: ((Sheet.Set) properties[i])
221: .put(new PropertySupport.ReadOnly(
222: DataObject.PROP_NAME, String.class,
223: NbBundle.getMessage(
224: PackageRootNode.class,
225: "PROP_name"), NbBundle
226: .getMessage(
227: PackageRootNode.class,
228: "HINT_name")) {
229:
230: public/*@Override*/Object getValue() {
231: return PackageRootNode.this
232: .getDisplayName();
233: }
234: });
235: ((Sheet.Set) properties[i])
236: .put(new PropertySupport.ReadOnly("ROOT_PATH",
237: String.class, //NOI18N
238: NbBundle.getMessage(
239: PackageRootNode.class,
240: "PROP_rootpath"), NbBundle
241: .getMessage(
242: PackageRootNode.class,
243: "HINT_rootpath")) {
244:
245: public/*@Override*/Object getValue() {
246: return FileUtil
247: .getFileDisplayName(PackageRootNode.this .file);
248: }
249: });
250: }
251: }
252: return properties;
253: }
254:
255: // XXX Paste types - probably not very nice
256: public void createPasteTypes(Transferable t, List list) {
257: if (t.isDataFlavorSupported(ExTransferable.multiFlavor)) {
258: try {
259: MultiTransferObject mto = (MultiTransferObject) t
260: .getTransferData(ExTransferable.multiFlavor);
261: List l = new ArrayList();
262: boolean isPackageFlavor = false;
263: boolean hasTheSameRoot = false;
264: int op = -1;
265: for (int i = 0; i < mto.getCount(); i++) {
266: Transferable pt = mto.getTransferableAt(i);
267: DataFlavor[] flavors = mto
268: .getTransferDataFlavors(i);
269: for (int j = 0; j < flavors.length; j++) {
270: if (PackageViewChildren.SUBTYPE
271: .equals(flavors[j].getSubType())
272: && PackageViewChildren.PRIMARY_TYPE
273: .equals(flavors[j]
274: .getPrimaryType())) {
275: if (op == -1) {
276: op = Integer
277: .valueOf(
278: flavors[j]
279: .getParameter(PackageViewChildren.MASK))
280: .intValue();
281: }
282: PackageViewChildren.PackageNode pkgNode = (PackageViewChildren.PackageNode) pt
283: .getTransferData(flavors[j]);
284: if (!((PackageViewChildren) getChildren())
285: .getRoot()
286: .equals(pkgNode.getRoot())) {
287: l.add(pkgNode);
288: } else {
289: hasTheSameRoot = true;
290: }
291: isPackageFlavor = true;
292: }
293: }
294: }
295: if (isPackageFlavor && !hasTheSameRoot) {
296: list
297: .add(new PackageViewChildren.PackagePasteType(
298: this .group.getRootFolder(),
299: (PackageViewChildren.PackageNode[]) l
300: .toArray(new PackageViewChildren.PackageNode[l
301: .size()]), op));
302: } else if (!isPackageFlavor) {
303: list.addAll(Arrays
304: .asList(getDataFolderNodeDelegate()
305: .getPasteTypes(t)));
306: }
307: } catch (UnsupportedFlavorException e) {
308: ErrorManager.getDefault().notify(e);
309: } catch (IOException e) {
310: ErrorManager.getDefault().notify(e);
311: }
312: } else {
313: DataFlavor[] flavors = t.getTransferDataFlavors();
314: FileObject root = this .group.getRootFolder();
315: boolean isPackageFlavor = false;
316: if (root != null && root.canWrite()) {
317: for (int i = 0; i < flavors.length; i++) {
318: if (PackageViewChildren.SUBTYPE.equals(flavors[i]
319: .getSubType())
320: && PackageViewChildren.PRIMARY_TYPE
321: .equals(flavors[i].getPrimaryType())) {
322: isPackageFlavor = true;
323: try {
324: int op = Integer
325: .valueOf(
326: flavors[i]
327: .getParameter(PackageViewChildren.MASK))
328: .intValue();
329: PackageViewChildren.PackageNode pkgNode = (PackageViewChildren.PackageNode) t
330: .getTransferData(flavors[i]);
331: if (!((PackageViewChildren) getChildren())
332: .getRoot()
333: .equals(pkgNode.getRoot())) {
334: list
335: .add(new PackageViewChildren.PackagePasteType(
336: root,
337: new PackageViewChildren.PackageNode[] { pkgNode },
338: op));
339: }
340: } catch (IOException ioe) {
341: ErrorManager.getDefault().notify(ioe);
342: } catch (UnsupportedFlavorException ufe) {
343: ErrorManager.getDefault().notify(ufe);
344: }
345: }
346: }
347: }
348: if (!isPackageFlavor) {
349: list.addAll(Arrays.asList(getDataFolderNodeDelegate()
350: .getPasteTypes(t)));
351: }
352: }
353: }
354:
355: public/*@Override*/PasteType getDropType(Transferable t,
356: int action, int index) {
357: PasteType pasteType = super .getDropType(t, action, index);
358: //The pasteType can be:
359: // 1) PackagePasteType - the t.flavor is package flavor
360: // 2) null or DataPasteType - the t.flavor in not package flavor
361: if (pasteType instanceof PackageViewChildren.PackagePasteType) {
362: ((PackageViewChildren.PackagePasteType) pasteType)
363: .setOperation(action);
364: }
365: return pasteType;
366: }
367:
368: // Private methods ---------------------------------------------------------
369:
370: private Node getDataFolderNodeDelegate() {
371: return ((DataFolder) getLookup().lookup(DataFolder.class))
372: .getNodeDelegate();
373: }
374:
375: private Image computeIcon(boolean opened, int type) {
376: Image image;
377: Icon icon = group.getIcon(opened);
378:
379: if (icon == null) {
380: image = opened ? getDataFolderNodeDelegate().getOpenedIcon(
381: type) : getDataFolderNodeDelegate().getIcon(type);
382: image = Utilities.mergeImages(image, PACKAGE_BADGE, 7, 7);
383: } else {
384: if (icon instanceof ImageIcon) {
385: image = ((ImageIcon) icon).getImage();
386: } else {
387: image = icon2image(icon);
388: }
389: }
390:
391: return image;
392: }
393:
394: private static Component CONVERTOR_COMPONENT = new Panel();
395:
396: static Image icon2image(Icon icon) {
397: int height = icon.getIconHeight();
398: int width = icon.getIconWidth();
399:
400: BufferedImage bImage = new BufferedImage(width, height,
401: BufferedImage.TYPE_INT_ARGB);
402: icon.paintIcon(CONVERTOR_COMPONENT, bImage.getGraphics(), 0, 0);
403:
404: return bImage;
405: }
406:
407: private static Lookup createLookup(SourceGroup group) {
408: // XXX Remove DataFolder when paste, find and refresh are reimplemented
409: FileObject rootFolder = group.getRootFolder();
410: DataFolder dataFolder = DataFolder.findFolder(rootFolder);
411: return Lookups.fixed(new Object[] { dataFolder,
412: new PathFinder(group) });
413: }
414:
415: /** If contained in the lookup can perform the search for a node
416: */
417: public static class PathFinder {
418:
419: private SourceGroup group;
420:
421: public PathFinder(SourceGroup group) {
422: this .group = group;
423: }
424:
425: public Node findPath(Node root, Object object) {
426: FileObject fo;
427: if (object instanceof FileObject) {
428: fo = (FileObject) object;
429: } else if (object instanceof DataObject) {
430: fo = ((DataObject) object).getPrimaryFile();
431: } else {
432: return null;
433: }
434:
435: FileObject groupRoot = group.getRootFolder();
436: if (FileUtil.isParentOf(groupRoot, fo) /* && group.contains( fo ) */) {
437: // The group contains the object
438:
439: String relPath = FileUtil
440: .getRelativePath(groupRoot, fo);
441: int lastSlashIndex = relPath.lastIndexOf('/'); // NOI18N
442:
443: String[] path = null;
444: if (fo.isFolder()) {
445: String packageName = relPath.replace('/', '.'); // NOI18N
446: path = new String[] { packageName };
447: } else if (lastSlashIndex == -1) {
448: path = new String[] { "", fo.getName() };
449: } else {
450: String packageName = relPath.substring(0,
451: lastSlashIndex).replace('/', '.'); // NOI18N
452: path = new String[] { packageName, fo.getName() };
453: }
454: try {
455: // XXX if there are two files differing only by extension in the package,
456: // this will be wrong...
457: return NodeOp.findPath(root, path);
458: } catch (NodeNotFoundException e) {
459: if (!fo.isFolder()) {
460: // If it is a DefaultDataObject, the node name contains the extension.
461: if (lastSlashIndex == -1) {
462: path = new String[] { "", fo.getNameExt() };
463: } else {
464: String packageName = relPath.substring(0,
465: lastSlashIndex).replace('/', '.'); // NOI18N
466: path = new String[] { packageName,
467: fo.getNameExt() };
468: }
469: try {
470: return NodeOp.findPath(root, path);
471: } catch (NodeNotFoundException e2) {
472: // already handled
473: }
474: }
475: // did not manage to find it after all... why?
476: return null;
477: }
478: } else if (groupRoot.equals(fo)) {
479: // First try to find default package
480: try {
481: return NodeOp.findPath(root, new String[] { "" }); // NOI18N
482: } catch (NodeNotFoundException e) {
483: // If it does not exists return this node
484: }
485: return root;
486: }
487:
488: return null;
489: }
490:
491: public String toString() {
492: return "PathFinder[" + group + "]"; // NOI18N
493: }
494:
495: }
496:
497: /**
498: * Produce a {@link SearchInfo} variant that is always searchable, for speed.
499: * @see "#48685"
500: */
501: static SearchInfo alwaysSearchableSearchInfo(SearchInfo i) {
502: return new AlwaysSearchableSearchInfo(i);
503: }
504:
505: private static final class AlwaysSearchableSearchInfo implements
506: SearchInfo {
507:
508: private final SearchInfo delegate;
509:
510: public AlwaysSearchableSearchInfo(SearchInfo delegate) {
511: this .delegate = delegate;
512: }
513:
514: public boolean canSearch() {
515: return true;
516: }
517:
518: public Iterator/*<DataObject>*/objectsToSearch() {
519: return delegate.objectsToSearch();
520: }
521:
522: }
523:
524: }
|