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