diff --git a/platform/platform-api/src/com/intellij/ui/TableScrollingUtil.java b/platform/platform-api/src/com/intellij/ui/TableScrollingUtil.java index 695bad005135..f0c00907ceaa 100644 --- a/platform/platform-api/src/com/intellij/ui/TableScrollingUtil.java +++ b/platform/platform-api/src/com/intellij/ui/TableScrollingUtil.java @@ -96,16 +96,30 @@ public class TableScrollingUtil { } private static int getLeadingRow(JTable table,Rectangle visibleRect) { - Point leadingPoint; + return table.rowAtPoint(getLeadingPoint(table, visibleRect)); + } + + private static Point getLeadingPoint(JTable table, Rectangle visibleRect) { + if (table.getComponentOrientation().isLeftToRight()) { + return new Point(visibleRect.x, visibleRect.y); + } + else { + return new Point(visibleRect.x + visibleRect.width, + visibleRect.y); + } + } + + public static int getReadableRow(JTable table, int maximumHiddenPart) { + Rectangle visibleRect = table.getVisibleRect(); + Point leadingPoint = getLeadingPoint(table, visibleRect); + int row = table.rowAtPoint(leadingPoint); + int column = table.columnAtPoint(leadingPoint); + if (leadingPoint.y - table.getCellRect(row, column, true).getY() <= maximumHiddenPart) { + return row; + } else { + return Math.min(row + 1, table.getRowCount() - 1); // just in case + } - if (table.getComponentOrientation().isLeftToRight()) { - leadingPoint = new Point(visibleRect.x, visibleRect.y); - } - else { - leadingPoint = new Point(visibleRect.x + visibleRect.width, - visibleRect.y); - } - return table.rowAtPoint(leadingPoint); } private static int getTrailingRow(JTable table,Rectangle visibleRect) { diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/VcsLogUiProperties.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/VcsLogUiProperties.java index 1db1181dfb3b..933ae718b05e 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/data/VcsLogUiProperties.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/data/VcsLogUiProperties.java @@ -43,6 +43,7 @@ public class VcsLogUiProperties implements PersistentStateComponent RECENTLY_FILTERED_USER_GROUPS = new ArrayDeque(); public Deque RECENTLY_FILTERED_BRANCH_GROUPS = new ArrayDeque(); } @@ -126,6 +127,14 @@ public class VcsLogUiProperties implements PersistentStateComponent users = new ArrayList(); diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/VcsLogUiImpl.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/VcsLogUiImpl.java index 8f7b10b33363..9f7153427509 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/VcsLogUiImpl.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/VcsLogUiImpl.java @@ -185,6 +185,14 @@ public class VcsLogUiImpl implements VcsLogUi, Disposable { return myUiProperties.isBek(); } + public void setShowRootNames(boolean isShowRootNames) { + myUiProperties.setShowRootNames(isShowRootNames); + } + + public boolean isShowRootNames() { + return myUiProperties.isShowRootNames(); + } + public void jumpToCommit(@NotNull Hash commitHash) { jumpTo(commitHash, new PairFunction() { @Override diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/MainFrame.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/MainFrame.java index 85bc266dc3c9..55d4d6a900ff 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/MainFrame.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/MainFrame.java @@ -91,7 +91,7 @@ public class MainFrame extends JPanel implements TypeSafeDataProvider { myToolbar = createActionsToolbar(); myDetailsSplitter = new Splitter(true, 0.7f); - myDetailsSplitter.setFirstComponent(ScrollPaneFactory.createScrollPane(myGraphTable)); + myDetailsSplitter.setFirstComponent(setupScrolledGraph()); setupDetailsSplitter(myUiProperties.isShowDetails()); JComponent toolbars = new JPanel(new BorderLayout()); @@ -156,6 +156,12 @@ public class MainFrame extends JPanel implements TypeSafeDataProvider { myDetailsSplitter.setSecondComponent(state ? myDetailsPanel : null); } + private JScrollPane setupScrolledGraph() { + JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(myGraphTable); + myGraphTable.viewportSet(scrollPane.getViewport()); + return scrollPane; + } + private static void setDefaultEmptyText(ChangesBrowser changesBrowser) { changesBrowser.getViewer().setEmptyText(""); } diff --git a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/VcsLogGraphTable.java b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/VcsLogGraphTable.java index f1d1eba2bc35..0a69398735ee 100644 --- a/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/VcsLogGraphTable.java +++ b/platform/vcs-log/impl/src/com/intellij/vcs/log/ui/frame/VcsLogGraphTable.java @@ -18,14 +18,17 @@ package com.intellij.vcs.log.ui.frame; import com.intellij.ide.CopyProvider; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.ide.CopyPasteManager; +import com.intellij.openapi.util.Couple; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.changes.Change; import com.intellij.openapi.vcs.changes.committed.CommittedChangesTreeBrowser; import com.intellij.openapi.vcs.changes.issueLinks.TableLinkMouseListener; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.ColoredTableCellRenderer; +import com.intellij.ui.JBColor; import com.intellij.ui.PopupHandler; import com.intellij.ui.TableScrollingUtil; +import com.intellij.ui.components.JBLabel; import com.intellij.ui.table.JBTable; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.text.DateFormatUtil; @@ -49,6 +52,10 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.TableModelEvent; +import javax.swing.table.AbstractTableModel; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import javax.swing.table.TableModel; @@ -67,6 +74,7 @@ import static com.intellij.vcs.log.printer.idea.PrintParameters.HEIGHT_CELL; public class VcsLogGraphTable extends JBTable implements TypeSafeDataProvider, CopyProvider { private static final int ROOT_INDICATOR_WIDTH = 5; + private static final int ROOT_NAME_MAX_WIDTH = 200; private static final int MAX_DEFAULT_AUTHOR_COLUMN_WIDTH = 200; private static final int MAX_ROWS_TO_CALC_WIDTH = 1000; private static final int MAX_ROWS_TO_CALC_OFFSET = 100; @@ -97,7 +105,7 @@ public class VcsLogGraphTable extends JBTable implements TypeSafeDataProvider, C myGraphCommitCellRender = new GraphCommitCellRender(myUI.getColorManager(), logDataHolder, myGraphCellPainter, myDataPack.getVisibleGraph(), this); - setDefaultRenderer(VirtualFile.class, new RootCellRenderer(myUI, myLogDataHolder.isMultiRoot())); + setDefaultRenderer(VirtualFile.class, new RootCellRenderer(myUI)); setDefaultRenderer(GraphCommitCell.class, myGraphCommitCellRender); setDefaultRenderer(String.class, new StringCellRenderer()); @@ -127,12 +135,8 @@ public class VcsLogGraphTable extends JBTable implements TypeSafeDataProvider, C private void setColumnPreferredSize() { for (int i = 0; i < getColumnCount(); i++) { TableColumn column = getColumnModel().getColumn(i); - if (i == GraphTableModel.ROOT_COLUMN) { // thin stripe or nothing - int rootWidth = myUI.getColorManager().isMultipleRoots() ? ROOT_INDICATOR_WIDTH : 0; - // NB: all further instructions and their order are important, otherwise the minimum size which is less than 15 won't be applied - column.setMinWidth(rootWidth); - column.setMaxWidth(rootWidth); - column.setPreferredWidth(rootWidth); + if (i == GraphTableModel.ROOT_COLUMN) { // thin stripe, or root name, or nothing + setRootColumnSize(column); } else if (i == GraphTableModel.COMMIT_COLUMN) { // let commit message occupy as much as possible column.setPreferredWidth(Short.MAX_VALUE); @@ -155,6 +159,33 @@ public class VcsLogGraphTable extends JBTable implements TypeSafeDataProvider, C } } + private void setRootColumnSize(TableColumn column) { + int rootWidth; + if (!myUI.getColorManager().isMultipleRoots()) { + rootWidth = 0; + } + else if (!myUI.isShowRootNames()) { + rootWidth = ROOT_INDICATOR_WIDTH; + } + else { + rootWidth = Math.min(calculateMaxRootWidth(), ROOT_NAME_MAX_WIDTH); + } + + // NB: all further instructions and their order are important, otherwise the minimum size which is less than 15 won't be applied + column.setMinWidth(rootWidth); + column.setMaxWidth(rootWidth); + column.setPreferredWidth(rootWidth); + } + + private int calculateMaxRootWidth() { + int width = 0; + for (VirtualFile file : myLogDataHolder.getRoots()) { + Font tableFont = UIManager.getFont("Table.font"); + width = Math.max(getFontMetrics(tableFont).stringWidth(file.getName() + " "), width); + } + return width; + } + private int calcMaxContentColumnWidth(int columnIndex, int maxRowsToCheck) { int maxWidth = 0; for (int row = 0; row < maxRowsToCheck; row++) { @@ -175,7 +206,7 @@ public class VcsLogGraphTable extends JBTable implements TypeSafeDataProvider, C if (column == GraphTableModel.ROOT_COLUMN) { Object at = getValueAt(row, column); if (at instanceof VirtualFile) { - return ((VirtualFile)at).getPresentableUrl(); + return "" + ((VirtualFile)at).getPresentableUrl() + "
Click to " + (myUI.isShowRootNames() ? "collapse" : "expand") + ""; } } return null; @@ -292,6 +323,19 @@ public class VcsLogGraphTable extends JBTable implements TypeSafeDataProvider, C } } + public void viewportSet(JViewport viewport) { + viewport.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + TableModel model = getModel(); + if (model instanceof AbstractTableModel) { + Couple visibleRows = TableScrollingUtil.getVisibleRows(VcsLogGraphTable.this); + ((AbstractTableModel)model).fireTableChanged(new TableModelEvent(model, visibleRows.first - 1, visibleRows.second, GraphTableModel.ROOT_COLUMN)); + } + } + }); + } + private class MyMouseAdapter extends MouseAdapter { private final TableLinkMouseListener myLinkListener; @@ -306,6 +350,8 @@ public class VcsLogGraphTable extends JBTable implements TypeSafeDataProvider, C } if (e.getClickCount() == 1) { + if (expandOrCollapseRoots(e)) return; + performAction(e, MyGraphMouseAction.Type.CLICK); } } @@ -320,6 +366,17 @@ public class VcsLogGraphTable extends JBTable implements TypeSafeDataProvider, C } } + private boolean expandOrCollapseRoots(MouseEvent e) { + int column = convertColumnIndexToModel(columnAtPoint(e.getPoint())); + if (column == GraphTableModel.ROOT_COLUMN && myLogDataHolder.isMultiRoot()) { + myUI.setShowRootNames(!myUI.isShowRootNames()); + setColumnPreferredSize(); + setRootColumnSize(getColumnModel().getColumn(column)); + return true; + } + return false; + } + private void performAction(@NotNull MouseEvent e, @NotNull final MyGraphMouseAction.Type actionType) { int row = PositionUtil.getRowIndex(e.getPoint()); if (row > getRowCount() - 1) { @@ -381,34 +438,78 @@ public class VcsLogGraphTable extends JBTable implements TypeSafeDataProvider, C return myLogDataHolder.isMultiRoot() ? rootColumn.getWidth() : 0; } - private static class RootCellRenderer extends JPanel implements TableCellRenderer { - + private static class RootCellRenderer extends JBLabel implements TableCellRenderer { @NotNull private final VcsLogUiImpl myUi; - @NotNull private Color myColor = UIUtil.getTableBackground(); + private boolean myHasBorder; - RootCellRenderer(@NotNull VcsLogUiImpl ui, boolean multiRoot) { + RootCellRenderer(@NotNull VcsLogUiImpl ui) { + super("", CENTER); myUi = ui; - int rootWidth = multiRoot ? ROOT_INDICATOR_WIDTH : 0; - setPreferredSize(new Dimension(rootWidth, -1)); } @Override protected void paintComponent(Graphics g) { g.setColor(myColor); - g.fillRect(0, 0, ROOT_INDICATOR_WIDTH - 1, HEIGHT_CELL); - UIUtil.drawLine((Graphics2D)g, ROOT_INDICATOR_WIDTH - 1, 0, ROOT_INDICATOR_WIDTH - 1, HEIGHT_CELL, null, - myUi.getColorManager().getRootIndicatorBorder()); + + int width = getWidth(); + + if (myHasBorder) { + g.fillRect(0, 0, width - 1, HEIGHT_CELL); + UIUtil.drawLine((Graphics2D)g, width - 1, 0, width - 1, HEIGHT_CELL, null, myUi.getColorManager().getRootIndicatorBorder()); + } + else { + g.fillRect(0, 0, width, HEIGHT_CELL); + } + super.paintComponent(g); } @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + String text; + Color color; + if (value instanceof VirtualFile) { - myColor = myUi.getColorManager().getRootColor((VirtualFile)value); + VirtualFile root = (VirtualFile)value; + int readableRow = TableScrollingUtil.getReadableRow(table, Math.round(HEIGHT_CELL * 0.5f)); + if (row < readableRow) { + text = ""; + } else if (row == 0 || !value.equals(table.getModel().getValueAt(row - 1, column)) || readableRow == row) { + text = root.getName(); + } else { + text = ""; + } + color = myUi.getColorManager().getRootColor(root); } else { - myColor = UIUtil.getTableBackground(isSelected); + text = null; + color = UIUtil.getTableBackground(isSelected); } + + if (myUi.isShowRootNames()) { + // pale colors, honors selection, does not have border + if (isSelected) { + myColor = UIUtil.getTableBackground(isSelected); + } + else { + //we create JBColor later + //noinspection UseJBColor + Color transparentColor = new Color(color.getRed(), color.getGreen(), color.getBlue(), 50); + myColor = new JBColor(transparentColor, transparentColor); + } + + setForeground(UIUtil.getTableForeground(isSelected)); + setText(text); + myHasBorder = false; + } + else { + // bright colors, does not know about the selection, has border + setForeground(UIUtil.getTableForeground(false)); + myColor = color; + setText(""); + myHasBorder = true; + } + return this; } }