/* * Copyright 2000-2016 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.vcs.log.ui.frame; import com.intellij.ide.IdeTooltip; import com.intellij.ide.IdeTooltipManager; import com.intellij.openapi.ui.popup.Balloon; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.changes.issueLinks.TableLinkMouseListener; import com.intellij.ui.HintHint; import com.intellij.ui.components.panels.Wrapper; import com.intellij.vcs.log.CommitId; import com.intellij.vcs.log.VcsShortCommitDetails; import com.intellij.vcs.log.data.LoadingDetails; import com.intellij.vcs.log.data.MainVcsLogUiProperties; import com.intellij.vcs.log.data.VcsLogData; import com.intellij.vcs.log.graph.EdgePrintElement; import com.intellij.vcs.log.graph.NodePrintElement; import com.intellij.vcs.log.graph.PrintElement; import com.intellij.vcs.log.graph.actions.GraphAction; import com.intellij.vcs.log.graph.actions.GraphAnswer; import com.intellij.vcs.log.impl.VcsLogUtil; import com.intellij.vcs.log.paint.GraphCellPainter; import com.intellij.vcs.log.paint.PositionUtil; import com.intellij.vcs.log.ui.VcsLogUiImpl; import com.intellij.vcs.log.ui.render.GraphCommitCellRenderer; import com.intellij.vcs.log.ui.render.SimpleColoredComponentLinkMouseListener; import com.intellij.vcs.log.ui.tables.GraphTableModel; import com.intellij.vcs.log.util.VcsUserUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.table.TableColumn; import java.awt.*; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.Collection; import java.util.Collections; /** * Processes mouse clicks and moves on the table */ public class GraphTableController { @NotNull private final VcsLogGraphTable myTable; @NotNull private final VcsLogUiImpl myUi; @NotNull private final VcsLogData myLogData; @NotNull private final GraphCellPainter myGraphCellPainter; @NotNull private final GraphCommitCellRenderer myCommitRenderer; public GraphTableController(@NotNull VcsLogGraphTable table, @NotNull VcsLogUiImpl ui, @NotNull VcsLogData logData, @NotNull GraphCellPainter graphCellPainter, @NotNull GraphCommitCellRenderer commitRenderer) { myTable = table; myUi = ui; myLogData = logData; myGraphCellPainter = graphCellPainter; myCommitRenderer = commitRenderer; MouseAdapter mouseAdapter = new MyMouseAdapter(); table.addMouseMotionListener(mouseAdapter); table.addMouseListener(mouseAdapter); } @Nullable PrintElement findPrintElement(@NotNull MouseEvent e) { int row = myTable.rowAtPoint(e.getPoint()); if (row >= 0 && row < myTable.getRowCount()) { return findPrintElement(row, e); } return null; } @Nullable private PrintElement findPrintElement(int row, @NotNull MouseEvent e) { Point point = calcPoint4Graph(e.getPoint()); Collection<? extends PrintElement> printElements = myTable.getVisibleGraph().getRowInfo(row).getPrintElements(); return myGraphCellPainter.getElementUnderCursor(printElements, point.x, point.y); } private void performGraphAction(@Nullable PrintElement printElement, @NotNull MouseEvent e, @NotNull GraphAction.Type actionType) { boolean isClickOnGraphElement = actionType == GraphAction.Type.MOUSE_CLICK && printElement != null; if (isClickOnGraphElement) { triggerElementClick(printElement); } VcsLogGraphTable.Selection previousSelection = myTable.getSelection(); GraphAnswer<Integer> answer = myTable.getVisibleGraph().getActionController().performAction(new GraphAction.GraphActionImpl(printElement, actionType)); handleGraphAnswer(answer, isClickOnGraphElement, previousSelection, e); } public void handleGraphAnswer(@Nullable GraphAnswer<Integer> answer, boolean dataCouldChange, @Nullable VcsLogGraphTable.Selection previousSelection, @Nullable MouseEvent e) { if (dataCouldChange) { myTable.getModel().fireTableDataChanged(); // since fireTableDataChanged clears selection we restore it here if (previousSelection != null) { previousSelection .restore(myTable.getVisibleGraph(), answer == null || (answer.getCommitToJump() != null && answer.doJump()), false); } } myUi.repaintUI(); // in case of repaintUI doing something more than just repainting this table in some distant future if (answer == null) { return; } if (answer.getCursorToSet() != null) { myTable.setCursor(answer.getCursorToSet()); } if (answer.getCommitToJump() != null) { Integer row = myTable.getModel().getVisiblePack().getVisibleGraph().getVisibleRowIndex(answer.getCommitToJump()); if (row != null && row >= 0 && answer.doJump()) { myTable.jumpToRow(row); // TODO wait for the full log and then jump return; } if (e != null) showToolTip(getArrowTooltipText(answer.getCommitToJump(), row), e); } } @NotNull private Point calcPoint4Graph(@NotNull Point clickPoint) { TableColumn rootColumn = myTable.getColumnModel().getColumn(GraphTableModel.ROOT_COLUMN); return new Point(clickPoint.x - (myUi.isMultipleRoots() ? rootColumn.getWidth() : 0), PositionUtil.getYInsideRow(clickPoint, myTable.getRowHeight())); } @NotNull private String getArrowTooltipText(int commit, @Nullable Integer row) { VcsShortCommitDetails details; if (row != null && row >= 0) { details = myTable.getModel().getShortDetails(row); // preload rows around the commit } else { details = myLogData.getMiniDetailsGetter().getCommitData(commit, Collections.singleton(commit)); // preload just the commit } String balloonText = ""; if (details instanceof LoadingDetails) { CommitId commitId = myLogData.getCommitId(commit); if (commitId != null) { balloonText = "Jump to commit" + " " + commitId.getHash().toShortString(); if (myUi.isMultipleRoots()) { balloonText += " in " + commitId.getRoot().getName(); } } } else { balloonText = "Jump to <b>\"" + StringUtil.shortenTextWithEllipsis(details.getSubject(), 50, 0, "...") + "\"</b> by " + VcsUserUtil.getShortPresentation(details.getAuthor()) + CommitPanel.formatDateTime(details.getAuthorTime()); } return balloonText; } private void showToolTip(@NotNull String text, @NotNull MouseEvent e) { // standard tooltip does not allow to customize its location, and locating tooltip above can obscure some important info Point point = new Point(e.getX() + 5, e.getY()); JEditorPane tipComponent = IdeTooltipManager.initPane(text, new HintHint(myTable, point).setAwtTooltip(true), null); IdeTooltip tooltip = new IdeTooltip(myTable, point, new Wrapper(tipComponent)).setPreferredPosition(Balloon.Position.atRight); IdeTooltipManager.getInstance().show(tooltip, false); } private void showOrHideCommitTooltip(int row, int column, @NotNull MouseEvent e) { if (!showTooltip(row, column, e.getPoint(), false)) { if (IdeTooltipManager.getInstance().hasCurrent()) { IdeTooltipManager.getInstance().hideCurrent(e); } } } private boolean showTooltip(int row, int column, @NotNull Point point, boolean now) { JComponent tipComponent = myCommitRenderer.getTooltip(myTable.getValueAt(row, column), calcPoint4Graph(point), row); if (tipComponent != null) { myTable.getExpandableItemsHandler().setEnabled(false); IdeTooltip tooltip = new IdeTooltip(myTable, point, new Wrapper(tipComponent)).setPreferredPosition(Balloon.Position.below); IdeTooltipManager.getInstance().show(tooltip, now); return true; } return false; } public void showTooltip(int row) { TableColumn rootColumn = myTable.getColumnModel().getColumn(GraphTableModel.ROOT_COLUMN); Point point = new Point(rootColumn.getWidth() + myCommitRenderer.getTooltipXCoordinate(row), row * myTable.getRowHeight() + myTable.getRowHeight() / 2); showTooltip(row, GraphTableModel.COMMIT_COLUMN, point, true); } private void performRootColumnAction() { MainVcsLogUiProperties properties = myUi.getProperties(); if (myUi.isMultipleRoots() && properties.exists(MainVcsLogUiProperties.SHOW_ROOT_NAMES)) { VcsLogUtil.triggerUsage("RootColumnClick"); properties.set(MainVcsLogUiProperties.SHOW_ROOT_NAMES, !properties.get(MainVcsLogUiProperties.SHOW_ROOT_NAMES)); } } private static void triggerElementClick(@NotNull PrintElement printElement) { if (printElement instanceof NodePrintElement) { VcsLogUtil.triggerUsage("GraphNodeClick"); } else if (printElement instanceof EdgePrintElement) { if (((EdgePrintElement)printElement).hasArrow()) { VcsLogUtil.triggerUsage("GraphArrowClick"); } } } private class MyMouseAdapter extends MouseAdapter { @NotNull private final TableLinkMouseListener myLinkListener = new SimpleColoredComponentLinkMouseListener(); @Override public void mouseClicked(MouseEvent e) { if (myLinkListener.onClick(e, e.getClickCount())) { return; } int row = myTable.rowAtPoint(e.getPoint()); if ((row >= 0 && row < myTable.getRowCount()) && e.getClickCount() == 1) { int column = myTable.convertColumnIndexToModel(myTable.columnAtPoint(e.getPoint())); if (column == GraphTableModel.ROOT_COLUMN) { performRootColumnAction(); } else if (column == GraphTableModel.COMMIT_COLUMN) { PrintElement printElement = findPrintElement(row, e); if (printElement != null) { performGraphAction(printElement, e, GraphAction.Type.MOUSE_CLICK); } } } } @Override public void mouseMoved(MouseEvent e) { if (myTable.isResizingColumns()) return; myTable.getExpandableItemsHandler().setEnabled(true); if (myLinkListener.getTagAt(e) != null) { myTable.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); return; } int row = myTable.rowAtPoint(e.getPoint()); if (row >= 0 && row < myTable.getRowCount()) { int column = myTable.convertColumnIndexToModel(myTable.columnAtPoint(e.getPoint())); if (column == GraphTableModel.ROOT_COLUMN) { myTable.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); return; } else if (column == GraphTableModel.COMMIT_COLUMN) { PrintElement printElement = findPrintElement(row, e); performGraphAction(printElement, e, GraphAction.Type.MOUSE_OVER); // if printElement is null, still need to unselect whatever was selected in a graph if (printElement == null) { showOrHideCommitTooltip(row, column, e); } return; } } myTable.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } @Override public void mouseEntered(MouseEvent e) { // Do nothing } @Override public void mouseExited(MouseEvent e) { myTable.getExpandableItemsHandler().setEnabled(true); } } }