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.update;
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;
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;
58 import java.awt.event.ActionEvent;
59 import java.awt.event.ActionListener;
62 import java.util.List;
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;
87 private Label myBefore;
88 private Label myAfter;
90 public UpdateInfoTree(@NotNull ContentManager contentManager,
91 @NotNull Project project,
92 UpdatedFiles updatedFiles,
94 ActionInfo actionInfo) {
95 super(contentManager, "reference.toolWindows.versionControl");
96 myActionInfo = actionInfo;
98 myFileStatusListener = new FileStatusListener() {
99 public void fileStatusesChanged() {
103 public void fileStatusChanged(@NotNull VirtualFile virtualFile) {
109 myUpdatedFiles = updatedFiles;
110 myRootName = rootName;
112 myFileStatusManager = FileStatusManager.getInstance(myProject);
113 myFileStatusManager.addFileStatusListener(myFileStatusListener);
116 myTreeExpander = new DefaultTreeExpander(myTree);
117 myTreeIterable = new MyTreeIterable();
120 public void dispose() {
122 Disposer.dispose(myRoot);
123 if (myFileStatusListener != null) {
124 myFileStatusManager.removeFileStatusListener(myFileStatusListener);
125 myFileStatusListener = null;
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);
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);
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;
161 private void createTree() {
162 SmartExpander.installOn(myTree);
163 SelectionSaver.installOn(myTree);
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();
175 mySelectedUrl = null;
176 mySelectedFile = null;
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();
188 return TreeSpeedSearch.NODE_DESCRIPTOR_TOSTRING.convert(path);
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,
198 popupMenu.getComponent().show(comp, x, y);
202 EditSourceOnDoubleClickHandler.install(myTree);
203 EditSourceOnEnterKeyHandler.install(myTree);
205 myTree.setSelectionRow(0);
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);
217 public Object getData(String dataId) {
218 if (myTreeBrowser.isVisible()) {
221 if (PlatformDataKeys.NAVIGATABLE.is(dataId)) {
222 if (mySelectedFile == null || !mySelectedFile.isValid()) return null;
223 return new OpenFileDescriptor(myProject, mySelectedFile);
225 else if (PlatformDataKeys.VIRTUAL_FILE_ARRAY.is(dataId)) {
226 return getVirtualFileArray();
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();
235 return myTreeExpander;
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)) {
243 } else if (VcsDataKeys.LABEL_AFTER.is(dataId)) {
247 return super.getData(dataId);
250 private class MyTreeIterator implements Iterator<VirtualFilePointer> {
251 private final Enumeration myEnum;
252 private VirtualFilePointer myNext;
254 private MyTreeIterator() {
255 myEnum = myRoot.depthFirstEnumeration();
259 public boolean hasNext() {
260 return myNext != null;
263 public VirtualFilePointer next() {
264 final VirtualFilePointer result = myNext;
269 private void step() {
271 while (myEnum.hasMoreElements()) {
272 final Object o = myEnum.nextElement();
273 if (o instanceof FileTreeNode) {
274 myNext = ((FileTreeNode) o).getFilePointer();
280 public void remove() {
281 throw new UnsupportedOperationException();
285 private class MyTreeIterable implements Iterable<VirtualFilePointer> {
286 public Iterator<VirtualFilePointer> iterator() {
287 return new MyTreeIterator();
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());
300 return VfsUtil.toVirtualFileArray(result);
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());
313 if (result.isEmpty()) return null;
314 return result.toArray(new File[result.size()]);
317 public void expandRootChildren() {
318 TreeNode root = (TreeNode)myTreeModel.getRoot();
320 if (root.getChildCount() == 1) {
321 myTree.expandPath(new TreePath(new Object[]{root, root.getChildAt(0)}));
325 public void setChangeLists(final List<CommittedChangeList> receivedChanges) {
326 final boolean hasEmptyCaches = CommittedChangesCache.getInstance(myProject).hasEmptyCaches();
328 ApplicationManager.getApplication().invokeLater(new Runnable() {
330 if (myLoadingChangeListsLabel != null) {
331 remove(myLoadingChangeListsLabel);
332 myLoadingChangeListsLabel = null;
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);
344 myTreeBrowser.appendEmptyText(" to initialize repository changes cache", SimpleTextAttributes.REGULAR_ATTRIBUTES);
347 }, myProject.getDisposed());
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);
355 public boolean isSelected(AnActionEvent e) {
356 return VcsConfiguration.getInstance(myProject).UPDATE_GROUP_BY_PACKAGES;
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);
364 public void update(final AnActionEvent e) {
366 e.getPresentation().setEnabled(!myGroupByChangeList);
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"));
375 public boolean isSelected(AnActionEvent e) {
376 return myGroupByChangeList;
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);
387 cardLayout.show(myCenterPanel, CARD_CHANGES);
391 public void update(final AnActionEvent e) {
393 e.getPresentation().setVisible(myCanGroupByChangeList);
397 public void setBefore(Label before) {
401 public void setAfter(Label after) {