package com.intellij.vcs.log.ui;
import com.google.common.util.concurrent.SettableFuture;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.vcs.ui.VcsBalloonProblemNotifier;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.util.PairFunction;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.UIUtil;
import com.intellij.vcs.log.*;
import com.intellij.vcs.log.data.*;
import com.intellij.vcs.log.data.MainVcsLogUiProperties.VcsLogHighlighterProperty;
import com.intellij.vcs.log.graph.PermanentGraph;
import com.intellij.vcs.log.graph.actions.GraphAction;
import com.intellij.vcs.log.graph.actions.GraphAnswer;
import com.intellij.vcs.log.impl.VcsLogImpl;
import com.intellij.vcs.log.ui.frame.MainFrame;
import com.intellij.vcs.log.ui.frame.VcsLogGraphTable;
import com.intellij.vcs.log.ui.tables.GraphTableModel;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.Future;
public class VcsLogUiImpl implements VcsLogUi, Disposable {
private static final Logger LOG = Logger.getInstance(VcsLogUiImpl.class);
public static final ExtensionPointName<VcsLogHighlighterFactory> LOG_HIGHLIGHTER_FACTORY_EP =
ExtensionPointName.create("com.intellij.logHighlighterFactory");
@NotNull private final MainFrame myMainFrame;
@NotNull private final Project myProject;
@NotNull private final VcsLogColorManager myColorManager;
@NotNull private final VcsLog myLog;
@NotNull private final MainVcsLogUiProperties myUiProperties;
@NotNull private final VcsLogFilterer myFilterer;
@NotNull private final Collection<VcsLogListener> myLogListeners = ContainerUtil.newArrayList();
@NotNull private final VisiblePackChangeListener myVisiblePackChangeListener;
@NotNull private final VcsLogUiPropertiesImpl.MainVcsLogUiPropertiesListener myPropertiesListener;
@NotNull private VisiblePack myVisiblePack;
public VcsLogUiImpl(@NotNull VcsLogData logData,
@NotNull Project project,
@NotNull VcsLogColorManager manager,
@NotNull MainVcsLogUiProperties uiProperties,
@NotNull VcsLogFilterer filterer) {
myProject = project;
myColorManager = manager;
myUiProperties = uiProperties;
Disposer.register(logData, this);
myFilterer = filterer;
myLog = new VcsLogImpl(logData, this);
myVisiblePack = VisiblePack.EMPTY;
myMainFrame = new MainFrame(logData, this, project, uiProperties, myLog, myVisiblePack);
for (VcsLogHighlighterFactory factory : Extensions.getExtensions(LOG_HIGHLIGHTER_FACTORY_EP, myProject)) {
getTable().addHighlighter(factory.createHighlighter(logData, this));
}
myVisiblePackChangeListener = visiblePack -> UIUtil.invokeLaterIfNeeded(() -> {
if (!Disposer.isDisposed(this)) {
setVisiblePack(visiblePack);
}
});
myFilterer.addVisiblePackChangeListener(myVisiblePackChangeListener);
myPropertiesListener = new MyVcsLogUiPropertiesListener();
myUiProperties.addChangeListener(myPropertiesListener);
}
public void requestFocus() {
// todo fix selection
final VcsLogGraphTable graphTable = myMainFrame.getGraphTable();
if (graphTable.getRowCount() > 0) {
IdeFocusManager.getInstance(myProject).requestFocus(graphTable, true).doWhenProcessed(() -> graphTable.setRowSelectionInterval(0, 0));
}
}
public void setVisiblePack(@NotNull VisiblePack pack) {
ApplicationManager.getApplication().assertIsDispatchThread();
boolean permGraphChanged = myVisiblePack.getDataPack() != pack.getDataPack();
myVisiblePack = pack;
myMainFrame.updateDataPack(myVisiblePack, permGraphChanged);
myPropertiesListener.onShowLongEdgesChanged();
fireFilterChangeEvent(myVisiblePack, permGraphChanged);
repaintUI();
}
@NotNull
public MainFrame getMainFrame() {
return myMainFrame;
}
public void repaintUI() {
myMainFrame.getGraphTable().repaint();
}
private void performLongAction(@NotNull final GraphAction graphAction, @NotNull final String title) {
ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> {
final GraphAnswer<Integer> answer = myVisiblePack.getVisibleGraph().getActionController().performAction(graphAction);
final Runnable updater = answer.getGraphUpdater();
ApplicationManager.getApplication().invokeLater(() -> {
assert updater != null : "Action:" +
title +
"\nController: " +
myVisiblePack.getVisibleGraph().getActionController() +
"\nAnswer:" +
answer;
updater.run();
getTable().handleAnswer(answer, true);
});
}, title, false, null, getMainFrame().getMainComponent());
}
public void expandAll() {
performLongAction(new GraphAction.GraphActionImpl(null, GraphAction.Type.BUTTON_EXPAND),
"Expanding " +
(myUiProperties.get(MainVcsLogUiProperties.BEK_SORT_TYPE) == PermanentGraph.SortType.LinearBek
? "merges..."
: "linear branches..."));
}
public void collapseAll() {
performLongAction(new GraphAction.GraphActionImpl(null, GraphAction.Type.BUTTON_COLLAPSE),
"Collapsing " +
(myUiProperties.get(MainVcsLogUiProperties.BEK_SORT_TYPE) == PermanentGraph.SortType.LinearBek
? "merges..."
: "linear branches..."));
}
public boolean isShowRootNames() {
return myUiProperties.get(MainVcsLogUiProperties.SHOW_ROOT_NAMES);
}
@Override
public boolean isHighlighterEnabled(@NotNull String id) {
VcsLogHighlighterProperty property = VcsLogHighlighterProperty.get(id);
return myUiProperties.exists(property) && myUiProperties.get(property);
}
@Override
public boolean areGraphActionsEnabled() {
return myMainFrame.areGraphActionsEnabled();
}
public boolean isCompactReferencesView() {
return myUiProperties.get(MainVcsLogUiProperties.COMPACT_REFERENCES_VIEW);
}
public boolean isShowTagNames() {
return myUiProperties.get(MainVcsLogUiProperties.SHOW_TAG_NAMES);
}
@NotNull
public Future<Boolean> jumpToCommit(@NotNull Hash commitHash, @NotNull VirtualFile root) {
SettableFuture<Boolean> future = SettableFuture.create();
jumpToCommit(commitHash, root, future);
return future;
}
public void jumpToCommit(@NotNull Hash commitHash, @NotNull VirtualFile root, @NotNull SettableFuture<Boolean> future) {
jumpTo(commitHash, (model, hash) -> model.getRowOfCommit(hash, root), future);
}
public void jumpToCommitByPartOfHash(@NotNull String commitHash, @NotNull SettableFuture<Boolean> future) {
jumpTo(commitHash, GraphTableModel::getRowOfCommitByPartOfHash, future);
}
private <T> void jumpTo(@NotNull final T commitId,
@NotNull final PairFunction<GraphTableModel, T, Integer> rowGetter,
@NotNull final SettableFuture<Boolean> future) {
if (future.isCancelled()) return;
GraphTableModel model = getTable().getModel();
int row = rowGetter.fun(model, commitId);
if (row >= 0) {
myMainFrame.getGraphTable().jumpToRow(row);
future.set(true);
}
else if (model.canRequestMore()) {
model.requestToLoadMore(() -> jumpTo(commitId, rowGetter, future));
}
else if (!myVisiblePack.isFull()) {
invokeOnChange(() -> jumpTo(commitId, rowGetter, future));
}
else {
commitNotFound(commitId.toString());
future.set(false);
}
}
private void showMessage(@NotNull MessageType messageType, @NotNull String message) {
LOG.info(message);
VcsBalloonProblemNotifier.showOverChangesView(myProject, message, messageType);
}
private void commitNotFound(@NotNull String commitHash) {
if (myMainFrame.getFilterUi().getFilters().isEmpty()) {
showMessage(MessageType.WARNING, "Commit " + commitHash + " not found");
}
else {
showMessage(MessageType.WARNING, "Commit " + commitHash + " doesn't exist or doesn't match the active filters");
}
}
@Override
public boolean isMultipleRoots() {
return myColorManager.isMultipleRoots(); // somewhy color manager knows about this
}
@NotNull
public VcsLogColorManager getColorManager() {
return myColorManager;
}
public void applyFiltersAndUpdateUi(@NotNull VcsLogFilterCollection filters) {
myFilterer.onFiltersChange(filters);
}
@NotNull
public VcsLogFilterer getFilterer() {
return myFilterer;
}
@NotNull
public VcsLogGraphTable getTable() {
return myMainFrame.getGraphTable();
}
@NotNull
public Project getProject() {
return myProject;
}
@NotNull
public JComponent getToolbar() {
return myMainFrame.getToolbar();
}
@NotNull
public VcsLog getVcsLog() {
return myLog;
}
@NotNull
@Override
public VcsLogFilterUi getFilterUi() {
return myMainFrame.getFilterUi();
}
@Override
@NotNull
public VisiblePack getDataPack() {
ApplicationManager.getApplication().assertIsDispatchThread();
return myVisiblePack;
}
@Override
public void addLogListener(@NotNull VcsLogListener listener) {
ApplicationManager.getApplication().assertIsDispatchThread();
myLogListeners.add(listener);
}
@Override
public void removeLogListener(@NotNull VcsLogListener listener) {
ApplicationManager.getApplication().assertIsDispatchThread();
myLogListeners.remove(listener);
}
private void fireFilterChangeEvent(@NotNull VisiblePack visiblePack, boolean refresh) {
ApplicationManager.getApplication().assertIsDispatchThread();
Collection<VcsLogListener> logListeners = new ArrayList<>(myLogListeners);
for (VcsLogListener listener : logListeners) {
listener.onChange(visiblePack, refresh);
}
}
public void invokeOnChange(@NotNull final Runnable runnable) {
addLogListener(new VcsLogListener() {
@Override
public void onChange(@NotNull VcsLogDataPack dataPack, boolean refreshHappened) {
runnable.run();
removeLogListener(this);
}
});
}
@Override
public void dispose() {
myUiProperties.removeChangeListener(myPropertiesListener);
myFilterer.removeVisiblePackChangeListener(myVisiblePackChangeListener);
getTable().removeAllHighlighters();
myVisiblePack = VisiblePack.EMPTY;
}
public MainVcsLogUiProperties getProperties() {
return myUiProperties;
}
private class MyVcsLogUiPropertiesListener extends VcsLogUiPropertiesImpl.MainVcsLogUiPropertiesListener {
@Override
public void onShowDetailsChanged() {
myMainFrame.showDetails(myUiProperties.get(MainVcsLogUiProperties.SHOW_DETAILS));
}
@Override
public void onShowLongEdgesChanged() {
myVisiblePack.getVisibleGraph().getActionController().setLongEdgesHidden(!myUiProperties.get(MainVcsLogUiProperties.SHOW_LONG_EDGES));
}
@Override
public void onBekChanged() {
myFilterer.onSortTypeChange(myUiProperties.get(MainVcsLogUiProperties.BEK_SORT_TYPE));
}
@Override
public void onShowRootNamesChanged() {
myMainFrame.getGraphTable().rootColumnUpdated();
}
@Override
public void onHighlighterChanged() {
repaintUI();
}
@Override
public void onCompactReferencesViewChanged() {
myMainFrame.getGraphTable().setCompactReferencesView(myUiProperties.get(MainVcsLogUiProperties.COMPACT_REFERENCES_VIEW));
}
@Override
public void onShowTagNamesChanged() {
myMainFrame.getGraphTable().setShowTagNames(myUiProperties.get(MainVcsLogUiProperties.SHOW_TAG_NAMES));
}
@Override
public void onTextFilterSettingsChanged() {
applyFiltersAndUpdateUi(myMainFrame.getFilterUi().getFilters());
}
}
}