2 * Copyright 2000-2016 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.vcs.log.ui.frame;
18 import com.intellij.ide.IdeTooltip;
19 import com.intellij.ide.IdeTooltipManager;
20 import com.intellij.openapi.ui.popup.Balloon;
21 import com.intellij.openapi.util.text.StringUtil;
22 import com.intellij.openapi.vcs.changes.issueLinks.TableLinkMouseListener;
23 import com.intellij.ui.HintHint;
24 import com.intellij.ui.components.panels.Wrapper;
25 import com.intellij.vcs.log.CommitId;
26 import com.intellij.vcs.log.VcsShortCommitDetails;
27 import com.intellij.vcs.log.data.LoadingDetails;
28 import com.intellij.vcs.log.data.VcsLogData;
29 import com.intellij.vcs.log.graph.EdgePrintElement;
30 import com.intellij.vcs.log.graph.NodePrintElement;
31 import com.intellij.vcs.log.graph.PrintElement;
32 import com.intellij.vcs.log.graph.actions.GraphAction;
33 import com.intellij.vcs.log.graph.actions.GraphAnswer;
34 import com.intellij.vcs.log.impl.VcsLogUtil;
35 import com.intellij.vcs.log.paint.GraphCellPainter;
36 import com.intellij.vcs.log.paint.PositionUtil;
37 import com.intellij.vcs.log.ui.VcsLogUiImpl;
38 import com.intellij.vcs.log.ui.render.GraphCommitCellRenderer;
39 import com.intellij.vcs.log.ui.tables.GraphTableModel;
40 import com.intellij.vcs.log.util.VcsUserUtil;
41 import org.jetbrains.annotations.NotNull;
42 import org.jetbrains.annotations.Nullable;
45 import javax.swing.table.TableColumn;
47 import java.awt.event.MouseAdapter;
48 import java.awt.event.MouseEvent;
49 import java.util.Collection;
50 import java.util.Collections;
53 * Processes mouse clicks and moves on the table
55 public class GraphTableController {
56 @NotNull private final VcsLogGraphTable myTable;
57 @NotNull private final VcsLogUiImpl myUi;
58 @NotNull private final VcsLogData myLogData;
59 @NotNull private final GraphCellPainter myGraphCellPainter;
60 @NotNull private final GraphCommitCellRenderer myCommitRenderer;
62 public GraphTableController(@NotNull VcsLogGraphTable table,
63 @NotNull VcsLogUiImpl ui,
64 @NotNull VcsLogData logData,
65 @NotNull GraphCellPainter graphCellPainter,
66 @NotNull GraphCommitCellRenderer commitRenderer) {
70 myGraphCellPainter = graphCellPainter;
71 myCommitRenderer = commitRenderer;
73 MouseAdapter mouseAdapter = new MyMouseAdapter();
74 table.addMouseMotionListener(mouseAdapter);
75 table.addMouseListener(mouseAdapter);
79 PrintElement findPrintElement(@NotNull MouseEvent e) {
80 int row = myTable.rowAtPoint(e.getPoint());
81 if (row >= 0 && row < myTable.getRowCount()) {
82 return findPrintElement(row, e);
88 private PrintElement findPrintElement(int row, @NotNull MouseEvent e) {
89 Point point = calcPoint4Graph(e.getPoint());
90 Collection<? extends PrintElement> printElements = myTable.getVisibleGraph().getRowInfo(row).getPrintElements();
91 return myGraphCellPainter.getElementUnderCursor(printElements, point.x, point.y);
94 private void performGraphAction(@Nullable PrintElement printElement, @NotNull MouseEvent e, @NotNull GraphAction.Type actionType) {
95 boolean isClickOnGraphElement = actionType == GraphAction.Type.MOUSE_CLICK && printElement != null;
96 if (isClickOnGraphElement) {
97 triggerElementClick(printElement);
100 VcsLogGraphTable.Selection previousSelection = myTable.getSelection();
101 GraphAnswer<Integer> answer =
102 myTable.getVisibleGraph().getActionController().performAction(new GraphAction.GraphActionImpl(printElement, actionType));
103 handleGraphAnswer(answer, isClickOnGraphElement, previousSelection, e);
106 public void handleGraphAnswer(@Nullable GraphAnswer<Integer> answer,
107 boolean dataCouldChange,
108 @Nullable VcsLogGraphTable.Selection previousSelection,
109 @Nullable MouseEvent e) {
110 if (dataCouldChange) {
111 myTable.getModel().fireTableDataChanged();
113 // since fireTableDataChanged clears selection we restore it here
114 if (previousSelection != null) {
116 .restore(myTable.getVisibleGraph(), answer == null || (answer.getCommitToJump() != null && answer.doJump()), false);
120 myUi.repaintUI(); // in case of repaintUI doing something more than just repainting this table in some distant future
122 if (answer == null) {
126 if (answer.getCursorToSet() != null) {
127 myTable.setCursor(answer.getCursorToSet());
129 if (answer.getCommitToJump() != null) {
130 Integer row = myTable.getModel().getVisiblePack().getVisibleGraph().getVisibleRowIndex(answer.getCommitToJump());
131 if (row != null && row >= 0 && answer.doJump()) {
132 myTable.jumpToRow(row);
133 // TODO wait for the full log and then jump
136 if (e != null) showToolTip(getArrowTooltipText(answer.getCommitToJump(), row), e);
141 private Point calcPoint4Graph(@NotNull Point clickPoint) {
142 TableColumn rootColumn = myTable.getColumnModel().getColumn(GraphTableModel.ROOT_COLUMN);
143 return new Point(clickPoint.x - (myLogData.isMultiRoot() ? rootColumn.getWidth() : 0),
144 PositionUtil.getYInsideRow(clickPoint, myTable.getRowHeight()));
148 private String getArrowTooltipText(int commit, @Nullable Integer row) {
149 VcsShortCommitDetails details;
150 if (row != null && row >= 0) {
151 details = myTable.getModel().getShortDetails(row); // preload rows around the commit
154 details = myLogData.getMiniDetailsGetter().getCommitData(commit, Collections.singleton(commit)); // preload just the commit
157 String balloonText = "";
158 if (details instanceof LoadingDetails) {
159 CommitId commitId = myLogData.getCommitId(commit);
160 if (commitId != null) {
161 balloonText = "Jump to commit" + " " + commitId.getHash().toShortString();
162 if (myUi.isMultipleRoots()) {
163 balloonText += " in " + commitId.getRoot().getName();
168 balloonText = "Jump to <b>\"" +
169 StringUtil.shortenTextWithEllipsis(details.getSubject(), 50, 0, "...") +
171 VcsUserUtil.getShortPresentation(details.getAuthor()) +
172 CommitPanel.formatDateTime(details.getAuthorTime());
177 private void showToolTip(@NotNull String text, @NotNull MouseEvent e) {
178 // standard tooltip does not allow to customize its location, and locating tooltip above can obscure some important info
179 Point point = new Point(e.getX() + 5, e.getY());
181 JEditorPane tipComponent = IdeTooltipManager.initPane(text, new HintHint(myTable, point).setAwtTooltip(true), null);
182 IdeTooltip tooltip = new IdeTooltip(myTable, point, new Wrapper(tipComponent)).setPreferredPosition(Balloon.Position.atRight);
183 IdeTooltipManager.getInstance().show(tooltip, false);
186 private void showOrHideCommitTooltip(int row, int column, @NotNull MouseEvent e) {
187 if (!showTooltip(row, column, e.getPoint(), false)) {
188 if (IdeTooltipManager.getInstance().hasCurrent()) {
189 IdeTooltipManager.getInstance().hideCurrent(e);
194 private boolean showTooltip(int row, int column, @NotNull Point point, boolean now) {
195 JComponent tipComponent = myCommitRenderer.getTooltip(myTable.getValueAt(row, column), calcPoint4Graph(point), row);
197 if (tipComponent != null) {
198 myTable.getExpandableItemsHandler().setEnabled(false);
200 new IdeTooltip(myTable, point, new Wrapper(tipComponent)).setPreferredPosition(Balloon.Position.below);
201 IdeTooltipManager.getInstance().show(tooltip, now);
207 public void showTooltip(int row) {
208 TableColumn rootColumn = myTable.getColumnModel().getColumn(GraphTableModel.ROOT_COLUMN);
209 Point point = new Point(rootColumn.getWidth() + myCommitRenderer.getTooltipXCoordinate(row),
210 row * myTable.getRowHeight() + myTable.getRowHeight() / 2);
211 showTooltip(row, GraphTableModel.COMMIT_COLUMN, point, true);
214 private void performRootColumnAction() {
215 if (myLogData.isMultiRoot()) {
216 VcsLogUtil.triggerUsage("RootColumnClick");
217 myUi.setShowRootNames(!myUi.isShowRootNames());
221 private static void triggerElementClick(@NotNull PrintElement printElement) {
222 if (printElement instanceof NodePrintElement) {
223 VcsLogUtil.triggerUsage("GraphNodeClick");
225 else if (printElement instanceof EdgePrintElement) {
226 if (((EdgePrintElement)printElement).hasArrow()) {
227 VcsLogUtil.triggerUsage("GraphArrowClick");
232 private class MyMouseAdapter extends MouseAdapter {
233 @NotNull private final TableLinkMouseListener myLinkListener = new TableLinkMouseListener();
236 public void mouseClicked(MouseEvent e) {
237 if (myLinkListener.onClick(e, e.getClickCount())) {
241 int row = myTable.rowAtPoint(e.getPoint());
242 if ((row >= 0 && row < myTable.getRowCount()) && e.getClickCount() == 1) {
243 int column = myTable.convertColumnIndexToModel(myTable.columnAtPoint(e.getPoint()));
244 if (column == GraphTableModel.ROOT_COLUMN) {
245 performRootColumnAction();
247 else if (column == GraphTableModel.COMMIT_COLUMN) {
248 PrintElement printElement = findPrintElement(row, e);
249 if (printElement != null) {
250 performGraphAction(printElement, e, GraphAction.Type.MOUSE_CLICK);
257 public void mouseMoved(MouseEvent e) {
258 if (myTable.isResizingColumns()) return;
259 myTable.getExpandableItemsHandler().setEnabled(true);
261 if (myLinkListener.getTagAt(e) != null) {
262 myTable.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
266 int row = myTable.rowAtPoint(e.getPoint());
267 if (row >= 0 && row < myTable.getRowCount()) {
268 int column = myTable.convertColumnIndexToModel(myTable.columnAtPoint(e.getPoint()));
269 if (column == GraphTableModel.ROOT_COLUMN) {
270 myTable.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
273 else if (column == GraphTableModel.COMMIT_COLUMN) {
274 PrintElement printElement = findPrintElement(row, e);
275 performGraphAction(printElement, e,
276 GraphAction.Type.MOUSE_OVER); // if printElement is null, still need to unselect whatever was selected in a graph
277 if (printElement == null) {
278 showOrHideCommitTooltip(row, column, e);
284 myTable.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
288 public void mouseEntered(MouseEvent e) {
293 public void mouseExited(MouseEvent e) {
294 myTable.getExpandableItemsHandler().setEnabled(true);