lst: fix popup size
[idea/community.git] / platform / diff-impl / src / com / intellij / diff / util / DiffDrawUtil.java
1 /*
2  * Copyright 2000-2015 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.diff.util;
17
18 import com.intellij.openapi.editor.Editor;
19 import com.intellij.openapi.editor.colors.EditorColors;
20 import com.intellij.openapi.editor.colors.EditorColorsManager;
21 import com.intellij.openapi.editor.colors.EditorColorsScheme;
22 import com.intellij.openapi.editor.markup.*;
23 import com.intellij.openapi.util.BooleanGetter;
24 import com.intellij.ui.JBColor;
25 import com.intellij.util.DocumentUtil;
26 import com.intellij.util.containers.ContainerUtil;
27 import com.intellij.util.ui.UIUtil;
28 import org.jetbrains.annotations.NotNull;
29 import org.jetbrains.annotations.Nullable;
30
31 import java.awt.*;
32 import java.awt.geom.CubicCurve2D;
33 import java.awt.geom.Path2D;
34 import java.util.Collections;
35 import java.util.List;
36
37 public class DiffDrawUtil {
38   private static final int STRIPE_LAYER = HighlighterLayer.ERROR - 1;
39   private static final int DEFAULT_LAYER = HighlighterLayer.SELECTION - 3;
40   private static final int INLINE_LAYER = HighlighterLayer.SELECTION - 2;
41   private static final int LINE_MARKER_LAYER = HighlighterLayer.SELECTION - 1;
42
43   private DiffDrawUtil() {
44   }
45
46   @NotNull
47   public static Color getDividerColor() {
48     return getDividerColor(null);
49   }
50
51   @NotNull
52   public static Color getDividerColor(@Nullable Editor editor) {
53     return getDividerColorFromScheme(editor != null ? editor.getColorsScheme() : EditorColorsManager.getInstance().getGlobalScheme());
54   }
55
56   @NotNull
57   public static Color getDividerColorFromScheme(@NotNull EditorColorsScheme scheme) {
58     Color gutterBackground = scheme.getColor(EditorColors.GUTTER_BACKGROUND);
59     if (gutterBackground == null) {
60       gutterBackground = EditorColors.GUTTER_BACKGROUND.getDefaultColor();
61     }
62     return gutterBackground;
63   }
64
65   public static void drawConnectorLineSeparator(@NotNull Graphics2D g,
66                                                 int x1, int x2,
67                                                 int start1, int end1,
68                                                 int start2, int end2) {
69     drawConnectorLineSeparator(g, x1, x2, start1, end1, start2, end2, null);
70   }
71
72   public static void drawConnectorLineSeparator(@NotNull Graphics2D g,
73                                                 int x1, int x2,
74                                                 int start1, int end1,
75                                                 int start2, int end2,
76                                                 @Nullable EditorColorsScheme scheme) {
77     DiffLineSeparatorRenderer.drawConnectorLine(g, x1, x2, start1, start2, end1 - start1, scheme);
78   }
79
80   public static void drawDoubleChunkBorderLine(@NotNull Graphics2D g, int x1, int x2, int y, @NotNull Color color) {
81     UIUtil.drawLine(g, x1, y, x2, y, null, color);
82     UIUtil.drawLine(g, x1, y + 1, x2, y + 1, null, color);
83   }
84
85   public static void drawChunkBorderLine(@NotNull Graphics2D g, int x1, int x2, int y, @NotNull Color color) {
86     UIUtil.drawLine(g, x1, y, x2, y, null, color);
87   }
88
89   public static void drawTrapezium(@NotNull Graphics2D g,
90                                    int x1, int x2,
91                                    int start1, int end1,
92                                    int start2, int end2,
93                                    @NotNull Color color) {
94     drawTrapezium(g, x1, x2, start1, end1, start2, end2, color, null);
95   }
96
97   public static void drawTrapezium(@NotNull Graphics2D g,
98                                    int x1, int x2,
99                                    int start1, int end1,
100                                    int start2, int end2,
101                                    @Nullable Color fillColor,
102                                    @Nullable Color borderColor) {
103     if (fillColor != null) {
104       final int[] xPoints = new int[]{x1, x2, x2, x1};
105       final int[] yPoints = new int[]{start1, start2, end2 + 1, end1 + 1};
106
107       g.setColor(fillColor);
108       g.fillPolygon(xPoints, yPoints, xPoints.length);
109     }
110
111     if (borderColor != null) {
112       g.setColor(borderColor);
113       g.drawLine(x1, start1, x2, start2);
114       g.drawLine(x1, end1, x2, end2);
115     }
116   }
117
118   public static void drawCurveTrapezium(@NotNull Graphics2D g,
119                                         int x1, int x2,
120                                         int start1, int end1,
121                                         int start2, int end2,
122                                         @NotNull Color color) {
123     drawCurveTrapezium(g, x1, x2, start1, end1, start2, end2, color, null);
124   }
125
126   public static void drawCurveTrapezium(@NotNull Graphics2D g,
127                                         int x1, int x2,
128                                         int start1, int end1,
129                                         int start2, int end2,
130                                         @Nullable Color fillColor,
131                                         @Nullable Color borderColor) {
132     Shape upperCurve = makeCurve(x1, x2, start1, start2, true);
133     Shape lowerCurve = makeCurve(x1, x2, end1 + 1, end2 + 1, false);
134     Shape lowerCurveBorder = makeCurve(x1, x2, end1, end2, false);
135
136     if (fillColor != null) {
137       Path2D path = new Path2D.Double();
138       path.append(upperCurve, true);
139       path.append(lowerCurve, true);
140
141       g.setColor(fillColor);
142       g.fill(path);
143     }
144
145     if (borderColor != null) {
146       g.setColor(borderColor);
147       g.draw(upperCurve);
148       g.draw(lowerCurveBorder);
149     }
150   }
151
152   public static final double CTRL_PROXIMITY_X = 0.3;
153
154   private static Shape makeCurve(int x1, int x2, int y1, int y2, boolean forward) {
155     int width = x2 - x1;
156     if (forward) {
157       return new CubicCurve2D.Double(x1, y1,
158                                      x1 + width * CTRL_PROXIMITY_X, y1,
159                                      x1 + width * (1.0 - CTRL_PROXIMITY_X), y2,
160                                      x1 + width, y2);
161     }
162     else {
163       return new CubicCurve2D.Double(x1 + width, y2,
164                                      x1 + width * (1.0 - CTRL_PROXIMITY_X), y2,
165                                      x1 + width * CTRL_PROXIMITY_X, y1,
166                                      x1, y1);
167     }
168   }
169
170   //
171   // Impl
172   //
173
174   @NotNull
175   private static TextAttributes getTextAttributes(@NotNull final TextDiffType type,
176                                                   @Nullable final Editor editor,
177                                                   final boolean ignored) {
178     return new TextAttributes() {
179       @Override
180       public Color getBackgroundColor() {
181         return ignored ? type.getIgnoredColor(editor) : type.getColor(editor);
182       }
183     };
184   }
185
186   @NotNull
187   private static TextAttributes getStripeTextAttributes(@NotNull final TextDiffType type,
188                                                         @NotNull final Editor editor) {
189     return new TextAttributes() {
190       @Override
191       public Color getErrorStripeColor() {
192         return type.getMarkerColor(editor);
193       }
194     };
195   }
196
197   private static void installGutterRenderer(@NotNull RangeHighlighter highlighter,
198                                             @NotNull TextDiffType type,
199                                             boolean ignoredFoldingOutline) {
200     highlighter.setLineMarkerRenderer(new DiffLineMarkerRenderer(type, ignoredFoldingOutline));
201   }
202
203   private static void installEmptyRangeRenderer(@NotNull RangeHighlighter highlighter,
204                                                 @NotNull TextDiffType type) {
205     highlighter.setCustomRenderer(new DiffEmptyHighlighterRenderer(type));
206   }
207
208   //
209   // Highlighters
210   //
211
212   // TODO: invalid highlighting of empty last line
213   // TODO: invalid highlighting in case on deletion '\n' before range
214   // TODO: desync of range and line markers
215
216   @NotNull
217   public static List<RangeHighlighter> createHighlighter(@NotNull Editor editor, int start, int end, @NotNull TextDiffType type) {
218     return createHighlighter(editor, start, end, type, false);
219   }
220
221   @NotNull
222   public static List<RangeHighlighter> createHighlighter(@NotNull Editor editor, int start, int end, @NotNull TextDiffType type,
223                                                          boolean ignored) {
224     return createHighlighter(editor, start, end, type, ignored, HighlighterTargetArea.EXACT_RANGE);
225   }
226
227
228   @NotNull
229   public static List<RangeHighlighter> createHighlighter(@NotNull Editor editor, int start, int end, @NotNull TextDiffType type,
230                                                          boolean ignored, @NotNull HighlighterTargetArea area) {
231     TextAttributes attributes = start != end ? getTextAttributes(type, editor, ignored) : null;
232     TextAttributes stripeAttributes = start != end ? getStripeTextAttributes(type, editor) : null;
233
234     RangeHighlighter highlighter = editor.getMarkupModel()
235       .addRangeHighlighter(start, end, DEFAULT_LAYER, attributes, area);
236
237     installGutterRenderer(highlighter, type, ignored);
238
239     if (stripeAttributes == null) return Collections.singletonList(highlighter);
240
241     RangeHighlighter stripeHighlighter = editor.getMarkupModel()
242       .addRangeHighlighter(start, end, STRIPE_LAYER, stripeAttributes, area);
243
244     return ContainerUtil.list(highlighter, stripeHighlighter);
245   }
246
247   @NotNull
248   public static List<RangeHighlighter> createInlineHighlighter(@NotNull Editor editor, int start, int end, @NotNull TextDiffType type) {
249     TextAttributes attributes = getTextAttributes(type, editor, false);
250
251     RangeHighlighter highlighter = editor.getMarkupModel()
252       .addRangeHighlighter(start, end, INLINE_LAYER, attributes, HighlighterTargetArea.EXACT_RANGE);
253
254     if (start == end) installEmptyRangeRenderer(highlighter, type);
255
256     return Collections.singletonList(highlighter);
257   }
258
259   @NotNull
260   public static List<RangeHighlighter> createLineMarker(@NotNull Editor editor, int line, @NotNull final TextDiffType type,
261                                                         @NotNull final SeparatorPlacement placement) {
262     return createLineMarker(editor, line, type, placement, false);
263   }
264
265   @NotNull
266   public static List<RangeHighlighter> createLineMarker(@NotNull final Editor editor, int line, @NotNull final TextDiffType type,
267                                                         @NotNull final SeparatorPlacement placement, final boolean doubleLine) {
268     LineSeparatorRenderer renderer = new LineSeparatorRenderer() {
269       @Override
270       public void drawLine(Graphics g, int x1, int x2, int y) {
271         // TODO: change LineSeparatorRenderer interface ?
272         Rectangle clip = g.getClipBounds();
273         x2 = clip.x + clip.width;
274         if (doubleLine) {
275           drawDoubleChunkBorderLine((Graphics2D)g, x1, x2, y, type.getColor(editor));
276         }
277         else {
278           drawChunkBorderLine((Graphics2D)g, x1, x2, y, type.getColor(editor));
279         }
280       }
281     };
282     return createLineMarker(editor, line, placement, type, renderer);
283   }
284
285   @NotNull
286   public static List<RangeHighlighter> createBorderLineMarker(@NotNull final Editor editor, int line,
287                                                               @NotNull final SeparatorPlacement placement) {
288     LineSeparatorRenderer renderer = new LineSeparatorRenderer() {
289       @Override
290       public void drawLine(Graphics g, int x1, int x2, int y) {
291         // TODO: change LineSeparatorRenderer interface ?
292         Rectangle clip = g.getClipBounds();
293         x2 = clip.x + clip.width;
294         g.setColor(JBColor.border());
295         g.drawLine(x1, y, x2, y);
296       }
297     };
298     return createLineMarker(editor, line, placement, null, renderer);
299   }
300
301   @NotNull
302   public static List<RangeHighlighter> createLineMarker(@NotNull final Editor editor, int line, @NotNull final SeparatorPlacement placement,
303                                                         @Nullable TextDiffType type, @NotNull LineSeparatorRenderer renderer) {
304     // We won't use addLineHighlighter as it will fail to add marker into empty document.
305     //RangeHighlighter highlighter = editor.getMarkupModel().addLineHighlighter(line, HighlighterLayer.SELECTION - 1, null);
306
307     int offset = DocumentUtil.getFirstNonSpaceCharOffset(editor.getDocument(), line);
308     RangeHighlighter highlighter = editor.getMarkupModel()
309       .addRangeHighlighter(offset, offset, LINE_MARKER_LAYER, null, HighlighterTargetArea.LINES_IN_RANGE);
310
311     highlighter.setLineSeparatorPlacement(placement);
312     highlighter.setLineSeparatorRenderer(renderer);
313
314     if (type == null) return Collections.singletonList(highlighter);
315
316     TextAttributes stripeAttributes = getStripeTextAttributes(type, editor);
317     RangeHighlighter stripeHighlighter = editor.getMarkupModel()
318       .addRangeHighlighter(offset, offset, STRIPE_LAYER, stripeAttributes, HighlighterTargetArea.LINES_IN_RANGE);
319
320     return ContainerUtil.list(highlighter, stripeHighlighter);
321   }
322
323   @NotNull
324   public static List<RangeHighlighter> createLineSeparatorHighlighter(@NotNull Editor editor,
325                                                                       int offset1,
326                                                                       int offset2,
327                                                                       @NotNull BooleanGetter condition) {
328     RangeHighlighter marker = editor.getMarkupModel()
329       .addRangeHighlighter(offset1, offset2, LINE_MARKER_LAYER, null, HighlighterTargetArea.LINES_IN_RANGE);
330
331     DiffLineSeparatorRenderer renderer = new DiffLineSeparatorRenderer(editor, condition);
332     marker.setLineSeparatorPlacement(SeparatorPlacement.TOP);
333     marker.setLineSeparatorRenderer(renderer);
334     marker.setLineMarkerRenderer(renderer);
335
336     return Collections.singletonList(marker);
337   }
338 }