diff --git a/project_view_pane/META-INF/plugin.xml b/project_view_pane/META-INF/plugin.xml
index 758cf6ae1..9121c0ff1 100644
--- a/project_view_pane/META-INF/plugin.xml
+++ b/project_view_pane/META-INF/plugin.xml
@@ -13,6 +13,7 @@
com.intellij.modules.lang
+
diff --git a/project_view_pane/src/org/jetbrains/plugins/sample/pane/ImagesProjectNode.java b/project_view_pane/src/org/jetbrains/plugins/sample/pane/ImagesProjectNode.java
new file mode 100644
index 000000000..43ea9f838
--- /dev/null
+++ b/project_view_pane/src/org/jetbrains/plugins/sample/pane/ImagesProjectNode.java
@@ -0,0 +1,171 @@
+package org.jetbrains.plugins.sample.pane;
+
+import com.intellij.icons.AllIcons;
+import com.intellij.ide.projectView.PresentationData;
+import com.intellij.ide.projectView.ProjectView;
+import com.intellij.ide.projectView.impl.ProjectViewImpl;
+import com.intellij.ide.util.treeView.AbstractTreeNode;
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.fileEditor.FileEditorManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Disposer;
+import com.intellij.openapi.util.Key;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.VirtualFileAdapter;
+import com.intellij.openapi.vfs.VirtualFileEvent;
+import com.intellij.psi.search.FilenameIndex;
+import com.intellij.util.Alarm;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import java.util.*;
+
+/**
+ * @author Anna Bulenkova
+ */
+public class ImagesProjectNode extends AbstractTreeNode {
+ private static final Key> IMAGES_PROJECT_DIRS = Key.create("images.files.or.directories");
+
+ public ImagesProjectNode(final Project project) {
+ super(project, project.getBaseDir());
+ scanImages(project);
+
+ subscribeToVFS(project);
+ }
+
+ public ImagesProjectNode(Project project, VirtualFile file) {
+ super(project, file);
+ }
+
+ private void scanImages(Project project) {
+ addAllByExt(project, "png");
+ addAllByExt(project, "jpg");
+ }
+
+ private void addAllByExt(Project project, String ext) {
+ final Set imagesFiles = getImagesFiles(project);
+ final VirtualFile projectDir = project.getBaseDir();
+ for (VirtualFile file : FilenameIndex.getAllFilesByExt(project, ext)) {
+ while (file != null && !file.equals(projectDir)) {
+ imagesFiles.add(file);
+ file = file.getParent();
+ }
+ }
+ }
+
+ @NotNull
+ private Set getImagesFiles(Project project) {
+ Set files = project.getUserData(IMAGES_PROJECT_DIRS);
+ if (files == null) {
+ files = new HashSet();
+ project.putUserData(IMAGES_PROJECT_DIRS, files);
+ }
+ return files;
+ }
+
+ @Override
+ protected VirtualFile getVirtualFile() {
+ return getValue();
+ }
+
+ @NotNull
+ @Override
+ public Collection extends AbstractTreeNode> getChildren() {
+ final List files = new ArrayList(0);
+ for (VirtualFile file : getValue().getChildren()) {
+ if (getImagesFiles(myProject).contains(file)) {
+ files.add(file);
+ }
+ }
+ if (files.isEmpty()) return Collections.emptyList();
+ final List nodes = new ArrayList(files.size());
+ final boolean alwaysOnTop = ((ProjectViewImpl) ProjectView.getInstance(myProject)).isFoldersAlwaysOnTop();
+ Collections.sort(files, new Comparator() {
+ @Override
+ public int compare(VirtualFile o1, VirtualFile o2) {
+ if (alwaysOnTop) {
+ final boolean d1 = o1.isDirectory();
+ final boolean d2 = o2.isDirectory();
+ if (d1 && !d2) return -1;
+ if (!d1 && d2) return 1;
+ }
+
+ return StringUtil.naturalCompare(o1.getName(), o2.getName());
+ }
+ });
+ for (VirtualFile file : files) {
+ nodes.add(new ImagesProjectNode(myProject, file));
+ }
+ return nodes;
+ }
+
+ @Override
+ protected void update(PresentationData data) {
+ data.setIcon(getValue().isDirectory() ? AllIcons.Nodes.Folder : getValue().getFileType().getIcon());
+ data.setPresentableText(getValue().getName());
+ }
+
+
+ @Override
+ public boolean canNavigate() {
+ return !getValue().isDirectory();
+ }
+
+ @Override
+ public boolean canNavigateToSource() {
+ return canNavigate();
+ }
+
+ @Override
+ public void navigate(boolean requestFocus) {
+ FileEditorManager.getInstance(myProject).openFile(getValue(), false);
+ }
+
+ private void subscribeToVFS(final Project project) {
+ final Alarm alarm = new Alarm(Alarm.ThreadToUse.POOLED_THREAD, project);
+ LocalFileSystem.getInstance().addVirtualFileListener(new VirtualFileAdapter() {
+ {
+ final VirtualFileAdapter me = this;
+ Disposer.register(project, new Disposable() {
+ @Override
+ public void dispose() {
+ LocalFileSystem.getInstance().removeVirtualFileListener(me);
+ }
+ });
+ }
+
+ @Override
+ public void fileCreated(@NotNull VirtualFileEvent event) {
+ handle(event);
+ }
+
+ @Override
+ public void fileDeleted(@NotNull VirtualFileEvent event) {
+ handle(event);
+ }
+
+ void handle(VirtualFileEvent event) {
+ final String filename = event.getFileName().toLowerCase();
+ if (filename.endsWith(".png") || filename.endsWith(".jpg")) {
+ alarm.cancelAllRequests();
+ alarm.addRequest(new Runnable() {
+ public void run() {
+ getImagesFiles(project).clear();
+ scanImages(project);
+ //noinspection SSBasedInspection
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ ProjectView.getInstance(myProject).getProjectViewPaneById(ImagesProjectViewPane.ID).updateFromRoot(true);
+ }
+ });
+ }
+ }, 1000);
+ }
+ }
+ });
+ }
+}
+
diff --git a/project_view_pane/src/org/jetbrains/plugins/sample/pane/ImagesProjectViewPane.java b/project_view_pane/src/org/jetbrains/plugins/sample/pane/ImagesProjectViewPane.java
new file mode 100644
index 000000000..7d5d902ed
--- /dev/null
+++ b/project_view_pane/src/org/jetbrains/plugins/sample/pane/ImagesProjectViewPane.java
@@ -0,0 +1,109 @@
+package org.jetbrains.plugins.sample.pane;
+
+import com.intellij.icons.AllIcons;
+import com.intellij.ide.SelectInTarget;
+import com.intellij.ide.impl.ProjectViewSelectInTarget;
+import com.intellij.ide.projectView.ViewSettings;
+import com.intellij.ide.projectView.impl.AbstractProjectViewPSIPane;
+import com.intellij.ide.projectView.impl.ProjectAbstractTreeStructureBase;
+import com.intellij.ide.projectView.impl.ProjectTreeStructure;
+import com.intellij.ide.projectView.impl.ProjectViewTree;
+import com.intellij.ide.util.treeView.AbstractTreeBuilder;
+import com.intellij.ide.util.treeView.AbstractTreeNode;
+import com.intellij.ide.util.treeView.AbstractTreeUpdater;
+import com.intellij.openapi.project.Project;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeModel;
+
+/**
+ * @author Anna Bulenkova
+ */
+public class ImagesProjectViewPane extends AbstractProjectViewPSIPane {
+ public static final String ID = "IMAGES";
+
+ protected ImagesProjectViewPane(Project project) {
+ super(project);
+ }
+
+ @Override
+ public String getTitle() {
+ return "Images";
+ }
+
+ @Override
+ public javax.swing.Icon getIcon() {
+ return AllIcons.FileTypes.Custom;
+ }
+
+ @NotNull
+ @Override
+ public String getId() {
+ return ID;
+ }
+
+ @Override
+ public int getWeight() {
+ return 10;
+ }
+
+ @Override
+ public SelectInTarget createSelectInTarget() {
+ return new ProjectViewSelectInTarget(myProject) {
+
+ @Override
+ public String toString() {
+ return "images";
+ }
+
+ @Nullable
+ @Override
+ public String getMinorViewId() {
+ return "images";
+ }
+
+ @Override
+ public float getWeight() {
+ return 10;
+ }
+ };
+ }
+
+ @Override
+ protected ProjectAbstractTreeStructureBase createStructure() {
+ return new ProjectTreeStructure(myProject, ID) {
+ @Override
+ protected AbstractTreeNode createRoot(Project project, ViewSettings settings) {
+ return new ImagesProjectNode(project);
+ }
+
+ @Override
+ public Object[] getChildElements(Object element) {
+ return super.getChildElements(element);
+ }
+ };
+ }
+
+ @Override
+ protected ProjectViewTree createTree(DefaultTreeModel model) {
+ return new ProjectViewTree(myProject, model) {
+ @Override
+ public DefaultMutableTreeNode getSelectedNode() {
+ return ImagesProjectViewPane.this.getSelectedNode();
+ }
+
+ @Override
+ public boolean isRootVisible() {
+ return true;
+ }
+ };
+ }
+
+ @Override
+ protected AbstractTreeUpdater createTreeUpdater(AbstractTreeBuilder builder) {
+ return new AbstractTreeUpdater(builder);
+ }
+}
+