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 final DefaultTreeModel model
;
48 private final ChangesBrowserNode root
;
50 public TreeModelBuilder(final Project project
, final boolean showFlatten
) {
52 this.showFlatten
= showFlatten
;
53 root
= ChangesBrowserNode
.create(myProject
, ROOT_NODE_VALUE
);
54 model
= new DefaultTreeModel(root
);
57 public DefaultTreeModel
buildModel(final List
<Change
> changes
, final ChangeNodeDecorator changeNodeDecorator
) {
58 final HashMap
<String
, ChangesBrowserNode
> foldersCache
= new HashMap
<String
, ChangesBrowserNode
>();
59 final ChangesGroupingPolicy policy
= createGroupingPolicy();
60 for (final Change change
: changes
) {
61 insertChangeNode(change
, foldersCache
, policy
, root
, new Computable
<ChangesBrowserNode
>() {
62 public ChangesBrowserNode
compute() {
63 return new ChangesBrowserChangeNode(myProject
, change
, changeNodeDecorator
);
68 collapseDirectories(model
, root
);
75 private ChangesGroupingPolicy
createGroupingPolicy() {
76 final ChangesGroupingPolicyFactory factory
= ChangesGroupingPolicyFactory
.getInstance(myProject
);
77 if (factory
!= null) {
78 return factory
.createGroupingPolicy(model
);
83 public DefaultTreeModel
buildModelFromFiles(final List
<VirtualFile
> files
) {
84 buildVirtualFiles(files
, null);
85 collapseDirectories(model
, root
);
90 public DefaultTreeModel
buildModelFromFilePaths(final Collection
<FilePath
> files
) {
91 buildFilePaths(files
, root
);
92 collapseDirectories(model
, root
);
97 private static class MyChangeNodeUnderChangeListDecorator
extends RemoteStatusChangeNodeDecorator
{
98 private final ChangeListRemoteState
.Reporter myReporter
;
100 private MyChangeNodeUnderChangeListDecorator(final RemoteRevisionsCache remoteRevisionsCache
, final ChangeListRemoteState
.Reporter reporter
) {
101 super(remoteRevisionsCache
);
102 myReporter
= reporter
;
106 protected void reportState(boolean state
) {
107 myReporter
.report(state
);
110 public void preDecorate(Change change
, ChangesBrowserNodeRenderer renderer
, boolean showFlatten
) {
114 public DefaultTreeModel
buildModel(final List
<?
extends ChangeList
> changeLists
,
115 final List
<VirtualFile
> unversionedFiles
,
116 final List
<LocallyDeletedChange
> locallyDeletedFiles
,
117 final List
<VirtualFile
> modifiedWithoutEditing
,
118 final MultiMap
<String
, VirtualFile
> switchedFiles
,
119 @Nullable Map
<VirtualFile
, String
> switchedRoots
,
120 @Nullable final List
<VirtualFile
> ignoredFiles
, @Nullable final List
<VirtualFile
> lockedFolders
,
121 @Nullable final Map
<VirtualFile
, LogicalLock
> logicallyLockedFiles
) {
122 buildModel(changeLists
);
124 if (!modifiedWithoutEditing
.isEmpty()) {
125 buildVirtualFiles(modifiedWithoutEditing
, ChangesBrowserNode
.MODIFIED_WITHOUT_EDITING_TAG
);
127 if (!unversionedFiles
.isEmpty()) {
128 buildVirtualFiles(unversionedFiles
, ChangesBrowserNode
.UNVERSIONED_FILES_TAG
);
130 if (switchedRoots
!= null && (! switchedRoots
.isEmpty())) {
131 buildSwitchedRoots(switchedRoots
);
133 if (!switchedFiles
.isEmpty()) {
134 buildSwitchedFiles(switchedFiles
);
136 if (ignoredFiles
!= null && !ignoredFiles
.isEmpty()) {
137 buildVirtualFiles(ignoredFiles
, ChangesBrowserNode
.IGNORED_FILES_TAG
);
139 if (lockedFolders
!= null && !lockedFolders
.isEmpty()) {
140 buildVirtualFiles(lockedFolders
, ChangesBrowserNode
.LOCKED_FOLDERS_TAG
);
142 if (logicallyLockedFiles
!= null && (! logicallyLockedFiles
.isEmpty())) {
143 buildLogicallyLockedFiles(logicallyLockedFiles
);
146 if (!locallyDeletedFiles
.isEmpty()) {
147 ChangesBrowserNode locallyDeletedNode
= ChangesBrowserNode
.create(myProject
, VcsBundle
.message("changes.nodetitle.locally.deleted.files"));
148 model
.insertNodeInto(locallyDeletedNode
, root
, root
.getChildCount());
149 buildLocallyDeletedPaths(locallyDeletedFiles
, locallyDeletedNode
);
152 collapseDirectories(model
, root
);
158 public DefaultTreeModel
buildModel(List
<?
extends ChangeList
> changeLists
) {
159 final RemoteRevisionsCache revisionsCache
= RemoteRevisionsCache
.getInstance(myProject
);
160 for (ChangeList list
: changeLists
) {
161 final Collection
<Change
> changes
= list
.getChanges();
162 final ChangeListRemoteState listRemoteState
= new ChangeListRemoteState(changes
.size());
163 ChangesBrowserNode listNode
= new ChangesBrowserChangeListNode(myProject
, list
, listRemoteState
);
164 model
.insertNodeInto(listNode
, root
, 0);
165 final HashMap
<String
, ChangesBrowserNode
> foldersCache
= new HashMap
<String
, ChangesBrowserNode
>();
166 final ChangesGroupingPolicy policy
= createGroupingPolicy();
168 for (final Change change
: changes
) {
169 final MyChangeNodeUnderChangeListDecorator decorator
=
170 new MyChangeNodeUnderChangeListDecorator(revisionsCache
, new ChangeListRemoteState
.Reporter(i
, listRemoteState
));
171 insertChangeNode(change
, foldersCache
, policy
, listNode
, new Computable
<ChangesBrowserNode
>() {
172 public ChangesBrowserNode
compute() {
173 return new ChangesBrowserChangeNode(myProject
, change
, decorator
);
182 private void buildVirtualFiles(final Iterator
<FilePath
> iterator
, @Nullable final Object tag
) {
183 final ChangesBrowserNode baseNode
= createNode(tag
);
184 final HashMap
<String
, ChangesBrowserNode
> foldersCache
= new HashMap
<String
, ChangesBrowserNode
>();
185 final ChangesGroupingPolicy policy
= createGroupingPolicy();
186 for (; ; iterator
.hasNext()) {
187 final FilePath path
= iterator
.next();
188 insertChangeNode(path
.getVirtualFile(), foldersCache
, policy
, baseNode
, defaultNodeCreator(path
.getVirtualFile()));
192 private void buildVirtualFiles(final List
<VirtualFile
> files
, @Nullable final Object tag
) {
193 final ChangesBrowserNode baseNode
= createNode(tag
);
194 insertFilesIntoNode(files
, baseNode
);
197 private ChangesBrowserNode
createNode(Object tag
) {
198 ChangesBrowserNode baseNode
;
200 baseNode
= ChangesBrowserNode
.create(myProject
, tag
);
201 model
.insertNodeInto(baseNode
, root
, root
.getChildCount());
209 private void insertFilesIntoNode(List
<VirtualFile
> files
, ChangesBrowserNode baseNode
) {
210 final HashMap
<String
, ChangesBrowserNode
> foldersCache
= new HashMap
<String
, ChangesBrowserNode
>();
211 final ChangesGroupingPolicy policy
= createGroupingPolicy();
212 for (VirtualFile file
: files
) {
213 insertChangeNode(file
, foldersCache
, policy
, baseNode
, defaultNodeCreator(file
));
217 private void buildLocallyDeletedPaths(final Collection
<LocallyDeletedChange
> locallyDeletedChanges
, final ChangesBrowserNode baseNode
) {
218 final HashMap
<String
, ChangesBrowserNode
> foldersCache
= new HashMap
<String
, ChangesBrowserNode
>();
219 final ChangesGroupingPolicy policy
= createGroupingPolicy();
220 for (LocallyDeletedChange change
: locallyDeletedChanges
) {
221 ChangesBrowserNode oldNode
= foldersCache
.get(change
.getPresentableUrl());
222 if (oldNode
== null) {
223 final ChangesBrowserNode node
= ChangesBrowserNode
.create(myProject
, change
);
224 final ChangesBrowserNode parent
= getParentNodeFor(node
, foldersCache
, policy
, baseNode
);
225 model
.insertNodeInto(node
, parent
, parent
.getChildCount());
226 foldersCache
.put(change
.getPresentableUrl(), node
);
231 private void buildFilePaths(final Collection
<FilePath
> filePaths
, final ChangesBrowserNode baseNode
) {
232 final HashMap
<String
, ChangesBrowserNode
> foldersCache
= new HashMap
<String
, ChangesBrowserNode
>();
233 final ChangesGroupingPolicy policy
= createGroupingPolicy();
234 for (FilePath file
: filePaths
) {
236 ChangesBrowserNode oldNode
= foldersCache
.get(file
);
237 if (oldNode
== null) {
238 final ChangesBrowserNode node
= ChangesBrowserNode
.create(myProject
, file
);
239 model
.insertNodeInto(node
, getParentNodeFor(node
, foldersCache
, policy
, baseNode
), 0);
240 foldersCache
.put(file
.getIOFile().getAbsolutePath(), node
);
245 private void buildSwitchedRoots(final Map
<VirtualFile
, String
> switchedRoots
) {
246 final ChangesBrowserNode rootsHeadNode
= ChangesBrowserNode
.create(myProject
, ChangesBrowserNode
.SWITCHED_ROOTS_TAG
);
247 rootsHeadNode
.setAttributes(SimpleTextAttributes
.GRAYED_BOLD_ATTRIBUTES
);
248 model
.insertNodeInto(rootsHeadNode
, root
, root
.getChildCount());
250 for (VirtualFile vf
: switchedRoots
.keySet()) {
251 final HashMap
<String
, ChangesBrowserNode
> foldersCache
= new HashMap
<String
, ChangesBrowserNode
>();
252 final ChangesGroupingPolicy policy
= createGroupingPolicy();
253 final ContentRevision cr
= new CurrentContentRevision(new FilePathImpl(vf
));
254 final Change change
= new Change(cr
, cr
, FileStatus
.NOT_CHANGED
);
255 final String branchName
= switchedRoots
.get(vf
);
256 insertChangeNode(vf
, foldersCache
, policy
, rootsHeadNode
, new Computable
<ChangesBrowserNode
>() {
257 public ChangesBrowserNode
compute() {
258 return new ChangesBrowserChangeNode(myProject
, change
, new ChangeNodeDecorator() {
259 public void decorate(Change change
, SimpleColoredComponent component
, boolean isShowFlatten
) {
261 public List
<Pair
<String
, Stress
>> stressPartsOfFileName(Change change
, String parentPath
) {
264 public void preDecorate(Change change
, ChangesBrowserNodeRenderer renderer
, boolean showFlatten
) {
265 renderer
.append("[" + branchName
+ "] ", SimpleTextAttributes
.GRAYED_BOLD_ATTRIBUTES
);
273 private void buildSwitchedFiles(final MultiMap
<String
, VirtualFile
> switchedFiles
) {
274 ChangesBrowserNode baseNode
= ChangesBrowserNode
.create(myProject
, ChangesBrowserNode
.SWITCHED_FILES_TAG
);
275 model
.insertNodeInto(baseNode
, root
, root
.getChildCount());
276 for(String branchName
: switchedFiles
.keySet()) {
277 final Collection
<VirtualFile
> switchedFileList
= switchedFiles
.get(branchName
);
278 if (switchedFileList
.size() > 0) {
279 ChangesBrowserNode branchNode
= ChangesBrowserNode
.create(myProject
, branchName
);
280 model
.insertNodeInto(branchNode
, baseNode
, baseNode
.getChildCount());
282 final HashMap
<String
, ChangesBrowserNode
> foldersCache
= new HashMap
<String
, ChangesBrowserNode
>();
283 final ChangesGroupingPolicy policy
= createGroupingPolicy();
284 for (VirtualFile file
: switchedFileList
) {
285 insertChangeNode(file
, foldersCache
, policy
, branchNode
, defaultNodeCreator(file
));
291 private void buildLogicallyLockedFiles(final Map
<VirtualFile
, LogicalLock
> logicallyLockedFiles
) {
292 final ChangesBrowserNode baseNode
= createNode(ChangesBrowserNode
.LOGICALLY_LOCKED_TAG
);
294 final HashMap
<String
, ChangesBrowserNode
> foldersCache
= new HashMap
<String
, ChangesBrowserNode
>();
295 final ChangesGroupingPolicy policy
= createGroupingPolicy();
296 for (Map
.Entry
<VirtualFile
, LogicalLock
> entry
: logicallyLockedFiles
.entrySet()) {
297 final VirtualFile file
= entry
.getKey();
298 final LogicalLock lock
= entry
.getValue();
299 final ChangesBrowserLogicallyLockedFile obj
= new ChangesBrowserLogicallyLockedFile(myProject
, file
, lock
);
300 insertChangeNode(obj
, foldersCache
, policy
, baseNode
,
301 defaultNodeCreator(obj
));
305 private Computable
<ChangesBrowserNode
> defaultNodeCreator(final Object change
) {
306 return new Computable
<ChangesBrowserNode
>() {
307 public ChangesBrowserNode
compute() {
308 return ChangesBrowserNode
.create(myProject
, change
);
313 private void insertChangeNode(final Object change
, final HashMap
<String
, ChangesBrowserNode
> foldersCache
,
314 final ChangesGroupingPolicy policy
,
315 final ChangesBrowserNode listNode
, final Computable
<ChangesBrowserNode
> nodeCreator
) {
316 final FilePath nodePath
= getPathForObject(change
);
317 ChangesBrowserNode oldNode
= (nodePath
== null) ?
null : foldersCache
.get(nodePath
.getIOFile().getAbsolutePath());
318 ChangesBrowserNode node
= nodeCreator
.compute();
319 if (oldNode
!= null) {
320 for(int i
=oldNode
.getChildCount()-1; i
>= 0; i
--) {
321 MutableTreeNode child
= (MutableTreeNode
) model
.getChild(oldNode
, i
);
322 model
.removeNodeFromParent(child
);
323 model
.insertNodeInto(child
, node
, model
.getChildCount(node
));
325 final MutableTreeNode parent
= (MutableTreeNode
)oldNode
.getParent();
326 int index
= model
.getIndexOfChild(parent
, oldNode
);
327 model
.removeNodeFromParent(oldNode
);
328 model
.insertNodeInto(node
, parent
, index
);
329 foldersCache
.put(nodePath
.getIOFile().getAbsolutePath(), node
);
332 ChangesBrowserNode parentNode
= getParentNodeFor(node
, foldersCache
, policy
, listNode
);
333 model
.insertNodeInto(node
, parentNode
, model
.getChildCount(parentNode
));
335 if (nodePath
!= null) {
336 foldersCache
.put(nodePath
.getIOFile().getAbsolutePath(), node
);
341 private void sortNodes() {
342 TreeUtil
.sort(model
, new Comparator() {
343 public int compare(final Object n1
, final Object n2
) {
344 final ChangesBrowserNode node1
= (ChangesBrowserNode
)n1
;
345 final ChangesBrowserNode node2
= (ChangesBrowserNode
)n2
;
347 final int classdiff
= node1
.getSortWeight() - node2
.getSortWeight();
348 if (classdiff
!= 0) return classdiff
;
350 return node1
.compareUserObjects(node2
.getUserObject());
354 model
.nodeStructureChanged((TreeNode
)model
.getRoot());
357 private static void collapseDirectories(DefaultTreeModel model
, ChangesBrowserNode node
) {
358 if (node
.getUserObject() instanceof FilePath
&& node
.getChildCount() == 1) {
359 final ChangesBrowserNode child
= (ChangesBrowserNode
)node
.getChildAt(0);
360 if (child
.getUserObject() instanceof FilePath
&& !child
.isLeaf()) {
361 ChangesBrowserNode parent
= (ChangesBrowserNode
)node
.getParent();
362 final int idx
= parent
.getIndex(node
);
363 model
.removeNodeFromParent(node
);
364 model
.removeNodeFromParent(child
);
365 model
.insertNodeInto(child
, parent
, idx
);
366 collapseDirectories(model
, parent
);
370 final Enumeration children
= node
.children();
371 while (children
.hasMoreElements()) {
372 ChangesBrowserNode child
= (ChangesBrowserNode
)children
.nextElement();
373 collapseDirectories(model
, child
);
378 public static FilePath
getPathForObject(Object o
) {
379 if (o
instanceof Change
) {
380 return ChangesUtil
.getFilePath((Change
)o
);
382 else if (o
instanceof VirtualFile
) {
383 return new FilePathImpl((VirtualFile
) o
);
385 else if (o
instanceof FilePath
) {
387 } else if (o
instanceof ChangesBrowserLogicallyLockedFile
) {
388 return new FilePathImpl(((ChangesBrowserLogicallyLockedFile
) o
).getUserObject());
389 } else if (o
instanceof LocallyDeletedChange
) {
390 return ((LocallyDeletedChange
) o
).getPath();
396 private ChangesBrowserNode
getParentNodeFor(ChangesBrowserNode node
,
397 Map
<String
, ChangesBrowserNode
> folderNodesCache
,
398 @Nullable ChangesGroupingPolicy policy
,
399 ChangesBrowserNode rootNode
) {
404 final FilePath path
= getPathForObject(node
.getUserObject());
406 if (policy
!= null) {
407 ChangesBrowserNode nodeFromPolicy
= policy
.getParentNodeFor(node
, rootNode
);
408 if (nodeFromPolicy
!= null) {
409 return nodeFromPolicy
;
413 FilePath parentPath
= path
.getParentPath();
414 if (parentPath
== null) {
418 ChangesBrowserNode parentNode
= folderNodesCache
.get(parentPath
.getIOFile().getAbsolutePath());
419 if (parentNode
== null) {
420 parentNode
= ChangesBrowserNode
.create(myProject
, parentPath
);
421 ChangesBrowserNode grandPa
= getParentNodeFor(parentNode
, folderNodesCache
, policy
, rootNode
);
422 model
.insertNodeInto(parentNode
, grandPa
, grandPa
.getChildCount());
423 folderNodesCache
.put(parentPath
.getIOFile().getAbsolutePath(), parentNode
);