blob: b96611dd0f25c034a02a56a8c4607860fced856c [file] [log] [blame]
package com.intellij.vcs.log.ui;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
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.util.EmptyRunnable;
import com.intellij.openapi.vcs.ui.VcsBalloonProblemNotifier;
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.graph.ChangeCursorActionRequest;
import com.intellij.vcs.log.graph.ClickGraphAction;
import com.intellij.vcs.log.graph.*;
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.AbstractVcsLogTableModel;
import gnu.trove.TIntHashSet;
import gnu.trove.TIntProcedure;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;
import java.awt.*;
import java.util.Collection;
public class VcsLogUiImpl implements VcsLogUi, Disposable {
public static final String POPUP_ACTION_GROUP = "Vcs.Log.ContextMenu";
public static final String TOOLBAR_ACTION_GROUP = "Vcs.Log.Toolbar";
public static final String VCS_LOG_TABLE_PLACE = "Vcs.Log.ContextMenu";
private static final Logger LOG = Logger.getInstance(VcsLogUiImpl.class);
@NotNull private final MainFrame myMainFrame;
@NotNull private final Project myProject;
@NotNull private final VcsLogColorManager myColorManager;
@NotNull private final VcsLogFilterer myFilterer;
@NotNull private final VcsLog myLog;
@NotNull private final VcsLogUiProperties myUiProperties;
@NotNull private final Collection<VcsLogFilterChangeListener> myFilterChangeListeners = ContainerUtil.newArrayList();
@NotNull private DataPack myDataPack;
public VcsLogUiImpl(@NotNull VcsLogDataHolder logDataHolder, @NotNull Project project, @NotNull VcsLogSettings settings,
@NotNull VcsLogColorManager manager, @NotNull VcsLogUiProperties uiProperties, @NotNull DataPack initialDataPack) {
myProject = project;
myColorManager = manager;
myUiProperties = uiProperties;
myDataPack = initialDataPack;
Disposer.register(logDataHolder, this);
myFilterer = new VcsLogFilterer(logDataHolder, this);
myLog = new VcsLogImpl(logDataHolder, this);
myMainFrame = new MainFrame(logDataHolder, this, project, settings, uiProperties, myLog, initialDataPack);
}
@NotNull
public MainFrame getMainFrame() {
return myMainFrame;
}
public void jumpToRow(final int rowIndex) {
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
myMainFrame.getGraphTable().jumpToRow(rowIndex);
}
});
}
public void setModel(@NotNull AbstractVcsLogTableModel newModel, @NotNull DataPack newDataPack,
@NotNull TIntHashSet previouslySelectedCommits) {
final VcsLogGraphTable table = getTable();
table.setModel(newModel);
restoreSelection(newModel, newDataPack, previouslySelectedCommits, table);
table.setPaintBusy(false);
}
private static void restoreSelection(@NotNull AbstractVcsLogTableModel newModel,
@NotNull DataPack newDataPack,
@NotNull TIntHashSet previouslySelectedCommits,
@NotNull final VcsLogGraphTable table) {
TIntHashSet rowsToSelect = findNewRowsToSelect(newModel, newDataPack, previouslySelectedCommits);
rowsToSelect.forEach(new TIntProcedure() {
@Override
public boolean execute(int row) {
table.addRowSelectionInterval(row, row);
return true;
}
});
}
@NotNull
private static TIntHashSet findNewRowsToSelect(@NotNull AbstractVcsLogTableModel newModel,
@NotNull DataPack dataPack,
@NotNull TIntHashSet selectedHashes) {
TIntHashSet rowsToSelect = new TIntHashSet();
if (newModel.getRowCount() == 0) {
// this should have been covered by facade.getVisibleCommitCount,
// but if the table is empty (no commits match the filter), the GraphFacade is not updated, because it can't handle it
// => it has previous values set.
return rowsToSelect;
}
GraphFacade facade = dataPack.getGraphFacade();
for (int row = 0; row < facade.getVisibleCommitCount()
&& rowsToSelect.size() < selectedHashes.size(); row++) { //stop iterating if found all hashes
int commit = facade.getCommitAtRow(row);
if (selectedHashes.contains(commit)) {
rowsToSelect.add(row);
}
}
return rowsToSelect;
}
public void repaintUI() {
myMainFrame.getGraphTable().repaint();
}
public void showAll() {
runUnderModalProgress("Expanding linear branches...", new Runnable() {
@Override
public void run() {
final GraphAnswer answer = myDataPack.getGraphFacade().performAction(LinearBranchesExpansionAction.EXPAND);
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
@Override
public void run() {
handleAnswer(answer);
}
});
}
});
}
public void hideAll() {
runUnderModalProgress("Collapsing linear branches...", new Runnable() {
@Override
public void run() {
final GraphAnswer answer = myDataPack.getGraphFacade().performAction(LinearBranchesExpansionAction.COLLAPSE);
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
@Override
public void run() {
handleAnswer(answer);
}
});
}
});
}
public void setLongEdgeVisibility(boolean visibility) {
handleAnswer(myDataPack.getGraphFacade().performAction(LongEdgesAction.valueOf(visibility)));
myUiProperties.setLongEdgesVisibility(visibility);
}
public boolean areLongEdgesHidden() {
return myDataPack.getGraphFacade().getInfoProvider().areLongEdgesHidden();
}
public void setBek(boolean bek) {
myUiProperties.setBek(bek);
final BekGraphAction bekGraphAction = new BekGraphAction(bek ? PermanentGraph.SortType.Bek : PermanentGraph.SortType.Normal);
runUnderModalProgress("Apply sort type...", new Runnable() {
@Override
public void run() {
final GraphAnswer answer = myDataPack.getGraphFacade().performAction(bekGraphAction);
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
handleAnswer(answer);
}
});
}
});
}
public boolean isBek() {
return myUiProperties.isBek();
}
public void click(int rowIndex) {
handleAnswer(myDataPack.getGraphFacade().performAction(new ClickGraphAction(rowIndex, null)));
}
public void jumpToCommit(@NotNull Hash commitHash) {
jumpTo(commitHash, new PairFunction<AbstractVcsLogTableModel, Hash, Integer>() {
@Override
public Integer fun(AbstractVcsLogTableModel model, Hash hash) {
return model.getRowOfCommit(hash);
}
});
}
public void jumpToCommitByPartOfHash(@NotNull String commitHash) {
jumpTo(commitHash, new PairFunction<AbstractVcsLogTableModel, String, Integer>() {
@Override
public Integer fun(AbstractVcsLogTableModel model, String hash) {
return model.getRowOfCommitByPartOfHash(hash);
}
});
}
public void handleAnswer(@Nullable GraphAnswer answer) {
repaintUI();
if (answer == null) {
return;
}
GraphChange graphChange = answer.getGraphChange();
if (graphChange != null) {
((AbstractTableModel)(getTable().getModel())).fireTableStructureChanged();
}
GraphActionRequest actionRequest = answer.getActionRequest();
if (actionRequest instanceof JumpToRowActionRequest) {
int row = ((JumpToRowActionRequest)actionRequest).getRow();
jumpToRow(row);
}
else if (actionRequest instanceof ChangeCursorActionRequest) {
myMainFrame.getGraphTable().setCursor(((ChangeCursorActionRequest)actionRequest).getCursor());
}
}
private <T> void jumpTo(@NotNull final T commitId, @NotNull final PairFunction<AbstractVcsLogTableModel, T, Integer> rowGetter) {
AbstractVcsLogTableModel model = getModel();
if (model == null) {
return;
}
int row = rowGetter.fun(model, commitId);
if (row >= 0) {
jumpToRow(row);
}
else if (model.canRequestMore()) {
model.requestToLoadMore(new Runnable() {
@Override
public void run() {
jumpTo(commitId, rowGetter);
}
});
}
else {
commitNotFound(commitId.toString());
}
}
@Nullable
private AbstractVcsLogTableModel getModel() {
TableModel model = getTable().getModel();
if (model instanceof AbstractVcsLogTableModel) {
return (AbstractVcsLogTableModel)model;
}
showMessage(MessageType.WARNING, "The log is not ready to search yet");
return null;
}
private void showMessage(@NotNull MessageType messageType, @NotNull String message) {
LOG.info(message);
VcsBalloonProblemNotifier.showOverChangesView(myProject, message, messageType);
}
private void commitNotFound(@NotNull String commitHash) {
if (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");
}
}
@NotNull
public VcsLogColorManager getColorManager() {
return myColorManager;
}
@NotNull
public VcsLogFilterer getFilterer() {
return myFilterer;
}
@NotNull
public TIntHashSet getSelectedCommits() {
int[] selectedRows = getTable().getSelectedRows();
return getCommitsAtRows(myDataPack.getGraphFacade(), selectedRows);
}
@NotNull
private static TIntHashSet getCommitsAtRows(@NotNull GraphFacade facade, int[] rows) {
TIntHashSet commits = new TIntHashSet();
for (int row : rows) {
int commit = facade.getCommitAtRow(row);
if (commit > 0) {
commits.add(commit);
}
}
return commits;
}
public void setDataPack(@NotNull DataPack dataPack) {
applyFiltersAndUpdateUi(dataPack);
}
private void applyFiltersAndUpdateUi(@NotNull final DataPack dataPack) {
runUnderModalProgress("Applying filters...", new Runnable() {
public void run() {
final TIntHashSet previouslySelected = getSelectedCommits();
final AbstractVcsLogTableModel newModel = myFilterer.applyFiltersAndUpdateUi(dataPack, getFilters());
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
@Override
public void run() {
myDataPack = dataPack;
setModel(newModel, myDataPack, previouslySelected);
myMainFrame.updateDataPack(myDataPack);
setLongEdgeVisibility(myUiProperties.areLongEdgesVisible());
myDataPack.getGraphFacade().performAction(new BekGraphAction(myUiProperties.isBek() ?
PermanentGraph.SortType.Bek : PermanentGraph.SortType.Normal));
fireFilterChangeEvent();
repaintUI();
if (newModel.getRowCount() == 0) { // getValueAt won't be called for empty model => need to explicitly request to load more
newModel.requestToLoadMore(EmptyRunnable.INSTANCE);
}
}
});
}
});
}
public void applyFiltersAndUpdateUi() {
ApplicationManager.getApplication().assertIsDispatchThread();
applyFiltersAndUpdateUi(myDataPack);
}
@NotNull
public VcsLogFilterCollection getFilters() {
return myMainFrame.getFilterUi().getFilters();
}
public VcsLogGraphTable getTable() {
return myMainFrame.getGraphTable();
}
@NotNull
public Project getProject() {
return myProject;
}
public void runUnderModalProgress(@NotNull final String task, @NotNull final Runnable runnable) {
getTable().executeWithoutRepaint(new Runnable() {
public void run() {
ProgressManager.getInstance().runProcessWithProgressSynchronously(runnable, task, false, null, getMainFrame().getMainComponent());
}
});
repaintUI();
}
public void setBranchesPanelVisible(boolean visible) {
myMainFrame.setBranchesPanelVisible(visible);
}
public Component getToolbar() {
return myMainFrame.getToolbar();
}
@NotNull
public VcsLog getVcsLog() {
return myLog;
}
@NotNull
@Override
public VcsLogFilterUi getFilterUi() {
return myMainFrame.getFilterUi();
}
@Override
@NotNull
public DataPack getDataPack() {
ApplicationManager.getApplication().assertIsDispatchThread();
return myDataPack;
}
@Override
public void addHighlighter(@NotNull VcsLogHighlighter highlighter) {
getTable().addHighlighter(highlighter);
repaintUI();
}
@Override
public void removeHighlighter(@NotNull VcsLogHighlighter highlighter) {
getTable().removeHighlighter(highlighter);
repaintUI();
}
@Override
public void addFilterChangeListener(@NotNull VcsLogFilterChangeListener listener) {
myFilterChangeListeners.add(listener);
}
@Override
public void removeFilterChangeListener(@NotNull VcsLogFilterChangeListener listener) {
myFilterChangeListeners.remove(listener);
}
private void fireFilterChangeEvent() {
for (VcsLogFilterChangeListener listener : myFilterChangeListeners) {
listener.filtersPossiblyChanged();
}
}
@Override
public void dispose() {
getTable().removeAllHighlighters();
}
}