properly disposed
[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.Icons;
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     final ShowUpdatedDiffAction diffAction = new ShowUpdatedDiffAction();
148     diffAction.registerCustomShortcutSet(CommonShortcuts.getDiff(), this);
149     group.add(diffAction);
150   }
151
152   protected JComponent createCenterPanel() {
153     myCenterPanel.add(CARD_STATUS, ScrollPaneFactory.createScrollPane(myTree));
154     myTreeBrowser = new CommittedChangesTreeBrowser(myProject, Collections.<CommittedChangeList>emptyList());
155     Disposer.register(this, myTreeBrowser);
156     myTreeBrowser.setHelpId(getHelpId());
157     myCenterPanel.add(CARD_CHANGES, myTreeBrowser);
158     return myCenterPanel;
159   }
160
161   private void createTree() {
162     SmartExpander.installOn(myTree);
163     SelectionSaver.installOn(myTree);
164     refreshTree();
165
166     myTree.addTreeSelectionListener(new TreeSelectionListener() {
167       public void valueChanged(TreeSelectionEvent e) {
168         AbstractTreeNode treeNode = (AbstractTreeNode)e.getPath().getLastPathComponent();
169         if (treeNode instanceof FileTreeNode) {
170           final VirtualFilePointer pointer = ((FileTreeNode)treeNode).getFilePointer();
171           mySelectedUrl = pointer.getUrl();
172           mySelectedFile = pointer.getFile();
173         }
174         else {
175           mySelectedUrl = null;
176           mySelectedFile = null;
177         }
178       }
179     });
180     myTree.setCellRenderer(new UpdateTreeCellRenderer());
181     TreeUtil.installActions(myTree);
182     new TreeSpeedSearch(myTree, new Convertor<TreePath, String>() {
183       public String convert(TreePath path) {
184         Object last = path.getLastPathComponent();
185         if (last instanceof AbstractTreeNode) {
186           return ((AbstractTreeNode)last).getName();
187         }
188         return TreeSpeedSearch.NODE_DESCRIPTOR_TOSTRING.convert(path);
189       }
190     });
191
192     myTree.addMouseListener(new PopupHandler() {
193       public void invokePopup(Component comp, int x, int y) {
194         final DefaultActionGroup group = (DefaultActionGroup)ActionManager.getInstance().getAction("UpdateActionGroup");
195         if (group != null) { //if no UpdateActionGroup was configured
196           ActionPopupMenu popupMenu = ActionManager.getInstance().createActionPopupMenu(ActionPlaces.UPDATE_POPUP,
197                                                                                         group);
198           popupMenu.getComponent().show(comp, x, y);
199         }
200       }
201     });
202     EditSourceOnDoubleClickHandler.install(myTree);
203     EditSourceOnEnterKeyHandler.install(myTree);
204
205     myTree.setSelectionRow(0);
206   }
207
208   private void refreshTree() {
209     myRoot = new UpdateRootNode(myUpdatedFiles, myProject, myRootName, myActionInfo);
210     myRoot.rebuild(VcsConfiguration.getInstance(myProject).UPDATE_GROUP_BY_PACKAGES);
211     myTreeModel = new DefaultTreeModel(myRoot);
212     myRoot.setTreeModel(myTreeModel);
213     myTree.setModel(myTreeModel);
214     myRoot.setTree(myTree);
215   }
216
217   public Object getData(String dataId) {
218     if (myTreeBrowser.isVisible()) {
219       return null;
220     }
221     if (PlatformDataKeys.NAVIGATABLE.is(dataId)) {
222       if (mySelectedFile == null || !mySelectedFile.isValid()) return null;
223       return new OpenFileDescriptor(myProject, mySelectedFile);
224     }
225     else if (PlatformDataKeys.VIRTUAL_FILE_ARRAY.is(dataId)) {
226       return getVirtualFileArray();
227     }
228     else if (VcsDataKeys.IO_FILE_ARRAY.is(dataId)) {
229       return getFileArray();
230     } else if (PlatformDataKeys.TREE_EXPANDER.is(dataId)) {
231       if (myGroupByChangeList) {
232         return myTreeBrowser.getTreeExpander();
233       }
234       else {
235         return myTreeExpander;
236       }
237     } else if (VcsDataKeys.UPDATE_VIEW_SELECTED_PATH.is(dataId)) {
238       return mySelectedUrl;
239     } else if (VcsDataKeys.UPDATE_VIEW_FILES_ITERABLE.is(dataId)) {
240       return myTreeIterable;
241     } else if (VcsDataKeys.LABEL_BEFORE.is(dataId)) {
242       return myBefore;
243     }  else if (VcsDataKeys.LABEL_AFTER.is(dataId)) {
244       return myAfter;
245     }
246
247     return super.getData(dataId);
248   }
249
250   private class MyTreeIterator implements Iterator<VirtualFilePointer> {
251     private final Enumeration myEnum;
252     private VirtualFilePointer myNext;
253
254     private MyTreeIterator() {
255       myEnum = myRoot.depthFirstEnumeration();
256       step();
257     }
258
259     public boolean hasNext() {
260       return myNext != null;
261     }
262
263     public VirtualFilePointer next() {
264       final VirtualFilePointer result = myNext;
265       step();
266       return result;
267     }
268
269     private void step() {
270       myNext = null;
271       while (myEnum.hasMoreElements()) {
272         final Object o = myEnum.nextElement();
273         if (o instanceof FileTreeNode) {
274           myNext = ((FileTreeNode) o).getFilePointer();
275           break;
276         }
277       }
278     }
279
280     public void remove() {
281       throw new UnsupportedOperationException();
282     }
283   }
284
285   private class MyTreeIterable implements Iterable<VirtualFilePointer> {
286     public Iterator<VirtualFilePointer> iterator() {
287       return new MyTreeIterator();
288     }
289   }
290
291   private VirtualFile[] getVirtualFileArray() {
292     ArrayList<VirtualFile> result = new ArrayList<VirtualFile>();
293     TreePath[] selectionPaths = myTree.getSelectionPaths();
294     if (selectionPaths != null) {
295       for (TreePath selectionPath : selectionPaths) {
296         AbstractTreeNode treeNode = (AbstractTreeNode)selectionPath.getLastPathComponent();
297         result.addAll(treeNode.getVirtualFiles());
298       }
299     }
300     return VfsUtil.toVirtualFileArray(result);
301   }
302
303   @Nullable
304   private File[] getFileArray() {
305     ArrayList<File> result = new ArrayList<File>();
306     TreePath[] selectionPaths = myTree.getSelectionPaths();
307     if (selectionPaths != null) {
308       for (TreePath selectionPath : selectionPaths) {
309         AbstractTreeNode treeNode = (AbstractTreeNode)selectionPath.getLastPathComponent();
310         result.addAll(treeNode.getFiles());
311       }
312     }
313     if (result.isEmpty()) return null;
314     return result.toArray(new File[result.size()]);
315   }
316
317   public void expandRootChildren() {
318     TreeNode root = (TreeNode)myTreeModel.getRoot();
319
320     if (root.getChildCount() == 1) {
321       myTree.expandPath(new TreePath(new Object[]{root, root.getChildAt(0)}));
322     }
323   }
324
325   public void setChangeLists(final List<CommittedChangeList> receivedChanges) {
326     final boolean hasEmptyCaches = CommittedChangesCache.getInstance(myProject).hasEmptyCaches();
327
328     ApplicationManager.getApplication().invokeLater(new Runnable() {
329       public void run() {
330         if (myLoadingChangeListsLabel != null) {
331           remove(myLoadingChangeListsLabel);
332           myLoadingChangeListsLabel = null;
333         }
334         myCommittedChangeLists = receivedChanges;
335         myTreeBrowser.setItems(myCommittedChangeLists, false, CommittedChangesBrowserUseCase.UPDATE);
336         myTreeBrowser.clearEmptyText();
337         if (hasEmptyCaches) {
338           myTreeBrowser.appendEmptyText("Click ", SimpleTextAttributes.REGULAR_ATTRIBUTES);
339           myTreeBrowser.appendEmptyText("Refresh", SimpleTextAttributes.LINK_ATTRIBUTES, new ActionListener() {
340             public void actionPerformed(final ActionEvent e) {
341               RefreshIncomingChangesAction.doRefresh(myProject);
342             }
343           });
344           myTreeBrowser.appendEmptyText(" to initialize repository changes cache", SimpleTextAttributes.REGULAR_ATTRIBUTES);
345         }
346       }
347     }, myProject.getDisposed());
348   }
349
350   private class MyGroupByPackagesAction extends ToggleAction implements DumbAware {
351     public MyGroupByPackagesAction() {
352       super(VcsBundle.message("action.name.group.by.packages"), null, Icons.GROUP_BY_PACKAGES);
353     }
354
355     public boolean isSelected(AnActionEvent e) {
356       return VcsConfiguration.getInstance(myProject).UPDATE_GROUP_BY_PACKAGES;
357     }
358
359     public void setSelected(AnActionEvent e, boolean state) {
360       VcsConfiguration.getInstance(myProject).UPDATE_GROUP_BY_PACKAGES = state;
361       myRoot.rebuild(VcsConfiguration.getInstance(myProject).UPDATE_GROUP_BY_PACKAGES);
362     }
363
364     public void update(final AnActionEvent e) {
365       super.update(e);
366       e.getPresentation().setEnabled(!myGroupByChangeList);
367     }
368   }
369
370   private class GroupByChangeListAction extends ToggleAction implements DumbAware {
371     public GroupByChangeListAction() {
372       super(VcsBundle.message("update.info.group.by.changelist"), null, IconLoader.getIcon("/objectBrowser/browser.png"));
373     }
374
375     public boolean isSelected(AnActionEvent e) {
376       return myGroupByChangeList;
377     }
378
379     public void setSelected(AnActionEvent e, boolean state) {
380       myGroupByChangeList = state;
381       VcsConfiguration.getInstance(myProject).UPDATE_GROUP_BY_CHANGELIST = myGroupByChangeList;
382       final CardLayout cardLayout = (CardLayout)myCenterPanel.getLayout();
383       if (!myGroupByChangeList) {
384         cardLayout.show(myCenterPanel, CARD_STATUS);
385       }
386       else {
387         cardLayout.show(myCenterPanel, CARD_CHANGES);
388       }
389     }
390
391     public void update(final AnActionEvent e) {
392       super.update(e);
393       e.getPresentation().setVisible(myCanGroupByChangeList);
394     }
395   }
396
397   public void setBefore(Label before) {
398     myBefore = before;
399   }
400
401   public void setAfter(Label after) {
402     myAfter = after;
403   }
404 }