diff --git a/platform/vcs-log/impl/src/META-INF/vcs-log.xml b/platform/vcs-log/impl/src/META-INF/vcs-log.xml index 5557d75befce..57d4ec3bae8a 100644 --- a/platform/vcs-log/impl/src/META-INF/vcs-log.xml +++ b/platform/vcs-log/impl/src/META-INF/vcs-log.xml @@ -97,6 +97,7 @@ + @@ -134,6 +135,8 @@ + + @@ -189,6 +192,7 @@ + diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/impl/CommonUiProperties.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/impl/CommonUiProperties.java index 62dc908c4b4d..e8985fd23928 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/impl/CommonUiProperties.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/impl/CommonUiProperties.java @@ -31,8 +31,9 @@ public class CommonUiProperties { public static final VcsLogUiProperty 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) { diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/impl/VcsLogUiPropertiesImpl.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/impl/VcsLogUiPropertiesImpl.java index 163a0272f644..5e55ded37346 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/impl/VcsLogUiPropertiesImpl.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/impl/VcsLogUiPropertiesImpl.java @@ -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 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) { diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/actions/ToggleLogColumnsActionGroup.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/actions/ToggleLogColumnsActionGroup.java new file mode 100644 index 000000000000..f177dd47f7e2 --- /dev/null +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/actions/ToggleLogColumnsActionGroup.java @@ -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 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 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 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)); + } + } +} diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/render/GraphCommitCellRenderer.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/render/GraphCommitCellRenderer.java index 18b94e8eb698..1108dc468cdf 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/render/GraphCommitCellRenderer.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/render/GraphCommitCellRenderer.java @@ -117,7 +117,7 @@ public class GraphCommitCellRenderer extends TypeSafeTableCellRenderer 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); } } diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/table/GraphTableModel.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/table/GraphTableModel.java index 548e165341aa..af0814acf8e3 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/table/GraphTableModel.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/table/GraphTableModel.java @@ -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)); diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/table/VcsLogGraphTable.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/table/VcsLogGraphTable.java index e9ba85f38c37..7beb6db38e25 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/table/VcsLogGraphTable.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/table/VcsLogGraphTable.java @@ -109,6 +109,9 @@ public class VcsLogGraphTable extends TableWithProgress implements DataProvider, @NotNull private final Collection myHighlighters = ContainerUtil.newArrayList(); + // BasicTableUI.viewIndexForColumn uses reference equality, so we should not change TableColumn during DnD. + private final List 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 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 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 getColumnOrderFromProperties() { + if (!myProperties.exists(CommonUiProperties.COLUMN_ORDER)) return null; + + List 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 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(); } } }