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
;
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
) {
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
);
71 collapseDirectories(model
, root
);
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
);
89 public DefaultTreeModel
buildModelFromFiles(final List
<VirtualFile
> files
) {
90 buildVirtualFiles(files
, null);
91 collapseDirectories(model
, root
);
96 public DefaultTreeModel
buildModelFromFilePaths(final Collection
<FilePath
> files
) {
97 buildFilePaths(files
, root
);
98 collapseDirectories(model
, root
);
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
;
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 buildVirtualFiles(modifiedWithoutEditing
, ChangesBrowserNode
.MODIFIED_WITHOUT_EDITING_TAG
);
133 if (!unversionedFiles
.isEmpty()) {
134 buildVirtualFiles(unversionedFiles
, ChangesBrowserNode
.UNVERSIONED_FILES_TAG
);
136 if (switchedRoots
!= null && (! switchedRoots
.isEmpty())) {
137 buildSwitchedRoots(switchedRoots
);
139 if (!switchedFiles
.isEmpty()) {
140 buildSwitchedFiles(switchedFiles
);
142 if (ignoredFiles
!= null && !ignoredFiles
.isEmpty()) {
143 buildVirtualFiles(ignoredFiles
, ChangesBrowserNode
.IGNORED_FILES_TAG
);
145 if (lockedFolders
!= null && !lockedFolders
.isEmpty()) {
146 buildVirtualFiles(lockedFolders
, ChangesBrowserNode
.LOCKED_FOLDERS_TAG
);
148 if (logicallyLockedFiles
!= null && (! logicallyLockedFiles
.isEmpty())) {
149 buildLogicallyLockedFiles(logicallyLockedFiles
);
152 if (!locallyDeletedFiles
.isEmpty()) {
153 ChangesBrowserNode locallyDeletedNode
= ChangesBrowserNode
.create(myProject
, VcsBundle
.message("changes.nodetitle.locally.deleted.files"));
154 model
.insertNodeInto(locallyDeletedNode
, root
, root
.getChildCount());
155 buildLocallyDeletedPaths(locallyDeletedFiles
, locallyDeletedNode
);
158 collapseDirectories(model
, root
);
164 public DefaultTreeModel
buildModel(List
<?
extends ChangeList
> changeLists
) {
165 final RemoteRevisionsCache revisionsCache
= RemoteRevisionsCache
.getInstance(myProject
);
166 for (ChangeList list
: changeLists
) {
167 final Collection
<Change
> changes
= list
.getChanges();
168 final ChangeListRemoteState listRemoteState
= new ChangeListRemoteState(changes
.size());
169 ChangesBrowserNode listNode
= new ChangesBrowserChangeListNode(myProject
, list
, listRemoteState
);
170 model
.insertNodeInto(listNode
, root
, 0);
171 final ChangesGroupingPolicy policy
= createGroupingPolicy();
173 for (final Change change
: changes
) {
174 final MyChangeNodeUnderChangeListDecorator decorator
=
175 new MyChangeNodeUnderChangeListDecorator(revisionsCache
, new ChangeListRemoteState
.Reporter(i
, listRemoteState
));
176 insertChangeNode(change
, policy
, listNode
, new Computable
<ChangesBrowserNode
>() {
177 public ChangesBrowserNode
compute() {
178 return new ChangesBrowserChangeNode(myProject
, change
, decorator
);
187 private void buildVirtualFiles(final Iterator
<FilePath
> iterator
, @Nullable final Object tag
) {
188 final ChangesBrowserNode baseNode
= createNode(tag
);
189 final ChangesGroupingPolicy policy
= createGroupingPolicy();
190 for (; ; iterator
.hasNext()) {
191 final FilePath path
= iterator
.next();
192 insertChangeNode(path
.getVirtualFile(), policy
, baseNode
, defaultNodeCreator(path
.getVirtualFile()));
196 private void buildVirtualFiles(final List
<VirtualFile
> files
, @Nullable final Object tag
) {
197 final ChangesBrowserNode baseNode
= createNode(tag
);
198 insertFilesIntoNode(files
, baseNode
);
201 private ChangesBrowserNode
createNode(Object tag
) {
202 ChangesBrowserNode baseNode
;
204 baseNode
= ChangesBrowserNode
.create(myProject
, tag
);
205 model
.insertNodeInto(baseNode
, root
, root
.getChildCount());
213 private void insertFilesIntoNode(List
<VirtualFile
> files
, ChangesBrowserNode baseNode
) {
214 final ChangesGroupingPolicy policy
= createGroupingPolicy();
215 for (VirtualFile file
: files
) {
216 insertChangeNode(file
, policy
, baseNode
, defaultNodeCreator(file
));
220 private void buildLocallyDeletedPaths(final Collection
<LocallyDeletedChange
> locallyDeletedChanges
, final ChangesBrowserNode baseNode
) {
221 final ChangesGroupingPolicy policy
= createGroupingPolicy();
222 for (LocallyDeletedChange change
: locallyDeletedChanges
) {
223 ChangesBrowserNode oldNode
= myFoldersCache
.get(change
.getPresentableUrl());
224 if (oldNode
== null) {
225 final ChangesBrowserNode node
= ChangesBrowserNode
.create(myProject
, change
);
226 final ChangesBrowserNode parent
= getParentNodeFor(node
, policy
, baseNode
);
227 model
.insertNodeInto(node
, parent
, parent
.getChildCount());
228 myFoldersCache
.put(change
.getPresentableUrl(), node
);
233 public void buildFilePaths(final Collection
<FilePath
> filePaths
, final ChangesBrowserNode baseNode
) {
234 final ChangesGroupingPolicy policy
= createGroupingPolicy();
235 for (FilePath file
: filePaths
) {
237 ChangesBrowserNode oldNode
= myFoldersCache
.get(file
.getIOFile().getAbsolutePath());
238 if (oldNode
== null) {
239 final ChangesBrowserNode node
= ChangesBrowserNode
.create(myProject
, file
);
240 final ChangesBrowserNode parentNode
= getParentNodeFor(node
, policy
, baseNode
);
241 model
.insertNodeInto(node
, parentNode
, 0);
242 myFoldersCache
.put(file
.getIOFile().getAbsolutePath(), node
);
247 private void buildSwitchedRoots(final Map
<VirtualFile
, String
> switchedRoots
) {
248 final ChangesBrowserNode rootsHeadNode
= ChangesBrowserNode
.create(myProject
, ChangesBrowserNode
.SWITCHED_ROOTS_TAG
);
249 rootsHeadNode
.setAttributes(SimpleTextAttributes
.GRAYED_BOLD_ATTRIBUTES
);
250 model
.insertNodeInto(rootsHeadNode
, root
, root
.getChildCount());
252 for (VirtualFile vf
: switchedRoots
.keySet()) {
253 final ChangesGroupingPolicy policy
= createGroupingPolicy();
254 final ContentRevision cr
= new CurrentContentRevision(new FilePathImpl(vf
));
255 final Change change
= new Change(cr
, cr
, FileStatus
.NOT_CHANGED
);
256 final String branchName
= switchedRoots
.get(vf
);
257 insertChangeNode(vf
, policy
, rootsHeadNode
, new Computable
<ChangesBrowserNode
>() {
258 public ChangesBrowserNode
compute() {
259 return new ChangesBrowserChangeNode(myProject
, change
, new ChangeNodeDecorator() {
260 public void decorate(Change change
, SimpleColoredComponent component
, boolean isShowFlatten
) {
262 public List
<Pair
<String
, Stress
>> stressPartsOfFileName(Change change
, String parentPath
) {
265 public void preDecorate(Change change
, ChangesBrowserNodeRenderer renderer
, boolean showFlatten
) {
266 renderer
.append("[" + branchName
+ "] ", SimpleTextAttributes
.GRAYED_BOLD_ATTRIBUTES
);
274 private void buildSwitchedFiles(final MultiMap
<String
, VirtualFile
> switchedFiles
) {
275 ChangesBrowserNode baseNode
= ChangesBrowserNode
.create(myProject
, ChangesBrowserNode
.SWITCHED_FILES_TAG
);
276 model
.insertNodeInto(baseNode
, root
, root
.getChildCount());
277 for(String branchName
: switchedFiles
.keySet()) {
278 final Collection
<VirtualFile
> switchedFileList
= switchedFiles
.get(branchName
);
279 if (switchedFileList
.size() > 0) {
280 ChangesBrowserNode branchNode
= ChangesBrowserNode
.create(myProject
, branchName
);
281 model
.insertNodeInto(branchNode
, baseNode
, baseNode
.getChildCount());
283 final ChangesGroupingPolicy policy
= createGroupingPolicy();
284 for (VirtualFile file
: switchedFileList
) {
285 insertChangeNode(file
, policy
, branchNode
, defaultNodeCreator(file
));
291 private void buildLogicallyLockedFiles(final Map
<VirtualFile
, LogicalLock
> logicallyLockedFiles
) {
292 final ChangesBrowserNode baseNode
= createNode(ChangesBrowserNode
.LOGICALLY_LOCKED_TAG
);
294 final ChangesGroupingPolicy policy
= createGroupingPolicy();
295 for (Map
.Entry
<VirtualFile
, LogicalLock
> entry
: logicallyLockedFiles
.entrySet()) {
296 final VirtualFile file
= entry
.getKey();
297 final LogicalLock lock
= entry
.getValue();
298 final ChangesBrowserLogicallyLockedFile obj
= new ChangesBrowserLogicallyLockedFile(myProject
, file
, lock
);
299 insertChangeNode(obj
, policy
, baseNode
,
300 defaultNodeCreator(obj
));
304 private Computable
<ChangesBrowserNode
> defaultNodeCreator(final Object change
) {
305 return new Computable
<ChangesBrowserNode
>() {
306 public ChangesBrowserNode
compute() {
307 return ChangesBrowserNode
.create(myProject
, change
);
312 private void insertChangeNode(final Object change
, final ChangesGroupingPolicy policy
,
313 final ChangesBrowserNode listNode
, final Computable
<ChangesBrowserNode
> nodeCreator
) {
314 final FilePath nodePath
= getPathForObject(change
);
315 ChangesBrowserNode oldNode
= (nodePath
== null) ?
null : myFoldersCache
.get(nodePath
.getIOFile().getAbsolutePath());
316 ChangesBrowserNode node
= nodeCreator
.compute();
317 if (oldNode
!= null) {
318 for(int i
=oldNode
.getChildCount()-1; i
>= 0; i
--) {
319 MutableTreeNode child
= (MutableTreeNode
) model
.getChild(oldNode
, i
);
320 model
.removeNodeFromParent(child
);
321 model
.insertNodeInto(child
, node
, model
.getChildCount(node
));
323 final MutableTreeNode parent
= (MutableTreeNode
)oldNode
.getParent();
324 int index
= model
.getIndexOfChild(parent
, oldNode
);
325 model
.removeNodeFromParent(oldNode
);
326 model
.insertNodeInto(node
, parent
, index
);
327 myFoldersCache
.put(nodePath
.getIOFile().getAbsolutePath(), node
);
330 ChangesBrowserNode parentNode
= getParentNodeFor(node
, policy
, listNode
);
331 model
.insertNodeInto(node
, parentNode
, model
.getChildCount(parentNode
));
333 if (nodePath
!= null) {
334 myFoldersCache
.put(nodePath
.getIOFile().getAbsolutePath(), node
);
339 private void sortNodes() {
340 TreeUtil
.sort(model
, new Comparator() {
341 public int compare(final Object n1
, final Object n2
) {
342 final ChangesBrowserNode node1
= (ChangesBrowserNode
)n1
;
343 final ChangesBrowserNode node2
= (ChangesBrowserNode
)n2
;
345 final int classdiff
= node1
.getSortWeight() - node2
.getSortWeight();
346 if (classdiff
!= 0) return classdiff
;
348 return node1
.compareUserObjects(node2
.getUserObject());
352 model
.nodeStructureChanged((TreeNode
)model
.getRoot());
355 private static void collapseDirectories(DefaultTreeModel model
, ChangesBrowserNode node
) {
356 if (node
.getUserObject() instanceof FilePath
&& node
.getChildCount() == 1) {
357 final ChangesBrowserNode child
= (ChangesBrowserNode
)node
.getChildAt(0);
358 if (child
.getUserObject() instanceof FilePath
&& !child
.isLeaf()) {
359 ChangesBrowserNode parent
= (ChangesBrowserNode
)node
.getParent();
360 final int idx
= parent
.getIndex(node
);
361 model
.removeNodeFromParent(node
);
362 model
.removeNodeFromParent(child
);
363 model
.insertNodeInto(child
, parent
, idx
);
364 collapseDirectories(model
, parent
);
368 final Enumeration children
= node
.children();
369 while (children
.hasMoreElements()) {
370 ChangesBrowserNode child
= (ChangesBrowserNode
)children
.nextElement();
371 collapseDirectories(model
, child
);
376 public static FilePath
getPathForObject(Object o
) {
377 if (o
instanceof Change
) {
378 return ChangesUtil
.getFilePath((Change
)o
);
380 else if (o
instanceof VirtualFile
) {
381 return new FilePathImpl((VirtualFile
) o
);
383 else if (o
instanceof FilePath
) {
385 } else if (o
instanceof ChangesBrowserLogicallyLockedFile
) {
386 return new FilePathImpl(((ChangesBrowserLogicallyLockedFile
) o
).getUserObject());
387 } else if (o
instanceof LocallyDeletedChange
) {
388 return ((LocallyDeletedChange
) o
).getPath();
394 private ChangesBrowserNode
getParentNodeFor(ChangesBrowserNode node
, @Nullable ChangesGroupingPolicy policy
, ChangesBrowserNode rootNode
) {
399 final FilePath path
= getPathForObject(node
.getUserObject());
401 if (policy
!= null) {
402 ChangesBrowserNode nodeFromPolicy
= policy
.getParentNodeFor(node
, rootNode
);
403 if (nodeFromPolicy
!= null) {
404 return nodeFromPolicy
;
408 FilePath parentPath
= path
.getParentPath();
409 if (parentPath
== null) {
413 final String parentKey
= parentPath
.getIOFile().getAbsolutePath();
414 ChangesBrowserNode parentNode
= myFoldersCache
.get(parentKey
);
415 if (parentNode
== null) {
416 parentNode
= ChangesBrowserNode
.create(myProject
, parentPath
);
417 ChangesBrowserNode grandPa
= getParentNodeFor(parentNode
, policy
, rootNode
);
418 model
.insertNodeInto(parentNode
, grandPa
, grandPa
.getChildCount());
419 myFoldersCache
.put(parentKey
, parentNode
);
425 public DefaultTreeModel
clearAndGetModel() {
426 root
.removeAllChildren();
427 model
= new DefaultTreeModel(root
);
428 myFoldersCache
.clear();
429 myPolicyInitialized
= false;
433 public boolean isEmpty() {
434 return model
.getChildCount(root
) == 0;