blob: cda36e4307b22b2be1d4b39bfa329e7685725ae4 [file] [log] [blame]
package com.intellij.vcs.log.ui;
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.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.Hash;
import com.intellij.vcs.log.VcsLog;
import com.intellij.vcs.log.VcsLogFilterCollection;
import com.intellij.vcs.log.VcsLogSettings;
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.VcsLogFilterer;
import com.intellij.vcs.log.data.VcsLogUiProperties;
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.impl.VcsLogImpl;
import com.intellij.vcs.log.printmodel.SelectController;
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 org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.table.TableModel;
import java.awt.*;
import java.util.Set;
/**
* @author erokhins
*/
public class VcsLogUI {
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(VcsLogUI.class);
@NotNull private final VcsLogDataHolder myLogDataHolder;
@NotNull private final MainFrame myMainFrame;
@NotNull private final Project myProject;
@NotNull private final VcsLogColorManager myColorManager;
@NotNull private final VcsLogUiProperties myUiProperties;
@NotNull private final VcsLogFilterer myFilterer;
@NotNull private final VcsLog myLog;
@Nullable private GraphElement prevGraphElement;
public VcsLogUI(@NotNull VcsLogDataHolder logDataHolder, @NotNull Project project, @NotNull VcsLogSettings settings,
@NotNull VcsLogColorManager manager, @NotNull VcsLogUiProperties uiProperties) {
myLogDataHolder = logDataHolder;
myProject = project;
myColorManager = manager;
myUiProperties = uiProperties;
myFilterer = new VcsLogFilterer(logDataHolder, this);
myLog = new VcsLogImpl(myLogDataHolder, this);
myMainFrame = new MainFrame(myLogDataHolder, this, project, settings, uiProperties, myLog);
project.getMessageBus().connect(project).subscribe(VcsLogDataHolder.REFRESH_COMPLETED, new Runnable() {
@Override
public void run() {
applyFiltersAndUpdateUi();
}
});
applyFiltersAndUpdateUi();
}
@NotNull
public MainFrame getMainFrame() {
return myMainFrame;
}
public void jumpToRow(final int rowIndex) {
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
myMainFrame.getGraphTable().jumpToRow(rowIndex);
click(rowIndex);
}
});
}
public void setModel(@NotNull AbstractVcsLogTableModel model) {
VcsLogGraphTable table = getTable();
int[] selectedRows = table.getSelectedRows();
TableModel previousModel = table.getModel();
table.setModel(model);
if (previousModel instanceof AbstractVcsLogTableModel) { // initially it is an empty DefaultTableModel
restoreSelection(table, (AbstractVcsLogTableModel)previousModel, selectedRows, model);
}
table.setPaintBusy(false);
}
private static void restoreSelection(@NotNull VcsLogGraphTable table, @NotNull AbstractVcsLogTableModel previousModel,
int[] previousSelectedRows, @NotNull AbstractVcsLogTableModel newModel) {
Set<Hash> selectedHashes = getHashesAtRows(previousModel, previousSelectedRows);
Set<Integer> rowsToSelect = findNewRowsToSelect(newModel, selectedHashes);
for (Integer row : rowsToSelect) {
table.addRowSelectionInterval(row, row);
}
}
@NotNull
private static Set<Hash> getHashesAtRows(@NotNull AbstractVcsLogTableModel model, int[] rows) {
Set<Hash> hashes = ContainerUtil.newHashSet();
for (int row : rows) {
Hash hash = model.getHashAtRow(row);
if (hash != null) {
hashes.add(hash);
}
}
return hashes;
}
@NotNull
private static Set<Integer> findNewRowsToSelect(@NotNull AbstractVcsLogTableModel model, @NotNull Set<Hash> selectedHashes) {
Set<Integer> rowsToSelect = ContainerUtil.newHashSet();
for (int row = 0; row < model.getRowCount() && rowsToSelect.size() < selectedHashes.size(); row++) {//stop iterating if found all hashes
Hash hash = model.getHashAtRow(row);
if (hash != null && selectedHashes.contains(hash)) {
rowsToSelect.add(row);
}
}
return rowsToSelect;
}
public void updateUI() {
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
myMainFrame.getGraphTable().repaint();
}
});
}
public void addToSelection(final Hash hash) {
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
int row = myLogDataHolder.getDataPack().getRowByHash(hash);
myMainFrame.getGraphTable().getSelectionModel().addSelectionInterval(row, row);
}
});
}
public void showAll() {
runUnderModalProgress("Expanding linear branches...", new Runnable() {
@Override
public void run() {
myLogDataHolder.getDataPack().getGraphModel().getFragmentManager().showAll();
updateUI();
jumpToRow(0);
}
});
}
public void hideAll() {
runUnderModalProgress("Collapsing linear branches...", new Runnable() {
@Override
public void run() {
myLogDataHolder.getDataPack().getGraphModel().getFragmentManager().hideAll();
updateUI();
jumpToRow(0);
}
});
}
public void setLongEdgeVisibility(boolean visibility) {
myLogDataHolder.getDataPack().getPrintCellModel().setLongEdgeVisibility(visibility);
updateUI();
}
public boolean areLongEdgesHidden() {
return myLogDataHolder.getDataPack().getPrintCellModel().areLongEdgesHidden();
}
public void over(@Nullable GraphElement graphElement) {
SelectController selectController = myLogDataHolder.getDataPack().getPrintCellModel().getSelectController();
FragmentManager fragmentManager = myLogDataHolder.getDataPack().getGraphModel().getFragmentManager();
if (graphElement == prevGraphElement) {
return;
}
else {
prevGraphElement = graphElement;
}
selectController.deselectAll();
if (graphElement == null) {
updateUI();
}
else {
GraphFragment graphFragment = fragmentManager.relateFragment(graphElement);
selectController.select(graphFragment);
updateUI();
}
}
public void click(@Nullable GraphElement graphElement) {
SelectController selectController = myLogDataHolder.getDataPack().getPrintCellModel().getSelectController();
final FragmentManager fragmentController = myLogDataHolder.getDataPack().getGraphModel().getFragmentManager();
selectController.deselectAll();
if (graphElement == null) {
return;
}
final GraphFragment fragment = fragmentController.relateFragment(graphElement);
if (fragment == null) {
return;
}
myMainFrame.getGraphTable().executeWithoutRepaint(new Runnable() {
@Override
public void run() {
UpdateRequest updateRequest = fragmentController.changeVisibility(fragment);
jumpToRow(updateRequest.from());
}
});
updateUI();
}
public void click(int rowIndex) {
DataPack dataPack = myLogDataHolder.getDataPack();
dataPack.getPrintCellModel().getCommitSelectController().deselectAll();
Node node = dataPack.getNode(rowIndex);
if (node != null) {
FragmentManager fragmentController = dataPack.getGraphModel().getFragmentManager();
dataPack.getPrintCellModel().getCommitSelectController().select(fragmentController.allCommitsCurrentBranch(node));
}
updateUI();
}
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);
}
});
}
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 (collectFilters().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 VcsLogDataHolder getLogDataHolder() {
return myLogDataHolder;
}
public void applyFiltersAndUpdateUi() {
runUnderModalProgress("Applying filters...", new Runnable() {
public void run() {
myFilterer.applyFiltersAndUpdateUi(collectFilters());
}
});
}
@NotNull
public VcsLogFilterCollection collectFilters() {
return myMainFrame.getFilterUi().getFilters();
}
public VcsLogGraphTable getTable() {
return myMainFrame.getGraphTable();
}
@NotNull
public VcsLogUiProperties getUiProperties() {
return myUiProperties;
}
@NotNull
public Project getProject() {
return myProject;
}
public void runUnderModalProgress(@NotNull String task, @NotNull Runnable runnable) {
ProgressManager.getInstance().runProcessWithProgressSynchronously(runnable, task, false, null, this.getMainFrame().getMainComponent());
}
public void setBranchesPanelVisible(boolean visible) {
myMainFrame.setBranchesPanelVisible(visible);
}
public Component getToolbar() {
return myMainFrame.getToolbar();
}
@NotNull
public VcsLog getVcsLog() {
return myLog;
}
}