5ad2fbe0a1e5e363a1b2a55e21242d48d07ed8a1
[idea/community.git] / platform / vcs-impl / src / com / intellij / openapi / vcs / update / UpdateInfoTree.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.openapi.vcs.update;
17
18 import com.intellij.history.Label;
19 import com.intellij.ide.DefaultTreeExpander;
20 import com.intellij.ide.TreeExpander;
21 import com.intellij.openapi.Disposable;
22 import com.intellij.openapi.actionSystem.*;
23 import com.intellij.openapi.application.ApplicationManager;
24 import com.intellij.openapi.fileEditor.OpenFileDescriptor;
25 import com.intellij.openapi.project.DumbAware;
26 import com.intellij.openapi.project.Project;
27 import com.intellij.openapi.ui.PanelWithActionsAndCloseButton;
28 import com.intellij.openapi.util.Disposer;
29 import com.intellij.openapi.util.IconLoader;
30 import com.intellij.openapi.vcs.*;
31 import com.intellij.openapi.vcs.changes.committed.CommittedChangesBrowserUseCase;
32 import com.intellij.openapi.vcs.changes.committed.CommittedChangesCache;
33 import com.intellij.openapi.vcs.changes.committed.CommittedChangesTreeBrowser;
34 import com.intellij.openapi.vcs.changes.committed.RefreshIncomingChangesAction;
35 import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList;
36 import com.intellij.openapi.vfs.VfsUtil;
37 import com.intellij.openapi.vfs.VirtualFile;
38 import com.intellij.openapi.vfs.pointers.VirtualFilePointer;
39 import com.intellij.ui.*;
40 import com.intellij.ui.content.ContentManager;
41 import com.intellij.ui.treeStructure.Tree;
42 import com.intellij.util.EditSourceOnDoubleClickHandler;
43 import com.intellij.util.EditSourceOnEnterKeyHandler;
44 import com.intellij.util.PlatformIcons;
45 import com.intellij.util.containers.Convertor;
46 import com.intellij.util.ui.tree.TreeUtil;
47 import org.jetbrains.annotations.NonNls;
48 import org.jetbrains.annotations.NotNull;
49 import org.jetbrains.annotations.Nullable;
50
51 import javax.swing.*;
52 import javax.swing.event.TreeSelectionEvent;
53 import javax.swing.event.TreeSelectionListener;
54 import javax.swing.tree.DefaultTreeModel;
55 import javax.swing.tree.TreeNode;
56 import javax.swing.tree.TreePath;
57 import java.awt.*;
58 import java.awt.event.ActionEvent;
59 import java.awt.event.ActionListener;
60 import java.io.File;
61 import java.util.*;
62 import java.util.List;
63
64 public class UpdateInfoTree extends PanelWithActionsAndCloseButton implements Disposable {
65   private VirtualFile mySelectedFile;
66   private String mySelectedUrl;
67   private final Tree myTree = new Tree();
68   @NotNull private final Project myProject;
69   private final UpdatedFiles myUpdatedFiles;
70   private UpdateRootNode myRoot;
71   private DefaultTreeModel myTreeModel;
72   private FileStatusListener myFileStatusListener;
73   private final FileStatusManager myFileStatusManager;
74   private final String myRootName;
75   private final ActionInfo myActionInfo;
76   private boolean myCanGroupByChangeList = false;
77   private boolean myGroupByChangeList = false;
78   private JLabel myLoadingChangeListsLabel;
79   private List<CommittedChangeList> myCommittedChangeLists;
80   private final JPanel myCenterPanel = new JPanel(new CardLayout());
81   @NonNls private static final String CARD_STATUS = "Status";
82   @NonNls private static final String CARD_CHANGES = "Changes";
83   private CommittedChangesTreeBrowser myTreeBrowser;
84   private final TreeExpander myTreeExpander;
85   private final MyTreeIterable myTreeIterable;
86
87   private Label myBefore;
88   private Label myAfter;
89
90   public UpdateInfoTree(@NotNull ContentManager contentManager,
91                         @NotNull Project project,
92                         UpdatedFiles updatedFiles,
93                         String rootName,
94                         ActionInfo actionInfo) {
95     super(contentManager, "reference.toolWindows.versionControl");
96     myActionInfo = actionInfo;
97
98     myFileStatusListener = new FileStatusListener() {
99       public void fileStatusesChanged() {
100         myTree.repaint();
101       }
102
103       public void fileStatusChanged(@NotNull VirtualFile virtualFile) {
104         myTree.repaint();
105       }
106     };
107
108     myProject = project;
109     myUpdatedFiles = updatedFiles;
110     myRootName = rootName;
111
112     myFileStatusManager = FileStatusManager.getInstance(myProject);
113     myFileStatusManager.addFileStatusListener(myFileStatusListener);
114     createTree();
115     init();
116     myTreeExpander = new DefaultTreeExpander(myTree);
117     myTreeIterable = new MyTreeIterable();
118   }
119
120   public void dispose() {
121     super.dispose();
122     Disposer.dispose(myRoot);
123     if (myFileStatusListener != null) {
124       myFileStatusManager.removeFileStatusListener(myFileStatusListener);
125       myFileStatusListener = null;
126     }
127   }
128
129   public void setCanGroupByChangeList(final boolean canGroupByChangeList) {
130     myCanGroupByChangeList = canGroupByChangeList;
131     if (myCanGroupByChangeList) {
132       myLoadingChangeListsLabel = new JLabel(VcsBundle.message("update.info.loading.changelists"));
133       add(myLoadingChangeListsLabel, BorderLayout.SOUTH);
134       myGroupByChangeList = VcsConfiguration.getInstance(myProject).UPDATE_GROUP_BY_CHANGELIST;
135       if (myGroupByChangeList) {
136         final CardLayout cardLayout = (CardLayout)myCenterPanel.getLayout();
137         cardLayout.show(myCenterPanel, CARD_CHANGES);
138       }
139     }
140   }
141
142   protected void addActionsTo(DefaultActionGroup group) {
143     group.add(new MyGroupByPackagesAction());
144     group.add(new GroupByChangeListAction());
145     group.add(ActionManager.getInstance().getAction(IdeActions.ACTION_EXPAND_ALL));
146     group.add(ActionManager.getInstance().getAction(IdeActions.ACTION_COLLAPSE_ALL));
147     group.add(ActionManager.getInstance().getAction("Diff.UpdatedFiles"));
148   }
149
150   protected JComponent createCenterPanel() {
151     myCenterPanel.add(CARD_STATUS, ScrollPaneFactory.createScrollPane(myTree));
152     myTreeBrowser = new CommittedChangesTreeBrowser(myProject, Collections.<CommittedChangeList>emptyList());
153     Disposer.register(this, myTreeBrowser);
154     myTreeBrowser.setHelpId(getHelpId());
155     myCenterPanel.add(CARD_CHANGES, myTreeBrowser);
156     return myCenterPanel;
157   }
158
159   private void createTree() {
160     SmartExpander.installOn(myTree);
161     SelectionSaver.installOn(myTree);
162     refreshTree();
163
164     myTree.addTreeSelectionListener(new TreeSelectionListener() {
165       public void valueChanged(TreeSelectionEvent e) {
166         AbstractTreeNode treeNode = (AbstractTreeNode)e.getPath().getLastPathComponent();
167         if (treeNode instanceof FileTreeNode) {
168           final VirtualFilePointer pointer = ((FileTreeNode)treeNode).getFilePointer();
169           mySelectedUrl = pointer.getUrl();
170           mySelectedFile = pointer.getFile();
171         }
172         else {
173           mySelectedUrl = null;
174           mySelectedFile = null;
175         }
176       }
177     });
178     myTree.setCellRenderer(new UpdateTreeCellRenderer());
179     TreeUtil.installActions(myTree);
180     new TreeSpeedSearch(myTree, new Convertor<TreePath, String>() {
181       public String convert(TreePath path) {
182         Object last = path.getLastPathComponent();
183         if (last instanceof AbstractTreeNode) {
184           return ((AbstractTreeNode)last).getName();
185         }
186         return TreeSpeedSearch.NODE_DESCRIPTOR_TOSTRING.convert(path);
187       }
188     });
189
190     myTree.addMouseListener(new PopupHandler() {
191       public void invokePopup(Component comp, int x, int y) {
192         final DefaultActionGroup group = (DefaultActionGroup)ActionManager.getInstance().getAction("UpdateActionGroup");
193         if (group != null) { //if no UpdateActionGroup was configured
194           ActionPopupMenu popupMenu = ActionManager.getInstance().createActionPopupMenu(ActionPlaces.UPDATE_POPUP,
195                                                                                         group);
196           popupMenu.getComponent().show(comp, x, y);
197         }
198       }
199     });
200     EditSourceOnDoubleClickHandler.install(myTree);
201     EditSourceOnEnterKeyHandler.install(myTree);
202
203     myTree.setSelectionRow(0);
204   }
205
206   private void refreshTree() {
207     myRoot = new UpdateRootNode(myUpdatedFiles, myProject, myRootName, myActionInfo);
208     myRoot.rebuild(VcsConfiguration.getInstance(myProject).UPDATE_GROUP_BY_PACKAGES);
209     myTreeModel = new DefaultTreeModel(myRoot);
210     myRoot.setTreeModel(myTreeModel);
211     myTree.setModel(myTreeModel);
212     myRoot.setTree(myTree);
213   }
214
215   public Object getData(String dataId) {
216     if (myTreeBrowser != null && myTreeBrowser.isVisible()) {
217       return null;
218     }
219     if (PlatformDataKeys.NAVIGATABLE.is(dataId)) {
220       if (mySelectedFile == null || !mySelectedFile.isValid()) return null;
221       return new OpenFileDescriptor(myProject, mySelectedFile);
222     }
223     else if (PlatformDataKeys.VIRTUAL_FILE_ARRAY.is(dataId)) {
224       return getVirtualFileArray();
225     }
226     else if (VcsDataKeys.IO_FILE_ARRAY.is(dataId)) {
227       return getFileArray();
228     } else if (PlatformDataKeys.TREE_EXPANDER.is(dataId)) {
229       if (myGroupByChangeList) {
230         return myTreeBrowser != null ? myTreeBrowser.getTreeExpander() : null;
231       }
232       else {
233         return myTreeExpander;
234       }
235     } else if (VcsDataKeys.UPDATE_VIEW_SELECTED_PATH.is(dataId)) {
236       return mySelectedUrl;
237     } else if (VcsDataKeys.UPDATE_VIEW_FILES_ITERABLE.is(dataId)) {
238       return myTreeIterable;
239     } else if (VcsDataKeys.LABEL_BEFORE.is(dataId)) {
240       return myBefore;
241     }  else if (VcsDataKeys.LABEL_AFTER.is(dataId)) {
242       return myAfter;
243     }
244
245     return super.getData(dataId);
246   }
247
248   private class MyTreeIterator implements Iterator<VirtualFilePointer> {
249     private final Enumeration myEnum;
250     private VirtualFilePointer myNext;
251
252     private MyTreeIterator() {
253       myEnum = myRoot.depthFirstEnumeration();
254       step();
255     }
256
257     public boolean hasNext() {
258       return myNext != null;
259     }
260
261     public VirtualFilePointer next() {
262       final VirtualFilePointer result = myNext;
263       step();
264       return result;
265     }
266
267     private void step() {
268       myNext = null;
269       while (myEnum.hasMoreElements()) {
270         final Object o = myEnum.nextElement();
271         if (o instanceof FileTreeNode) {
272           myNext = ((FileTreeNode) o).getFilePointer();
273           break;
274         }
275       }
276     }
277
278     public void remove() {
279       throw new UnsupportedOperationException();
280     }
281   }
282
283   private class MyTreeIterable implements Iterable<VirtualFilePointer> {
284     public Iterator<VirtualFilePointer> iterator() {
285       return new MyTreeIterator();
286     }
287   }
288
289   private VirtualFile[] getVirtualFileArray() {
290     ArrayList<VirtualFile> result = new ArrayList<VirtualFile>();
291     TreePath[] selectionPaths = myTree.getSelectionPaths();
292     if (selectionPaths != null) {
293       for (TreePath selectionPath : selectionPaths) {
294         AbstractTreeNode treeNode = (AbstractTreeNode)selectionPath.getLastPathComponent();
295         result.addAll(treeNode.getVirtualFiles());
296       }
297     }
298     return VfsUtil.toVirtualFileArray(result);
299   }
300
301   @Nullable
302   private File[] getFileArray() {
303     ArrayList<File> result = new ArrayList<File>();
304     TreePath[] selectionPaths = myTree.getSelectionPaths();
305     if (selectionPaths != null) {
306       for (TreePath selectionPath : selectionPaths) {
307         AbstractTreeNode treeNode = (AbstractTreeNode)selectionPath.getLastPathComponent();
308         result.addAll(treeNode.getFiles());
309       }
310     }
311     if (result.isEmpty()) return null;
312     return result.toArray(new File[result.size()]);
313   }
314
315   public void expandRootChildren() {
316     TreeNode root = (TreeNode)myTreeModel.getRoot();
317
318     if (root.getChildCount() == 1) {
319       myTree.expandPath(new TreePath(new Object[]{root, root.getChildAt(0)}));
320     }
321   }
322
323   public void setChangeLists(final List<CommittedChangeList> receivedChanges) {
324     final boolean hasEmptyCaches = CommittedChangesCache.getInstance(myProject).hasEmptyCaches();
325
326     ApplicationManager.getApplication().invokeLater(new Runnable() {
327       public void run() {
328         if (myLoadingChangeListsLabel != null) {
329           remove(myLoadingChangeListsLabel);
330           myLoadingChangeListsLabel = null;
331         }
332         myCommittedChangeLists = receivedChanges;
333         myTreeBrowser.setItems(myCommittedChangeLists, CommittedChangesBrowserUseCase.UPDATE);
334         if (hasEmptyCaches) {
335           myTreeBrowser.getEmptyText()
336             .appendText("Click ")
337             .appendText("Refresh", SimpleTextAttributes.LINK_ATTRIBUTES, new ActionListener() {
338               public void actionPerformed(final ActionEvent e) {
339                 RefreshIncomingChangesAction.doRefresh(myProject);
340               }
341             })
342             .appendText(" to initialize repository changes cache");
343         }
344       }
345     }, myProject.getDisposed());
346   }
347
348   private class MyGroupByPackagesAction extends ToggleAction implements DumbAware {
349     public MyGroupByPackagesAction() {
350       super(VcsBundle.message("action.name.group.by.packages"), null, PlatformIcons.GROUP_BY_PACKAGES);
351     }
352
353     public boolean isSelected(AnActionEvent e) {
354       return VcsConfiguration.getInstance(myProject).UPDATE_GROUP_BY_PACKAGES;
355     }
356
357     public void setSelected(AnActionEvent e, boolean state) {
358       VcsConfiguration.getInstance(myProject).UPDATE_GROUP_BY_PACKAGES = state;
359       myRoot.rebuild(VcsConfiguration.getInstance(myProject).UPDATE_GROUP_BY_PACKAGES);
360     }
361
362     public void update(final AnActionEvent e) {
363       super.update(e);
364       e.getPresentation().setEnabled(!myGroupByChangeList);
365     }
366   }
367
368   private class GroupByChangeListAction extends ToggleAction implements DumbAware {
369     public GroupByChangeListAction() {
370       super(VcsBundle.message("update.info.group.by.changelist"), null, IconLoader.getIcon("/objectBrowser/browser.png"));
371     }
372
373     public boolean isSelected(AnActionEvent e) {
374       return myGroupByChangeList;
375     }
376
377     public void setSelected(AnActionEvent e, boolean state) {
378       myGroupByChangeList = state;
379       VcsConfiguration.getInstance(myProject).UPDATE_GROUP_BY_CHANGELIST = myGroupByChangeList;
380       final CardLayout cardLayout = (CardLayout)myCenterPanel.getLayout();
381       if (!myGroupByChangeList) {
382         cardLayout.show(myCenterPanel, CARD_STATUS);
383       }
384       else {
385         cardLayout.show(myCenterPanel, CARD_CHANGES);
386       }
387     }
388
389     public void update(final AnActionEvent e) {
390       super.update(e);
391       e.getPresentation().setVisible(myCanGroupByChangeList);
392     }
393   }
394
395   public void setBefore(Label before) {
396     myBefore = before;
397   }
398
399   public void setAfter(Label after) {
400     myAfter = after;
401   }
402 }