1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/python.project2/src/org/netbeans/modules/python/project2/ImportantFilesImplementation.java Thu Jan 14 18:37:20 2016 +0100
1.3 @@ -0,0 +1,176 @@
1.4 +/*
1.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
1.6 + *
1.7 + * Copyright 2016 Oracle and/or its affiliates. All rights reserved.
1.8 + *
1.9 + * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
1.10 + * Other names may be trademarks of their respective owners.
1.11 + *
1.12 + * The contents of this file are subject to the terms of either the GNU
1.13 + * General Public License Version 2 only ("GPL") or the Common
1.14 + * Development and Distribution License("CDDL") (collectively, the
1.15 + * "License"). You may not use this file except in compliance with the
1.16 + * License. You can obtain a copy of the License at
1.17 + * http://www.netbeans.org/cddl-gplv2.html
1.18 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
1.19 + * specific language governing permissions and limitations under the
1.20 + * License. When distributing the software, include this License Header
1.21 + * Notice in each file and include the License file at
1.22 + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
1.23 + * particular file as subject to the "Classpath" exception as provided
1.24 + * by Oracle in the GPL Version 2 section of the License file that
1.25 + * accompanied this code. If applicable, add the following below the
1.26 + * License Header, with the fields enclosed by brackets [] replaced by
1.27 + * your own identifying information:
1.28 + * "Portions Copyrighted [year] [name of copyright owner]"
1.29 + *
1.30 + * If you wish your version of this file to be governed by only the CDDL
1.31 + * or only the GPL Version 2, indicate your decision by adding
1.32 + * "[Contributor] elects to include this software in this distribution
1.33 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
1.34 + * single choice of license, a recipient has the option to distribute
1.35 + * your version of this file under either the CDDL, the GPL Version 2 or
1.36 + * to extend the choice of license to its licensees as provided above.
1.37 + * However, if you add GPL Version 2 code and therefore, elected the GPL
1.38 + * Version 2 license, then the option applies only if the new code is
1.39 + * made subject to such option by the copyright holder.
1.40 + *
1.41 + * Contributor(s):
1.42 + *
1.43 + * Portions Copyrighted 2016 Sun Microsystems, Inc.
1.44 + */
1.45 +package org.netbeans.modules.python.project2;
1.46 +
1.47 +import java.util.Collection;
1.48 +import java.util.Objects;
1.49 +import javax.swing.event.ChangeListener;
1.50 +import org.netbeans.api.annotations.common.CheckForNull;
1.51 +import org.netbeans.api.annotations.common.NullAllowed;
1.52 +import org.openide.filesystems.FileObject;
1.53 +import org.openide.util.Parameters;
1.54 +
1.55 +/**
1.56 + *
1.57 + * @author jenselme
1.58 + */
1.59 +/**
1.60 + * Information about important files.
1.61 + * <p>
1.62 + * Implementations are expected to be found in project's lookup.
1.63 + * @since 1.73
1.64 + * @see org.netbeans.modules.web.common.spi.ImportantFilesImplementation;
1.65 + */
1.66 +public interface ImportantFilesImplementation {
1.67 +
1.68 + /**
1.69 + * Gets information about all important files.
1.70 + * @return information about all important files; can be empty but never {@code null}
1.71 + */
1.72 + Collection<FileInfo> getFiles();
1.73 +
1.74 + /**
1.75 + * Adds listener to be notified when important files change.
1.76 + * @param listener listener to be notified when important files change
1.77 + */
1.78 + void addChangeListener(ChangeListener listener);
1.79 +
1.80 + /**
1.81 + * Removes listener.
1.82 + * @param listener listener
1.83 + */
1.84 + void removeChangeListener(ChangeListener listener);
1.85 +
1.86 + //~ Inner classes
1.87 +
1.88 + /**
1.89 + * Information about important file.
1.90 + */
1.91 + final class FileInfo {
1.92 +
1.93 + private final FileObject file;
1.94 + private final String displayName;
1.95 + private final String description;
1.96 +
1.97 +
1.98 + /**
1.99 + * Creates information for the given file.
1.100 + * @param file file, cannot be a directory
1.101 + */
1.102 + public FileInfo(FileObject file) {
1.103 + this(file, null, null);
1.104 + }
1.105 +
1.106 + /**
1.107 + * Creates information for the given file with custom display name
1.108 + * and/or custom description.
1.109 + * @param file file, cannot be a directory
1.110 + * @param displayName custom display name, can be {@code null}
1.111 + * @param description custom description, can be {@code null}
1.112 + */
1.113 + public FileInfo(FileObject file, @NullAllowed String displayName, @NullAllowed String description) {
1.114 + Parameters.notNull("file", file); // NOI18N
1.115 + if (file.isFolder()) {
1.116 + throw new IllegalArgumentException("File cannot be a directory");
1.117 + }
1.118 + this.file = file;
1.119 + this.displayName = displayName;
1.120 + this.description = description;
1.121 + }
1.122 +
1.123 + /**
1.124 + * Gets file.
1.125 + * @return file
1.126 + */
1.127 + public FileObject getFile() {
1.128 + return file;
1.129 + }
1.130 +
1.131 + /**
1.132 + * Gets display name, can be {@code null}.
1.133 + * @return display name, can be {@code null}
1.134 + */
1.135 + @CheckForNull
1.136 + public String getDisplayName() {
1.137 + return displayName;
1.138 + }
1.139 +
1.140 + /**
1.141 + * Gets description, can be {@code null}.
1.142 + * @return description, can be {@code null}
1.143 + */
1.144 + @CheckForNull
1.145 + public String getDescription() {
1.146 + return description;
1.147 + }
1.148 +
1.149 + @Override
1.150 + public String toString() {
1.151 + return "FileInfo{" + "file=" + file + ", displayName=" + displayName + ", description=" + description + '}'; // NOI18N
1.152 + }
1.153 +
1.154 + @Override
1.155 + public int hashCode() {
1.156 + int hash = 3;
1.157 + hash = 71 * hash + Objects.hashCode(this.file);
1.158 + return hash;
1.159 + }
1.160 +
1.161 + @Override
1.162 + public boolean equals(Object obj) {
1.163 + if (obj == null) {
1.164 + return false;
1.165 + }
1.166 + if (getClass() != obj.getClass()) {
1.167 + return false;
1.168 + }
1.169 + final FileInfo other = (FileInfo) obj;
1.170 + if (!Objects.equals(this.file, other.file)) {
1.171 + return false;
1.172 + }
1.173 + return true;
1.174 + }
1.175 +
1.176 + }
1.177 +
1.178 +}
1.179 +
2.1 --- a/python.project2/src/org/netbeans/modules/python/project2/ImportantFilesNode.java Sun Jan 10 15:02:08 2016 +0100
2.2 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
2.3 @@ -1,87 +0,0 @@
2.4 -/*
2.5 - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
2.6 - *
2.7 - * Copyright 2016 Oracle and/or its affiliates. All rights reserved.
2.8 - *
2.9 - * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
2.10 - * Other names may be trademarks of their respective owners.
2.11 - *
2.12 - * The contents of this file are subject to the terms of either the GNU
2.13 - * General Public License Version 2 only ("GPL") or the Common
2.14 - * Development and Distribution License("CDDL") (collectively, the
2.15 - * "License"). You may not use this file except in compliance with the
2.16 - * License. You can obtain a copy of the License at
2.17 - * http://www.netbeans.org/cddl-gplv2.html
2.18 - * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
2.19 - * specific language governing permissions and limitations under the
2.20 - * License. When distributing the software, include this License Header
2.21 - * Notice in each file and include the License file at
2.22 - * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
2.23 - * particular file as subject to the "Classpath" exception as provided
2.24 - * by Oracle in the GPL Version 2 section of the License file that
2.25 - * accompanied this code. If applicable, add the following below the
2.26 - * License Header, with the fields enclosed by brackets [] replaced by
2.27 - * your own identifying information:
2.28 - * "Portions Copyrighted [year] [name of copyright owner]"
2.29 - *
2.30 - * If you wish your version of this file to be governed by only the CDDL
2.31 - * or only the GPL Version 2, indicate your decision by adding
2.32 - * "[Contributor] elects to include this software in this distribution
2.33 - * under the [CDDL or GPL Version 2] license." If you do not indicate a
2.34 - * single choice of license, a recipient has the option to distribute
2.35 - * your version of this file under either the CDDL, the GPL Version 2 or
2.36 - * to extend the choice of license to its licensees as provided above.
2.37 - * However, if you add GPL Version 2 code and therefore, elected the GPL
2.38 - * Version 2 license, then the option applies only if the new code is
2.39 - * made subject to such option by the copyright holder.
2.40 - *
2.41 - * Contributor(s):
2.42 - *
2.43 - * Portions Copyrighted 2016 Sun Microsystems, Inc.
2.44 - */
2.45 -package org.netbeans.modules.python.project2;
2.46 -
2.47 -import java.awt.Image;
2.48 -import org.netbeans.api.annotations.common.StaticResource;
2.49 -import org.netbeans.api.project.Project;
2.50 -import org.openide.filesystems.FileUtil;
2.51 -import org.openide.loaders.DataFolder;
2.52 -import org.openide.loaders.DataObject;
2.53 -import org.openide.loaders.DataObjectNotFoundException;
2.54 -import org.openide.nodes.FilterNode;
2.55 -import org.openide.util.ImageUtilities;
2.56 -
2.57 -/**
2.58 - *
2.59 - * @author jenselme
2.60 - */
2.61 -class ImportantFilesNode extends FilterNode {
2.62 - @StaticResource
2.63 - private static final String IMAGE = "org/netbeans/modules/"
2.64 - + "python/project2/resources/brokenProjectBadge.gif";
2.65 -
2.66 - public ImportantFilesNode(Project proj) throws DataObjectNotFoundException {
2.67 - super(DataObject.find(proj.getProjectDirectory()).getNodeDelegate(), new ImportantFilesChildren());
2.68 - proj.getProjectDirectory().getChildren(false);
2.69 - }
2.70 -
2.71 - @Override
2.72 - public String getDisplayName() {
2.73 - return "Important Files";
2.74 - }
2.75 -
2.76 - @Override
2.77 - public Image getIcon(int type) {
2.78 - DataFolder root = DataFolder.findFolder(FileUtil.getConfigRoot());
2.79 - Image original = root.getNodeDelegate().getIcon(type);
2.80 - return ImageUtilities.mergeImages(original,
2.81 - ImageUtilities.loadImage(IMAGE), 7, 7);
2.82 - }
2.83 - @Override
2.84 - public Image getOpenedIcon(int type) {
2.85 - DataFolder root = DataFolder.findFolder(FileUtil.getConfigRoot());
2.86 - Image original = root.getNodeDelegate().getIcon(type);
2.87 - return ImageUtilities.mergeImages(original,
2.88 - ImageUtilities.loadImage(IMAGE), 7, 7);
2.89 - }
2.90 -}
3.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
3.2 +++ b/python.project2/src/org/netbeans/modules/python/project2/ImportantFilesSupport.java Thu Jan 14 18:37:20 2016 +0100
3.3 @@ -0,0 +1,193 @@
3.4 +/*
3.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3.6 + *
3.7 + * Copyright 2016 Oracle and/or its affiliates. All rights reserved.
3.8 + *
3.9 + * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
3.10 + * Other names may be trademarks of their respective owners.
3.11 + *
3.12 + * The contents of this file are subject to the terms of either the GNU
3.13 + * General Public License Version 2 only ("GPL") or the Common
3.14 + * Development and Distribution License("CDDL") (collectively, the
3.15 + * "License"). You may not use this file except in compliance with the
3.16 + * License. You can obtain a copy of the License at
3.17 + * http://www.netbeans.org/cddl-gplv2.html
3.18 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
3.19 + * specific language governing permissions and limitations under the
3.20 + * License. When distributing the software, include this License Header
3.21 + * Notice in each file and include the License file at
3.22 + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
3.23 + * particular file as subject to the "Classpath" exception as provided
3.24 + * by Oracle in the GPL Version 2 section of the License file that
3.25 + * accompanied this code. If applicable, add the following below the
3.26 + * License Header, with the fields enclosed by brackets [] replaced by
3.27 + * your own identifying information:
3.28 + * "Portions Copyrighted [year] [name of copyright owner]"
3.29 + *
3.30 + * If you wish your version of this file to be governed by only the CDDL
3.31 + * or only the GPL Version 2, indicate your decision by adding
3.32 + * "[Contributor] elects to include this software in this distribution
3.33 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
3.34 + * single choice of license, a recipient has the option to distribute
3.35 + * your version of this file under either the CDDL, the GPL Version 2 or
3.36 + * to extend the choice of license to its licensees as provided above.
3.37 + * However, if you add GPL Version 2 code and therefore, elected the GPL
3.38 + * Version 2 license, then the option applies only if the new code is
3.39 + * made subject to such option by the copyright holder.
3.40 + *
3.41 + * Contributor(s):
3.42 + *
3.43 + * Portions Copyrighted 2016 Sun Microsystems, Inc.
3.44 + */
3.45 +package org.netbeans.modules.python.project2;
3.46 +
3.47 +import java.util.ArrayList;
3.48 +import java.util.Collection;
3.49 +import java.util.Collections;
3.50 +import java.util.List;
3.51 +import java.util.concurrent.CopyOnWriteArrayList;
3.52 +import javax.swing.event.ChangeListener;
3.53 +import org.netbeans.api.annotations.common.CheckForNull;
3.54 +import org.netbeans.api.annotations.common.NullAllowed;
3.55 +import org.openide.filesystems.FileChangeAdapter;
3.56 +import org.openide.filesystems.FileChangeListener;
3.57 +import org.openide.filesystems.FileEvent;
3.58 +import org.openide.filesystems.FileObject;
3.59 +import org.openide.filesystems.FileRenameEvent;
3.60 +import org.openide.util.ChangeSupport;
3.61 +import org.openide.util.Parameters;
3.62 +import org.openide.util.WeakListeners;
3.63 +
3.64 +/**
3.65 + *
3.66 + * @author jenselme
3.67 + * @see org.netbeans.modules.web.common.spi.ImportantFilesSupport
3.68 + */
3.69 +class ImportantFilesSupport {
3.70 + private final FileObject directory;
3.71 + final List<String> fileNames;
3.72 + final FileChangeListener fileChangeListener = new FilesListener();
3.73 + private final ChangeSupport changeSupport = new ChangeSupport(this);
3.74 +
3.75 + private ImportantFilesSupport(FileObject directory, String... fileNames) {
3.76 + assert directory != null;
3.77 + assert fileNames != null;
3.78 + this.directory = directory;
3.79 + this.fileNames = new CopyOnWriteArrayList<>(fileNames);
3.80 + }
3.81 +
3.82 + /**
3.83 + * Creates new support for the given directory and file name(s).
3.84 + *
3.85 + * @param directory directory
3.86 + * @param fileNames file name(s)
3.87 + * @return new support
3.88 + */
3.89 + public static ImportantFilesSupport create(FileObject directory, String... fileNames) {
3.90 + Parameters.notNull("directory", directory); // NOI18N
3.91 + Parameters.notNull("fileNames", fileNames); // NOI18N
3.92 + ImportantFilesSupport support = new ImportantFilesSupport(directory, fileNames);
3.93 + directory.addFileChangeListener(WeakListeners.create(FileChangeListener.class, support.fileChangeListener, directory));
3.94 + return support;
3.95 + }
3.96 +
3.97 + /**
3.98 + * Gets information about all important files.
3.99 + *
3.100 + * @param fileInfoCreator custom {@link FileInfoCreator}, can be
3.101 + * {@code null} (in such case,
3.102 + * {@link ImportantFilesImplementation.FileInfo#FileInfo(FileObject)} is
3.103 + * used)
3.104 + * @return information about all important files; can be empty but never
3.105 + * {@code null}
3.106 + */
3.107 + public Collection<ImportantFilesImplementation.FileInfo> getFiles(@NullAllowed FileInfoCreator fileInfoCreator) {
3.108 + List<ImportantFilesImplementation.FileInfo> files = new ArrayList<>();
3.109 + for (String name : fileNames) {
3.110 + FileObject fo = directory.getFileObject(name);
3.111 + if (fo != null) {
3.112 + ImportantFilesImplementation.FileInfo info = null;
3.113 + if (fileInfoCreator != null) {
3.114 + info = fileInfoCreator.create(fo);
3.115 + }
3.116 + if (info == null) {
3.117 + info = new ImportantFilesImplementation.FileInfo(fo);
3.118 + }
3.119 + files.add(info);
3.120 + }
3.121 + }
3.122 + if (files.isEmpty()) {
3.123 + return Collections.emptyList();
3.124 + }
3.125 + return files;
3.126 + }
3.127 +
3.128 + /**
3.129 + * Adds listener to be notified when important files change.
3.130 + *
3.131 + * @param listener listener to be notified when important files change
3.132 + */
3.133 + public void addChangeListener(ChangeListener listener) {
3.134 + changeSupport.addChangeListener(listener);
3.135 + }
3.136 +
3.137 + /**
3.138 + * Removes listener.
3.139 + *
3.140 + * @param listener listener
3.141 + */
3.142 + public void removeChangeListener(ChangeListener listener) {
3.143 + changeSupport.removeChangeListener(listener);
3.144 + }
3.145 +
3.146 + void fireChange() {
3.147 + changeSupport.fireChange();
3.148 + }
3.149 +
3.150 + //~ Inner classes
3.151 + /**
3.152 + * {@link ImportantFilesImplementation.FileInfo} creator for the given
3.153 + * {@link FileObject}.
3.154 + */
3.155 + public interface FileInfoCreator {
3.156 +
3.157 + /**
3.158 + * Creates {@link ImportantFilesImplementation.FileInfo} for the given
3.159 + * {@link FileObject}.
3.160 + *
3.161 + * @param fileObject FileObject to be used
3.162 + * @return {@link ImportantFilesImplementation.FileInfo} for the given
3.163 + * {@link FileObject}, can be {
3.164 + * @null}
3.165 + */
3.166 + @CheckForNull
3.167 + ImportantFilesImplementation.FileInfo create(FileObject fileObject);
3.168 +
3.169 + }
3.170 +
3.171 + private final class FilesListener extends FileChangeAdapter {
3.172 +
3.173 + @Override
3.174 + public void fileRenamed(FileRenameEvent fe) {
3.175 + check(fe.getFile().getNameExt());
3.176 + check(fe.getName() + "." + fe.getExt()); // NOI18N
3.177 + }
3.178 +
3.179 + @Override
3.180 + public void fileDeleted(FileEvent fe) {
3.181 + check(fe.getFile().getNameExt());
3.182 + }
3.183 +
3.184 + @Override
3.185 + public void fileDataCreated(FileEvent fe) {
3.186 + check(fe.getFile().getNameExt());
3.187 + }
3.188 +
3.189 + private void check(String filename) {
3.190 + if (fileNames.contains(filename)) {
3.191 + fireChange();
3.192 + }
3.193 + }
3.194 +
3.195 + }
3.196 +}
4.1 --- a/python.project2/src/org/netbeans/modules/python/project2/PythonImportantFilesNodeFactory.java Sun Jan 10 15:02:08 2016 +0100
4.2 +++ b/python.project2/src/org/netbeans/modules/python/project2/PythonImportantFilesNodeFactory.java Thu Jan 14 18:37:20 2016 +0100
4.3 @@ -42,11 +42,9 @@
4.4 package org.netbeans.modules.python.project2;
4.5
4.6 import org.netbeans.api.project.Project;
4.7 +import org.netbeans.modules.python.project2.ui.ImportantFiles;
4.8 import org.netbeans.spi.project.ui.support.NodeFactory;
4.9 -import org.netbeans.spi.project.ui.support.NodeFactorySupport;
4.10 import org.netbeans.spi.project.ui.support.NodeList;
4.11 -import org.openide.loaders.DataObjectNotFoundException;
4.12 -import org.openide.util.Exceptions;
4.13
4.14 /**
4.15 *
4.16 @@ -56,19 +54,7 @@
4.17 public class PythonImportantFilesNodeFactory implements NodeFactory {
4.18 @Override
4.19 public NodeList createNodes(Project project) {
4.20 - //Optionally, only return a new node
4.21 - //if some item is in the project's lookup:
4.22 - //MyCoolLookupItem item = project.getLookup().lookup(MyCoolLookupItem.class);
4.23 - //if (item != null) {
4.24 - try {
4.25 - ImportantFilesNode nd = new ImportantFilesNode(project);
4.26 - return NodeFactorySupport.fixedNodeList(nd);
4.27 - } catch (DataObjectNotFoundException ex) {
4.28 - Exceptions.printStackTrace(ex);
4.29 - }
4.30 - //If the above try/catch fails, e.g.,
4.31 - //our item isn't in the lookup,
4.32 - //then return an empty list of nodes:
4.33 - return NodeFactorySupport.fixedNodeList();
4.34 + NodeFactory nd = ImportantFiles.forPython2Project();
4.35 + return nd.createNodes(project);
4.36 }
4.37 }
5.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
5.2 +++ b/python.project2/src/org/netbeans/modules/python/project2/ui/ImportantFiles.java Thu Jan 14 18:37:20 2016 +0100
5.3 @@ -0,0 +1,402 @@
5.4 +/*
5.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
5.6 + *
5.7 + * Copyright 2016 Oracle and/or its affiliates. All rights reserved.
5.8 + *
5.9 + * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
5.10 + * Other names may be trademarks of their respective owners.
5.11 + *
5.12 + * The contents of this file are subject to the terms of either the GNU
5.13 + * General Public License Version 2 only ("GPL") or the Common
5.14 + * Development and Distribution License("CDDL") (collectively, the
5.15 + * "License"). You may not use this file except in compliance with the
5.16 + * License. You can obtain a copy of the License at
5.17 + * http://www.netbeans.org/cddl-gplv2.html
5.18 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
5.19 + * specific language governing permissions and limitations under the
5.20 + * License. When distributing the software, include this License Header
5.21 + * Notice in each file and include the License file at
5.22 + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
5.23 + * particular file as subject to the "Classpath" exception as provided
5.24 + * by Oracle in the GPL Version 2 section of the License file that
5.25 + * accompanied this code. If applicable, add the following below the
5.26 + * License Header, with the fields enclosed by brackets [] replaced by
5.27 + * your own identifying information:
5.28 + * "Portions Copyrighted [year] [name of copyright owner]"
5.29 + *
5.30 + * If you wish your version of this file to be governed by only the CDDL
5.31 + * or only the GPL Version 2, indicate your decision by adding
5.32 + * "[Contributor] elects to include this software in this distribution
5.33 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
5.34 + * single choice of license, a recipient has the option to distribute
5.35 + * your version of this file under either the CDDL, the GPL Version 2 or
5.36 + * to extend the choice of license to its licensees as provided above.
5.37 + * However, if you add GPL Version 2 code and therefore, elected the GPL
5.38 + * Version 2 license, then the option applies only if the new code is
5.39 + * made subject to such option by the copyright holder.
5.40 + *
5.41 + * Contributor(s):
5.42 + *
5.43 + * Portions Copyrighted 2016 Sun Microsystems, Inc.
5.44 + */
5.45 +package org.netbeans.modules.python.project2.ui;
5.46 +
5.47 +import java.awt.EventQueue;
5.48 +import java.awt.Image;
5.49 +import java.beans.PropertyChangeEvent;
5.50 +import java.beans.PropertyChangeListener;
5.51 +import java.beans.PropertyVetoException;
5.52 +import java.beans.VetoableChangeListener;
5.53 +import java.util.ArrayList;
5.54 +import java.util.Collections;
5.55 +import java.util.Comparator;
5.56 +import java.util.LinkedHashSet;
5.57 +import java.util.List;
5.58 +import java.util.Set;
5.59 +import java.util.logging.Level;
5.60 +import java.util.logging.Logger;
5.61 +import javax.swing.Action;
5.62 +import javax.swing.event.ChangeEvent;
5.63 +import javax.swing.event.ChangeListener;
5.64 +import org.netbeans.api.annotations.common.CheckForNull;
5.65 +import org.netbeans.api.annotations.common.NonNull;
5.66 +import org.netbeans.api.annotations.common.StaticResource;
5.67 +import org.netbeans.api.project.Project;
5.68 +import org.netbeans.modules.python.project2.ImportantFilesImplementation;
5.69 +import org.netbeans.spi.project.ui.support.NodeFactory;
5.70 +import org.netbeans.spi.project.ui.support.NodeList;
5.71 +import org.openide.filesystems.FileAttributeEvent;
5.72 +import org.openide.filesystems.FileChangeListener;
5.73 +import org.openide.filesystems.FileEvent;
5.74 +import org.openide.filesystems.FileObject;
5.75 +import org.openide.filesystems.FileRenameEvent;
5.76 +import org.openide.filesystems.FileStateInvalidException;
5.77 +import org.openide.filesystems.FileUtil;
5.78 +import org.openide.filesystems.StatusDecorator;
5.79 +import org.openide.loaders.DataFolder;
5.80 +import org.openide.loaders.DataObject;
5.81 +import org.openide.loaders.DataObjectNotFoundException;
5.82 +import org.openide.nodes.AbstractNode;
5.83 +import org.openide.nodes.Children;
5.84 +import org.openide.nodes.FilterNode;
5.85 +import org.openide.nodes.Node;
5.86 +import org.openide.util.ChangeSupport;
5.87 +import org.openide.util.Exceptions;
5.88 +import org.openide.util.ImageUtilities;
5.89 +import org.openide.util.Lookup;
5.90 +import org.openide.util.LookupEvent;
5.91 +import org.openide.util.LookupListener;
5.92 +import org.openide.util.WeakListeners;
5.93 +
5.94 +/**
5.95 + *
5.96 + * @author jenselme
5.97 + * @see org.netbeans.modules.web.common.ui.ImportantFiles
5.98 + */
5.99 +public class ImportantFiles {
5.100 + static final Logger LOGGER = Logger.getLogger(ImportantFiles.class.getName());
5.101 + static final String[] PYTHON_IMPORTANT_FILES = {
5.102 + "setup.py", // NOI18N
5.103 + "development.ini", // NOI18N, Pyramid config file
5.104 + "production.ini", // NOI18N, Pyramid config file
5.105 + ".hgignore", // NOI18N
5.106 + ".gitignore", // NOI18N
5.107 + "MANIFEST", // NOI18N
5.108 + "MANIFEST.in", // NOI18N
5.109 + "README", // NOI18N
5.110 + "README.md", // NOI18N
5.111 + "README.rst", // NOI18N
5.112 + "LICENSE", // NOI18N
5.113 + "COPYING", // NOI18N
5.114 + };
5.115 +
5.116 + private ImportantFiles() {
5.117 + }
5.118 +
5.119 + public static NodeFactory forPython2Project() {
5.120 + return new ImportantFilesNodeFactory();
5.121 + }
5.122 +
5.123 + public static class ImportantFilesNodeFactory implements NodeFactory {
5.124 + @Override
5.125 + public NodeList<?> createNodes(@NonNull Project project) {
5.126 + return new ImportantFilesNodeList(project);
5.127 + }
5.128 + }
5.129 +
5.130 + private static class ImportantFilesNodeList implements NodeList<Object>, LookupListener, ChangeListener {
5.131 + private static final Object IMPORTANT_FILES_KEY = new Object();
5.132 +
5.133 + private final Lookup.Result<ImportantFilesImplementation> lookupResult;
5.134 + private final ImportantFilesChildren importantFilesChildren;
5.135 + final ChangeSupport changeSupport = new ChangeSupport(this);
5.136 +
5.137 + private Node importantFilesNode;
5.138 +
5.139 + ImportantFilesNodeList(@NonNull Project project) {
5.140 + importantFilesChildren = new ImportantFilesChildren(project, changeSupport);
5.141 + lookupResult = project.getLookup().lookupResult(ImportantFilesImplementation.class);
5.142 + }
5.143 +
5.144 + @Override
5.145 + public List<Object> keys() {
5.146 + if (!importantFilesChildren.hasImportantFiles()) {
5.147 + return Collections.emptyList();
5.148 + }
5.149 + return Collections.singletonList(IMPORTANT_FILES_KEY);
5.150 + }
5.151 +
5.152 + @Override
5.153 + public void addChangeListener(ChangeListener listener) {
5.154 + changeSupport.addChangeListener(listener);
5.155 + }
5.156 +
5.157 + @Override
5.158 + public void removeChangeListener(ChangeListener listener) {
5.159 + changeSupport.removeChangeListener(listener);
5.160 + }
5.161 +
5.162 + @Override
5.163 + public synchronized Node node(Object key) {
5.164 + assert key == IMPORTANT_FILES_KEY : "Unexpected key " + key;//NOI18N
5.165 + if (importantFilesNode == null) {
5.166 + importantFilesNode = new ImportantFilesNode(importantFilesChildren);
5.167 + }
5.168 + return importantFilesNode;
5.169 + }
5.170 +
5.171 + @Override
5.172 + public void addNotify() {
5.173 + lookupResult.addLookupListener(WeakListeners.create(LookupListener.class, this, lookupResult));
5.174 + for (ImportantFilesImplementation provider : lookupResult.allInstances()) {
5.175 + provider.addChangeListener(WeakListeners.change(this, provider));
5.176 + }
5.177 + }
5.178 +
5.179 + @Override
5.180 + public void removeNotify() {
5.181 + }
5.182 +
5.183 + @Override
5.184 + public void resultChanged(LookupEvent ev) {
5.185 + fireChange();
5.186 + }
5.187 +
5.188 + @Override
5.189 + public void stateChanged(ChangeEvent e) {
5.190 + importantFilesChildren.refreshImportantFiles();
5.191 + fireChange();
5.192 + }
5.193 +
5.194 + private void fireChange() {
5.195 + EventQueue.invokeLater(new Runnable() {
5.196 + @Override
5.197 + public void run() {
5.198 + changeSupport.fireChange();
5.199 + }
5.200 + });
5.201 + }
5.202 + }
5.203 +
5.204 + private static final class ImportantFilesNode extends AbstractNode {
5.205 +
5.206 + @StaticResource
5.207 + private static final String BADGE = "org/netbeans/modules/python/project2/resources/py_25_16.png"; // NOI18N
5.208 +
5.209 + private final Node iconDelegate;
5.210 +
5.211 + ImportantFilesNode(Children children) {
5.212 + super(children);
5.213 + iconDelegate = DataFolder.findFolder(FileUtil.getConfigRoot()).getNodeDelegate();
5.214 + }
5.215 +
5.216 + @Override
5.217 + public String getDisplayName() {
5.218 + return "Important Files";
5.219 + }
5.220 +
5.221 + @Override
5.222 + public Image getIcon(int type) {
5.223 + return ImageUtilities.mergeImages(iconDelegate.getIcon(type), ImageUtilities.loadImage(BADGE), 7, 7);
5.224 + }
5.225 +
5.226 + @Override
5.227 + public Image getOpenedIcon(int type) {
5.228 + return getIcon(type);
5.229 + }
5.230 +
5.231 + @Override
5.232 + public Action[] getActions(boolean context) {
5.233 + return new Action[0];
5.234 + }
5.235 +
5.236 + }
5.237 +
5.238 + private static final class ImportantFilesChildren extends Children.Keys<ImportantFilesImplementation.FileInfo> {
5.239 +
5.240 + private static final Logger LOGGER = Logger.getLogger(ImportantFilesChildren.class.getName());
5.241 + private final FileObject projectDirectory;
5.242 + private final ChangeSupport changeSupport;
5.243 +
5.244 + ImportantFilesChildren(@NonNull Project project, ChangeSupport changeSupport) {
5.245 + super(true);
5.246 + this.projectDirectory = project.getProjectDirectory();
5.247 + this.changeSupport = changeSupport;
5.248 +
5.249 + FileUtil.addFileChangeListener(new FileChangeListener() {
5.250 + @Override
5.251 + public void fileFolderCreated(FileEvent fe) {
5.252 + }
5.253 +
5.254 + @Override
5.255 + public void fileDataCreated(FileEvent fe) {
5.256 + refreshImportantFiles();
5.257 + fireChange();
5.258 + }
5.259 +
5.260 + @Override
5.261 + public void fileChanged(FileEvent fe) {
5.262 + }
5.263 +
5.264 + @Override
5.265 + public void fileDeleted(FileEvent fe) {
5.266 + refreshImportantFiles();
5.267 + fireChange();
5.268 + }
5.269 +
5.270 + @Override
5.271 + public void fileRenamed(FileRenameEvent fe) {
5.272 + refreshImportantFiles();
5.273 + fireChange();
5.274 + }
5.275 +
5.276 + @Override
5.277 + public void fileAttributeChanged(FileAttributeEvent fe) {
5.278 + }
5.279 + });
5.280 + }
5.281 +
5.282 + public boolean hasImportantFiles() {
5.283 + return !getImportantFiles().isEmpty();
5.284 + }
5.285 +
5.286 + private void refreshImportantFiles() {
5.287 + setKeys();
5.288 + }
5.289 +
5.290 + private void fireChange() {
5.291 + EventQueue.invokeLater(new Runnable() {
5.292 + @Override
5.293 + public void run() {
5.294 + changeSupport.fireChange();
5.295 + }
5.296 + });
5.297 + }
5.298 +
5.299 + @Override
5.300 + protected Node[] createNodes(ImportantFilesImplementation.FileInfo key) {
5.301 + assert key != null;
5.302 + try {
5.303 + return new Node[]{new ImportantFileNode(key)};
5.304 + } catch (DataObjectNotFoundException ex) {
5.305 + LOGGER.log(Level.WARNING, null, ex);
5.306 + }
5.307 + return new Node[0];
5.308 + }
5.309 +
5.310 + @Override
5.311 + protected void addNotify() {
5.312 + setKeys();
5.313 + }
5.314 +
5.315 + @Override
5.316 + protected void removeNotify() {
5.317 + setKeys(Collections.<ImportantFilesImplementation.FileInfo>emptyList());
5.318 + }
5.319 +
5.320 + private void setKeys() {
5.321 + List<ImportantFilesImplementation.FileInfo> importantFiles = getImportantFiles();
5.322 + Collections.sort(importantFiles, new FileInfoComparator());
5.323 + setKeys(importantFiles);
5.324 + }
5.325 +
5.326 + private List<ImportantFilesImplementation.FileInfo> getImportantFiles() {
5.327 + Set<ImportantFilesImplementation.FileInfo> importantFiles = new LinkedHashSet<>();
5.328 + for (String fileName : PYTHON_IMPORTANT_FILES) {
5.329 + final FileObject file = projectDirectory.getFileObject(fileName);
5.330 + if (file != null) {
5.331 + importantFiles.add(new ImportantFilesImplementation.FileInfo(file));
5.332 + }
5.333 + }
5.334 +
5.335 + return new ArrayList<>(importantFiles);
5.336 + }
5.337 +
5.338 + }
5.339 +
5.340 + private static final class ImportantFileNode extends FilterNode {
5.341 +
5.342 + private final ImportantFilesImplementation.FileInfo fileInfo;
5.343 +
5.344 + ImportantFileNode(ImportantFilesImplementation.FileInfo fileInfo) throws DataObjectNotFoundException {
5.345 + super(DataObject.find(fileInfo.getFile()).getNodeDelegate());
5.346 + this.fileInfo = fileInfo;
5.347 + }
5.348 +
5.349 + @Override
5.350 + public String getDisplayName() {
5.351 + String displayName = fileInfo.getDisplayName();
5.352 + if (displayName != null) {
5.353 + return displayName;
5.354 + }
5.355 + return super.getDisplayName();
5.356 + }
5.357 +
5.358 + @Override
5.359 + public String getHtmlDisplayName() {
5.360 + String displayName = getDisplayName();
5.361 + assert displayName != null : fileInfo;
5.362 + StatusDecorator statusDecorator = getStatusDecorator();
5.363 + if (statusDecorator != null) {
5.364 + return statusDecorator.annotateNameHtml(displayName, Collections.singleton(fileInfo.getFile()));
5.365 + }
5.366 + return displayName;
5.367 + }
5.368 +
5.369 + @Override
5.370 + public String getShortDescription() {
5.371 + String description = fileInfo.getDescription();
5.372 + if (description != null) {
5.373 + return description;
5.374 + }
5.375 + return super.getShortDescription();
5.376 + }
5.377 +
5.378 + @CheckForNull
5.379 + private StatusDecorator getStatusDecorator() {
5.380 + try {
5.381 + return fileInfo.getFile().getFileSystem().getDecorator();
5.382 + } catch (FileStateInvalidException ex) {
5.383 + LOGGER.log(Level.INFO, null, ex);
5.384 + }
5.385 + return null;
5.386 + }
5.387 +
5.388 + }
5.389 +
5.390 + private static final class FileInfoComparator implements Comparator<ImportantFilesImplementation.FileInfo> {
5.391 +
5.392 + @Override
5.393 + public int compare(ImportantFilesImplementation.FileInfo fileInfo1, ImportantFilesImplementation.FileInfo fileInfo2) {
5.394 + FileObject file1 = fileInfo1.getFile();
5.395 + FileObject file2 = fileInfo2.getFile();
5.396 + try {
5.397 + return DataFolder.SortMode.FOLDER_NAMES.compare(DataObject.find(file1), DataObject.find(file2));
5.398 + } catch (DataObjectNotFoundException ex) {
5.399 + return file1.getNameExt().compareToIgnoreCase(file2.getNameExt());
5.400 + }
5.401 + }
5.402 +
5.403 + }
5.404 +
5.405 +}