/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved. * * Oracle and Java are registered trademarks of Oracle and/or its affiliates. * Other names may be trademarks of their respective owners. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common * Development and Distribution License("CDDL") (collectively, the * "License"). You may not use this file except in compliance with the * License. You can obtain a copy of the License at * http://www.netbeans.org/cddl-gplv2.html * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the * specific language governing permissions and limitations under the * License. When distributing the software, include this License Header * Notice in each file and include the License file at * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the GPL Version 2 section of the License file that * accompanied this code. If applicable, add the following below the * License Header, with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * * Contributor(s): * * The Original Software is NetBeans. The Initial Developer of the Original * Software is Sun Microsystems, Inc. Portions Copyright 1997-2008 Sun * Microsystems, Inc. All Rights Reserved. * * If you wish your version of this file to be governed by only the CDDL * or only the GPL Version 2, indicate your decision by adding * "[Contributor] elects to include this software in this distribution * under the [CDDL or GPL Version 2] license." If you do not indicate a * single choice of license, a recipient has the option to distribute * your version of this file under either the CDDL, the GPL Version 2 or * to extend the choice of license to its licensees as provided above. * However, if you add GPL Version 2 code and therefore, elected the GPL * Version 2 license, then the option applies only if the new code is * made subject to such option by the copyright holder. */ package org.netbeans.modules.ruby.railsprojects.ui; import java.awt.EventQueue; import java.awt.Image; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.ResourceBundle; import java.util.Set; import javax.swing.Action; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.netbeans.modules.ruby.railsprojects.GenerateAction; import org.netbeans.modules.ruby.railsprojects.RailsActionProvider; import org.netbeans.api.project.Project; import org.netbeans.api.project.ProjectUtils; import org.netbeans.api.project.SourceGroup; import org.netbeans.api.project.Sources; import org.netbeans.api.ruby.platform.RubyPlatform; import org.netbeans.modules.ruby.codecoverage.RubyCoverageProvider; import org.netbeans.modules.ruby.railsprojects.MigrateAction; import org.netbeans.modules.ruby.railsprojects.RailsProject; import org.netbeans.modules.ruby.railsprojects.plugins.PluginAction; import org.netbeans.modules.ruby.rubyproject.AutoTestSupport; import org.netbeans.modules.ruby.rubyproject.IrbAction; import org.netbeans.modules.ruby.rubyproject.RSpecSupport; import org.netbeans.modules.ruby.rubyproject.TestActionConfiguration; import org.netbeans.modules.ruby.rubyproject.UpdateHelper; import org.netbeans.modules.ruby.rubyproject.bundler.BundlerSupport; import org.netbeans.modules.ruby.rubyproject.rake.RakeRunnerAction; import org.netbeans.modules.ruby.rubyproject.spi.TestRunner.TestType; import org.netbeans.modules.ruby.rubyproject.ui.RubyBaseLogicalViewProvider; import org.netbeans.modules.ruby.spi.project.support.rake.RakeProjectEvent; import org.netbeans.spi.project.ActionProvider; import org.netbeans.modules.ruby.spi.project.support.rake.PropertyEvaluator; import org.netbeans.modules.ruby.spi.project.support.rake.RakeProjectListener; import org.netbeans.modules.ruby.spi.project.support.rake.ReferenceHelper; import org.netbeans.spi.project.ui.support.CommonProjectActions; import org.netbeans.spi.project.ui.support.NodeFactorySupport; import org.netbeans.spi.project.ui.support.DefaultProjectOperations; import org.netbeans.spi.project.ui.support.ProjectSensitiveActions; import org.openide.ErrorManager; import org.openide.actions.FindAction; 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.nodes.AbstractNode; import org.openide.nodes.Node; import org.openide.util.HelpCtx; import org.openide.util.NbBundle; import org.openide.util.RequestProcessor; import org.openide.util.Utilities; import org.openide.util.WeakListeners; import org.openide.util.actions.SystemAction; import org.openide.util.lookup.Lookups; /** * Logical view provider for Rails project. */ public final class RailsLogicalViewProvider extends RubyBaseLogicalViewProvider { private static final RequestProcessor requestProcessor = new RequestProcessor("Rails Annotation"); // NOI18N public RailsLogicalViewProvider( final RailsProject project, final UpdateHelper helper, final PropertyEvaluator evaluator, final ReferenceHelper resolver) { super(project, helper, evaluator, resolver); } public Node createLogicalView() { return new RailsLogicalViewRootNode(); } @Override protected Node findWithPathFinder(final Node root, final FileObject target) { TreeRootNode.PathFinder pf2 = root.getLookup().lookup(TreeRootNode.PathFinder.class); if (pf2 != null) { Node n = pf2.findPath(root, target); if (n != null) { return n; } } return null; } /** Filter node containin additional features for the Rails physical. */ private final class RailsLogicalViewRootNode extends AbstractNode implements Runnable, FileStatusListener, ChangeListener, PropertyChangeListener { private Set<FileObject> files; private Map<FileSystem, FileStatusListener> fileSystemListeners; private RequestProcessor.Task task; private final Object privateLock = new Object(); private boolean iconChange; private boolean nameChange; private ChangeListener sourcesListener; private Map<SourceGroup, PropertyChangeListener> groupsListeners; private final RSpecSupport rspecSupport; public RailsLogicalViewRootNode() { super(NodeFactorySupport.createCompositeChildren(getProject(), "Projects/org-netbeans-modules-ruby-railsprojects/Nodes"), // NOI18N Lookups.singleton(getProject())); setIconBaseWithExtension("org/netbeans/modules/ruby/railsprojects/ui/resources/rails.png"); // NOI18N super.setName( ProjectUtils.getInformation( getProject() ).getDisplayName() ); setProjectFiles(getProject()); getUpdateHelper().getRakeProjectHelper().addRakeProjectListener(new RakeProjectListener() { public void configurationXmlChanged(RakeProjectEvent ev) { fireShortDescriptionChange(); } public void propertiesChanged(RakeProjectEvent ev) { fireShortDescriptionChange(); } }); this.rspecSupport = new RSpecSupport(getProject()); } private void fireShortDescriptionChange() { // cf. issue #149066 EventQueue.invokeLater(new Runnable() { public void run() { fireShortDescriptionChange(null, null); } }); } public @Override String getShortDescription() { String platformDesc = RubyPlatform.platformDescriptionFor(getProject()); if (platformDesc == null) { platformDesc = NbBundle.getMessage(RailsLogicalViewProvider.class, "RailsLogicalViewProvider.PlatformNotFound"); } String dirName = FileUtil.getFileDisplayName(getProject().getProjectDirectory()); return NbBundle.getMessage(RailsLogicalViewProvider.class, "RailsLogicalViewProvider.ProjectTooltipDescription", dirName, platformDesc); } protected final void setProjectFiles(Project project) { Sources sources = ProjectUtils.getSources(project); // returns singleton if (sourcesListener == null) { sourcesListener = WeakListeners.change(this, sources); sources.addChangeListener(sourcesListener); } setGroups(Arrays.asList(sources.getSourceGroups(Sources.TYPE_GENERIC))); } private final void setGroups(Collection<SourceGroup> groups) { if (groupsListeners != null) { Iterator it = groupsListeners.keySet().iterator(); while (it.hasNext()) { SourceGroup group = (SourceGroup) it.next(); PropertyChangeListener pcl = groupsListeners.get(group); group.removePropertyChangeListener(pcl); } } groupsListeners = new HashMap<SourceGroup, PropertyChangeListener>(); Set<FileObject> roots = new HashSet<FileObject>(); Iterator it = groups.iterator(); for (SourceGroup group : groups) { PropertyChangeListener pcl = WeakListeners.propertyChange(this, group); groupsListeners.put(group, pcl); group.addPropertyChangeListener(pcl); FileObject fo = group.getRootFolder(); roots.add(fo); } setFiles(roots); } protected final void setFiles(Set<FileObject> files) { if (fileSystemListeners != null) { for (FileSystem fs : fileSystemListeners.keySet()) { FileStatusListener fsl = fileSystemListeners.get(fs); fs.removeFileStatusListener(fsl); } } fileSystemListeners = new HashMap<FileSystem, FileStatusListener>(); this.files = files; if (files == null) { return; } Iterator it = files.iterator(); Set<FileSystem> hookedFileSystems = new HashSet<FileSystem>(); while (it.hasNext()) { FileObject fo = (FileObject) it.next(); try { FileSystem fs = fo.getFileSystem(); if (hookedFileSystems.contains(fs)) { continue; } hookedFileSystems.add(fs); FileStatusListener fsl = FileUtil.weakFileStatusListener(this, fs); fs.addFileStatusListener(fsl); fileSystemListeners.put(fs, fsl); } catch (FileStateInvalidException e) { ErrorManager err = ErrorManager.getDefault(); err.annotate(e, ErrorManager.UNKNOWN, "Cannot get " + fo + " filesystem, ignoring...", null, null, null); // NOI18N err.notify(ErrorManager.INFORMATIONAL, e); } } } public @Override Image getIcon(int type) { Image img = super.getIcon(type); if (files != null && files.iterator().hasNext()) { try { FileObject fo = files.iterator().next(); img = fo.getFileSystem().getStatus().annotateIcon(img, type, files); } catch (FileStateInvalidException e) { ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e); } } return img; } public @Override Image getOpenedIcon(int type) { Image img = getMyOpenedIcon(type); if (files != null && files.iterator().hasNext()) { try { FileObject fo = files.iterator().next(); img = fo.getFileSystem().getStatus().annotateIcon(img, type, files); } catch (FileStateInvalidException e) { ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e); } } return img; } private Image getMyOpenedIcon(int type) { Image original = super.getOpenedIcon(type); //return broken || illegalState ? ImageUtilities.mergeImages(original, brokenProjectBadge, 8, 0) : original; return original; } public void run() { boolean fireIcon; boolean fireName; synchronized (privateLock) { fireIcon = iconChange; fireName = nameChange; iconChange = false; nameChange = false; } if (fireIcon) { fireIconChange(); fireOpenedIconChange(); } if (fireName) { fireDisplayNameChange(null, null); } } public void annotationChanged(FileStatusEvent event) { if (task == null) { task = requestProcessor.create(this); } synchronized (privateLock) { if ((iconChange == false && event.isIconChange()) || (nameChange == false && event.isNameChange())) { Iterator it = files.iterator(); while (it.hasNext()) { FileObject fo = (FileObject) it.next(); if (event.hasChanged(fo)) { iconChange |= event.isIconChange(); nameChange |= event.isNameChange(); } } } } task.schedule(50); // batch by 50 ms } // sources change public void stateChanged(ChangeEvent e) { setProjectFiles(getProject()); fireShortDescriptionChange(); } // group change public void propertyChange(PropertyChangeEvent evt) { setProjectFiles(getProject()); } public @Override Action[] getActions( boolean context ) { return getAdditionalActions(); } public @Override boolean canRename() { return true; } public @Override void setName(String s) { DefaultProjectOperations.performDefaultRenameOperation(getProject(), s); } public @Override HelpCtx getHelpCtx() { return new HelpCtx(RailsLogicalViewRootNode.class); } // Private methods ------------------------------------------------------------- private Action[] getAdditionalActions() { bundlerSupport.initialize(); ResourceBundle bundle = NbBundle.getBundle(RailsLogicalViewProvider.class); List<Action> actions = new ArrayList<Action>(); actions.add(SystemAction.get(GenerateAction.class)); actions.add(null); actions.add(CommonProjectActions.newFileAction()); actions.add(null); actions.add(SystemAction.get(RakeRunnerAction.class)); actions.add(SystemAction.get(IrbAction.class)); actions.add(SystemAction.get(MigrateAction.class)); actions.add(null); actions.add(ProjectSensitiveActions.projectCommandAction(RailsActionProvider.COMMAND_RAILS_CONSOLE, bundle.getString("LBL_ConsoleAction_Name"), null)); // NOI18N actions.add(SystemAction.get(PluginAction.class)); actions.add(null); actions.add(ProjectSensitiveActions.projectCommandAction(ActionProvider.COMMAND_RUN, bundle.getString("LBL_RunAction_Name"), null)); // NOI18N if (AutoTestSupport.isInstalled(getProject(), TestType.AUTOTEST) && TestActionConfiguration.enable(RailsActionProvider.COMMAND_AUTOTEST, getProject())) { actions.add(ProjectSensitiveActions.projectCommandAction(RailsActionProvider.COMMAND_AUTOTEST, bundle.getString("LBL_AutoTest"), null)); // NOI18N } if (AutoTestSupport.isInstalled(getProject(), TestType.AUTOSPEC) && TestActionConfiguration.enable(RailsActionProvider.COMMAND_AUTOSPEC, getProject())) { actions.add(ProjectSensitiveActions.projectCommandAction(RailsActionProvider.COMMAND_AUTOSPEC, bundle.getString("LBL_AutoSpec"), null)); // NOI18N } if (rspecSupport.isRSpecInstalled() && TestActionConfiguration.enable(RailsActionProvider.COMMAND_RSPEC, getProject())) { actions.add(ProjectSensitiveActions.projectCommandAction(RailsActionProvider.COMMAND_RSPEC, bundle.getString("LBL_RSpec"), null)); // NOI18N } actions.add(ProjectSensitiveActions.projectCommandAction(ActionProvider.COMMAND_DEBUG, bundle.getString("LBL_DebugAction_Name"), null)); // NOI18N if (TestActionConfiguration.enable(RailsActionProvider.COMMAND_TEST, getProject())) { actions.add(ProjectSensitiveActions.projectCommandAction(ActionProvider.COMMAND_TEST, bundle.getString("LBL_TestAction_Name"), null)); // NOI18N } if (bundlerSupport.installed()) { actions.add(bundlerSupport.createAction()); } actions.add(RubyCoverageProvider.createCoverageAction(getProject())); actions.add(null); actions.add(CommonProjectActions.setProjectConfigurationAction()); actions.add(null); actions.add(CommonProjectActions.setAsMainProjectAction()); actions.add(CommonProjectActions.openSubprojectsAction()); actions.add(CommonProjectActions.closeProjectAction()); actions.add(null); actions.add(CommonProjectActions.renameProjectAction()); actions.add(CommonProjectActions.moveProjectAction()); actions.add(CommonProjectActions.copyProjectAction()); actions.add(CommonProjectActions.deleteProjectAction()); actions.add(null); actions.add(SystemAction.get(FindAction.class)); // honor 57874 contact actions.add(null); actions.addAll(Utilities.actionsForPath("Projects/Actions")); // NOI18N actions.add(null); actions.add(CommonProjectActions.customizeProjectAction()); return actions.toArray(new Action[actions.size()]); } public @Override String toString() { return super.toString() + "[project=" + getProject() + "]"; // NOI18N } } }