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 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
);
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();
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
);
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
;
212 baseNode
= ChangesBrowserNode
.create(myProject
, tag
);
213 model
.insertNodeInto(baseNode
, root
, root
.getChildCount());
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
) {
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
) {
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
);
338 ChangesBrowserNode parentNode
= getParentNodeFor(node
, policy
, listNode
);
339 model
.insertNodeInto(node
, parentNode
, model
.getChildCount(parentNode
));
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
);
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
) {
393 } else if (o
instanceof ChangesBrowserLogicallyLockedFile
) {
394 return new FilePathImpl(((ChangesBrowserLogicallyLockedFile
) o
).getUserObject());
395 } else if (o
instanceof LocallyDeletedChange
) {
396 return ((LocallyDeletedChange
) o
).getPath();
402 private ChangesBrowserNode
getParentNodeFor(ChangesBrowserNode node
, @Nullable ChangesGroupingPolicy policy
, ChangesBrowserNode 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) {
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
);
433 public DefaultTreeModel
clearAndGetModel() {
434 root
.removeAllChildren();
435 model
= new DefaultTreeModel(root
);
436 myFoldersCache
.clear();
437 myPolicyInitialized
= false;
441 public boolean isEmpty() {
442 return model
.getChildCount(root
) == 0;