8660219195bad50494650078ed54970cfdd724c3
[idea/community.git] / platform / vcs-log / impl / src / com / intellij / vcs / log / ui / frame / GraphTableController.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.vcs.log.ui.frame;
17
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;
43
44 import javax.swing.*;
45 import javax.swing.table.TableColumn;
46 import java.awt.*;
47 import java.awt.event.MouseAdapter;
48 import java.awt.event.MouseEvent;
49 import java.util.Collection;
50 import java.util.Collections;
51
52 /**
53  * Processes mouse clicks and moves on the table
54  */
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;
61
62   public GraphTableController(@NotNull VcsLogGraphTable table,
63                               @NotNull VcsLogUiImpl ui,
64                               @NotNull VcsLogData logData,
65                               @NotNull GraphCellPainter graphCellPainter,
66                               @NotNull GraphCommitCellRenderer commitRenderer) {
67     myTable = table;
68     myUi = ui;
69     myLogData = logData;
70     myGraphCellPainter = graphCellPainter;
71     myCommitRenderer = commitRenderer;
72
73     MouseAdapter mouseAdapter = new MyMouseAdapter();
74     table.addMouseMotionListener(mouseAdapter);
75     table.addMouseListener(mouseAdapter);
76   }
77
78   @Nullable
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);
83     }
84     return null;
85   }
86
87   @Nullable
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);
92   }
93
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);
98     }
99
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);
104   }
105
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();
112
113       // since fireTableDataChanged clears selection we restore it here
114       if (previousSelection != null) {
115         previousSelection
116           .restore(myTable.getVisibleGraph(), answer == null || (answer.getCommitToJump() != null && answer.doJump()), false);
117       }
118     }
119
120     myUi.repaintUI(); // in case of repaintUI doing something more than just repainting this table in some distant future
121
122     if (answer == null) {
123       return;
124     }
125
126     if (answer.getCursorToSet() != null) {
127       myTable.setCursor(answer.getCursorToSet());
128     }
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
134         return;
135       }
136       if (e != null) showToolTip(getArrowTooltipText(answer.getCommitToJump(), row), e);
137     }
138   }
139
140   @NotNull
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()));
145   }
146
147   @NotNull
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
152     }
153     else {
154       details = myLogData.getMiniDetailsGetter().getCommitData(commit, Collections.singleton(commit)); // preload just the commit
155     }
156
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();
164         }
165       }
166     }
167     else {
168       balloonText = "Jump to <b>\"" +
169                     StringUtil.shortenTextWithEllipsis(details.getSubject(), 50, 0, "...") +
170                     "\"</b> by " +
171                     VcsUserUtil.getShortPresentation(details.getAuthor()) +
172                     CommitPanel.formatDateTime(details.getAuthorTime());
173     }
174     return balloonText;
175   }
176
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());
180
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);
184   }
185
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);
190       }
191     }
192   }
193
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);
196
197     if (tipComponent != null) {
198       myTable.getExpandableItemsHandler().setEnabled(false);
199       IdeTooltip tooltip =
200         new IdeTooltip(myTable, point, new Wrapper(tipComponent)).setPreferredPosition(Balloon.Position.below);
201       IdeTooltipManager.getInstance().show(tooltip, now);
202       return true;
203     }
204     return false;
205   }
206
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);
212   }
213
214   private void performRootColumnAction() {
215     if (myLogData.isMultiRoot()) {
216       VcsLogUtil.triggerUsage("RootColumnClick");
217       myUi.setShowRootNames(!myUi.isShowRootNames());
218     }
219   }
220
221   private static void triggerElementClick(@NotNull PrintElement printElement) {
222     if (printElement instanceof NodePrintElement) {
223       VcsLogUtil.triggerUsage("GraphNodeClick");
224     }
225     else if (printElement instanceof EdgePrintElement) {
226       if (((EdgePrintElement)printElement).hasArrow()) {
227         VcsLogUtil.triggerUsage("GraphArrowClick");
228       }
229     }
230   }
231
232   private class MyMouseAdapter extends MouseAdapter {
233     @NotNull private final TableLinkMouseListener myLinkListener = new TableLinkMouseListener();
234
235     @Override
236     public void mouseClicked(MouseEvent e) {
237       if (myLinkListener.onClick(e, e.getClickCount())) {
238         return;
239       }
240
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();
246         }
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);
251           }
252         }
253       }
254     }
255
256     @Override
257     public void mouseMoved(MouseEvent e) {
258       if (myTable.isResizingColumns()) return;
259       myTable.getExpandableItemsHandler().setEnabled(true);
260
261       if (myLinkListener.getTagAt(e) != null) {
262         myTable.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
263         return;
264       }
265
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));
271           return;
272         }
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);
279           }
280           return;
281         }
282       }
283
284       myTable.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
285     }
286
287     @Override
288     public void mouseEntered(MouseEvent e) {
289       // Do nothing
290     }
291
292     @Override
293     public void mouseExited(MouseEvent e) {
294       myTable.getExpandableItemsHandler().setEnabled(true);
295     }
296   }
297 }