VCS: fix group by structure for Changes | Local
[fedora-idea.git] / platform / vcs-impl / src / com / intellij / openapi / vcs / changes / ui / TreeModelBuilder.java
blob64fefab61f4ea822a3fae966ac5ee88ea8285040
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.openapi.vcs.changes.ui;
18 import com.intellij.openapi.project.Project;
19 import com.intellij.openapi.util.Computable;
20 import com.intellij.openapi.util.Pair;
21 import com.intellij.openapi.vcs.FilePath;
22 import com.intellij.openapi.vcs.FilePathImpl;
23 import com.intellij.openapi.vcs.FileStatus;
24 import com.intellij.openapi.vcs.VcsBundle;
25 import com.intellij.openapi.vcs.changes.*;
26 import com.intellij.openapi.vfs.VirtualFile;
27 import com.intellij.ui.SimpleColoredComponent;
28 import com.intellij.ui.SimpleTextAttributes;
29 import com.intellij.util.containers.MultiMap;
30 import com.intellij.util.ui.tree.TreeUtil;
31 import org.jetbrains.annotations.NonNls;
32 import org.jetbrains.annotations.Nullable;
34 import javax.swing.tree.DefaultTreeModel;
35 import javax.swing.tree.MutableTreeNode;
36 import javax.swing.tree.TreeNode;
37 import java.util.*;
39 /**
40 * @author max
42 public class TreeModelBuilder {
43 @NonNls public static final String ROOT_NODE_VALUE = "root";
45 private final Project myProject;
46 private final boolean showFlatten;
47 private DefaultTreeModel model;
48 private final ChangesBrowserNode root;
49 private boolean myPolicyInitialized;
50 private ChangesGroupingPolicy myPolicy;
51 private final HashMap<String, ChangesBrowserNode> myFoldersCache;
53 public TreeModelBuilder(final Project project, final boolean showFlatten) {
54 myProject = project;
55 this.showFlatten = showFlatten;
56 root = ChangesBrowserNode.create(myProject, ROOT_NODE_VALUE);
57 model = new DefaultTreeModel(root);
58 myFoldersCache = new HashMap<String, ChangesBrowserNode>();
61 public DefaultTreeModel buildModel(final List<Change> changes, final ChangeNodeDecorator changeNodeDecorator) {
62 final ChangesGroupingPolicy policy = createGroupingPolicy();
63 for (final Change change : changes) {
64 insertChangeNode(change, policy, root, new Computable<ChangesBrowserNode>() {
65 public ChangesBrowserNode compute() {
66 return new ChangesBrowserChangeNode(myProject, change, changeNodeDecorator);
68 });
71 collapseDirectories(model, root);
72 sortNodes();
74 return model;
77 @Nullable
78 private ChangesGroupingPolicy createGroupingPolicy() {
79 if (! myPolicyInitialized) {
80 myPolicyInitialized = true;
81 final ChangesGroupingPolicyFactory factory = ChangesGroupingPolicyFactory.getInstance(myProject);
82 if (factory != null) {
83 myPolicy = factory.createGroupingPolicy(model);
86 return myPolicy;
89 public DefaultTreeModel buildModelFromFiles(final List<VirtualFile> files) {
90 buildVirtualFiles(files, null);
91 collapseDirectories(model, root);
92 sortNodes();
93 return model;
96 public DefaultTreeModel buildModelFromFilePaths(final Collection<FilePath> files) {
97 buildFilePaths(files, root);
98 collapseDirectories(model, root);
99 sortNodes();
100 return model;
103 private static class MyChangeNodeUnderChangeListDecorator extends RemoteStatusChangeNodeDecorator {
104 private final ChangeListRemoteState.Reporter myReporter;
106 private MyChangeNodeUnderChangeListDecorator(final RemoteRevisionsCache remoteRevisionsCache, final ChangeListRemoteState.Reporter reporter) {
107 super(remoteRevisionsCache);
108 myReporter = reporter;
111 @Override
112 protected void reportState(boolean state) {
113 myReporter.report(state);
116 public void preDecorate(Change change, ChangesBrowserNodeRenderer renderer, boolean showFlatten) {
120 public DefaultTreeModel buildModel(final List<? extends ChangeList> changeLists,
121 final List<VirtualFile> unversionedFiles,
122 final List<LocallyDeletedChange> locallyDeletedFiles,
123 final List<VirtualFile> modifiedWithoutEditing,
124 final MultiMap<String, VirtualFile> switchedFiles,
125 @Nullable Map<VirtualFile, String> switchedRoots,
126 @Nullable final List<VirtualFile> ignoredFiles, @Nullable final List<VirtualFile> lockedFolders,
127 @Nullable final Map<VirtualFile, LogicalLock> logicallyLockedFiles) {
128 buildModel(changeLists);
130 if (!modifiedWithoutEditing.isEmpty()) {
131 myFoldersCache.clear();
132 buildVirtualFiles(modifiedWithoutEditing, ChangesBrowserNode.MODIFIED_WITHOUT_EDITING_TAG);
134 if (!unversionedFiles.isEmpty()) {
135 myFoldersCache.clear();
136 buildVirtualFiles(unversionedFiles, ChangesBrowserNode.UNVERSIONED_FILES_TAG);
138 if (switchedRoots != null && (! switchedRoots.isEmpty())) {
139 myFoldersCache.clear();
140 buildSwitchedRoots(switchedRoots);
142 if (!switchedFiles.isEmpty()) {
143 myFoldersCache.clear();
144 buildSwitchedFiles(switchedFiles);
146 if (ignoredFiles != null && !ignoredFiles.isEmpty()) {
147 myFoldersCache.clear();
148 buildVirtualFiles(ignoredFiles, ChangesBrowserNode.IGNORED_FILES_TAG);
150 if (lockedFolders != null && !lockedFolders.isEmpty()) {
151 myFoldersCache.clear();
152 buildVirtualFiles(lockedFolders, ChangesBrowserNode.LOCKED_FOLDERS_TAG);
154 if (logicallyLockedFiles != null && (! logicallyLockedFiles.isEmpty())) {
155 myFoldersCache.clear();
156 buildLogicallyLockedFiles(logicallyLockedFiles);
159 if (!locallyDeletedFiles.isEmpty()) {
160 myFoldersCache.clear();
161 ChangesBrowserNode locallyDeletedNode = ChangesBrowserNode.create(myProject, VcsBundle.message("changes.nodetitle.locally.deleted.files"));
162 model.insertNodeInto(locallyDeletedNode, root, root.getChildCount());
163 buildLocallyDeletedPaths(locallyDeletedFiles, locallyDeletedNode);
166 collapseDirectories(model, root);
167 sortNodes();
169 return model;
172 public DefaultTreeModel buildModel(List<? extends ChangeList> changeLists) {
173 final RemoteRevisionsCache revisionsCache = RemoteRevisionsCache.getInstance(myProject);
174 for (ChangeList list : changeLists) {
175 final Collection<Change> changes = list.getChanges();
176 final ChangeListRemoteState listRemoteState = new ChangeListRemoteState(changes.size());
177 ChangesBrowserNode listNode = new ChangesBrowserChangeListNode(myProject, list, listRemoteState);
178 model.insertNodeInto(listNode, root, 0);
179 final ChangesGroupingPolicy policy = createGroupingPolicy();
180 int i = 0;
181 for (final Change change : changes) {
182 final MyChangeNodeUnderChangeListDecorator decorator =
183 new MyChangeNodeUnderChangeListDecorator(revisionsCache, new ChangeListRemoteState.Reporter(i, listRemoteState));
184 insertChangeNode(change, policy, listNode, new Computable<ChangesBrowserNode>() {
185 public ChangesBrowserNode compute() {
186 return new ChangesBrowserChangeNode(myProject, change, decorator);
189 ++ i;
192 return model;
195 private void buildVirtualFiles(final Iterator<FilePath> iterator, @Nullable final Object tag) {
196 final ChangesBrowserNode baseNode = createNode(tag);
197 final ChangesGroupingPolicy policy = createGroupingPolicy();
198 for (; ; iterator.hasNext()) {
199 final FilePath path = iterator.next();
200 insertChangeNode(path.getVirtualFile(), policy, baseNode, defaultNodeCreator(path.getVirtualFile()));
204 private void buildVirtualFiles(final List<VirtualFile> files, @Nullable final Object tag) {
205 final ChangesBrowserNode baseNode = createNode(tag);
206 insertFilesIntoNode(files, baseNode);
209 private ChangesBrowserNode createNode(Object tag) {
210 ChangesBrowserNode baseNode;
211 if (tag != null) {
212 baseNode = ChangesBrowserNode.create(myProject, tag);
213 model.insertNodeInto(baseNode, root, root.getChildCount());
215 else {
216 baseNode = root;
218 return baseNode;
221 private void insertFilesIntoNode(List<VirtualFile> files, ChangesBrowserNode baseNode) {
222 final ChangesGroupingPolicy policy = createGroupingPolicy();
223 for (VirtualFile file : files) {
224 insertChangeNode(file, policy, baseNode, defaultNodeCreator(file));
228 private void buildLocallyDeletedPaths(final Collection<LocallyDeletedChange> locallyDeletedChanges, final ChangesBrowserNode baseNode) {
229 final ChangesGroupingPolicy policy = createGroupingPolicy();
230 for (LocallyDeletedChange change : locallyDeletedChanges) {
231 ChangesBrowserNode oldNode = myFoldersCache.get(change.getPresentableUrl());
232 if (oldNode == null) {
233 final ChangesBrowserNode node = ChangesBrowserNode.create(myProject, change);
234 final ChangesBrowserNode parent = getParentNodeFor(node, policy, baseNode);
235 model.insertNodeInto(node, parent, parent.getChildCount());
236 myFoldersCache.put(change.getPresentableUrl(), node);
241 public void buildFilePaths(final Collection<FilePath> filePaths, final ChangesBrowserNode baseNode) {
242 final ChangesGroupingPolicy policy = createGroupingPolicy();
243 for (FilePath file : filePaths) {
244 assert file != null;
245 ChangesBrowserNode oldNode = myFoldersCache.get(file.getIOFile().getAbsolutePath());
246 if (oldNode == null) {
247 final ChangesBrowserNode node = ChangesBrowserNode.create(myProject, file);
248 final ChangesBrowserNode parentNode = getParentNodeFor(node, policy, baseNode);
249 model.insertNodeInto(node, parentNode, 0);
250 myFoldersCache.put(file.getIOFile().getAbsolutePath(), node);
255 private void buildSwitchedRoots(final Map<VirtualFile, String> switchedRoots) {
256 final ChangesBrowserNode rootsHeadNode = ChangesBrowserNode.create(myProject, ChangesBrowserNode.SWITCHED_ROOTS_TAG);
257 rootsHeadNode.setAttributes(SimpleTextAttributes.GRAYED_BOLD_ATTRIBUTES);
258 model.insertNodeInto(rootsHeadNode, root, root.getChildCount());
260 for (VirtualFile vf : switchedRoots.keySet()) {
261 final ChangesGroupingPolicy policy = createGroupingPolicy();
262 final ContentRevision cr = new CurrentContentRevision(new FilePathImpl(vf));
263 final Change change = new Change(cr, cr, FileStatus.NOT_CHANGED);
264 final String branchName = switchedRoots.get(vf);
265 insertChangeNode(vf, policy, rootsHeadNode, new Computable<ChangesBrowserNode>() {
266 public ChangesBrowserNode compute() {
267 return new ChangesBrowserChangeNode(myProject, change, new ChangeNodeDecorator() {
268 public void decorate(Change change, SimpleColoredComponent component, boolean isShowFlatten) {
270 public List<Pair<String, Stress>> stressPartsOfFileName(Change change, String parentPath) {
271 return null;
273 public void preDecorate(Change change, ChangesBrowserNodeRenderer renderer, boolean showFlatten) {
274 renderer.append("[" + branchName + "] ", SimpleTextAttributes.GRAYED_BOLD_ATTRIBUTES);
282 private void buildSwitchedFiles(final MultiMap<String, VirtualFile> switchedFiles) {
283 ChangesBrowserNode baseNode = ChangesBrowserNode.create(myProject, ChangesBrowserNode.SWITCHED_FILES_TAG);
284 model.insertNodeInto(baseNode, root, root.getChildCount());
285 for(String branchName: switchedFiles.keySet()) {
286 final Collection<VirtualFile> switchedFileList = switchedFiles.get(branchName);
287 if (switchedFileList.size() > 0) {
288 ChangesBrowserNode branchNode = ChangesBrowserNode.create(myProject, branchName);
289 model.insertNodeInto(branchNode, baseNode, baseNode.getChildCount());
291 final ChangesGroupingPolicy policy = createGroupingPolicy();
292 for (VirtualFile file : switchedFileList) {
293 insertChangeNode(file, policy, branchNode, defaultNodeCreator(file));
299 private void buildLogicallyLockedFiles(final Map<VirtualFile, LogicalLock> logicallyLockedFiles) {
300 final ChangesBrowserNode baseNode = createNode(ChangesBrowserNode.LOGICALLY_LOCKED_TAG);
302 final ChangesGroupingPolicy policy = createGroupingPolicy();
303 for (Map.Entry<VirtualFile, LogicalLock> entry : logicallyLockedFiles.entrySet()) {
304 final VirtualFile file = entry.getKey();
305 final LogicalLock lock = entry.getValue();
306 final ChangesBrowserLogicallyLockedFile obj = new ChangesBrowserLogicallyLockedFile(myProject, file, lock);
307 insertChangeNode(obj, policy, baseNode,
308 defaultNodeCreator(obj));
312 private Computable<ChangesBrowserNode> defaultNodeCreator(final Object change) {
313 return new Computable<ChangesBrowserNode>() {
314 public ChangesBrowserNode compute() {
315 return ChangesBrowserNode.create(myProject, change);
320 private void insertChangeNode(final Object change, final ChangesGroupingPolicy policy,
321 final ChangesBrowserNode listNode, final Computable<ChangesBrowserNode> nodeCreator) {
322 final FilePath nodePath = getPathForObject(change);
323 ChangesBrowserNode oldNode = (nodePath == null) ? null : myFoldersCache.get(nodePath.getIOFile().getAbsolutePath());
324 ChangesBrowserNode node = nodeCreator.compute();
325 if (oldNode != null) {
326 for(int i=oldNode.getChildCount()-1; i >= 0; i--) {
327 MutableTreeNode child = (MutableTreeNode) model.getChild(oldNode, i);
328 model.removeNodeFromParent(child);
329 model.insertNodeInto(child, node, model.getChildCount(node));
331 final MutableTreeNode parent = (MutableTreeNode)oldNode.getParent();
332 int index = model.getIndexOfChild(parent, oldNode);
333 model.removeNodeFromParent(oldNode);
334 model.insertNodeInto(node, parent, index);
335 myFoldersCache.put(nodePath.getIOFile().getAbsolutePath(), node);
337 else {
338 ChangesBrowserNode parentNode = getParentNodeFor(node, policy, listNode);
339 model.insertNodeInto(node, parentNode, model.getChildCount(parentNode));
340 // ?
341 if (nodePath != null) {
342 myFoldersCache.put(nodePath.getIOFile().getAbsolutePath(), node);
347 private void sortNodes() {
348 TreeUtil.sort(model, new Comparator() {
349 public int compare(final Object n1, final Object n2) {
350 final ChangesBrowserNode node1 = (ChangesBrowserNode)n1;
351 final ChangesBrowserNode node2 = (ChangesBrowserNode)n2;
353 final int classdiff = node1.getSortWeight() - node2.getSortWeight();
354 if (classdiff != 0) return classdiff;
356 return node1.compareUserObjects(node2.getUserObject());
360 model.nodeStructureChanged((TreeNode)model.getRoot());
363 private static void collapseDirectories(DefaultTreeModel model, ChangesBrowserNode node) {
364 if (node.getUserObject() instanceof FilePath && node.getChildCount() == 1) {
365 final ChangesBrowserNode child = (ChangesBrowserNode)node.getChildAt(0);
366 if (child.getUserObject() instanceof FilePath && !child.isLeaf()) {
367 ChangesBrowserNode parent = (ChangesBrowserNode)node.getParent();
368 final int idx = parent.getIndex(node);
369 model.removeNodeFromParent(node);
370 model.removeNodeFromParent(child);
371 model.insertNodeInto(child, parent, idx);
372 collapseDirectories(model, parent);
375 else {
376 final Enumeration children = node.children();
377 while (children.hasMoreElements()) {
378 ChangesBrowserNode child = (ChangesBrowserNode)children.nextElement();
379 collapseDirectories(model, child);
384 public static FilePath getPathForObject(Object o) {
385 if (o instanceof Change) {
386 return ChangesUtil.getFilePath((Change)o);
388 else if (o instanceof VirtualFile) {
389 return new FilePathImpl((VirtualFile) o);
391 else if (o instanceof FilePath) {
392 return (FilePath)o;
393 } else if (o instanceof ChangesBrowserLogicallyLockedFile) {
394 return new FilePathImpl(((ChangesBrowserLogicallyLockedFile) o).getUserObject());
395 } else if (o instanceof LocallyDeletedChange) {
396 return ((LocallyDeletedChange) o).getPath();
399 return null;
402 private ChangesBrowserNode getParentNodeFor(ChangesBrowserNode node, @Nullable ChangesGroupingPolicy policy, ChangesBrowserNode rootNode) {
403 if (showFlatten) {
404 return rootNode;
407 final FilePath path = getPathForObject(node.getUserObject());
409 if (policy != null) {
410 ChangesBrowserNode nodeFromPolicy = policy.getParentNodeFor(node, rootNode);
411 if (nodeFromPolicy != null) {
412 return nodeFromPolicy;
416 FilePath parentPath = path.getParentPath();
417 if (parentPath == null) {
418 return rootNode;
421 final String parentKey = parentPath.getIOFile().getAbsolutePath();
422 ChangesBrowserNode parentNode = myFoldersCache.get(parentKey);
423 if (parentNode == null) {
424 parentNode = ChangesBrowserNode.create(myProject, parentPath);
425 ChangesBrowserNode grandPa = getParentNodeFor(parentNode, policy, rootNode);
426 model.insertNodeInto(parentNode, grandPa, grandPa.getChildCount());
427 myFoldersCache.put(parentKey, parentNode);
430 return parentNode;
433 public DefaultTreeModel clearAndGetModel() {
434 root.removeAllChildren();
435 model = new DefaultTreeModel(root);
436 myFoldersCache.clear();
437 myPolicyInitialized = false;
438 return model;
441 public boolean isEmpty() {
442 return model.getChildCount(root) == 0;