From: irengrig Date: Fri, 12 Feb 2010 13:24:43 +0000 (+0300) Subject: VCS: asynch load of committed changes when do "Browse changes" X-Git-Tag: 94.297~16 X-Git-Url: https://repo.or.cz/w/fedora-idea.git/commitdiff_plain/db4875863c8996bf64f781ac578e7a4cab314139 VCS: asynch load of committed changes when do "Browse changes" --- diff --git a/platform/lang-impl/src/com/intellij/openapi/vcs/changes/ui/ChangesModuleGroupingPolicy.java b/platform/lang-impl/src/com/intellij/openapi/vcs/changes/ui/ChangesModuleGroupingPolicy.java index ed0fc07f76..7cde5c9b92 100644 --- a/platform/lang-impl/src/com/intellij/openapi/vcs/changes/ui/ChangesModuleGroupingPolicy.java +++ b/platform/lang-impl/src/com/intellij/openapi/vcs/changes/ui/ChangesModuleGroupingPolicy.java @@ -54,6 +54,10 @@ public class ChangesModuleGroupingPolicy implements ChangesGroupingPolicy { return null; } + public void clear() { + myModuleCache.clear(); + } + private ChangesBrowserNode getNodeForModule(Module module, ChangesBrowserNode root) { ChangesBrowserNode node = myModuleCache.get(module); if (node == null) { diff --git a/platform/platform-api/src/com/intellij/ide/util/treeView/TreeState.java b/platform/platform-api/src/com/intellij/ide/util/treeView/TreeState.java index 6299c27c6e..c7195efde3 100644 --- a/platform/platform-api/src/com/intellij/ide/util/treeView/TreeState.java +++ b/platform/platform-api/src/com/intellij/ide/util/treeView/TreeState.java @@ -78,10 +78,12 @@ public class TreeState implements JDOMExternalizable { private final List> myExpandedPaths; private final List> mySelectedPaths; + private boolean myScrollToSelection; private TreeState(List> expandedPaths, final List> selectedPaths) { myExpandedPaths = expandedPaths; mySelectedPaths = selectedPaths; + myScrollToSelection = true; } public TreeState() { @@ -256,11 +258,12 @@ public class TreeState implements JDOMExternalizable { } } + // todo private void applySelected(final JTree tree, final DefaultMutableTreeNode node) { TreeUtil.unselect(tree, node); List selectionPaths = new ArrayList(); for (List pathElements : mySelectedPaths) { - applySelectedTo(pathElements, tree.getModel().getRoot(), tree, selectionPaths); + applySelectedTo(pathElements, tree.getModel().getRoot(), tree, selectionPaths, myScrollToSelection); } if (selectionPaths.size() > 1) { @@ -344,7 +347,7 @@ public class TreeState implements JDOMExternalizable { private static void applySelectedTo(final List path, Object root, JTree tree, - final List outSelectionPaths) { + final List outSelectionPaths, final boolean scrollToSelection) { for (int i = 1; i < path.size(); i++) { if (!(root instanceof DefaultMutableTreeNode)) return; @@ -355,7 +358,11 @@ public class TreeState implements JDOMExternalizable { if (!(root instanceof DefaultMutableTreeNode)) return; final TreePath pathInNewTree = new TreePath(((DefaultMutableTreeNode) root).getPath()); - TreeUtil.selectPath(tree, pathInNewTree); + if (scrollToSelection) { + TreeUtil.selectPath(tree, pathInNewTree); + } else { + tree.setSelectionPath(pathInNewTree); + } outSelectionPaths.add(pathInNewTree); } @@ -419,5 +426,8 @@ public class TreeState implements JDOMExternalizable { } } + public void setScrollToSelection(boolean scrollToSelection) { + myScrollToSelection = scrollToSelection; + } } diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/changes/committed/ChangeListFilteringStrategy.java b/platform/vcs-api/src/com/intellij/openapi/vcs/changes/committed/ChangeListFilteringStrategy.java index 9349da6d1f..430b4ccb35 100644 --- a/platform/vcs-api/src/com/intellij/openapi/vcs/changes/committed/ChangeListFilteringStrategy.java +++ b/platform/vcs-api/src/com/intellij/openapi/vcs/changes/committed/ChangeListFilteringStrategy.java @@ -33,6 +33,10 @@ public interface ChangeListFilteringStrategy { void addChangeListener(ChangeListener listener); void removeChangeListener(ChangeListener listener); + @Nullable + void resetFilterBase(); + void appendFilterBase(List changeLists); + @NotNull List filterChangeLists(List changeLists); @@ -55,6 +59,13 @@ public interface ChangeListFilteringStrategy { public void removeChangeListener(ChangeListener listener) { } + @Nullable + public void resetFilterBase() { + } + + public void appendFilterBase(List changeLists) { + } + @NotNull public List filterChangeLists(List changeLists) { return changeLists; diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/committed/ColumnFilteringStrategy.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/committed/ColumnFilteringStrategy.java index f97df91614..5b8c409595 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/committed/ColumnFilteringStrategy.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/committed/ColumnFilteringStrategy.java @@ -23,6 +23,7 @@ import com.intellij.ui.ColoredListCellRenderer; import com.intellij.ui.SimpleTextAttributes; import com.intellij.util.ArrayUtil; import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.Convertor; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -32,10 +33,8 @@ import javax.swing.event.ChangeListener; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import java.awt.*; -import java.util.ArrayList; -import java.util.Collection; +import java.util.*; import java.util.List; -import java.util.TreeSet; import java.util.concurrent.CopyOnWriteArrayList; /** @@ -46,12 +45,18 @@ public class ColumnFilteringStrategy extends JPanel implements ChangeListFilteri private final CopyOnWriteArrayList myListeners = ContainerUtil.createEmptyCOWList(); private final ChangeListColumn myColumn; private final Class myProviderClass; + private final MyListModel myModel; + private final CommittedChangeListToStringConvertor ourConvertorInstance = new CommittedChangeListToStringConvertor(); + + private Object[] myPrefferedSelection; public ColumnFilteringStrategy(final ChangeListColumn column, final Class providerClass) { setLayout(new BorderLayout()); + myModel = new MyListModel(); myValueList = new JList(); add(new JScrollPane(myValueList)); + myValueList.setModel(myModel); myValueList.getSelectionModel().addListSelectionListener(new ListSelectionListener() { public void valueChanged(final ListSelectionEvent e) { for(ChangeListener listener: myListeners) { @@ -91,30 +96,8 @@ public class ColumnFilteringStrategy extends JPanel implements ChangeListFilteri } public void setFilterBase(List changeLists) { - final Object oldSelection = myValueList.getSelectedValue(); - final Collection values = new TreeSet(); - for(CommittedChangeList changeList: changeLists) { - if (myProviderClass == null || myProviderClass.isInstance(changeList.getVcs().getCommittedChangesProvider())) { - //noinspection unchecked - values.add(myColumn.getValue(ReceivedChangeList.unwrap(changeList)).toString()); - } - } - final String[] valueArray = ArrayUtil.toStringArray(values); - myValueList.setModel(new AbstractListModel() { - public int getSize() { - return valueArray.length+1; - } - - public Object getElementAt(final int index) { - if (index == 0) { - return VcsBundle.message("committed.changes.filter.all"); - } - return valueArray [index-1]; - } - }); - if (oldSelection != null) { - myValueList.setSelectedValue(oldSelection, false); - } + myPrefferedSelection = null; + appendFilterBase(changeLists); } public void addChangeListener(final ChangeListener listener) { @@ -125,6 +108,36 @@ public class ColumnFilteringStrategy extends JPanel implements ChangeListFilteri myListeners.remove(listener); } + public void resetFilterBase() { + myPrefferedSelection = myValueList.getSelectedValues(); + myValueList.clearSelection(); + myModel.clear(); + myValueList.revalidate(); + myValueList.repaint(); + } + + public void appendFilterBase(List changeLists) { + final Object[] oldSelection = myModel.isEmpty() ? myPrefferedSelection : myValueList.getSelectedValues(); + + myModel.addNext(changeLists, ourConvertorInstance); + if (oldSelection != null) { + for (Object o : oldSelection) { + myValueList.setSelectedValue(o, false); + } + } + myValueList.revalidate(); + myValueList.repaint(); + } + + private class CommittedChangeListToStringConvertor implements Convertor { + public String convert(CommittedChangeList o) { + if (myProviderClass == null || myProviderClass.isInstance(o.getVcs().getCommittedChangesProvider())) { + return myColumn.getValue(ReceivedChangeList.unwrap(o)).toString(); + } + return null; + } + } + @NotNull public List filterChangeLists(List changeLists) { final Object[] selection = myValueList.getSelectedValues(); @@ -145,4 +158,43 @@ public class ColumnFilteringStrategy extends JPanel implements ChangeListFilteri } return result; } + + private static class MyListModel extends AbstractListModel { + private volatile String[] myValues; + + private MyListModel() { + myValues = ArrayUtil.EMPTY_STRING_ARRAY; + } + + public void addNext(final Collection values, final Convertor convertor) { + final TreeSet set = new TreeSet(Arrays.asList(myValues)); + for (T value : values) { + final String converted = convertor.convert(value); + if (converted != null) { + // also works as filter + set.add(converted); + } + } + myValues = ArrayUtil.toStringArray(set); + } + + public int getSize() { + return myValues.length + 1; + } + + public boolean isEmpty() { + return myValues.length == 0; + } + + public Object getElementAt(int index) { + if (index == 0) { + return VcsBundle.message("committed.changes.filter.all"); + } + return myValues[index-1]; + } + + public void clear() { + myValues = ArrayUtil.EMPTY_STRING_ARRAY; + } + } } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/committed/CommittedChangesPanel.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/committed/CommittedChangesPanel.java index 6bf55c7eda..57f3aa19d7 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/committed/CommittedChangesPanel.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/committed/CommittedChangesPanel.java @@ -24,22 +24,30 @@ package com.intellij.openapi.vcs.changes.committed; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.*; +import com.intellij.openapi.vcs.changes.BackgroundFromStartOption; import com.intellij.openapi.vcs.versionBrowser.ChangeBrowserSettings; import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.FilterComponent; +import com.intellij.util.AsynchConsumer; +import com.intellij.util.BufferedListConsumer; import com.intellij.util.Consumer; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; import java.awt.*; import java.util.ArrayList; import java.util.Collections; @@ -54,10 +62,12 @@ public class CommittedChangesPanel extends JPanel implements TypeSafeDataProvide private ChangeBrowserSettings mySettings; private final RepositoryLocation myLocation; private int myMaxCount = 0; - private final FilterComponent myFilterComponent = new MyFilterComponent(); + private final MyFilterComponent myFilterComponent = new MyFilterComponent(); private List myChangesFromProvider; private final JLabel myErrorLabel = new JLabel(); private final List myShouldBeCalledOnDispose; + private volatile boolean myDisposed; + private volatile boolean myInLoad; public CommittedChangesPanel(Project project, final CommittedChangesProvider provider, final ChangeBrowserSettings settings, @Nullable final RepositoryLocation location, @Nullable ActionGroup extraActions) { @@ -94,6 +104,7 @@ public class CommittedChangesPanel extends JPanel implements TypeSafeDataProvide final AnAction anAction = ActionManager.getInstance().getAction("CommittedChanges.Refresh"); anAction.registerCustomShortcutSet(CommonShortcuts.getRerun(), this); + myBrowser.addFilter(myFilterComponent); } public RepositoryLocation getRepositoryLocation() { @@ -121,26 +132,54 @@ public class CommittedChangesPanel extends JPanel implements TypeSafeDataProvide } private void refreshChangesFromLocation() { - final Ref refEx = new Ref(); - final Ref> changes = new Ref>(); - boolean completed = ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() { - public void run() { + myBrowser.reset(); + + myInLoad = true; + myBrowser.setLoading(true); + ProgressManager.getInstance().run(new Task.Backgroundable(myProject, "Loading changes", true, BackgroundFromStartOption.getInstance()) { + @Override + public void run(@NotNull final ProgressIndicator indicator) { try { - changes.set(myProvider.getCommittedChanges(mySettings, myLocation, myMaxCount)); + final AsynchConsumer> appender = new AsynchConsumer>() { + public void finished() { + } + + public void consume(final List list) { + new AbstractCalledLater(myProject, ModalityState.stateForComponent(myBrowser)) { + public void run() { + myBrowser.append(list); + } + }.callMe(); + } + }; + final BufferedListConsumer bufferedListConsumer = new BufferedListConsumer(30, appender); + + myProvider.loadCommittedChanges(mySettings, myLocation, myMaxCount, new AsynchConsumer() { + public void finished() { + bufferedListConsumer.flush(); + } + public void consume(CommittedChangeList committedChangeList) { + if (myDisposed) { + indicator.cancel(); + } + ProgressManager.checkCanceled(); + bufferedListConsumer.consumeOne(committedChangeList); + } + }); } - catch (VcsException ex) { - refEx.set(ex); + catch (final VcsException e) { + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + LOG.info(e); + Messages.showErrorDialog(myProject, "Error refreshing view: " + StringUtil.join(e.getMessages(), "\n"), "Committed Changes"); + } + }); + } finally { + myInLoad = false; + myBrowser.setLoading(false); } } - }, "Loading changes", true, myProject); - if (!refEx.isNull()) { - LOG.info(refEx.get()); - Messages.showErrorDialog(myProject, "Error refreshing view: " + StringUtil.join(refEx.get().getMessages(), "\n"), "Committed Changes"); - } - else if (completed) { - myChangesFromProvider = changes.get(); - updateFilteredModel(false); - } + }); } private void refreshChangesFromCache(final boolean cacheOnly) { @@ -166,6 +205,34 @@ public class CommittedChangesPanel extends JPanel implements TypeSafeDataProvide }); } + private static class FilterHelper { + private final String[] myParts; + + FilterHelper(final String filterString) { + myParts = filterString.split(" "); + for(int i = 0; i < myParts.length; ++ i) { + myParts [i] = myParts [i].toLowerCase(); + } + } + + public boolean filter(@NotNull final CommittedChangeList cl) { + return changeListMatches(cl, myParts); + } + + private static boolean changeListMatches(@NotNull final CommittedChangeList changeList, final String[] filterWords) { + for(String word: filterWords) { + final String comment = changeList.getComment(); + final String committer = changeList.getCommitterName(); + if ((comment != null && comment.toLowerCase().indexOf(word) >= 0) || + (committer != null && committer.toLowerCase().indexOf(word) >= 0) || + Long.toString(changeList.getNumber()).indexOf(word) >= 0) { + return true; + } + } + return false; + } + } + private void updateFilteredModel(final boolean keepFilter) { if (myChangesFromProvider == null) { return; @@ -175,13 +242,10 @@ public class CommittedChangesPanel extends JPanel implements TypeSafeDataProvide myBrowser.setItems(myChangesFromProvider, keepFilter, CommittedChangesBrowserUseCase.COMMITTED); } else { - final String[] strings = myFilterComponent.getFilter().split(" "); - for(int i=0; i filteredChanges = new ArrayList(); for(CommittedChangeList changeList: myChangesFromProvider) { - if (changeListMatches(changeList, strings)) { + if (filterHelper.filter(changeList)) { filteredChanges.add(changeList); } } @@ -189,19 +253,6 @@ public class CommittedChangesPanel extends JPanel implements TypeSafeDataProvide } } - private static boolean changeListMatches(@NotNull final CommittedChangeList changeList, final String[] filterWords) { - for(String word: filterWords) { - final String comment = changeList.getComment(); - final String committer = changeList.getCommitterName(); - if ((comment != null && comment.toLowerCase().indexOf(word) >= 0) || - (committer != null && committer.toLowerCase().indexOf(word) >= 0) || - Long.toString(changeList.getNumber()).indexOf(word) >= 0) { - return true; - } - } - return false; - } - public void setChangesFilter() { CommittedChangesFilterDialog filterDialog = new CommittedChangesFilterDialog(myProject, myProvider.createFilterUI(true), mySettings); filterDialog.show(); @@ -222,19 +273,51 @@ public class CommittedChangesPanel extends JPanel implements TypeSafeDataProvide for (Runnable runnable : myShouldBeCalledOnDispose) { runnable.run(); } + myDisposed = true; } public void setErrorText(String text) { myErrorLabel.setText(text); } - private class MyFilterComponent extends FilterComponent { + private class MyFilterComponent extends FilterComponent implements ChangeListFilteringStrategy { + private final List myList; + public MyFilterComponent() { super("COMMITTED_CHANGES_FILTER_HISTORY", 20); + myList = new ArrayList(); } public void filter() { - updateFilteredModel(true); + for (ChangeListener changeListener : myList) { + changeListener.stateChanged(new ChangeEvent(this)); + } + } + public JComponent getFilterUI() { + return null; + } + public void setFilterBase(List changeLists) { + } + public void addChangeListener(ChangeListener listener) { + myList.add(listener); + } + public void removeChangeListener(ChangeListener listener) { + myList.remove(listener); + } + public void resetFilterBase() { + } + public void appendFilterBase(List changeLists) { + } + @NotNull + public List filterChangeLists(List changeLists) { + final FilterHelper filterHelper = new FilterHelper(myFilterComponent.getFilter()); + final List result = new ArrayList(); + for (CommittedChangeList list : changeLists) { + if (filterHelper.filter(list)) { + result.add(list); + } + } + return result; } } @@ -244,4 +327,8 @@ public class CommittedChangesPanel extends JPanel implements TypeSafeDataProvide notification.execute(project, root, myChangesFromProvider); } } + + public boolean isInLoad() { + return myInLoad; + } } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/committed/CommittedChangesTreeBrowser.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/committed/CommittedChangesTreeBrowser.java index 0f831e71a5..9be7b60e0a 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/committed/CommittedChangesTreeBrowser.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/committed/CommittedChangesTreeBrowser.java @@ -9,6 +9,7 @@ import com.intellij.ide.DefaultTreeExpander; import com.intellij.ide.TreeExpander; import com.intellij.ide.actions.ContextHelpAction; import com.intellij.ide.ui.SplitterProportionsDataImpl; +import com.intellij.ide.util.treeView.TreeState; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.application.ApplicationManager; @@ -40,6 +41,7 @@ import com.intellij.ui.treeStructure.actions.ExpandAllAction; import com.intellij.util.messages.MessageBusConnection; import com.intellij.util.messages.Topic; import com.intellij.util.ui.TreeWithEmptyText; +import com.intellij.util.ui.UIUtil; import com.intellij.util.ui.tree.TreeUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -92,6 +94,12 @@ public class CommittedChangesTreeBrowser extends JPanel implements TypeSafeDataP private final WiseSplitter myInnerSplitter; private MessageBusConnection myConnection; + private List myFilteredChangeLists; + private Object[] mySelectedFiltering; + private JLabel myLoadingLabel; + private int[] mySelectionRows; + private TreeState myState; + private JPanel myLoadingPanel; public CommittedChangesTreeBrowser(final Project project, final List changeLists) { super(new BorderLayout()); @@ -121,7 +129,16 @@ public class CommittedChangesTreeBrowser extends JPanel implements TypeSafeDataP myToolbarPanel = new JPanel(new BorderLayout()); myLeftPanel.add(myToolbarPanel, BorderLayout.NORTH); myFilterSplitter = new Splitter(false, 0.5f); + + myLoadingPanel = new JPanel(new BorderLayout()); + myLoadingPanel.setBackground(UIUtil.getToolTipBackground()); + myLoadingLabel = new JLabel("Loading..."); + + myLoadingPanel.add(myLoadingLabel, BorderLayout.CENTER); + + myToolbarPanel.add(myLoadingPanel, BorderLayout.CENTER); myFilterSplitter.setSecondComponent(new JScrollPane(myChangesTree)); + myLoadingPanel.setVisible(false); myLeftPanel.add(myFilterSplitter, BorderLayout.CENTER); final Splitter splitter = new Splitter(false, 0.7f); splitter.setFirstComponent(myLeftPanel); @@ -163,6 +180,11 @@ public class CommittedChangesTreeBrowser extends JPanel implements TypeSafeDataP }); } + public void addFilter(final ChangeListFilteringStrategy strategy) { + myFilteringStrategy.addStrategy("permanent", strategy); + strategy.addChangeListener(myFilterChangeListener); + } + private void updateGrouping() { if (myGroupingStrategy.changedSinceApply()) { ApplicationManager.getApplication().invokeLater(new Runnable() { @@ -173,8 +195,8 @@ public class CommittedChangesTreeBrowser extends JPanel implements TypeSafeDataP } } - private TreeModel buildTreeModel() { - final List filteredChangeLists = myFilteringStrategy.filterChangeLists(myChangeLists); + private TreeModel buildTreeModel(final List filteredChangeLists) { + myFilteredChangeLists = filteredChangeLists; DefaultMutableTreeNode root = new DefaultMutableTreeNode(); DefaultTreeModel model = new DefaultTreeModel(root); DefaultMutableTreeNode lastGroupNode = null; @@ -236,7 +258,8 @@ public class CommittedChangesTreeBrowser extends JPanel implements TypeSafeDataP } private void updateModel() { - myChangesTree.setModel(buildTreeModel()); + final List filteredChangeLists = myFilteringStrategy.filterChangeLists(myChangeLists); + myChangesTree.setModel(buildTreeModel(filteredChangeLists)); TreeUtil.expandAll(myChangesTree); } @@ -421,6 +444,29 @@ public class CommittedChangesTreeBrowser extends JPanel implements TypeSafeDataP }); } + // for appendable view + public void reset() { + myChangeLists.clear(); + myFilteringStrategy.resetFilterBase(); + + myState = TreeState.createOn(myChangesTree, (DefaultMutableTreeNode)myChangesTree.getModel().getRoot()); + updateModel(); + } + + public void append(final List list) { + final TreeState state = (myChangeLists.isEmpty() && myState != null) ? myState : + TreeState.createOn(myChangesTree, (DefaultMutableTreeNode)myChangesTree.getModel().getRoot()); + state.setScrollToSelection(false); + myChangeLists.addAll(list); + + myFilteringStrategy.appendFilterBase(list); + + myChangesTree.setModel(buildTreeModel(myFilteringStrategy.filterChangeLists(myChangeLists))); + state.applyTo(myChangesTree, (DefaultMutableTreeNode)myChangesTree.getModel().getRoot()); + TreeUtil.expandAll(myChangesTree); + myProject.getMessageBus().syncPublisher(ITEMS_RELOADED).itemsReloaded(); + } + public static class CommittedChangeListRenderer extends ColoredTreeCellRenderer { private final static DateFormat myDateFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT); private static final SimpleTextAttributes LINK_ATTRIBUTES = new SimpleTextAttributes(SimpleTextAttributes.STYLE_UNDERLINE, Color.blue); @@ -584,7 +630,7 @@ public class CommittedChangesTreeBrowser extends JPanel implements TypeSafeDataP private class ChangesBrowserTree extends TreeWithEmptyText implements TypeSafeDataProvider { public ChangesBrowserTree() { - super(buildTreeModel()); + super(buildTreeModel(myFilteringStrategy.filterChangeLists(myChangeLists))); } @Override @@ -687,4 +733,14 @@ public class CommittedChangesTreeBrowser extends JPanel implements TypeSafeDataP void itemsReloaded(); void emptyRefresh(); } + + public void setLoading(final boolean value) { + new AbstractCalledLater(myProject, ModalityState.NON_MODAL) { + public void run() { + myLoadingPanel.setVisible(value); + myToolbarPanel.revalidate(); + myToolbarPanel.repaint(); + } + }.callMe(); + } } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/committed/CompositeChangeListFilteringStrategy.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/committed/CompositeChangeListFilteringStrategy.java index c4bab1651a..6f40a434df 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/committed/CompositeChangeListFilteringStrategy.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/committed/CompositeChangeListFilteringStrategy.java @@ -56,6 +56,18 @@ public class CompositeChangeListFilteringStrategy implements ChangeListFiltering } } + public void resetFilterBase() { + for (ChangeListFilteringStrategy delegate : myDelegates.values()) { + delegate.resetFilterBase(); + } + } + + public void appendFilterBase(List changeLists) { + for (ChangeListFilteringStrategy delegate : myDelegates.values()) { + delegate.appendFilterBase(changeLists); + } + } + @NotNull public List filterChangeLists(final List changeLists) { List result = new ArrayList(changeLists); diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/committed/RefreshCommittedAction.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/committed/RefreshCommittedAction.java index c5906e5495..59eae65e73 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/committed/RefreshCommittedAction.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/committed/RefreshCommittedAction.java @@ -18,8 +18,8 @@ package com.intellij.openapi.vcs.changes.committed; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.PlatformDataKeys; -import com.intellij.openapi.project.Project; import com.intellij.openapi.project.DumbAware; +import com.intellij.openapi.project.Project; import com.intellij.openapi.vcs.changes.ui.ChangesViewContentManager; /** @@ -30,6 +30,7 @@ public class RefreshCommittedAction extends AnAction implements DumbAware { Project project = e.getData(PlatformDataKeys.PROJECT); CommittedChangesPanel panel = ChangesViewContentManager.getInstance(project).getActiveComponent(CommittedChangesPanel.class); assert panel != null; + if (panel.isInLoad()) return; if (panel.getRepositoryLocation() != null) { panel.refreshChanges(false); } @@ -42,7 +43,7 @@ public class RefreshCommittedAction extends AnAction implements DumbAware { Project project = e.getData(PlatformDataKeys.PROJECT); if (project != null) { CommittedChangesPanel panel = ChangesViewContentManager.getInstance(project).getActiveComponent(CommittedChangesPanel.class); - e.getPresentation().setEnabled(panel != null); + e.getPresentation().setEnabled(panel != null && (! panel.isInLoad())); } else { e.getPresentation().setEnabled(false); diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/committed/StructureFilteringStrategy.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/committed/StructureFilteringStrategy.java index cf527acf3f..08e33a978d 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/committed/StructureFilteringStrategy.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/committed/StructureFilteringStrategy.java @@ -15,6 +15,7 @@ */ package com.intellij.openapi.vcs.changes.committed; +import com.intellij.ide.util.treeView.TreeState; import com.intellij.openapi.project.Project; import com.intellij.openapi.vcs.FilePath; import com.intellij.openapi.vcs.VcsBundle; @@ -24,8 +25,8 @@ import com.intellij.openapi.vcs.changes.ui.ChangesBrowserNode; import com.intellij.openapi.vcs.changes.ui.ChangesBrowserNodeRenderer; import com.intellij.openapi.vcs.changes.ui.TreeModelBuilder; import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; -import com.intellij.util.containers.ContainerUtil; import com.intellij.ui.treeStructure.Tree; +import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -36,7 +37,6 @@ import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; -import javax.swing.tree.TreeNode; import java.awt.*; import java.util.*; import java.util.List; @@ -71,7 +71,8 @@ public class StructureFilteringStrategy implements ChangeListFilteringStrategy { if (myUI == null) { myUI = new MyUI(); } - myUI.buildModel(changeLists); + myUI.reset(); + myUI.append(changeLists); } public void addChangeListener(ChangeListener listener) { @@ -82,6 +83,14 @@ public class StructureFilteringStrategy implements ChangeListFilteringStrategy { myListeners.remove(listener); } + public void resetFilterBase() { + myUI.reset(); + } + + public void appendFilterBase(List changeLists) { + myUI.append(changeLists); + } + @NotNull public List filterChangeLists(List changeLists) { if (mySelection.size() == 0) { @@ -111,6 +120,8 @@ public class StructureFilteringStrategy implements ChangeListFilteringStrategy { private class MyUI extends JPanel { private final Tree myStructureTree; private boolean myRendererInitialized; + private final TreeModelBuilder myBuilder; + private TreeState myState; public MyUI() { setLayout(new BorderLayout()); @@ -129,35 +140,40 @@ public class StructureFilteringStrategy implements ChangeListFilteringStrategy { } }); add(new JScrollPane(myStructureTree), BorderLayout.CENTER); + myBuilder = new TreeModelBuilder(myProject, false); } - public void buildModel(final List changeLists) { - final Set filePaths = new HashSet(); - for(CommittedChangeList changeList: changeLists) { - for(Change change: changeList.getChanges()) { - filePaths.add(ChangesUtil.getFilePath(change)); - } - } - final TreeModelBuilder builder = new TreeModelBuilder(myProject, false); - final DefaultTreeModel model = builder.buildModelFromFilePaths(filePaths); - deleteLeafNodes((DefaultMutableTreeNode) model.getRoot()); - myStructureTree.setModel(model); + public void initRenderer() { if (!myRendererInitialized) { myRendererInitialized = true; myStructureTree.setCellRenderer(new ChangesBrowserNodeRenderer(myProject, false, false)); } } - private void deleteLeafNodes(final DefaultMutableTreeNode node) { - for(int i=node.getChildCount()-1; i >= 0; i--) { - final TreeNode child = node.getChildAt(i); - if (child.isLeaf()) { - node.remove(i); - } - else { - deleteLeafNodes((DefaultMutableTreeNode) child); + public void reset() { + myState = TreeState.createOn(myStructureTree, (DefaultMutableTreeNode) myStructureTree.getModel().getRoot()); + myStructureTree.setModel(myBuilder.clearAndGetModel()); + } + + public void append(final List changeLists) { + final TreeState localState = (myState != null) && myBuilder.isEmpty() ? myState : TreeState.createOn(myStructureTree, (DefaultMutableTreeNode) myStructureTree.getModel().getRoot()); + + final Set filePaths = new HashSet(); + for(CommittedChangeList changeList: changeLists) { + for(Change change: changeList.getChanges()) { + final FilePath path = ChangesUtil.getFilePath(change); + if (path.getParentPath() != null) { + filePaths.add(path.getParentPath()); + } } } + + final DefaultTreeModel model = myBuilder.buildModelFromFilePaths(filePaths); + myStructureTree.setModel(model); + localState.applyTo(myStructureTree, (DefaultMutableTreeNode) myStructureTree.getModel().getRoot()); + myStructureTree.revalidate(); + myStructureTree.repaint(); + initRenderer(); } } } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/ChangesGroupingPolicy.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/ChangesGroupingPolicy.java index fc4477ee65..4002220fb2 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/ChangesGroupingPolicy.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/ChangesGroupingPolicy.java @@ -20,4 +20,6 @@ import org.jetbrains.annotations.Nullable; public interface ChangesGroupingPolicy { @Nullable ChangesBrowserNode getParentNodeFor(final ChangesBrowserNode node, final ChangesBrowserNode rootNode); + + void clear(); } \ No newline at end of file diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/TreeModelBuilder.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/TreeModelBuilder.java index ef1b6e1098..7908373b29 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/TreeModelBuilder.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/TreeModelBuilder.java @@ -44,21 +44,24 @@ public class TreeModelBuilder { private final Project myProject; private final boolean showFlatten; - private final DefaultTreeModel model; + private DefaultTreeModel model; private final ChangesBrowserNode root; + private boolean myPolicyInitialized; + private ChangesGroupingPolicy myPolicy; + private final HashMap myFoldersCache; public TreeModelBuilder(final Project project, final boolean showFlatten) { myProject = project; this.showFlatten = showFlatten; root = ChangesBrowserNode.create(myProject, ROOT_NODE_VALUE); model = new DefaultTreeModel(root); + myFoldersCache = new HashMap(); } public DefaultTreeModel buildModel(final List changes, final ChangeNodeDecorator changeNodeDecorator) { - final HashMap foldersCache = new HashMap(); final ChangesGroupingPolicy policy = createGroupingPolicy(); for (final Change change : changes) { - insertChangeNode(change, foldersCache, policy, root, new Computable() { + insertChangeNode(change, policy, root, new Computable() { public ChangesBrowserNode compute() { return new ChangesBrowserChangeNode(myProject, change, changeNodeDecorator); } @@ -73,11 +76,14 @@ public class TreeModelBuilder { @Nullable private ChangesGroupingPolicy createGroupingPolicy() { - final ChangesGroupingPolicyFactory factory = ChangesGroupingPolicyFactory.getInstance(myProject); - if (factory != null) { - return factory.createGroupingPolicy(model); + if (! myPolicyInitialized) { + myPolicyInitialized = true; + final ChangesGroupingPolicyFactory factory = ChangesGroupingPolicyFactory.getInstance(myProject); + if (factory != null) { + myPolicy = factory.createGroupingPolicy(model); + } } - return null; + return myPolicy; } public DefaultTreeModel buildModelFromFiles(final List files) { @@ -162,13 +168,12 @@ public class TreeModelBuilder { final ChangeListRemoteState listRemoteState = new ChangeListRemoteState(changes.size()); ChangesBrowserNode listNode = new ChangesBrowserChangeListNode(myProject, list, listRemoteState); model.insertNodeInto(listNode, root, 0); - final HashMap foldersCache = new HashMap(); final ChangesGroupingPolicy policy = createGroupingPolicy(); int i = 0; for (final Change change : changes) { final MyChangeNodeUnderChangeListDecorator decorator = new MyChangeNodeUnderChangeListDecorator(revisionsCache, new ChangeListRemoteState.Reporter(i, listRemoteState)); - insertChangeNode(change, foldersCache, policy, listNode, new Computable() { + insertChangeNode(change, policy, listNode, new Computable() { public ChangesBrowserNode compute() { return new ChangesBrowserChangeNode(myProject, change, decorator); } @@ -181,11 +186,10 @@ public class TreeModelBuilder { private void buildVirtualFiles(final Iterator iterator, @Nullable final Object tag) { final ChangesBrowserNode baseNode = createNode(tag); - final HashMap foldersCache = new HashMap(); final ChangesGroupingPolicy policy = createGroupingPolicy(); for (; ; iterator.hasNext()) { final FilePath path = iterator.next(); - insertChangeNode(path.getVirtualFile(), foldersCache, policy, baseNode, defaultNodeCreator(path.getVirtualFile())); + insertChangeNode(path.getVirtualFile(), policy, baseNode, defaultNodeCreator(path.getVirtualFile())); } } @@ -207,37 +211,35 @@ public class TreeModelBuilder { } private void insertFilesIntoNode(List files, ChangesBrowserNode baseNode) { - final HashMap foldersCache = new HashMap(); final ChangesGroupingPolicy policy = createGroupingPolicy(); for (VirtualFile file : files) { - insertChangeNode(file, foldersCache, policy, baseNode, defaultNodeCreator(file)); + insertChangeNode(file, policy, baseNode, defaultNodeCreator(file)); } } private void buildLocallyDeletedPaths(final Collection locallyDeletedChanges, final ChangesBrowserNode baseNode) { - final HashMap foldersCache = new HashMap(); final ChangesGroupingPolicy policy = createGroupingPolicy(); for (LocallyDeletedChange change : locallyDeletedChanges) { - ChangesBrowserNode oldNode = foldersCache.get(change.getPresentableUrl()); + ChangesBrowserNode oldNode = myFoldersCache.get(change.getPresentableUrl()); if (oldNode == null) { final ChangesBrowserNode node = ChangesBrowserNode.create(myProject, change); - final ChangesBrowserNode parent = getParentNodeFor(node, foldersCache, policy, baseNode); + final ChangesBrowserNode parent = getParentNodeFor(node, policy, baseNode); model.insertNodeInto(node, parent, parent.getChildCount()); - foldersCache.put(change.getPresentableUrl(), node); + myFoldersCache.put(change.getPresentableUrl(), node); } } } - private void buildFilePaths(final Collection filePaths, final ChangesBrowserNode baseNode) { - final HashMap foldersCache = new HashMap(); + public void buildFilePaths(final Collection filePaths, final ChangesBrowserNode baseNode) { final ChangesGroupingPolicy policy = createGroupingPolicy(); for (FilePath file : filePaths) { assert file != null; - ChangesBrowserNode oldNode = foldersCache.get(file); + ChangesBrowserNode oldNode = myFoldersCache.get(file.getIOFile().getAbsolutePath()); if (oldNode == null) { final ChangesBrowserNode node = ChangesBrowserNode.create(myProject, file); - model.insertNodeInto(node, getParentNodeFor(node, foldersCache, policy, baseNode), 0); - foldersCache.put(file.getIOFile().getAbsolutePath(), node); + final ChangesBrowserNode parentNode = getParentNodeFor(node, policy, baseNode); + model.insertNodeInto(node, parentNode, 0); + myFoldersCache.put(file.getIOFile().getAbsolutePath(), node); } } } @@ -248,12 +250,11 @@ public class TreeModelBuilder { model.insertNodeInto(rootsHeadNode, root, root.getChildCount()); for (VirtualFile vf : switchedRoots.keySet()) { - final HashMap foldersCache = new HashMap(); final ChangesGroupingPolicy policy = createGroupingPolicy(); final ContentRevision cr = new CurrentContentRevision(new FilePathImpl(vf)); final Change change = new Change(cr, cr, FileStatus.NOT_CHANGED); final String branchName = switchedRoots.get(vf); - insertChangeNode(vf, foldersCache, policy, rootsHeadNode, new Computable() { + insertChangeNode(vf, policy, rootsHeadNode, new Computable() { public ChangesBrowserNode compute() { return new ChangesBrowserChangeNode(myProject, change, new ChangeNodeDecorator() { public void decorate(Change change, SimpleColoredComponent component, boolean isShowFlatten) { @@ -279,10 +280,9 @@ public class TreeModelBuilder { ChangesBrowserNode branchNode = ChangesBrowserNode.create(myProject, branchName); model.insertNodeInto(branchNode, baseNode, baseNode.getChildCount()); - final HashMap foldersCache = new HashMap(); final ChangesGroupingPolicy policy = createGroupingPolicy(); for (VirtualFile file : switchedFileList) { - insertChangeNode(file, foldersCache, policy, branchNode, defaultNodeCreator(file)); + insertChangeNode(file, policy, branchNode, defaultNodeCreator(file)); } } } @@ -291,13 +291,12 @@ public class TreeModelBuilder { private void buildLogicallyLockedFiles(final Map logicallyLockedFiles) { final ChangesBrowserNode baseNode = createNode(ChangesBrowserNode.LOGICALLY_LOCKED_TAG); - final HashMap foldersCache = new HashMap(); final ChangesGroupingPolicy policy = createGroupingPolicy(); for (Map.Entry entry : logicallyLockedFiles.entrySet()) { final VirtualFile file = entry.getKey(); final LogicalLock lock = entry.getValue(); final ChangesBrowserLogicallyLockedFile obj = new ChangesBrowserLogicallyLockedFile(myProject, file, lock); - insertChangeNode(obj, foldersCache, policy, baseNode, + insertChangeNode(obj, policy, baseNode, defaultNodeCreator(obj)); } } @@ -310,11 +309,10 @@ public class TreeModelBuilder { }; } - private void insertChangeNode(final Object change, final HashMap foldersCache, - final ChangesGroupingPolicy policy, + private void insertChangeNode(final Object change, final ChangesGroupingPolicy policy, final ChangesBrowserNode listNode, final Computable nodeCreator) { final FilePath nodePath = getPathForObject(change); - ChangesBrowserNode oldNode = (nodePath == null) ? null : foldersCache.get(nodePath.getIOFile().getAbsolutePath()); + ChangesBrowserNode oldNode = (nodePath == null) ? null : myFoldersCache.get(nodePath.getIOFile().getAbsolutePath()); ChangesBrowserNode node = nodeCreator.compute(); if (oldNode != null) { for(int i=oldNode.getChildCount()-1; i >= 0; i--) { @@ -326,14 +324,14 @@ public class TreeModelBuilder { int index = model.getIndexOfChild(parent, oldNode); model.removeNodeFromParent(oldNode); model.insertNodeInto(node, parent, index); - foldersCache.put(nodePath.getIOFile().getAbsolutePath(), node); + myFoldersCache.put(nodePath.getIOFile().getAbsolutePath(), node); } else { - ChangesBrowserNode parentNode = getParentNodeFor(node, foldersCache, policy, listNode); + ChangesBrowserNode parentNode = getParentNodeFor(node, policy, listNode); model.insertNodeInto(node, parentNode, model.getChildCount(parentNode)); // ? if (nodePath != null) { - foldersCache.put(nodePath.getIOFile().getAbsolutePath(), node); + myFoldersCache.put(nodePath.getIOFile().getAbsolutePath(), node); } } } @@ -393,10 +391,7 @@ public class TreeModelBuilder { return null; } - private ChangesBrowserNode getParentNodeFor(ChangesBrowserNode node, - Map folderNodesCache, - @Nullable ChangesGroupingPolicy policy, - ChangesBrowserNode rootNode) { + private ChangesBrowserNode getParentNodeFor(ChangesBrowserNode node, @Nullable ChangesGroupingPolicy policy, ChangesBrowserNode rootNode) { if (showFlatten) { return rootNode; } @@ -415,14 +410,27 @@ public class TreeModelBuilder { return rootNode; } - ChangesBrowserNode parentNode = folderNodesCache.get(parentPath.getIOFile().getAbsolutePath()); + final String parentKey = parentPath.getIOFile().getAbsolutePath(); + ChangesBrowserNode parentNode = myFoldersCache.get(parentKey); if (parentNode == null) { parentNode = ChangesBrowserNode.create(myProject, parentPath); - ChangesBrowserNode grandPa = getParentNodeFor(parentNode, folderNodesCache, policy, rootNode); + ChangesBrowserNode grandPa = getParentNodeFor(parentNode, policy, rootNode); model.insertNodeInto(parentNode, grandPa, grandPa.getChildCount()); - folderNodesCache.put(parentPath.getIOFile().getAbsolutePath(), parentNode); + myFoldersCache.put(parentKey, parentNode); } return parentNode; } + + public DefaultTreeModel clearAndGetModel() { + root.removeAllChildren(); + model = new DefaultTreeModel(root); + myFoldersCache.clear(); + myPolicyInitialized = false; + return model; + } + + public boolean isEmpty() { + return model.getChildCount(root) == 0; + } } diff --git a/plugins/cvs/cvs-plugin/src/com/intellij/cvsSupport2/changeBrowser/CvsCommittedChangesProvider.java b/plugins/cvs/cvs-plugin/src/com/intellij/cvsSupport2/changeBrowser/CvsCommittedChangesProvider.java index 3fe68b23dc..0faf93794a 100644 --- a/plugins/cvs/cvs-plugin/src/com/intellij/cvsSupport2/changeBrowser/CvsCommittedChangesProvider.java +++ b/plugins/cvs/cvs-plugin/src/com/intellij/cvsSupport2/changeBrowser/CvsCommittedChangesProvider.java @@ -156,10 +156,13 @@ public class CvsCommittedChangesProvider implements CachingCommittedChangesProvi dateFrom = calendar.getTime(); } final ChangeBrowserSettings.Filter filter = settings.createFilter(); + final Set controlSet = new HashSet(); final CvsResult executionResult = runRLogOperation(connectionSettings, module, dateFrom, dateTo, new Consumer() { public void consume(LogInformationWrapper wrapper) { final RevisionWrapper revisionWrapper = builder.revisionWrapperFromLog(wrapper); final CvsChangeList changeList = builder.addRevision(revisionWrapper); + if (controlSet.contains(changeList.getCommitDate())) return; + controlSet.add(changeList.getCommitDate()); if (filter.accepts(changeList)) { consumer.consume(changeList); } diff --git a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/RootsAndBranches.java b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/RootsAndBranches.java index 53f319470f..d4c11c2e51 100644 --- a/plugins/svn4idea/src/org/jetbrains/idea/svn/history/RootsAndBranches.java +++ b/plugins/svn4idea/src/org/jetbrains/idea/svn/history/RootsAndBranches.java @@ -852,6 +852,12 @@ public class RootsAndBranches implements CommittedChangeListDecorator { myListener = null; } + public void resetFilterBase() { + } + + public void appendFilterBase(List changeLists) { + } + @NotNull public List filterChangeLists(final List changeLists) { if ((! myFilterAlien.isSelected(null)) && (! myFilterNotMerged.isSelected(null)) && (! myFilterMerged.isSelected(null))) {