/* * Copyright (c) 2012 Sam Harwell, Tunnel Vision Laboratories LLC * All rights reserved. * * The source code of this document is proprietary work, and is not licensed for * distribution. For information about licensing, contact Sam Harwell at: * sam@tunnelvisionlabs.com */ package org.tvl.goworks.project; import java.awt.Image; import java.util.Arrays; import java.util.Collections; import java.util.Set; import javax.swing.Icon; import org.netbeans.api.annotations.common.StaticResource; import org.netbeans.api.project.Project; import org.netbeans.api.project.SourceGroup; import org.netbeans.spi.search.SearchInfoDefinitionFactory; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileStateInvalidException; import org.openide.filesystems.FileStatusEvent; import org.openide.filesystems.FileStatusListener; import org.openide.filesystems.FileSystem; import org.openide.filesystems.FileUtil; import org.openide.loaders.DataFolder; import org.openide.loaders.DataObject; import org.openide.nodes.AbstractNode; import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.nodes.PropertySupport; import org.openide.nodes.Sheet; import org.openide.util.Exceptions; import org.openide.util.ImageUtilities; import org.openide.util.Lookup; import org.openide.util.NbBundle; import org.openide.util.RequestProcessor; import org.openide.util.lookup.Lookups; import org.openide.util.lookup.ProxyLookup; /** * * @author Sam Harwell */ final class PackageRootNode extends AbstractNode implements Runnable, FileStatusListener { @StaticResource private static final String PACKAGE_BADGE = "org/tvl/goworks/project/ui/resources/packageBadge.gif"; static final RequestProcessor PKG_VIEW_RP = new RequestProcessor(PackageRootNode.class.getName(),1); private final SourceGroup _group; private final FileObject _file; private final Set<FileObject> _files; private FileStatusListener _fileSystemListener; private RequestProcessor.Task _task; private volatile boolean _iconChange; private volatile boolean _nameChange; public PackageRootNode(Project project, SourceGroup group) { this(project, group, new PackageViewChildren(group)); } private PackageRootNode(Project project, SourceGroup group, Children children) { super(children, new ProxyLookup(createLookup(group), Lookups.fixed(project, SearchInfoDefinitionFactory.createSearchInfoBySubnodes(children)))); this._group = group; this._file = group.getRootFolder(); this._files = Collections.singleton(_file); try { FileSystem fs = _file.getFileSystem(); this._fileSystemListener = FileUtil.weakFileStatusListener(this, fs); fs.addFileStatusListener(_fileSystemListener); } catch (FileStateInvalidException e) { Exceptions.printStackTrace(Exceptions.attachMessage(e,"Can not get " + _file + " filesystem, ignoring...")); //NOI18N } setName(group.getName()); setDisplayName(group.getDisplayName()); } @Override public Image getIcon(int type) { return computeIcon(false, type); } @Override public Image getOpenedIcon(int type) { return computeIcon(true, type); } @Override public String getDisplayName() { String s = super.getDisplayName(); try { s = _file.getFileSystem().getStatus().annotateName(s, _files); } catch (FileStateInvalidException ex) { Exceptions.printStackTrace(ex); } return s; } @Override public String getHtmlDisplayName() { try { FileSystem.Status status = _file.getFileSystem().getStatus(); if (status instanceof FileSystem.HtmlStatus) { FileSystem.HtmlStatus htmlStatus = (FileSystem.HtmlStatus)status; String result = htmlStatus.annotateNameHtml(super.getDisplayName(), _files); if (!super.getDisplayName().equals(result)) { return result; } } } catch (FileStateInvalidException ex) { Exceptions.printStackTrace(ex); } return super.getHtmlDisplayName(); } private Node getDataFolderNodeDelegate() { DataFolder df = getLookup().lookup(DataFolder.class); try { if (df.isValid()) { return df.getNodeDelegate(); } } catch (IllegalStateException e) { //The data systems API is not thread save, //the DataObject may become invalid after isValid call and before //getNodeDelegate call, we have to catch the ISE. When the DataObject //is valid - other cause rethrow it otherwise return leaf node. //todo: The DataObject.getNodedelegate should throw specialized exception type. if (df.isValid()) { throw e; } } return new AbstractNode(Children.LEAF); } private Image computeIcon( boolean opened, int type ) { Image image; Icon icon = _group.getIcon( opened ); if ( icon == null ) { image = opened ? getDataFolderNodeDelegate().getOpenedIcon( type ) : getDataFolderNodeDelegate().getIcon( type ); image = ImageUtilities.mergeImages(image, ImageUtilities.loadImage(PACKAGE_BADGE), 7, 7); } else { image = ImageUtilities.icon2Image(icon); } return image; } private static Lookup createLookup( SourceGroup group ) { // XXX Remove DataFolder when paste, find and refresh are reimplemented FileObject rootFolder = group.getRootFolder(); DataFolder dataFolder = DataFolder.findFolder( rootFolder ); return Lookups.fixed(dataFolder); } @Override public void run() { if (_iconChange) { fireIconChange(); fireOpenedIconChange(); _iconChange = false; } if (_nameChange) { fireDisplayNameChange(null, null); _nameChange = false; } } @Override public void annotationChanged(FileStatusEvent event) { if (_task == null) { _task = PKG_VIEW_RP.create(this); } if ((!_iconChange && event.isIconChange()) || (!_nameChange && event.isNameChange())) { if (event.hasChanged(_file)) { _iconChange |= event.isIconChange(); _nameChange |= event.isNameChange(); } } _task.schedule(50); // batch by 50 ms } // Show reasonable properties of the DataFolder, //it shows the sorting names as rw property, the name as ro property and the path to root as ro property @NbBundle.Messages({ "PROP_name=Name", "HINT_name=Package Name", "PROP_rootpath=Source Root", "HINT_rootpath=Source Root" }) @Override public PropertySet[] getPropertySets() { final PropertySet[] properties = getDataFolderNodeDelegate().getPropertySets(); final PropertySet[] newProperties = Arrays.copyOf(properties, properties.length); for (int i=0; i< newProperties.length; i++) { if (Sheet.PROPERTIES.equals(newProperties[i].getName())) { //Replace the Sheet.PROPERTIES by the new one //having the ro name property and ro path property newProperties[i] = Sheet.createPropertiesSet(); ((Sheet.Set)newProperties[i]).put(new PropertySupport.ReadOnly<String>(DataObject.PROP_NAME, String.class, Bundle.PROP_name(), Bundle.HINT_name()) { @Override public String getValue() { return PackageRootNode.this.getDisplayName(); } }); ((Sheet.Set)newProperties[i]).put(new PropertySupport.ReadOnly<String>("ROOT_PATH", String.class, //NOI18N Bundle.PROP_rootpath(), Bundle.HINT_rootpath()) { @Override public String getValue() { return FileUtil.getFileDisplayName(PackageRootNode.this._file); } }); } } return newProperties; } }