IDEA-116312 vcs-log: allow to customize shown columns in VCS Log

This commit is contained in:
Aleksey Pivovarov
2019-02-21 16:00:31 +03:00
committed by Julia Beliaeva
parent bbdbfa086a
commit 7a90c69dd6
8 changed files with 227 additions and 105 deletions
@@ -97,6 +97,7 @@
<keyboard-shortcut first-keystroke="control L" keymap="$default"/>
<keyboard-shortcut first-keystroke="control L" keymap="Visual Studio" remove="true"/>
</action>
<action class="com.intellij.vcs.log.ui.actions.ToggleLogColumnsActionGroup" id="Vcs.Log.ToggleColumns"/>
<group class="com.intellij.vcs.log.ui.actions.HighlightersActionGroup" id="Vcs.Log.HighlightersActionGroup"/>
<action class="com.intellij.vcs.log.ui.actions.OpenAnotherLogTabAction" id="Vcs.Log.OpenAnotherTab"/>
<action class="com.intellij.vcs.log.ui.actions.RefreshLogAction" id="Vcs.Log.Refresh" use-shortcut-of="Refresh"/>
@@ -134,6 +135,8 @@
<reference id="Vcs.Log.CollapseAll"/>
<reference id="Vcs.Log.ExpandAll"/>
<reference id="Vcs.Log.HighlightersActionGroup"/>
<separator/>
<reference id="Vcs.Log.ToggleColumns"/>
</group>
<group id="Vcs.Log.TextFilterSettings" class="com.intellij.vcs.log.ui.actions.VcsLogToolbarPopupActionGroup"
icon="AllIcons.General.GearPlain" text="Text Filter Settings" description="Text Filter Settings" popup="true">
@@ -189,6 +192,7 @@
<reference id="Vcs.Log.ShowAllAffected"/>
<separator/>
<reference id="Vcs.Log.ShowOtherBranches"/>
<reference id="Vcs.Log.ToggleColumns"/>
<separator/>
<reference id="Vcs.Log.LayoutConfiguration"/>
<separator/>
@@ -31,8 +31,9 @@ public class CommonUiProperties {
public static final VcsLogUiProperty<Boolean> SHOW_ROOT_NAMES = new VcsLogUiProperty<>("Table.ShowRootNames");
static {
COLUMN_WIDTH.put(GraphTableModel.AUTHOR_COLUMN, new TableColumnProperty("Author", GraphTableModel.AUTHOR_COLUMN));
COLUMN_WIDTH.put(GraphTableModel.DATE_COLUMN, new TableColumnProperty("Date", GraphTableModel.DATE_COLUMN));
for (int columnIndex : GraphTableModel.DYNAMIC_COLUMNS) {
COLUMN_WIDTH.put(columnIndex, new TableColumnProperty(GraphTableModel.COLUMN_NAMES[columnIndex], columnIndex));
}
}
public static void saveColumnWidth(@NotNull VcsLogUiProperties properties, int column, int width) {
@@ -17,6 +17,7 @@ package com.intellij.vcs.log.impl;
import com.intellij.openapi.components.PersistentStateComponent;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.ContainerUtilRt;
import com.intellij.vcs.log.graph.PermanentGraph;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -25,6 +26,8 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import static com.intellij.vcs.log.ui.table.GraphTableModel.*;
/**
* Stores UI configuration based on user activity and preferences.
*/
@@ -94,7 +97,9 @@ public abstract class VcsLogUiPropertiesImpl<S extends VcsLogUiPropertiesImpl.St
}
else if (CommonUiProperties.COLUMN_ORDER.equals(property)) {
List<Integer> order = getState().COLUMN_ORDER;
if (order == null) order = ContainerUtil.newArrayList();
if (order == null || order.isEmpty()) {
order = ContainerUtilRt.newArrayList(ROOT_COLUMN, COMMIT_COLUMN, AUTHOR_COLUMN, DATE_COLUMN);
}
return (T)order;
}
else if (property instanceof VcsLogHighlighterProperty) {
@@ -0,0 +1,89 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.vcs.log.ui.actions;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.actionSystem.ActionGroup;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.ToggleAction;
import com.intellij.openapi.project.DumbAware;
import com.intellij.vcs.log.impl.CommonUiProperties;
import com.intellij.vcs.log.impl.VcsLogUiProperties;
import com.intellij.vcs.log.ui.VcsLogInternalDataKeys;
import com.intellij.vcs.log.ui.table.GraphTableModel;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
public class ToggleLogColumnsActionGroup extends ActionGroup implements DumbAware {
public ToggleLogColumnsActionGroup() {
super("Show Columns", true);
}
@Override
public void update(@NotNull AnActionEvent e) {
super.update(e);
e.getPresentation().setEnabledAndVisible(isEnabledAndVisible(e));
e.getPresentation().setIcon(e.isFromActionToolbar() ? AllIcons.Actions.Show : null);
}
@NotNull
@Override
public AnAction[] getChildren(@Nullable AnActionEvent e) {
List<AnAction> actions = new ArrayList<>();
for (int column : GraphTableModel.DYNAMIC_COLUMNS) {
actions.add(new ToggleColumnAction(column));
}
return actions.toArray(AnAction.EMPTY_ARRAY);
}
private static boolean isEnabledAndVisible(@NotNull AnActionEvent e) {
VcsLogUiProperties properties = e.getData(VcsLogInternalDataKeys.LOG_UI_PROPERTIES);
return properties != null && properties.exists(CommonUiProperties.COLUMN_ORDER);
}
private static class ToggleColumnAction extends ToggleAction implements DumbAware {
private final int myIndex;
private ToggleColumnAction(int index) {
super(GraphTableModel.COLUMN_NAMES[index]);
myIndex = index;
}
@Override
public boolean isSelected(@NotNull AnActionEvent e) {
VcsLogUiProperties properties = e.getData(VcsLogInternalDataKeys.LOG_UI_PROPERTIES);
if (properties != null && properties.exists(CommonUiProperties.COLUMN_ORDER)) {
List<Integer> columnOrder = properties.get(CommonUiProperties.COLUMN_ORDER);
return columnOrder.contains(myIndex);
}
return false;
}
@Override
public void setSelected(@NotNull AnActionEvent e, boolean state) {
VcsLogUiProperties properties = e.getRequiredData(VcsLogInternalDataKeys.LOG_UI_PROPERTIES);
assert properties.exists(CommonUiProperties.COLUMN_ORDER);
List<Integer> columnOrder = new ArrayList<>(properties.get(CommonUiProperties.COLUMN_ORDER));
if (columnOrder.contains(myIndex)) {
columnOrder.remove((Integer)myIndex);
}
else {
columnOrder.add(myIndex);
}
properties.set(CommonUiProperties.COLUMN_ORDER, columnOrder);
}
@Override
public void update(@NotNull AnActionEvent e) {
super.update(e);
e.getPresentation().setEnabledAndVisible(isEnabledAndVisible(e));
}
}
}
@@ -117,7 +117,7 @@ public class GraphCommitCellRenderer extends TypeSafeTableCellRenderer<GraphComm
}
private int getColumnWidth() {
return myGraphTable.getColumnByModelIndex(GraphTableModel.COMMIT_COLUMN).getWidth();
return myGraphTable.getCommitColumn().getWidth();
}
public void setCompactReferencesView(boolean compact) {
@@ -21,6 +21,7 @@ import com.intellij.openapi.ui.popup.Balloon;
import com.intellij.openapi.vcs.changes.issueLinks.TableLinkMouseListener;
import com.intellij.ui.SimpleColoredComponent;
import com.intellij.ui.components.panels.Wrapper;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.util.ui.JBUI;
import com.intellij.vcs.log.CommitId;
@@ -264,7 +265,8 @@ public class GraphTableController {
// and by the left border otherwise
int commitColumnIndex = myTable.convertColumnIndexToView(COMMIT_COLUMN);
boolean useLeftBorder = c > commitColumnIndex;
if ((useLeftBorder ? isOnLeftBorder(e, c) : isOnRightBorder(e, c)) && (column == AUTHOR_COLUMN || column == DATE_COLUMN)) {
if ((useLeftBorder ? isOnLeftBorder(e, c) : isOnRightBorder(e, c)) &&
ArrayUtil.indexOf(DYNAMIC_COLUMNS, column) != -1) {
myTable.resetColumnWidth(column);
}
else {
@@ -273,7 +275,8 @@ public class GraphTableController {
int c2 =
myTable.columnAtPoint(new Point(e.getPoint().x + (useLeftBorder ? 1 : -1) * JBUI.scale(BORDER_THICKNESS), e.getPoint().y));
int column2 = myTable.convertColumnIndexToModel(c2);
if ((useLeftBorder ? isOnLeftBorder(e, c2) : isOnRightBorder(e, c2)) && (column2 == AUTHOR_COLUMN || column2 == DATE_COLUMN)) {
if ((useLeftBorder ? isOnLeftBorder(e, c2) : isOnRightBorder(e, c2)) &&
ArrayUtil.indexOf(DYNAMIC_COLUMNS, column) != -1) {
myTable.resetColumnWidth(column2);
}
}
@@ -28,8 +28,10 @@ public class GraphTableModel extends AbstractTableModel {
public static final int COMMIT_COLUMN = 1;
public static final int AUTHOR_COLUMN = 2;
public static final int DATE_COLUMN = 3;
private static final int COLUMN_COUNT = DATE_COLUMN + 1;
private static final String[] COLUMN_NAMES = {"", "Subject", "Author", "Date"};
public static final int HASH_COLUMN = 4;
private static final int COLUMN_COUNT = HASH_COLUMN + 1;
public static final String[] COLUMN_NAMES = {"", "Subject", "Author", "Date", "Hash"};
public static final int[] DYNAMIC_COLUMNS = {AUTHOR_COLUMN, DATE_COLUMN, HASH_COLUMN};
private static final int UP_PRELOAD_COUNT = 20;
private static final int DOWN_PRELOAD_COUNT = 40;
@@ -135,6 +137,8 @@ public class GraphTableModel extends AbstractTableModel {
else {
return DateFormatUtil.formatDateTime(data.getAuthorTime());
}
case HASH_COLUMN:
return data.getId().toShortString();
default:
throw new IllegalArgumentException("columnIndex is " + columnIndex + " > " + (getColumnCount() - 1));
}
@@ -155,8 +159,8 @@ public class GraphTableModel extends AbstractTableModel {
case COMMIT_COLUMN:
return GraphCommitCell.class;
case AUTHOR_COLUMN:
return String.class;
case DATE_COLUMN:
case HASH_COLUMN:
return String.class;
default:
throw new IllegalArgumentException("columnIndex is " + column + " > " + (getColumnCount() - 1));
@@ -109,6 +109,9 @@ public class VcsLogGraphTable extends TableWithProgress implements DataProvider,
@NotNull private final Collection<VcsLogHighlighter> myHighlighters = ContainerUtil.newArrayList();
// BasicTableUI.viewIndexForColumn uses reference equality, so we should not change TableColumn during DnD.
private final List<TableColumn> myTableColumns = new ArrayList<>();
public VcsLogGraphTable(@NotNull AbstractVcsLogUi ui,
@NotNull VcsLogData logData,
@NotNull VisiblePack initialDataPack,
@@ -160,16 +163,18 @@ public class VcsLogGraphTable extends TableWithProgress implements DataProvider,
};
}
protected void initColumns() {
private void initColumns() {
setColumnModel(new MyTableColumnModel(myProperties));
createDefaultColumnsFromModel();
ContainerUtil.addAll(myTableColumns, getColumnModel().getColumns());
setAutoCreateColumnsFromModel(false); // otherwise sizes are recalculated after each TableColumn re-initialization
onColumnOrderSettingChanged();
setRootColumnSize();
for (int column = 0; column < getColumnCount(); column++) {
getColumnByModelIndex(column).setResizable(column != ROOT_COLUMN);
for (int i = 0; i < getColumnModel().getColumnCount(); i++) {
TableColumn column = getColumnModel().getColumn(i);
column.setResizable(column.getModelIndex() != ROOT_COLUMN);
}
}
@@ -201,51 +206,43 @@ public class VcsLogGraphTable extends TableWithProgress implements DataProvider,
}
public void onColumnOrderSettingChanged() {
if (myProperties.exists(CommonUiProperties.COLUMN_ORDER)) {
List<Integer> columnOrder = myProperties.get(CommonUiProperties.COLUMN_ORDER);
TableColumnModel columnModel = getColumnModel();
int columnCount = getColumnModel().getColumnCount();
boolean dataCorrect = true;
if (columnOrder.size() != columnCount) {
dataCorrect = false;
}
else {
for (int i = 0; i < columnCount; i++) {
Integer expectedColumnIndex = columnOrder.get(i);
if (expectedColumnIndex < 0 || expectedColumnIndex >= columnCount) {
dataCorrect = false;
break;
}
if (expectedColumnIndex != getColumnModel().getColumn(i).getModelIndex()) {
// need to put column with model index columnOrder.get(i) into position i
// let's find it
// since we are going from left to right, we know that columns on the left are already placed correctly
// so only need to check columns on the right
int foundColumnIndex = -1;
for (int j = i + 1; j < columnCount; j++) {
if (getColumnModel().getColumn(j).getModelIndex() == expectedColumnIndex) {
foundColumnIndex = j;
break;
}
}
if (foundColumnIndex < 0) {
dataCorrect = false;
break;
}
else {
((MyTableColumnModel)getColumnModel()).moveWithoutChecks(foundColumnIndex, i);
}
}
}
List<Integer> columnOrder = getColumnOrderFromProperties();
if (columnOrder != null) {
int columnCount = columnModel.getColumnCount();
for (int i = columnCount - 1; i >= 0; i--) {
columnModel.removeColumn(columnModel.getColumn(i));
}
if (!dataCorrect) {
if (!columnOrder.isEmpty()) {
LOG.debug("Incorrect column order was saved in properties " + columnOrder + ", replacing it with current order.");
}
saveColumnOrderToSettings();
for (Integer expectedColumnIndex : columnOrder) {
columnModel.addColumn(myTableColumns.get(expectedColumnIndex));
}
}
reLayout();
}
@Nullable
private List<Integer> getColumnOrderFromProperties() {
if (!myProperties.exists(CommonUiProperties.COLUMN_ORDER)) return null;
List<Integer> columnOrder = myProperties.get(CommonUiProperties.COLUMN_ORDER);
if (isValidColumnOrder(columnOrder)) return columnOrder;
LOG.debug("Incorrect column order was saved in properties " + columnOrder + ", replacing it with default order.");
saveColumnOrderToSettings();
return null;
}
private boolean isValidColumnOrder(@NotNull List<Integer> columnOrder) {
int columnCount = getModel().getColumnCount();
if (!columnOrder.contains(ROOT_COLUMN)) return false;
if (!columnOrder.contains(COMMIT_COLUMN)) return false;
for (Integer index : columnOrder) {
if (index == null || index < 0 || index >= columnCount) return false;
}
return true;
}
private void saveColumnOrderToSettings() {
@@ -291,68 +288,96 @@ public class VcsLogGraphTable extends TableWithProgress implements DataProvider,
}
private void updateAuthorAndDataWidth() {
for (int i : new int[]{AUTHOR_COLUMN, DATE_COLUMN}) {
int width = CommonUiProperties.getColumnWidth(myProperties, i);
for (int columnIndex : DYNAMIC_COLUMNS) {
TableColumn column = getColumnByModelIndex(columnIndex);
if (column == null) continue;
int width = CommonUiProperties.getColumnWidth(myProperties, columnIndex);
if (width <= 0 || width > getWidth()) {
if (i != AUTHOR_COLUMN || !myAuthorColumnInitialized) {
width = getColumnWidthFromData(i);
if (columnIndex != AUTHOR_COLUMN || !myAuthorColumnInitialized) {
width = getColumnWidthFromData(column);
}
else {
width = -1;
}
}
if (width > 0 && width != getColumnByModelIndex(i).getPreferredWidth()) {
getColumnByModelIndex(i).setPreferredWidth(width);
if (width > 0 && width != column.getPreferredWidth()) {
column.setPreferredWidth(width);
}
}
updateCommitColumnWidth();
}
private int getColumnWidthFromData(int i) {
private int getColumnWidthFromData(@NotNull TableColumn column) {
int index = column.getModelIndex();
Font tableFont = getTableFont();
if (i == AUTHOR_COLUMN) {
int width = getColumnByModelIndex(i).getPreferredWidth();
if (index == AUTHOR_COLUMN) {
if (getModel().getRowCount() <= 0) {
return column.getPreferredWidth();
}
// detect author with the longest name
if (getModel().getRowCount() > 0) {
int maxRowsToCheck = Math.min(MAX_ROWS_TO_CALC_WIDTH, getRowCount());
int maxAuthorWidth = 0;
int unloaded = 0;
for (int row = 0; row < maxRowsToCheck; row++) {
String value = getModel().getValueAt(row, AUTHOR_COLUMN).toString();
if (value.isEmpty()) {
unloaded++;
continue;
}
Font font = tableFont;
VcsLogHighlighter.TextStyle style = getStyle(row, convertColumnIndexToView(AUTHOR_COLUMN), false, false).getTextStyle();
if (BOLD.equals(style)) {
font = tableFont.deriveFont(Font.BOLD);
}
else if (ITALIC.equals(style)) {
font = tableFont.deriveFont(Font.ITALIC);
}
maxAuthorWidth = Math.max(getFontMetrics(font).stringWidth(value + "*"), maxAuthorWidth);
int maxRowsToCheck = Math.min(MAX_ROWS_TO_CALC_WIDTH, getRowCount());
int maxAuthorWidth = 0;
int unloaded = 0;
for (int row = 0; row < maxRowsToCheck; row++) {
String value = getModel().getValueAt(row, AUTHOR_COLUMN).toString();
if (value.isEmpty()) {
unloaded++;
continue;
}
width = Math.min(maxAuthorWidth + myStringCellRenderer.getHorizontalTextPadding(), JBUI.scale(MAX_DEFAULT_AUTHOR_COLUMN_WIDTH));
if (unloaded * 2 <= maxRowsToCheck) myAuthorColumnInitialized = true;
Font font = tableFont;
VcsLogHighlighter.TextStyle style = getStyle(row, convertColumnIndexToView(AUTHOR_COLUMN), false, false).getTextStyle();
if (BOLD.equals(style)) {
font = tableFont.deriveFont(Font.BOLD);
}
else if (ITALIC.equals(style)) {
font = tableFont.deriveFont(Font.ITALIC);
}
maxAuthorWidth = Math.max(getFontMetrics(font).stringWidth(value + "*"), maxAuthorWidth);
}
int width = Math.min(maxAuthorWidth + myStringCellRenderer.getHorizontalTextPadding(), JBUI.scale(MAX_DEFAULT_AUTHOR_COLUMN_WIDTH));
if (unloaded * 2 <= maxRowsToCheck) myAuthorColumnInitialized = true;
return width;
}
else if (i == DATE_COLUMN) {
else if (index == DATE_COLUMN) {
// all dates have nearly equal sizes
return getFontMetrics(getTableFont().deriveFont(Font.BOLD)).stringWidth(DateFormatUtil.formatDateTime(new Date())) +
myStringCellRenderer.getHorizontalTextPadding();
}
throw new IllegalArgumentException("Can only calculate author or date columns width from data, yet given column " + i);
else if (index == HASH_COLUMN) {
if (getModel().getRowCount() <= 0) {
return column.getPreferredWidth();
}
// all hashes have nearly equal sizes
String hash = getModel().getValueAt(0, HASH_COLUMN).toString();
return getFontMetrics(getTableFont().deriveFont(Font.BOLD)).stringWidth(hash) +
myStringCellRenderer.getHorizontalTextPadding();
}
LOG.error("Can only calculate author, hash or date columns width from data, yet given column " + index);
return column.getPreferredWidth();
}
@Nullable
public TableColumn getColumnByModelIndex(int index) {
int viewIndex = convertColumnIndexToView(index);
return viewIndex != -1 ? getColumnModel().getColumn(viewIndex) : null;
}
@NotNull
public TableColumn getColumnByModelIndex(int index) {
return getColumnModel().getColumn(convertColumnIndexToView(index));
public TableColumn getRootColumn() {
//noinspection ConstantConditions
return getColumnByModelIndex(ROOT_COLUMN);
}
@NotNull
public TableColumn getCommitColumn() {
//noinspection ConstantConditions
return getColumnByModelIndex(COMMIT_COLUMN);
}
private static Font getTableFont() {
@@ -364,15 +389,14 @@ public class VcsLogGraphTable extends TableWithProgress implements DataProvider,
for (int i = 0; i < getColumnCount(); i++) {
if (i == COMMIT_COLUMN) continue;
TableColumn column = getColumnByModelIndex(i);
size -= column.getPreferredWidth();
if (column != null) size -= column.getPreferredWidth();
}
TableColumn commitColumn = getColumnByModelIndex(COMMIT_COLUMN);
commitColumn.setPreferredWidth(size);
getCommitColumn().setPreferredWidth(size);
}
private void setRootColumnSize() {
TableColumn column = getColumnByModelIndex(ROOT_COLUMN);
TableColumn column = getRootColumn();
int rootWidth;
if (!myColorManager.isMultipleRoots()) {
rootWidth = 0;
@@ -643,7 +667,7 @@ public class VcsLogGraphTable extends TableWithProgress implements DataProvider,
if (myColorManager.isMultipleRoots()) {
g.setColor(getRootBackgroundColor(getModel().getRoot(lastRow), myColorManager));
int rootWidth = getColumnByModelIndex(ROOT_COLUMN).getWidth();
int rootWidth = getRootColumn().getWidth();
if (!isShowRootNames()) rootWidth -= JBUI.scale(ROOT_INDICATOR_WHITE_WIDTH);
g.fillRect(x, y, rootWidth, height);
@@ -761,14 +785,10 @@ public class VcsLogGraphTable extends TableWithProgress implements DataProvider,
// and TableColumnModelListener.columnMarginChanged does not provide any information which column was changed
if (getTableHeader().getResizingColumn() == null) return;
if ("width".equals(evt.getPropertyName())) {
TableColumn authorColumn = getColumnByModelIndex(AUTHOR_COLUMN);
if (authorColumn.equals(evt.getSource())) {
CommonUiProperties.saveColumnWidth(myProperties, AUTHOR_COLUMN, authorColumn.getWidth());
}
else {
TableColumn dateColumn = getColumnByModelIndex(DATE_COLUMN);
if (dateColumn.equals(evt.getSource())) {
CommonUiProperties.saveColumnWidth(myProperties, DATE_COLUMN, dateColumn.getWidth());
for (int columnIndex : DYNAMIC_COLUMNS) {
TableColumn column = getColumnByModelIndex(columnIndex);
if (evt.getSource().equals(column)) {
CommonUiProperties.saveColumnWidth(myProperties, columnIndex, column.getWidth());
}
}
}
@@ -778,12 +798,8 @@ public class VcsLogGraphTable extends TableWithProgress implements DataProvider,
@Override
public void moveColumn(int columnIndex, int newIndex) {
if (convertColumnIndexToModel(columnIndex) == ROOT_COLUMN || convertColumnIndexToModel(newIndex) == ROOT_COLUMN) return;
moveWithoutChecks(columnIndex, newIndex);
saveColumnOrderToSettings();
}
public void moveWithoutChecks(int columnIndex, int newIndex) {
super.moveColumn(columnIndex, newIndex);
saveColumnOrderToSettings();
}
}
}