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