mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[log] Introduce filtering
I. Summary We are able to filter graph by branches: not-wanted branches will be just hidden with all their commits (unless they belong to a branch that is to be shown). We are not able to filter graph by other details yet: filtering, say, by user can make "holes" in the graph, and we are not ready to support this now. Therefore, if user filters just by branch, we continue to show the graph to him, but if he switches on a filter which is not supported by graph (user, full-text), we turn the graph off by substituting the TableModel with NoGraphTableModel which just shows commit subject in the first column. II. UI Copy the same UI as it were in the old Git Log: a text field for full-text search, popup menus for filtering by branch and by user. Structure filter is not implemented yet. The code of these classic-style filters seems to be tighly connected with the old Log infrastructure => they are reimplemented from scratch. Add a few niceties to popup controls: let them be focusable, draw the focus border around them, make them keyboard friendly. III. Details VcsLogFilterer: * VcsLogFilterer applies filters to the log. At first applies to the graph (to be able to filter by branch), then it applies other filters to details. * The whole graph which was built can be filtered by branch. However details only of the recent commits are filtered by text/user, because details of older commits are not available. Get these details via VcsLogDataHolder#getTopCommitDetails(). * TODO: If there will be no or little results, we should load more commit details from the VCS. This will be implemented later. VcsLogUI: * Don't store the table model in VcsLogUI: it can be applied to the table at once. * To keep filtering after refresh, apply filters instead of just creating new model.
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
package com.intellij.vcs.log.data;
|
||||
|
||||
import com.intellij.util.Function;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.vcs.log.Hash;
|
||||
import com.intellij.vcs.log.VcsRef;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* TODO use VcsRef instead of String
|
||||
*/
|
||||
public class VcsLogBranchFilter implements VcsLogGraphFilter {
|
||||
|
||||
@NotNull private final Collection<Hash> myMatchingHeads;
|
||||
private final String myBranchName;
|
||||
|
||||
public VcsLogBranchFilter(Collection<VcsRef> allRefs, final String branchName) {
|
||||
myBranchName = branchName;
|
||||
myMatchingHeads = ContainerUtil.mapNotNull(allRefs, new Function<VcsRef, Hash>() {
|
||||
@Override
|
||||
public Hash fun(VcsRef ref) {
|
||||
if (ref.getName().equals(branchName)) {
|
||||
return ref.getCommitHash();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(@NotNull Hash hash) {
|
||||
return myMatchingHeads.contains(hash);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "on: " + myBranchName;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,6 +16,8 @@
|
||||
package com.intellij.vcs.log.data;
|
||||
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.diagnostic.Attachment;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.progress.BackgroundTaskQueue;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.progress.Task;
|
||||
@@ -85,6 +87,8 @@ public class VcsLogDataHolder implements Disposable {
|
||||
|
||||
public static final Topic<Runnable> REFRESH_COMPLETED = Topic.create("Vcs.Log.Completed", Runnable.class);
|
||||
|
||||
private static final Logger LOG = Logger.getInstance(VcsLogDataHolder.class);
|
||||
|
||||
@NotNull private final Project myProject;
|
||||
@NotNull private final VcsLogObjectsFactory myFactory;
|
||||
@NotNull private final Map<VirtualFile, VcsLogProvider> myLogProviders;
|
||||
@@ -386,6 +390,39 @@ public class VcsLogDataHolder implements Disposable {
|
||||
return myLogProviders.get(root);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ordered part of the recent commits, details of which are always stored in the cache.
|
||||
*/
|
||||
@NotNull
|
||||
public Collection<TimedVcsCommit> getTopCommits() {
|
||||
return myLogData.getTopCommits();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns details of recent commits ordered in the "right way" (as in the log).
|
||||
*/
|
||||
@NotNull
|
||||
public Collection<VcsFullCommitDetails> getTopCommitDetails() {
|
||||
final Collection<TimedVcsCommit> topCommits = getTopCommits();
|
||||
return ContainerUtil.mapNotNull(topCommits, new Function<TimedVcsCommit, VcsFullCommitDetails>() {
|
||||
@Nullable
|
||||
@Override
|
||||
public VcsFullCommitDetails fun(TimedVcsCommit commit) {
|
||||
Hash hash = commit.getHash();
|
||||
VcsFullCommitDetails details = myTopCommitsDetailsCache.get(hash);
|
||||
if (details != null) {
|
||||
return details;
|
||||
}
|
||||
|
||||
// shouldn't happen
|
||||
LOG.error("No details were stored for commit " + hash,
|
||||
new Attachment("details_cache.txt", myTopCommitsDetailsCache.toString()),
|
||||
new Attachment("top_commits.txt", topCommits.toString()));
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains full logs per repository root & references per root.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.intellij.vcs.log.data;
|
||||
|
||||
import com.intellij.vcs.log.VcsFullCommitDetails;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Filter which needs {@link VcsFullCommitDetails} to work.
|
||||
*
|
||||
* @see VcsLogGraphFilter
|
||||
*/
|
||||
public interface VcsLogDetailsFilter extends VcsLogFilter {
|
||||
|
||||
boolean matches(@NotNull VcsFullCommitDetails details);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.intellij.vcs.log.data;
|
||||
|
||||
/**
|
||||
* Marker interface indicating that this is a filter for the VCS log.
|
||||
*/
|
||||
public interface VcsLogFilter {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package com.intellij.vcs.log.data;
|
||||
|
||||
import com.intellij.openapi.util.Condition;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.util.Function;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.vcs.log.VcsFullCommitDetails;
|
||||
import com.intellij.vcs.log.graph.elements.Node;
|
||||
import com.intellij.vcs.log.graphmodel.GraphModel;
|
||||
import com.intellij.vcs.log.ui.VcsLogUI;
|
||||
import com.intellij.vcs.log.ui.tables.GraphTableModel;
|
||||
import com.intellij.vcs.log.ui.tables.NoGraphTableModel;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public class VcsLogFilterer {
|
||||
|
||||
private static final Function<Node,Boolean> ALL_NODES_VISIBLE = new Function<Node, Boolean>() {
|
||||
@Override
|
||||
public Boolean fun(Node node) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
@NotNull private final VcsLogDataHolder myLogDataHolder;
|
||||
@NotNull private final VcsLogUI myUI;
|
||||
|
||||
public VcsLogFilterer(@NotNull VcsLogDataHolder logDataHolder, @NotNull VcsLogUI ui) {
|
||||
myLogDataHolder = logDataHolder;
|
||||
myUI = ui;
|
||||
}
|
||||
|
||||
public void applyFiltersAndUpdateUi(@NotNull Collection<VcsLogFilter> filters) {
|
||||
GraphModel graphModel = myLogDataHolder.getDataPack().getGraphModel();
|
||||
List<VcsLogGraphFilter> graphFilters = ContainerUtil.findAll(filters, VcsLogGraphFilter.class);
|
||||
List<VcsLogDetailsFilter> detailsFilters = ContainerUtil.findAll(filters, VcsLogDetailsFilter.class);
|
||||
|
||||
// it is important to apply graph filters first:
|
||||
// if we apply other filters first, we loose the graph and won't be able to apple graph filters in that case
|
||||
// (e.g. won't be able to find out if a commit belongs to the branch selected by user).
|
||||
|
||||
// hide invisible nodes from the graph
|
||||
if (!graphFilters.isEmpty()) {
|
||||
applyGraphFilters(graphModel, graphFilters);
|
||||
}
|
||||
else {
|
||||
graphModel.setVisibleBranchesNodes(ALL_NODES_VISIBLE);
|
||||
}
|
||||
|
||||
// apply details filters, and use simple table without graph (we can't filter by details and keep the graph yet).
|
||||
if (!detailsFilters.isEmpty()) {
|
||||
List<Pair<VcsFullCommitDetails, VirtualFile>> filteredCommits = filterByDetails(graphModel, detailsFilters);
|
||||
myUI.setModel(new NoGraphTableModel(filteredCommits, myLogDataHolder.getDataPack().getRefsModel()));
|
||||
}
|
||||
else {
|
||||
myUI.setModel(new GraphTableModel(myLogDataHolder));
|
||||
}
|
||||
|
||||
myUI.updateUI();
|
||||
}
|
||||
|
||||
private static void applyGraphFilters(GraphModel graphModel, final List<VcsLogGraphFilter> onGraphFilters) {
|
||||
graphModel.setVisibleBranchesNodes(new Function<Node, Boolean>() {
|
||||
@Override
|
||||
public Boolean fun(final Node node) {
|
||||
return !ContainerUtil.exists(onGraphFilters, new Condition<VcsLogGraphFilter>() {
|
||||
@Override
|
||||
public boolean value(VcsLogGraphFilter filter) {
|
||||
return !filter.matches(node.getCommitHash());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private List<Pair<VcsFullCommitDetails, VirtualFile>> filterByDetails(final GraphModel graphModel,
|
||||
final List<VcsLogDetailsFilter> detailsFilters) {
|
||||
return ContainerUtil.mapNotNull(myLogDataHolder.getTopCommitDetails(),
|
||||
new Function<VcsFullCommitDetails, Pair<VcsFullCommitDetails,VirtualFile>>() {
|
||||
@Override
|
||||
public Pair<VcsFullCommitDetails, VirtualFile> fun(final VcsFullCommitDetails details) {
|
||||
boolean allFilterMatch = !ContainerUtil.exists(detailsFilters, new Condition<VcsLogDetailsFilter>() {
|
||||
@Override
|
||||
public boolean value(VcsLogDetailsFilter filter) {
|
||||
return !filter.matches(details);
|
||||
}
|
||||
});
|
||||
Node node = graphModel.getNodeIfVisible(details.getHash());
|
||||
if (node == null || !allFilterMatch) {
|
||||
return null;
|
||||
}
|
||||
return Pair.create(details, node.getBranch().getRepositoryRoot());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.intellij.vcs.log.data;
|
||||
|
||||
import com.intellij.vcs.log.Hash;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Filter that is able to work on graph.
|
||||
*/
|
||||
public interface VcsLogGraphFilter extends VcsLogFilter {
|
||||
|
||||
boolean matches(@NotNull Hash hash);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.intellij.vcs.log.data;
|
||||
|
||||
import com.intellij.vcs.log.VcsFullCommitDetails;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* TODO use some special User class instead of String
|
||||
*/
|
||||
public class VcsLogUserFilter implements VcsLogDetailsFilter {
|
||||
|
||||
@NotNull private final String myUser;
|
||||
|
||||
public VcsLogUserFilter(@NotNull String user) {
|
||||
myUser = user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(@NotNull VcsFullCommitDetails detail) {
|
||||
return detail.getAuthorName().toLowerCase().contains(myUser) || detail.getAuthorEmail().toLowerCase().contains(myUser);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,17 +8,19 @@ import com.intellij.vcs.log.Hash;
|
||||
import com.intellij.vcs.log.compressedlist.UpdateRequest;
|
||||
import com.intellij.vcs.log.data.DataPack;
|
||||
import com.intellij.vcs.log.data.VcsLogDataHolder;
|
||||
import com.intellij.vcs.log.data.VcsLogFilter;
|
||||
import com.intellij.vcs.log.data.VcsLogFilterer;
|
||||
import com.intellij.vcs.log.graph.elements.GraphElement;
|
||||
import com.intellij.vcs.log.graph.elements.Node;
|
||||
import com.intellij.vcs.log.graphmodel.FragmentManager;
|
||||
import com.intellij.vcs.log.graphmodel.GraphFragment;
|
||||
import com.intellij.vcs.log.printmodel.SelectController;
|
||||
import com.intellij.vcs.log.ui.frame.MainFrame;
|
||||
import com.intellij.vcs.log.ui.tables.GraphTableModel;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.table.TableModel;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* @author erokhins
|
||||
@@ -30,23 +32,22 @@ public class VcsLogUI {
|
||||
@NotNull private final VcsLogDataHolder myLogDataHolder;
|
||||
@NotNull private final MainFrame myMainFrame;
|
||||
@NotNull private final VcsLogColorManager myColorManager;
|
||||
@NotNull private final VcsLogFilterer myFilterer;
|
||||
|
||||
@Nullable private GraphElement prevGraphElement;
|
||||
@NotNull private TableModel myGraphModel;
|
||||
|
||||
public VcsLogUI(@NotNull VcsLogDataHolder logDataHolder, @NotNull Project project, @NotNull VcsLogColorManager manager) {
|
||||
myLogDataHolder = logDataHolder;
|
||||
myColorManager = manager;
|
||||
myFilterer = new VcsLogFilterer(logDataHolder, this);
|
||||
myMainFrame = new MainFrame(myLogDataHolder, this, project);
|
||||
project.getMessageBus().connect(project).subscribe(VcsLogDataHolder.REFRESH_COMPLETED, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
reloadModel();
|
||||
updateUI();
|
||||
applyFilters();
|
||||
}
|
||||
});
|
||||
reloadModel();
|
||||
updateUI();
|
||||
applyFilters();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -64,15 +65,14 @@ public class VcsLogUI {
|
||||
});
|
||||
}
|
||||
|
||||
public void reloadModel() {
|
||||
myGraphModel = new GraphTableModel(myLogDataHolder);
|
||||
public void setModel(@NotNull TableModel model) {
|
||||
myMainFrame.getGraphTable().setModel(model);
|
||||
}
|
||||
|
||||
public void updateUI() {
|
||||
UIUtil.invokeLaterIfNeeded(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
myMainFrame.getGraphTable().setModel(myGraphModel);
|
||||
myMainFrame.getGraphTable().setPreferredColumnWidths();
|
||||
myMainFrame.getGraphTable().repaint();
|
||||
myMainFrame.refresh();
|
||||
@@ -180,4 +180,24 @@ public class VcsLogUI {
|
||||
public VcsLogColorManager getColorManager() {
|
||||
return myColorManager;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public VcsLogFilterer getFilterer() {
|
||||
return myFilterer;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public VcsLogDataHolder getLogDataHolder() {
|
||||
return myLogDataHolder;
|
||||
}
|
||||
|
||||
private void applyFilters() {
|
||||
myFilterer.applyFiltersAndUpdateUi(collectFilters());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Collection<VcsLogFilter> collectFilters() {
|
||||
return myMainFrame.getFilterUi().getFilters();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2000-2013 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.vcs.log.ui.filter;
|
||||
|
||||
import com.intellij.openapi.actionSystem.ActionGroup;
|
||||
import com.intellij.openapi.actionSystem.DefaultActionGroup;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.vcs.log.RefGroup;
|
||||
import com.intellij.vcs.log.VcsLogProvider;
|
||||
import com.intellij.vcs.log.VcsLogRefManager;
|
||||
import com.intellij.vcs.log.VcsRef;
|
||||
import com.intellij.vcs.log.data.VcsLogBranchFilter;
|
||||
import com.intellij.vcs.log.data.VcsLogFilter;
|
||||
import com.intellij.vcs.log.impl.VcsLogUtil;
|
||||
import com.intellij.vcs.log.ui.VcsLogUI;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
class BranchFilterPopupComponent extends FilterPopupComponent {
|
||||
|
||||
@NotNull private final VcsLogUI myUi;
|
||||
|
||||
BranchFilterPopupComponent(@NotNull VcsLogClassicFilterUi filterUi, @NotNull VcsLogUI ui) {
|
||||
super(filterUi, "Branch");
|
||||
myUi = ui;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ActionGroup createActionGroup() {
|
||||
DefaultActionGroup actionGroup = new DefaultActionGroup();
|
||||
|
||||
actionGroup.add(createAllAction());
|
||||
|
||||
Collection<VcsRef> allRefs = myUi.getLogDataHolder().getDataPack().getRefsModel().getBranches();
|
||||
for (Map.Entry<VirtualFile, Collection<VcsRef>> entry : VcsLogUtil.groupRefsByRoot(allRefs).entrySet()) {
|
||||
VirtualFile root = entry.getKey();
|
||||
Collection<VcsRef> refs = entry.getValue();
|
||||
VcsLogProvider provider = myUi.getLogDataHolder().getLogProvider(root);
|
||||
VcsLogRefManager refManager = provider.getReferenceManager();
|
||||
List<RefGroup> groups = refManager.group(refs);
|
||||
|
||||
for (RefGroup group : groups) {
|
||||
if (group.getRefs().size() == 1) {
|
||||
actionGroup.add(new SetValueAction(group.getRefs().iterator().next().getName(), this));
|
||||
}
|
||||
else {
|
||||
DefaultActionGroup innerGroup = new DefaultActionGroup();
|
||||
for (VcsRef ref : group.getRefs()) {
|
||||
innerGroup.add(new SetValueAction(ref.getName(), this));
|
||||
}
|
||||
actionGroup.add(innerGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
return actionGroup;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected VcsLogFilter getFilter() {
|
||||
String value = getValue();
|
||||
return value == ALL
|
||||
? null
|
||||
: new VcsLogBranchFilter(myUi.getLogDataHolder().getDataPack().getRefsModel().getBranches(), value);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* Copyright 2000-2013 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.vcs.log.ui.filter;
|
||||
|
||||
import com.intellij.icons.AllIcons;
|
||||
import com.intellij.ide.DataManager;
|
||||
import com.intellij.openapi.actionSystem.ActionGroup;
|
||||
import com.intellij.openapi.actionSystem.AnAction;
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import com.intellij.openapi.project.DumbAwareAction;
|
||||
import com.intellij.openapi.ui.popup.JBPopupFactory;
|
||||
import com.intellij.openapi.ui.popup.ListPopup;
|
||||
import com.intellij.ui.ClickListener;
|
||||
import com.intellij.ui.RoundedLineBorder;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import com.intellij.vcs.log.data.VcsLogFilter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.Border;
|
||||
import java.awt.event.*;
|
||||
|
||||
/**
|
||||
* Base class for components which allow to set up filter for the VCS Log, by displaying a popup with available choices.
|
||||
*/
|
||||
abstract class FilterPopupComponent extends JPanel {
|
||||
|
||||
/**
|
||||
* Special value that indicates that no filtering is on.
|
||||
*/
|
||||
protected static final String ALL = "All";
|
||||
|
||||
private static final int GAP_BEFORE_ARROW = 3;
|
||||
private static final int BORDER_SIZE = 2;
|
||||
private static final Border INNER_MARGIN_BORDER = BorderFactory.createEmptyBorder(2, 2, 2, 2);
|
||||
private static final Border FOCUSED_BORDER = createFocusedBorder();
|
||||
private static final Border UNFOCUSED_BORDER = createUnfocusedBorder();
|
||||
|
||||
@NotNull private final JLabel myFilterNameLabel;
|
||||
@NotNull private final JLabel myFilterValueLabel;
|
||||
@NotNull private final VcsLogClassicFilterUi myFilterUi;
|
||||
|
||||
FilterPopupComponent(@NotNull VcsLogClassicFilterUi filterUi, @NotNull String filterName) {
|
||||
myFilterUi = filterUi;
|
||||
myFilterNameLabel = new JLabel(filterName + ": ");
|
||||
myFilterValueLabel = new JLabel(ALL);
|
||||
setDefaultForeground();
|
||||
setFocusable(true);
|
||||
setBorder(UNFOCUSED_BORDER);
|
||||
|
||||
setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
|
||||
add(myFilterNameLabel);
|
||||
add(myFilterValueLabel);
|
||||
add(Box.createHorizontalStrut(GAP_BEFORE_ARROW));
|
||||
add(new JLabel(AllIcons.Ide.Statusbar_arrows));
|
||||
|
||||
showPopupMenuOnClick();
|
||||
showPopupMenuFromKeyboard();
|
||||
indicateHovering();
|
||||
indicateFocusing();
|
||||
}
|
||||
|
||||
private static Border createFocusedBorder() {
|
||||
return BorderFactory.createCompoundBorder(new RoundedLineBorder(UIUtil.getHeaderActiveColor(), 10, BORDER_SIZE),
|
||||
INNER_MARGIN_BORDER);
|
||||
}
|
||||
|
||||
private static Border createUnfocusedBorder() {
|
||||
return BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(BORDER_SIZE, BORDER_SIZE, BORDER_SIZE, BORDER_SIZE),
|
||||
INNER_MARGIN_BORDER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create popup actions available under this filter.
|
||||
*/
|
||||
protected abstract ActionGroup createActionGroup();
|
||||
|
||||
/**
|
||||
* Return the filter currently selected by this component.
|
||||
*/
|
||||
@Nullable
|
||||
protected abstract VcsLogFilter getFilter();
|
||||
|
||||
protected void setValue(@NotNull String newValue) {
|
||||
myFilterValueLabel.setText(newValue);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getValue() {
|
||||
return myFilterValueLabel.getText();
|
||||
}
|
||||
|
||||
protected void applyFilters() {
|
||||
myFilterUi.applyFilters();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the special action that indicates that no filtering is selected in this component.
|
||||
*/
|
||||
protected AnAction createAllAction() {
|
||||
return new AllAction(this);
|
||||
}
|
||||
|
||||
private void indicateFocusing() {
|
||||
addFocusListener(new FocusAdapter() {
|
||||
@Override
|
||||
public void focusGained(FocusEvent e) {
|
||||
setBorder(FOCUSED_BORDER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void focusLost(FocusEvent e) {
|
||||
setBorder(UNFOCUSED_BORDER);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void showPopupMenuFromKeyboard() {
|
||||
addKeyListener(new KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
if (e.getKeyCode() == KeyEvent.VK_ENTER || e.getKeyCode() == KeyEvent.VK_DOWN) {
|
||||
showPopupMenu();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void showPopupMenuOnClick() {
|
||||
new ClickListener() {
|
||||
@Override
|
||||
public boolean onClick(MouseEvent event, int clickCount) {
|
||||
showPopupMenu();
|
||||
return true;
|
||||
}
|
||||
}.installOn(this);
|
||||
}
|
||||
|
||||
private void indicateHovering() {
|
||||
addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent e) {
|
||||
setOnHoverForeground();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(MouseEvent e) {
|
||||
setDefaultForeground();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setDefaultForeground() {
|
||||
myFilterNameLabel.setForeground(UIUtil.isUnderDarcula() ? UIUtil.getLabelForeground() : UIUtil.getInactiveTextColor());
|
||||
myFilterValueLabel.setForeground(UIUtil.isUnderDarcula() ? UIUtil.getLabelForeground() :
|
||||
UIUtil.getInactiveTextColor().darker().darker());
|
||||
}
|
||||
|
||||
private void setOnHoverForeground() {
|
||||
myFilterNameLabel.setForeground(UIUtil.isUnderDarcula() ? UIUtil.getLabelForeground() : UIUtil.getTextAreaForeground());
|
||||
myFilterValueLabel.setForeground(UIUtil.isUnderDarcula() ? UIUtil.getLabelForeground() : UIUtil.getTextFieldForeground());
|
||||
}
|
||||
|
||||
private void showPopupMenu() {
|
||||
ListPopup popup = JBPopupFactory.getInstance().createActionGroupPopup(null, createActionGroup(),
|
||||
DataManager.getInstance().getDataContext(this),
|
||||
JBPopupFactory.ActionSelectionAid.SPEEDSEARCH, false);
|
||||
popup.showUnderneathOf(this);
|
||||
}
|
||||
|
||||
private static class AllAction extends SetValueAction {
|
||||
AllAction(@NotNull FilterPopupComponent component) {
|
||||
super(ALL, component);
|
||||
}
|
||||
}
|
||||
|
||||
protected static class SetValueAction extends DumbAwareAction {
|
||||
|
||||
private final String myValue;
|
||||
private final FilterPopupComponent myFilterComponent;
|
||||
|
||||
SetValueAction(String value, FilterPopupComponent filterComponent) {
|
||||
super(value);
|
||||
myValue = value;
|
||||
myFilterComponent = filterComponent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(AnActionEvent e) {
|
||||
myFilterComponent.setValue(myValue);
|
||||
myFilterComponent.applyFilters();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright 2000-2013 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.vcs.log.ui.filter;
|
||||
|
||||
import com.intellij.openapi.actionSystem.ActionGroup;
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import com.intellij.openapi.actionSystem.DefaultActionGroup;
|
||||
import com.intellij.openapi.project.DumbAwareAction;
|
||||
import com.intellij.openapi.ui.popup.JBPopup;
|
||||
import com.intellij.openapi.ui.popup.JBPopupAdapter;
|
||||
import com.intellij.openapi.ui.popup.JBPopupFactory;
|
||||
import com.intellij.openapi.ui.popup.LightweightWindowEvent;
|
||||
import com.intellij.ui.components.JBTextField;
|
||||
import com.intellij.vcs.log.data.VcsLogFilter;
|
||||
import com.intellij.vcs.log.data.VcsLogUserFilter;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.awt.event.KeyAdapter;
|
||||
import java.awt.event.KeyEvent;
|
||||
|
||||
/**
|
||||
* Show a popup to select a user or enter the user name.
|
||||
*/
|
||||
class UserFilterPopupComponent extends FilterPopupComponent {
|
||||
|
||||
UserFilterPopupComponent(VcsLogClassicFilterUi filterUi) {
|
||||
super(filterUi, "User");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ActionGroup createActionGroup() {
|
||||
DefaultActionGroup group = new DefaultActionGroup();
|
||||
group.add(createAllAction());
|
||||
// TODO show recently selected users
|
||||
group.add(new SelectUserAction());
|
||||
return group;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected VcsLogFilter getFilter() {
|
||||
String value = getValue();
|
||||
if (value == ALL) {
|
||||
return null;
|
||||
}
|
||||
return new VcsLogUserFilter(value);
|
||||
}
|
||||
|
||||
private class SelectUserAction extends DumbAwareAction {
|
||||
|
||||
SelectUserAction() {
|
||||
super("Select...");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(AnActionEvent e) {
|
||||
final JBTextField textField = new JBTextField(10);
|
||||
|
||||
final JBPopup popup = JBPopupFactory.getInstance().createComponentPopupBuilder(textField, textField)
|
||||
.setCancelOnClickOutside(true)
|
||||
.setCancelOnWindowDeactivation(true)
|
||||
.setCancelKeyEnabled(true)
|
||||
.setRequestFocus(true)
|
||||
.createPopup();
|
||||
|
||||
textField.addKeyListener(new KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
|
||||
popup.closeOk(e);
|
||||
}
|
||||
else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
|
||||
popup.cancel(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
popup.addListener(new JBPopupAdapter() {
|
||||
@Override
|
||||
public void onClosed(LightweightWindowEvent event) {
|
||||
if (event.isOk()) {
|
||||
String user = textField.getText();
|
||||
setValue(user);
|
||||
applyFilters();
|
||||
}
|
||||
}
|
||||
});
|
||||
popup.showUnderneathOf(UserFilterPopupComponent.this);
|
||||
textField.requestFocus();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2000-2013 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.vcs.log.ui.filter;
|
||||
|
||||
import com.intellij.ui.SearchTextField;
|
||||
import com.intellij.util.Function;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import com.intellij.vcs.log.data.VcsLogFilter;
|
||||
import com.intellij.vcs.log.data.VcsLogFilterer;
|
||||
import com.intellij.vcs.log.ui.VcsLogUI;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*/
|
||||
public class VcsLogClassicFilterUi implements VcsLogFilterUi {
|
||||
|
||||
@NotNull private final VcsLogFilterer myFilterer;
|
||||
@NotNull private final JComponent myRootPanel;
|
||||
@NotNull private final List<FilterPopupComponent> myFilterPopupComponents;
|
||||
@NotNull private final SearchTextField myTextFilter;
|
||||
|
||||
public VcsLogClassicFilterUi(@NotNull VcsLogUI ui) {
|
||||
myFilterer = ui.getFilterer();
|
||||
|
||||
JLabel filterCaption = new JLabel("Filter:");
|
||||
filterCaption.setForeground(UIUtil.isUnderDarcula() ? UIUtil.getLabelForeground() : UIUtil.getInactiveTextColor());
|
||||
|
||||
myTextFilter = new SearchTextField();
|
||||
myTextFilter.getTextEditor().addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
applyFilters();
|
||||
}
|
||||
});
|
||||
FilterPopupComponent branchFilter = new BranchFilterPopupComponent(this, ui);
|
||||
FilterPopupComponent userFilter = new UserFilterPopupComponent(this);
|
||||
|
||||
myFilterPopupComponents = ContainerUtil.newArrayList();
|
||||
myFilterPopupComponents.add(branchFilter);
|
||||
myFilterPopupComponents.add(userFilter);
|
||||
|
||||
myRootPanel = new JPanel();
|
||||
myRootPanel.add(filterCaption);
|
||||
myRootPanel.add(myTextFilter);
|
||||
myRootPanel.add(branchFilter);
|
||||
myRootPanel.add(userFilter);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public JComponent getRootComponent() {
|
||||
return myRootPanel;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Collection<VcsLogFilter> getFilters() {
|
||||
List<VcsLogFilter> filters = getPopupFilters();
|
||||
if (!myTextFilter.getText().isEmpty()) {
|
||||
filters.add(new VcsLogTextFilter(myTextFilter.getText()));
|
||||
}
|
||||
return filters;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<VcsLogFilter> getPopupFilters() {
|
||||
return new ArrayList<VcsLogFilter>(ContainerUtil.mapNotNull(myFilterPopupComponents,
|
||||
new Function<FilterPopupComponent, VcsLogFilter>() {
|
||||
@Override
|
||||
public VcsLogFilter fun(FilterPopupComponent filterComponent) {
|
||||
return filterComponent.getFilter();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
void applyFilters() {
|
||||
myFilterer.applyFiltersAndUpdateUi(getFilters());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.intellij.vcs.log.ui.filter;
|
||||
|
||||
import com.intellij.vcs.log.data.VcsLogFilter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Graphical UI for filtering commits in the log.
|
||||
*
|
||||
* @author Kirill Likhodedov
|
||||
*/
|
||||
public interface VcsLogFilterUi {
|
||||
|
||||
/**
|
||||
* Returns the component which will be added to the Log toolbar.
|
||||
*/
|
||||
@NotNull
|
||||
JComponent getRootComponent();
|
||||
|
||||
/**
|
||||
* Returns the filters currently active, i.e. switched on by user.
|
||||
*/
|
||||
@NotNull
|
||||
Collection<VcsLogFilter> getFilters();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2000-2013 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.vcs.log.ui.filter;
|
||||
|
||||
import com.intellij.vcs.log.VcsFullCommitDetails;
|
||||
import com.intellij.vcs.log.data.VcsLogDetailsFilter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
class VcsLogTextFilter implements VcsLogDetailsFilter {
|
||||
|
||||
@NotNull private final String myText;
|
||||
|
||||
public VcsLogTextFilter(@NotNull String text) {
|
||||
myText = text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(@NotNull VcsFullCommitDetails details) {
|
||||
return details.getFullMessage().toLowerCase().contains(myText);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,10 +6,15 @@ import com.intellij.openapi.actionSystem.*;
|
||||
import com.intellij.openapi.components.ServiceManager;
|
||||
import com.intellij.openapi.project.DumbAwareAction;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.ui.JBColor;
|
||||
import com.intellij.ui.SeparatorComponent;
|
||||
import com.intellij.ui.SeparatorOrientation;
|
||||
import com.intellij.vcs.log.VcsLogSettings;
|
||||
import com.intellij.vcs.log.data.VcsLogDataHolder;
|
||||
import com.intellij.vcs.log.ui.GitLogIcons;
|
||||
import com.intellij.vcs.log.ui.filter.VcsLogClassicFilterUi;
|
||||
import com.intellij.vcs.log.ui.VcsLogUI;
|
||||
import com.intellij.vcs.log.ui.filter.VcsLogFilterUi;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
@@ -26,6 +31,7 @@ public class MainFrame {
|
||||
@NotNull private final JPanel myMainPanel;
|
||||
@NotNull private final ActiveSurface myActiveSurface;
|
||||
@NotNull private final VcsLogSettings mySettings;
|
||||
@NotNull private final VcsLogFilterUi myFilterUi;
|
||||
|
||||
public MainFrame(@NotNull VcsLogDataHolder logDataHolder, @NotNull VcsLogUI vcsLogUI, @NotNull Project project) {
|
||||
myLogDataHolder = logDataHolder;
|
||||
@@ -36,9 +42,15 @@ public class MainFrame {
|
||||
myActiveSurface = new ActiveSurface(logDataHolder, vcsLogUI, project);
|
||||
myActiveSurface.setupDetailsSplitter(mySettings.isShowDetails());
|
||||
|
||||
JComponent toolbar = Box.createHorizontalBox();
|
||||
myFilterUi = new VcsLogClassicFilterUi(myUI);
|
||||
toolbar.add(myFilterUi.getRootComponent());
|
||||
toolbar.add(new SeparatorComponent(JBColor.LIGHT_GRAY, SeparatorOrientation.VERTICAL));
|
||||
toolbar.add(createActionsToolbar());
|
||||
|
||||
myMainPanel = new JPanel();
|
||||
myMainPanel.setLayout(new BorderLayout());
|
||||
myMainPanel.add(createToolbar(), BorderLayout.NORTH);
|
||||
myMainPanel.add(toolbar, BorderLayout.NORTH);
|
||||
myMainPanel.add(myActiveSurface, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
@@ -46,7 +58,12 @@ public class MainFrame {
|
||||
return myActiveSurface.getGraphTable();
|
||||
}
|
||||
|
||||
private JComponent createToolbar() {
|
||||
@NotNull
|
||||
public VcsLogFilterUi getFilterUi() {
|
||||
return myFilterUi;
|
||||
}
|
||||
|
||||
private JComponent createActionsToolbar() {
|
||||
AnAction hideBranchesAction = new DumbAwareAction("Collapse linear branches", "Collapse linear branches", GitLogIcons.SPIDER) {
|
||||
@Override
|
||||
public void actionPerformed(AnActionEvent e) {
|
||||
|
||||
Reference in New Issue
Block a user