VCS: hide many unversioned files - allow to browse them in a separate dialog
authorirengrig <Irina.Chernushina@jetbrains.com>
Fri, 12 Mar 2010 08:48:26 +0000 (11:48 +0300)
committerirengrig <Irina.Chernushina@jetbrains.com>
Fri, 12 Mar 2010 08:48:26 +0000 (11:48 +0300)
platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangeListManagerImpl.java
platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangesViewManager.java
platform/vcs-impl/src/com/intellij/openapi/vcs/changes/UnversionedViewDialog.java [new file with mode: 0644]
platform/vcs-impl/src/com/intellij/openapi/vcs/changes/VirtualFileHolder.java
platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/ChangesBrowserManyUnversionedFilesNode.java [new file with mode: 0644]
platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/ChangesListView.java
platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/TreeModelBuilder.java

index 8dd23e8abf91a3055955e62037d87945448b0d67..6a201f6dda929553c617d49c0daa33bdf74a4179 100644 (file)
@@ -446,7 +446,12 @@ public class ChangeListManagerImpl extends ChangeListManagerEx implements Projec
   }
 
   List<VirtualFile> getUnversionedFiles() {
-    return new ArrayList<VirtualFile>(myComposite.getVFHolder(FileHolder.HolderType.UNVERSIONED).getFiles());
+    return myComposite.getVFHolder(FileHolder.HolderType.UNVERSIONED).getFiles();
+  }
+
+  Pair<Integer, Integer> getUnversionedFilesSize() {
+    final VirtualFileHolder holder = myComposite.getVFHolder(FileHolder.HolderType.UNVERSIONED);
+    return new Pair<Integer, Integer>(holder.getSize(), holder.getNumDirs());
   }
 
   List<VirtualFile> getModifiedWithoutEditing() {
index fffb8c462249df20942b1ac2547b608c4a815b10..4d5a12ddef873ade546e471924844c1da5b3463d 100644 (file)
@@ -58,8 +58,11 @@ import java.awt.*;
 import java.awt.event.InputEvent;
 import java.awt.event.KeyEvent;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
 
 public class ChangesViewManager extends AbstractProjectComponent implements JDOMExternalizable {
+  public static final int UNVERSIONED_MAX_SIZE = 50;
   private boolean SHOW_FLATTEN_MODE = true;
   private boolean SHOW_IGNORED_MODE = false;
 
@@ -216,8 +219,14 @@ public class ChangesViewManager extends AbstractProjectComponent implements JDOM
   void refreshView() {
     if (myDisposed || ! myProject.isInitialized() || ApplicationManager.getApplication().isUnitTestMode()) return;
     ChangeListManagerImpl changeListManager = ChangeListManagerImpl.getInstanceImpl(myProject);
-    myView.updateModel(changeListManager.getChangeListsCopy(),
-                       changeListManager.getUnversionedFiles(),
+
+    final Pair<Integer, Integer> unv = changeListManager.getUnversionedFilesSize();
+    final boolean manyUnversioned = unv.getFirst() > UNVERSIONED_MAX_SIZE;
+    final Trinity<List<VirtualFile>, Integer, Integer> unversionedPair =
+      new Trinity<List<VirtualFile>, Integer, Integer>(manyUnversioned ? Collections.<VirtualFile>emptyList() : changeListManager.getUnversionedFiles(), unv.getFirst(),
+                                                       unv.getSecond());
+
+    myView.updateModel(changeListManager.getChangeListsCopy(), unversionedPair,
                        changeListManager.getDeletedFiles(),
                        changeListManager.getModifiedWithoutEditing(),
                        changeListManager.getSwitchedFilesMap(),
diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/UnversionedViewDialog.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/UnversionedViewDialog.java
new file mode 100644 (file)
index 0000000..b766619
--- /dev/null
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2000-2010 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.openapi.vcs.changes;
+
+import com.intellij.ide.CommonActionsManager;
+import com.intellij.ide.TreeExpander;
+import com.intellij.ide.util.treeView.TreeState;
+import com.intellij.openapi.actionSystem.*;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ModalityState;
+import com.intellij.openapi.project.DumbAware;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.DialogWrapper;
+import com.intellij.openapi.vcs.VcsBundle;
+import com.intellij.openapi.vcs.changes.actions.MoveChangesToAnotherListAction;
+import com.intellij.openapi.vcs.changes.ui.ChangesBrowserNode;
+import com.intellij.openapi.vcs.changes.ui.ChangesBrowserNodeRenderer;
+import com.intellij.openapi.vcs.changes.ui.ChangesListView;
+import com.intellij.openapi.vcs.changes.ui.TreeModelBuilder;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.Icons;
+import com.intellij.util.ui.tree.TreeUtil;
+
+import javax.swing.*;
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.TreePath;
+import java.awt.*;
+import java.util.Arrays;
+import java.util.List;
+
+public class UnversionedViewDialog extends DialogWrapper {
+  private JPanel myPanel;
+  private final ChangesListView myView;
+  private final ChangeListManager myChangeListManager;
+  private boolean myInRefresh;
+  private final Project myProject;
+  private boolean myFlattenState;
+
+  public UnversionedViewDialog(final Project project) {
+    super(project, true);
+    setTitle("Unversioned files");
+    myProject = project;
+    myView = new ChangesListView(project) {
+      @Override
+      public void calcData(DataKey key, DataSink sink) {
+        super.calcData(key, sink);
+        if (ChangesListView.UNVERSIONED_FILES_DATA_KEY.is(key.getName())) {
+          sink.put(key, Arrays.asList(getSelectedFiles()));
+        }
+      }
+    };
+    myChangeListManager = ChangeListManager.getInstance(project);
+    createPanel();
+    setOKButtonText("Close");
+
+    init();
+    initData(((ChangeListManagerImpl) myChangeListManager).getUnversionedFiles());
+    myView.setMinimumSize(new Dimension(100, 100));
+  }
+
+  @Override
+  protected Action[] createActions() {
+    return new Action[]{getOKAction()};
+  }
+
+  private void initData(final List<VirtualFile> files) {
+    final TreeState state = TreeState.createOn(myView, (ChangesBrowserNode)myView.getModel().getRoot());
+
+    TreeModelBuilder builder = new TreeModelBuilder(myProject, myFlattenState);
+    final DefaultTreeModel model = builder.buildModelFromFiles(files);
+    myView.setModel(model);
+    myView.setCellRenderer(new ChangesBrowserNodeRenderer(myProject, myFlattenState, true));
+    myView.expandPath(new TreePath(((ChangesBrowserNode)model.getRoot()).getPath()));
+
+    state.applyTo(myView);
+  }
+
+  private void createPanel() {
+    myPanel = new JPanel(new BorderLayout());
+
+    final DefaultActionGroup group = new DefaultActionGroup();
+    final CommonActionsManager cam = CommonActionsManager.getInstance();
+    final Expander expander = new Expander();
+    final AnAction expandAction = cam.createExpandAllAction(expander, myView);
+    group.add(expandAction);
+    final AnAction collapseAction = cam.createCollapseAllAction(expander, myView);
+    group.add(collapseAction);
+    group.add(new ToggleShowFlattenAction());
+    group.add(new MoveChangesToAnotherListAction() {
+      @Override
+      public void actionPerformed(AnActionEvent e) {
+        super.actionPerformed(e);
+        refreshView();
+      }
+    });
+
+    myView.setMenuActions(group);
+    myView.setShowFlatten(false);
+
+    final ActionToolbar actionToolbar = ActionManager.getInstance().createActionToolbar("UNVERSIONED_DIALOG", group, false);
+    myPanel.add(actionToolbar.getComponent(), BorderLayout.WEST);
+    myPanel.add(new JScrollPane(myView), BorderLayout.CENTER);
+  }
+
+  @Override
+  protected String getDimensionServiceKey() {
+    return "com.intellij.openapi.vcs.changes.UnversionedViewDialog";
+  }
+
+  @Override
+  public JComponent getPreferredFocusedComponent() {
+    return myView;
+  }
+
+  @Override
+  protected JComponent createCenterPanel() {
+    return myPanel;
+  }
+
+  private class Expander implements TreeExpander {
+    public void expandAll() {
+      TreeUtil.expandAll(myView);
+    }
+
+    public boolean canExpand() {
+      return true;
+    }
+
+    public void collapseAll() {
+      TreeUtil.collapseAll(myView, 1);
+      TreeUtil.expand(myView, 0);
+    }
+
+    public boolean canCollapse() {
+      return true;
+    }
+  }
+
+  private void refreshView() {
+    ApplicationManager.getApplication().assertIsDispatchThread();
+
+    if (myInRefresh) return;
+    myInRefresh = true;
+    
+    myChangeListManager.invokeAfterUpdate(new Runnable() {
+      public void run() {
+        try {
+          initData(((ChangeListManagerImpl) myChangeListManager).getUnversionedFiles());
+        } finally {
+          myInRefresh = false;
+        }
+      }
+    }, InvokeAfterUpdateMode.BACKGROUND_NOT_CANCELLABLE, "", ModalityState.current());
+  }
+
+  public class ToggleShowFlattenAction extends ToggleAction implements DumbAware {
+    public ToggleShowFlattenAction() {
+      super(VcsBundle.message("changes.action.show.directories.text"),
+            VcsBundle.message("changes.action.show.directories.description"),
+            Icons.DIRECTORY_CLOSED_ICON);
+      myFlattenState = false;
+    }
+
+    public boolean isSelected(AnActionEvent e) {
+      return !myFlattenState;
+    }
+
+    public void setSelected(AnActionEvent e, boolean state) {
+      myFlattenState = !state;
+      myView.setShowFlatten(myFlattenState);
+      refreshView();
+    }
+  }
+}
index c8293e05030ee1131c018b5a157a9e6fe0963537..de483f58930d70cbc21c15aab3c67483ce3dbf9e 100644 (file)
@@ -17,6 +17,7 @@ package com.intellij.openapi.vcs.changes;
 
 import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Computable;
 import com.intellij.openapi.vcs.FilePath;
 import com.intellij.openapi.vcs.FilePathImpl;
 import com.intellij.openapi.vcs.ProjectLevelVcsManager;
@@ -31,6 +32,7 @@ public class VirtualFileHolder implements FileHolder {
   private final Set<VirtualFile> myFiles = new HashSet<VirtualFile>();
   private final Project myProject;
   private final HolderType myType;
+  private int myNumDirs;
 
   public VirtualFileHolder(Project project, final HolderType type) {
     myProject = project;
@@ -43,13 +45,16 @@ public class VirtualFileHolder implements FileHolder {
 
   public void cleanAll() {
     myFiles.clear();
+    myNumDirs = 0;
   }
 
-  static void cleanScope(final Project project, final Collection<VirtualFile> files, final VcsDirtyScope scope) {
-    ApplicationManager.getApplication().runReadAction(new Runnable() {
-      public void run() {
+  // returns number of removed directories
+  static int cleanScope(final Project project, final Collection<VirtualFile> files, final VcsDirtyScope scope) {
+    return ApplicationManager.getApplication().runReadAction(new Computable<Integer>() {
+      public Integer compute() {
+        int result = 0;
         // to avoid deadlocks caused by incorrect lock ordering, need to lock on this after taking read action
-        if (project.isDisposed() || files.isEmpty()) return;
+        if (project.isDisposed() || files.isEmpty()) return 0;
         final List<VirtualFile> currentFiles = new ArrayList<VirtualFile>(files);
         if (scope.getRecursivelyDirtyDirectories().size() == 0) {
           final Set<FilePath> dirtyFiles = scope.getDirtyFiles();
@@ -58,12 +63,16 @@ public class VirtualFileHolder implements FileHolder {
             VirtualFile f = dirtyFile.getVirtualFile();
             if (f != null) {
               files.remove(f);
+              if (f.isDirectory()) ++ result;
             }
             else {
               if (!cleanedDroppedFiles) {
                 cleanedDroppedFiles = true;
                 for(VirtualFile file: currentFiles) {
-                  if (fileDropped(project, file)) files.remove(file);
+                  if (fileDropped(project, file)) {
+                    files.remove(file);
+                    if (file.isDirectory()) ++ result;
+                  }
                 }
               }
             }
@@ -73,15 +82,17 @@ public class VirtualFileHolder implements FileHolder {
           for (VirtualFile file : currentFiles) {
             if (fileDropped(project, file) || scope.belongsTo(new FilePathImpl(file))) {
               files.remove(file);
+              if (file.isDirectory()) ++ result;
             }
           }
         }
+        return result;
       }
     });
   }
 
   public void cleanScope(final VcsDirtyScope scope) {
-    cleanScope(myProject, myFiles, scope);
+    myNumDirs -= cleanScope(myProject, myFiles, scope);
   }
 
   private static boolean fileDropped(final Project project, final VirtualFile file) {
@@ -90,10 +101,12 @@ public class VirtualFileHolder implements FileHolder {
 
   public void addFile(VirtualFile file) {
     myFiles.add(file);
+    if (file.isDirectory()) ++ myNumDirs;
   }
 
   public void removeFile(VirtualFile file) {
     myFiles.remove(file);
+    if (file.isDirectory()) -- myNumDirs;
   }
 
   public List<VirtualFile> getFiles() {
@@ -103,6 +116,7 @@ public class VirtualFileHolder implements FileHolder {
   public VirtualFileHolder copy() {
     final VirtualFileHolder copyHolder = new VirtualFileHolder(myProject, myType);
     copyHolder.myFiles.addAll(myFiles);
+    copyHolder.myNumDirs = myNumDirs;
     return copyHolder;
   }
 
@@ -124,4 +138,12 @@ public class VirtualFileHolder implements FileHolder {
   public int hashCode() {
     return myFiles.hashCode();
   }
+
+  public int getSize() {
+    return myFiles.size();
+  }
+
+  public int getNumDirs() {
+    return myNumDirs;
+  }
 }
diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/ChangesBrowserManyUnversionedFilesNode.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/ChangesBrowserManyUnversionedFilesNode.java
new file mode 100644 (file)
index 0000000..5983827
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2000-2010 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.openapi.vcs.changes.ui;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vcs.changes.ChangeListOwner;
+import com.intellij.openapi.vcs.changes.UnversionedViewDialog;
+import com.intellij.ui.SimpleTextAttributes;
+
+public class ChangesBrowserManyUnversionedFilesNode extends ChangesBrowserNode {
+  private final Project myProject;
+  private final int myUnversionedSize;
+  private final int myDirsSize;
+  private final MyUnversionedShower myShower;
+
+  public ChangesBrowserManyUnversionedFilesNode(Project project, int unversionedSize, int dirsSize) {
+    super(UNVERSIONED_FILES_TAG);
+    myProject = project;
+    myUnversionedSize = unversionedSize;
+    myDirsSize = dirsSize;
+    myShower = new MyUnversionedShower(myProject);
+  }
+
+  public boolean canAcceptDrop(final ChangeListDragBean dragBean) {
+    return false;
+  }
+
+  public void acceptDrop(final ChangeListOwner dragOwner, final ChangeListDragBean dragBean) {
+  }
+
+  @Override
+  public void render(ChangesBrowserNodeRenderer renderer, boolean selected, boolean expanded, boolean hasFocus) {
+    renderer.append(userObject.toString(), SimpleTextAttributes.REGULAR_ATTRIBUTES);
+    final String s = "(" + (myDirsSize > 0 ? myDirsSize + " directories and " : "") + (myUnversionedSize - myDirsSize) + " files) ";
+    renderer.append(s, SimpleTextAttributes.GRAY_ITALIC_ATTRIBUTES);
+    renderer.append(" Click to browse", SimpleTextAttributes.LINK_ATTRIBUTES, myShower);
+  }
+
+  private static class MyUnversionedShower implements Runnable {
+    private final Project myProject;
+
+    public MyUnversionedShower(Project project) {
+      myProject = project;
+    }
+
+    public void run() {
+      final UnversionedViewDialog dialog = new UnversionedViewDialog(myProject);
+      dialog.show();
+    }
+  }
+}
index ac42ae182fc7a5c87adce7166a6fb7ad0ccf8dda..3159ff6a118f77e542e0c019993e48fbead35271 100644 (file)
@@ -24,6 +24,7 @@ import com.intellij.openapi.fileChooser.actions.VirtualFileDeleteProvider;
 import com.intellij.openapi.fileEditor.OpenFileDescriptor;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.util.Trinity;
 import com.intellij.openapi.vcs.*;
 import com.intellij.openapi.vcs.changes.*;
 import com.intellij.openapi.vcs.changes.issueLinks.TreeLinkMouseListener;
@@ -134,7 +135,7 @@ public class ChangesListView extends Tree implements TypeSafeDataProvider, Advan
     myShowFlatten = showFlatten;
   }
 
-  public void updateModel(List<? extends ChangeList> changeLists, List<VirtualFile> unversionedFiles, final List<LocallyDeletedChange> locallyDeletedFiles,
+  public void updateModel(List<? extends ChangeList> changeLists, Trinity<List<VirtualFile>, Integer, Integer> unversionedFiles, final List<LocallyDeletedChange> locallyDeletedFiles,
                           List<VirtualFile> modifiedWithoutEditing,
                           MultiMap<String, VirtualFile> switchedFiles,
                           @Nullable Map<VirtualFile, String> switchedRoots,
@@ -256,7 +257,7 @@ public class ChangesListView extends Tree implements TypeSafeDataProvider, Advan
     return files;
   }
 
-  private VirtualFile[] getSelectedFiles() {
+  protected VirtualFile[] getSelectedFiles() {
     final Change[] changes = getSelectedChanges();
     Collection<VirtualFile> files = new HashSet<VirtualFile>();
     for (Change change : changes) {
index a93db56c25219a52bd98b87dadb3f86082e188d5..acf3a6ee4af9b333e6a47b25f0b44fae15ec6a77 100644 (file)
@@ -18,6 +18,7 @@ package com.intellij.openapi.vcs.changes.ui;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.Computable;
 import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.util.Trinity;
 import com.intellij.openapi.vcs.FilePath;
 import com.intellij.openapi.vcs.FilePathImpl;
 import com.intellij.openapi.vcs.FileStatus;
@@ -118,7 +119,7 @@ public class TreeModelBuilder {
   }
 
   public DefaultTreeModel buildModel(final List<? extends ChangeList> changeLists,
-                                     final List<VirtualFile> unversionedFiles,
+                                     final Trinity<List<VirtualFile>, Integer, Integer> unversionedFiles,
                                      final List<LocallyDeletedChange> locallyDeletedFiles,
                                      final List<VirtualFile> modifiedWithoutEditing,
                                      final MultiMap<String, VirtualFile> switchedFiles,
@@ -132,9 +133,16 @@ public class TreeModelBuilder {
       resetGrouping();
       buildVirtualFiles(modifiedWithoutEditing, ChangesBrowserNode.MODIFIED_WITHOUT_EDITING_TAG);
     }
-    if (!unversionedFiles.isEmpty()) {
+    final boolean manyUnversioned = unversionedFiles.getSecond() > unversionedFiles.getFirst().size();
+    if (manyUnversioned || (! unversionedFiles.getFirst().isEmpty())) {
       resetGrouping();
-      buildVirtualFiles(unversionedFiles, ChangesBrowserNode.UNVERSIONED_FILES_TAG);
+
+      if (manyUnversioned) {
+        final ChangesBrowserNode baseNode = new ChangesBrowserManyUnversionedFilesNode(myProject, unversionedFiles.getSecond(), unversionedFiles.getThird());
+        model.insertNodeInto(baseNode, root, root.getChildCount());
+      } else {
+        buildVirtualFiles(unversionedFiles.getFirst(), ChangesBrowserNode.UNVERSIONED_FILES_TAG);
+      }
     }
     if (switchedRoots != null && (! switchedRoots.isEmpty())) {
       resetGrouping();