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