5bc6854939f78bceeba532ea862ca05b320db4bc
[idea/community.git] / platform / vcs-impl / src / com / intellij / openapi / vcs / changes / committed / CommittedChangesPanel.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
17 /*
18  * Created by IntelliJ IDEA.
19  * User: yole
20  * Date: 05.12.2006
21  * Time: 19:39:22
22  */
23 package com.intellij.openapi.vcs.changes.committed;
24
25 import com.intellij.openapi.Disposable;
26 import com.intellij.openapi.actionSystem.*;
27 import com.intellij.openapi.application.ApplicationManager;
28 import com.intellij.openapi.application.ModalityState;
29 import com.intellij.openapi.diagnostic.Logger;
30 import com.intellij.openapi.progress.ProgressIndicator;
31 import com.intellij.openapi.progress.ProgressManager;
32 import com.intellij.openapi.progress.Task;
33 import com.intellij.openapi.project.Project;
34 import com.intellij.openapi.ui.Messages;
35 import com.intellij.openapi.util.text.StringUtil;
36 import com.intellij.openapi.vcs.*;
37 import com.intellij.openapi.vcs.changes.BackgroundFromStartOption;
38 import com.intellij.openapi.vcs.versionBrowser.ChangeBrowserSettings;
39 import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList;
40 import com.intellij.openapi.vfs.VirtualFile;
41 import com.intellij.ui.FilterComponent;
42 import com.intellij.util.AsynchConsumer;
43 import com.intellij.util.BufferedListConsumer;
44 import com.intellij.util.Consumer;
45 import org.jetbrains.annotations.NotNull;
46 import org.jetbrains.annotations.Nullable;
47
48 import javax.swing.*;
49 import javax.swing.event.ChangeEvent;
50 import javax.swing.event.ChangeListener;
51 import java.awt.*;
52 import java.util.ArrayList;
53 import java.util.Collections;
54 import java.util.LinkedList;
55 import java.util.List;
56
57 public class CommittedChangesPanel extends JPanel implements TypeSafeDataProvider, Disposable {
58   private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.changes.committed.CommittedChangesPanel");
59
60   private final CommittedChangesTreeBrowser myBrowser;
61   private final Project myProject;
62   private CommittedChangesProvider myProvider;
63   private ChangeBrowserSettings mySettings;
64   private final RepositoryLocation myLocation;
65   private int myMaxCount = 0;
66   private final MyFilterComponent myFilterComponent = new MyFilterComponent();
67   private final JLabel myErrorLabel = new JLabel();
68   private final List<Runnable> myShouldBeCalledOnDispose;
69   private volatile boolean myDisposed;
70   private volatile boolean myInLoad;
71
72   public CommittedChangesPanel(Project project, final CommittedChangesProvider provider, final ChangeBrowserSettings settings,
73                                @Nullable final RepositoryLocation location, @Nullable ActionGroup extraActions) {
74     super(new BorderLayout());
75     mySettings = settings;
76     myProject = project;
77     myProvider = provider;
78     myLocation = location;
79     myShouldBeCalledOnDispose = new ArrayList<Runnable>();
80     myBrowser = new CommittedChangesTreeBrowser(project, new ArrayList<CommittedChangeList>());
81     add(myBrowser, BorderLayout.CENTER);
82
83     myErrorLabel.setForeground(Color.red);
84     add(myErrorLabel, BorderLayout.SOUTH);
85
86     final VcsCommittedViewAuxiliary auxiliary = provider.createActions(myBrowser, location);
87
88     JPanel toolbarPanel = new JPanel();
89     toolbarPanel.setLayout(new BoxLayout(toolbarPanel, BoxLayout.X_AXIS));
90
91     ActionGroup group = (ActionGroup) ActionManager.getInstance().getAction("CommittedChangesToolbar");
92
93     ActionToolbar toolBar = myBrowser.createGroupFilterToolbar(project, group, extraActions,
94                                                                auxiliary != null ? auxiliary.getToolbarActions() : Collections.<AnAction>emptyList());
95     toolbarPanel.add(toolBar.getComponent());
96     toolbarPanel.add(Box.createHorizontalGlue());
97     toolbarPanel.add(myFilterComponent);
98     myFilterComponent.setMinimumSize(myFilterComponent.getPreferredSize());
99     myFilterComponent.setMaximumSize(myFilterComponent.getPreferredSize());
100     myBrowser.setToolBar(toolbarPanel);
101
102     if (auxiliary != null) {
103       myShouldBeCalledOnDispose.add(auxiliary.getCalledOnViewDispose());
104       myBrowser.setTableContextMenu(group, (auxiliary.getPopupActions() == null) ? Collections.<AnAction>emptyList() : auxiliary.getPopupActions());
105     } else {
106       myBrowser.setTableContextMenu(group, Collections.<AnAction>emptyList());
107     }
108     
109     final AnAction anAction = ActionManager.getInstance().getAction("CommittedChanges.Refresh");
110     anAction.registerCustomShortcutSet(CommonShortcuts.getRerun(), this);
111     myBrowser.addFilter(myFilterComponent);
112   }
113
114   public RepositoryLocation getRepositoryLocation() {
115     return myLocation;
116   }
117
118   public void setMaxCount(final int maxCount) {
119     myMaxCount = maxCount;
120   }
121
122   public void setProvider(final CommittedChangesProvider provider) {
123     if (myProvider != provider) {
124       myProvider = provider;
125       mySettings = provider.createDefaultSettings(); 
126     }
127   }
128
129   public void refreshChanges(final boolean cacheOnly) {
130     if (myLocation != null) {
131       refreshChangesFromLocation();
132     }
133     else {
134       refreshChangesFromCache(cacheOnly);
135     }
136   }
137
138   private void refreshChangesFromLocation() {
139     myBrowser.reset();
140
141     myInLoad = true;
142     myBrowser.setLoading(true);
143     ProgressManager.getInstance().run(new Task.Backgroundable(myProject, "Loading changes", true, BackgroundFromStartOption.getInstance()) {
144       
145       public void run(@NotNull final ProgressIndicator indicator) {
146         try {
147           final AsynchConsumer<List<CommittedChangeList>> appender = new AsynchConsumer<List<CommittedChangeList>>() {
148             public void finished() {
149             }
150
151             public void consume(final List<CommittedChangeList> list) {
152               new AbstractCalledLater(myProject, ModalityState.stateForComponent(myBrowser)) {
153                 public void run() {
154                   myBrowser.append(list);
155                 }
156               }.callMe();
157             }
158           };
159           final BufferedListConsumer<CommittedChangeList> bufferedListConsumer = new BufferedListConsumer<CommittedChangeList>(30, appender,-1);
160
161           myProvider.loadCommittedChanges(mySettings, myLocation, myMaxCount, new AsynchConsumer<CommittedChangeList>() {
162             public void finished() {
163               bufferedListConsumer.flush();
164             }
165             public void consume(CommittedChangeList committedChangeList) {
166               if (myDisposed) {
167                 indicator.cancel();
168               }
169               ProgressManager.checkCanceled();
170               bufferedListConsumer.consumeOne(committedChangeList);
171             }
172           });
173         }
174         catch (final VcsException e) {
175           ApplicationManager.getApplication().invokeLater(new Runnable() {
176             public void run() {
177               LOG.info(e);
178               Messages.showErrorDialog(myProject, "Error refreshing view: " + StringUtil.join(e.getMessages(), "\n"), "Committed Changes");
179             }
180           });
181         } finally {
182           myInLoad = false;
183           myBrowser.setLoading(false);
184         }
185       }
186     });
187   }
188
189   private void refreshChangesFromCache(final boolean cacheOnly) {
190     final CommittedChangesCache cache = CommittedChangesCache.getInstance(myProject);
191     cache.hasCachesForAnyRoot(new Consumer<Boolean>() {
192       public void consume(final Boolean notEmpty) {
193         if (! notEmpty) {
194           if (cacheOnly) {
195             myBrowser.setEmptyText(VcsBundle.message("committed.changes.not.loaded.message"));
196             return;
197           }
198           if (!CacheSettingsDialog.showSettingsDialog(myProject)) return;
199         }
200         cache.getProjectChangesAsync(mySettings, myMaxCount, cacheOnly,
201                                      new Consumer<List<CommittedChangeList>>() {
202                                        public void consume(final List<CommittedChangeList> committedChangeLists) {
203                                          updateFilteredModel(false, committedChangeLists);
204                                          }
205                                        },
206                                      new Consumer<List<VcsException>>() {
207                                        public void consume(final List<VcsException> vcsExceptions) {
208                                          AbstractVcsHelper.getInstance(myProject).showErrors(vcsExceptions, "Error refreshing VCS history");
209                                        }
210                                      });
211       }
212     });
213   }
214
215   private static class FilterHelper {
216     private final String[] myParts;
217
218     FilterHelper(final String filterString) {
219       myParts = filterString.split(" ");
220       for(int i = 0; i < myParts.length; ++ i) {
221         myParts [i] = myParts [i].toLowerCase();
222       }
223     }
224
225     public boolean filter(@NotNull final CommittedChangeList cl) {
226       return changeListMatches(cl, myParts);
227     }
228
229     private static boolean changeListMatches(@NotNull final CommittedChangeList changeList, final String[] filterWords) {
230       for(String word: filterWords) {
231         final String comment = changeList.getComment();
232         final String committer = changeList.getCommitterName();
233         if ((comment != null && comment.toLowerCase().indexOf(word) >= 0) ||
234             (committer != null && committer.toLowerCase().indexOf(word) >= 0) ||
235             Long.toString(changeList.getNumber()).indexOf(word) >= 0) {
236           return true;
237         }
238       }
239       return false;
240     }
241   }
242
243   private void updateFilteredModel(final boolean keepFilter, List<CommittedChangeList> committedChangeLists) {
244     if (committedChangeLists == null) {
245       return;
246     }
247     myBrowser.setEmptyText(VcsBundle.message("committed.changes.empty.message"));
248     if (StringUtil.isEmpty(myFilterComponent.getFilter())) {
249       myBrowser.setItems(committedChangeLists, keepFilter, CommittedChangesBrowserUseCase.COMMITTED);
250     }
251     else {
252       final FilterHelper filterHelper = new FilterHelper(myFilterComponent.getFilter());
253       List<CommittedChangeList> filteredChanges = new ArrayList<CommittedChangeList>();
254       for(CommittedChangeList changeList: committedChangeLists) {
255         if (filterHelper.filter(changeList)) {
256           filteredChanges.add(changeList);
257         }
258       }
259       myBrowser.setItems(filteredChanges, keepFilter, CommittedChangesBrowserUseCase.COMMITTED);
260     }
261   }
262
263   public void setChangesFilter() {
264     CommittedChangesFilterDialog filterDialog = new CommittedChangesFilterDialog(myProject, myProvider.createFilterUI(true), mySettings);
265     filterDialog.show();
266     if (filterDialog.isOK()) {
267       mySettings = filterDialog.getSettings();
268       refreshChanges(false);
269     }
270   }
271
272   public void calcData(DataKey key, DataSink sink) {
273     if (key.equals(VcsDataKeys.CHANGES) || key.equals(VcsDataKeys.CHANGE_LISTS)) {
274       myBrowser.calcData(key, sink);
275     }
276   }
277
278   public void dispose() {
279     myBrowser.dispose();
280     for (Runnable runnable : myShouldBeCalledOnDispose) {
281       runnable.run();
282     }
283     myDisposed = true;
284   }
285
286   public void setErrorText(String text) {
287     myErrorLabel.setText(text);
288   }
289
290   private class MyFilterComponent extends FilterComponent implements ChangeListFilteringStrategy {
291     private final List<ChangeListener> myList;
292
293     public MyFilterComponent() {
294       super("COMMITTED_CHANGES_FILTER_HISTORY", 20);
295       myList = new ArrayList<ChangeListener>();
296     }
297
298     public void filter() {
299       for (ChangeListener changeListener : myList) {
300         changeListener.stateChanged(new ChangeEvent(this));
301       }
302     }
303     public JComponent getFilterUI() {
304       return null;
305     }
306     public void setFilterBase(List<CommittedChangeList> changeLists) {
307     }
308     public void addChangeListener(ChangeListener listener) {
309       myList.add(listener);
310     }
311     public void removeChangeListener(ChangeListener listener) {
312       myList.remove(listener);
313     }
314     public void resetFilterBase() {
315     }
316     public void appendFilterBase(List<CommittedChangeList> changeLists) {
317     }
318     @NotNull
319     public List<CommittedChangeList> filterChangeLists(List<CommittedChangeList> changeLists) {
320       final FilterHelper filterHelper = new FilterHelper(myFilterComponent.getFilter());
321       final List<CommittedChangeList> result = new ArrayList<CommittedChangeList>();
322       for (CommittedChangeList list : changeLists) {
323         if (filterHelper.filter(list)) {
324           result.add(list);
325         }
326       }
327       return result;
328     }
329   }
330
331   public void passCachedListsToListener(final VcsConfigurationChangeListener.DetailedNotification notification,
332                                         final Project project, final VirtualFile root) {
333     final LinkedList<CommittedChangeList> resultList = new LinkedList<CommittedChangeList>();
334     myBrowser.reportLoadedLists(new CommittedChangeListsListener() {
335       public void onBeforeStartReport() {
336       }
337       public boolean report(CommittedChangeList list) {
338         resultList.add(list);
339         return false;
340       }
341       public void onAfterEndReport() {
342         if (! resultList.isEmpty()) {
343           notification.execute(project, root, resultList);
344         }
345       }
346     });
347   }
348
349   public boolean isInLoad() {
350     return myInLoad;
351   }
352 }