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