[vcs-log] fix links in table
[idea/community.git] / platform / vcs-log / impl / src / com / intellij / vcs / log / ui / render / GraphCommitCellRenderer.java
1 package com.intellij.vcs.log.ui.render;
2
3 import com.intellij.openapi.util.registry.Registry;
4 import com.intellij.openapi.vcs.changes.issueLinks.IssueLinkRenderer;
5 import com.intellij.openapi.vcs.changes.issueLinks.TableLinkMouseListener;
6 import com.intellij.ui.SimpleColoredComponent;
7 import com.intellij.ui.SimpleColoredRenderer;
8 import com.intellij.ui.SimpleTextAttributes;
9 import com.intellij.util.ObjectUtils;
10 import com.intellij.util.ui.JBUI;
11 import com.intellij.util.ui.UIUtil;
12 import com.intellij.vcs.log.VcsRef;
13 import com.intellij.vcs.log.data.VcsLogData;
14 import com.intellij.vcs.log.graph.EdgePrintElement;
15 import com.intellij.vcs.log.graph.PrintElement;
16 import com.intellij.vcs.log.paint.GraphCellPainter;
17 import com.intellij.vcs.log.paint.PaintParameters;
18 import com.intellij.vcs.log.ui.frame.VcsLogGraphTable;
19 import com.intellij.vcs.log.ui.tables.GraphTableModel;
20 import org.jetbrains.annotations.NotNull;
21 import org.jetbrains.annotations.Nullable;
22
23 import javax.swing.*;
24 import java.awt.*;
25 import java.awt.event.MouseEvent;
26 import java.awt.image.BufferedImage;
27 import java.util.Collection;
28
29 public class GraphCommitCellRenderer extends TypeSafeTableCellRenderer<GraphCommitCell> {
30   private static final int MAX_GRAPH_WIDTH = 6;
31   private static final int VERTICAL_PADDING = JBUI.scale(7);
32
33   @NotNull private final VcsLogData myLogData;
34   @NotNull private final VcsLogGraphTable myGraphTable;
35
36   @NotNull private final MyComponent myComponent;
37   @NotNull private final MyComponent myTemplateComponent;
38   @Nullable private final ReferencePainter myTooltipPainter;
39
40   public GraphCommitCellRenderer(@NotNull VcsLogData logData,
41                                  @NotNull GraphCellPainter painter,
42                                  @NotNull VcsLogGraphTable table) {
43     myLogData = logData;
44     myGraphTable = table;
45
46     myTooltipPainter = isRedesignedLabels() ? new LabelPainter(myLogData) : null;
47     myComponent = new MyComponent(logData, painter, table);
48     myTemplateComponent = new MyComponent(logData, painter, table);
49   }
50
51   @Override
52   protected SimpleColoredComponent getTableCellRendererComponentImpl(@NotNull JTable table,
53                                                                      @NotNull GraphCommitCell value,
54                                                                      boolean isSelected,
55                                                                      boolean hasFocus,
56                                                                      int row,
57                                                                      int column) {
58     myComponent.customize(value, isSelected, hasFocus, row, column);
59     return myComponent;
60   }
61
62   public static boolean isRedesignedLabels() {
63     return Registry.is("vcs.log.labels.redesign");
64   }
65
66   @Nullable
67   public JComponent getTooltip(@NotNull Object value, @NotNull Point point, int row) {
68     if (myTooltipPainter == null) return null;
69
70     GraphCommitCell cell = getValue(value);
71     Collection<VcsRef> refs = cell.getRefsToThisCommit();
72     if (!refs.isEmpty()) {
73       myTooltipPainter.customizePainter(myComponent, refs, myComponent.getBackground(), myComponent.getForeground(),
74                                         true/*counterintuitive, but true*/, getColumnWidth());
75       if (getReferencesWidth(row) >= getColumnWidth() - point.getX()) {
76         return new TooltipReferencesPanel(myLogData, myTooltipPainter, refs);
77       }
78     }
79     return null;
80   }
81
82   public int getPreferredHeight() {
83     return myComponent.getPreferredHeight();
84   }
85
86   private int getReferencesWidth(int row) {
87     GraphCommitCell cell = getValue(myGraphTable.getModel().getValueAt(row, GraphTableModel.COMMIT_COLUMN));
88     Collection<VcsRef> refs = cell.getRefsToThisCommit();
89     if (!refs.isEmpty()) {
90       myTemplateComponent.customize(cell, myGraphTable.isRowSelected(row), myGraphTable.hasFocus(),
91                                     row, GraphTableModel.COMMIT_COLUMN);
92       return myTemplateComponent.getReferencePainter().getSize().width;
93     }
94
95     return 0;
96   }
97
98   public int getTooltipXCoordinate(int row) {
99     int referencesWidth = getReferencesWidth(row);
100     if (referencesWidth != 0) {
101       return getColumnWidth() - referencesWidth / 2;
102     }
103     return getColumnWidth() / 2;
104   }
105
106   private int getColumnWidth() {
107     return myGraphTable.getColumnModel().getColumn(GraphTableModel.COMMIT_COLUMN).getWidth();
108   }
109
110   public TableLinkMouseListener createLinkListener() {
111     return new MyTableLinkMouseListener();
112   }
113
114   private static class MyComponent extends SimpleColoredRenderer {
115     @NotNull private final VcsLogData myLogData;
116     @NotNull private final VcsLogGraphTable myGraphTable;
117     @NotNull private final GraphCellPainter myPainter;
118     @NotNull private final IssueLinkRenderer myIssueLinkRenderer;
119     @NotNull private final ReferencePainter myReferencePainter;
120
121     @NotNull protected PaintInfo myGraphImage = new PaintInfo(UIUtil.createImage(1, 1, BufferedImage.TYPE_INT_ARGB), 0);
122     @NotNull private Font myFont;
123     private int myHeight;
124
125     public MyComponent(@NotNull VcsLogData data, @NotNull GraphCellPainter painter, @NotNull VcsLogGraphTable table) {
126       myLogData = data;
127       myPainter = painter;
128       myGraphTable = table;
129
130       myReferencePainter = isRedesignedLabels() ? new LabelPainter(myLogData) : new RectangleReferencePainter(myLogData);
131
132       myIssueLinkRenderer = new IssueLinkRenderer(myLogData.getProject(), this);
133       myFont = RectanglePainter.getFont();
134       myHeight = calculateHeight();
135     }
136
137     @NotNull
138     @Override
139     public Dimension getPreferredSize() {
140       Dimension preferredSize = super.getPreferredSize();
141       int referencesSize = myReferencePainter.isLeftAligned() ? 0 : myReferencePainter.getSize().width;
142       return new Dimension(preferredSize.width + referencesSize, getPreferredHeight());
143     }
144
145     @Override
146     public void paintComponent(Graphics g) {
147       super.paintComponent(g);
148
149       int graphImageWidth = myGraphImage.getWidth();
150
151       if (!myReferencePainter.isLeftAligned()) {
152         int start = Math.max(graphImageWidth, getWidth() - myReferencePainter.getSize().width);
153         myReferencePainter.paint((Graphics2D)g, start, 0, getHeight());
154       }
155       else {
156         myReferencePainter.paint((Graphics2D)g, graphImageWidth, 0, getHeight());
157       }
158
159       UIUtil.drawImage(g, myGraphImage.getImage(), 0, 0, null);
160     }
161
162     public void customize(@NotNull GraphCommitCell cell, boolean isSelected, boolean hasFocus, int row, int column) {
163       clear();
164       setPaintFocusBorder(false);
165       acquireState(myGraphTable, isSelected, hasFocus, row, column);
166       getCellState().updateRenderer(this);
167       setBorder(null);
168
169       myGraphImage = getGraphImage(cell.getPrintElements());
170
171       SimpleTextAttributes style = myGraphTable.applyHighlighters(this, row, column, hasFocus, isSelected);
172
173       Collection<VcsRef> refs = cell.getRefsToThisCommit();
174       Color baseForeground = ObjectUtils.assertNotNull(myGraphTable.getBaseStyle(row, column, hasFocus, isSelected).getForeground());
175
176       append(""); // appendTextPadding wont work without this
177       if (myReferencePainter.isLeftAligned()) {
178         myReferencePainter.customizePainter(this, refs, getBackground(), baseForeground, isSelected,
179                                             0 /*left aligned painter does not use available width*/);
180
181         appendTextPadding(myGraphImage.getWidth() + myReferencePainter.getSize().width);
182         myIssueLinkRenderer.appendTextWithLinks(cell.getText(), style);
183       }
184       else {
185         appendTextPadding(myGraphImage.getWidth());
186         myIssueLinkRenderer.appendTextWithLinks(cell.getText(), style);
187         myReferencePainter.customizePainter(this, refs, getBackground(), baseForeground, isSelected,
188                                             getAvailableWidth(column));
189       }
190     }
191
192     private int getAvailableWidth(int column) {
193       int columnWidth = myGraphTable.getColumnModel().getColumn(column).getWidth();
194       return Math.min(columnWidth - super.getPreferredSize().width, columnWidth / 3);
195     }
196
197     private int calculateHeight() {
198       return Math.max(myReferencePainter.getSize().height, getFontMetrics(myFont).getHeight() + VERTICAL_PADDING);
199     }
200
201     public int getPreferredHeight() {
202       Font font = RectanglePainter.getFont();
203       if (myFont != font) {
204         myFont = font;
205         myHeight = calculateHeight();
206       }
207       return myHeight;
208     }
209
210     @NotNull
211     private PaintInfo getGraphImage(@NotNull Collection<? extends PrintElement> printElements) {
212       double maxIndex = 0;
213       for (PrintElement printElement : printElements) {
214         maxIndex = Math.max(maxIndex, printElement.getPositionInCurrentRow());
215         if (printElement instanceof EdgePrintElement) {
216           maxIndex = Math.max(maxIndex,
217                               (printElement.getPositionInCurrentRow() + ((EdgePrintElement)printElement).getPositionInOtherRow()) / 2.0);
218         }
219       }
220       maxIndex++;
221
222       maxIndex = Math.max(maxIndex, Math.min(MAX_GRAPH_WIDTH, myGraphTable.getVisibleGraph().getRecommendedWidth()));
223       BufferedImage image = UIUtil.createImage((int)(PaintParameters.getNodeWidth(myGraphTable.getRowHeight()) * (maxIndex + 2)),
224                                                myGraphTable.getRowHeight(),
225                                                BufferedImage.TYPE_INT_ARGB);
226       Graphics2D g2 = image.createGraphics();
227       myPainter.draw(g2, printElements);
228
229       int width = (int)(maxIndex * PaintParameters.getNodeWidth(myGraphTable.getRowHeight()));
230       return new PaintInfo(image, width);
231     }
232
233     @NotNull
234     public ReferencePainter getReferencePainter() {
235       return myReferencePainter;
236     }
237   }
238
239   private static class PaintInfo {
240     private final int myWidth;
241     @NotNull private final Image myImage;
242
243     PaintInfo(@NotNull Image image, int width) {
244       myImage = image;
245       myWidth = width;
246     }
247
248     @NotNull
249     Image getImage() {
250       return myImage;
251     }
252
253     /**
254      * Returns the "interesting" width of the painted image, i.e. the width which the text in the table should be offset by. <br/>
255      * It can be smaller than the width of {@link #getImage() the image}, because we allow the text to cover part of the graph
256      * (some diagonal edges, etc.)
257      */
258     int getWidth() {
259       return myWidth;
260     }
261   }
262
263   public class MyTableLinkMouseListener extends TableLinkMouseListener {
264     @Override
265     protected Object tryGetTag(@NotNull MouseEvent e, @NotNull JTable table, int row, int column) {
266       SimpleColoredComponent component =
267         getTableCellRendererComponentImpl(table, getValue(table.getValueAt(row, column)), false, false, row, column);
268       Rectangle rc = table.getCellRect(row, column, false);
269       return component.getFragmentTagAt(e.getX() - rc.x);
270     }
271   }
272 }