4e8962494f08d5bbbb85c5c47fe74910c9dd50ba
[idea/community.git] / platform / vcs-impl / src / com / intellij / openapi / vcs / ex / LineStatusTrackerDrawing.java
1 /*
2  * Copyright 2000-2014 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.openapi.vcs.ex;
17
18 import com.intellij.codeInsight.hint.EditorFragmentComponent;
19 import com.intellij.codeInsight.hint.HintManagerImpl;
20 import com.intellij.openapi.actionSystem.*;
21 import com.intellij.openapi.actionSystem.ex.ActionUtil;
22 import com.intellij.openapi.diff.DiffColors;
23 import com.intellij.openapi.editor.Document;
24 import com.intellij.openapi.editor.Editor;
25 import com.intellij.openapi.editor.EditorFactory;
26 import com.intellij.openapi.editor.ScrollType;
27 import com.intellij.openapi.editor.colors.EditorColors;
28 import com.intellij.openapi.editor.colors.EditorColorsManager;
29 import com.intellij.openapi.editor.colors.EditorColorsScheme;
30 import com.intellij.openapi.editor.ex.DocumentEx;
31 import com.intellij.openapi.editor.ex.EditorEx;
32 import com.intellij.openapi.editor.ex.EditorGutterComponentEx;
33 import com.intellij.openapi.editor.highlighter.EditorHighlighter;
34 import com.intellij.openapi.editor.highlighter.EditorHighlighterFactory;
35 import com.intellij.openapi.editor.markup.ActiveGutterRenderer;
36 import com.intellij.openapi.editor.markup.LineMarkerRenderer;
37 import com.intellij.openapi.editor.markup.TextAttributes;
38 import com.intellij.openapi.fileEditor.FileDocumentManager;
39 import com.intellij.openapi.vcs.actions.ShowNextChangeMarkerAction;
40 import com.intellij.openapi.vcs.actions.ShowPrevChangeMarkerAction;
41 import com.intellij.openapi.vfs.VirtualFile;
42 import com.intellij.ui.ColoredSideBorder;
43 import com.intellij.ui.HintHint;
44 import com.intellij.ui.HintListener;
45 import com.intellij.ui.LightweightHint;
46 import com.intellij.util.ui.UIUtil;
47 import org.jetbrains.annotations.NotNull;
48 import org.jetbrains.annotations.Nullable;
49
50 import javax.swing.*;
51 import java.awt.*;
52 import java.awt.event.MouseAdapter;
53 import java.awt.event.MouseEvent;
54 import java.util.EventObject;
55 import java.util.List;
56
57 /**
58  * @author irengrig
59  */
60 public class LineStatusTrackerDrawing {
61   private LineStatusTrackerDrawing() {
62   }
63
64   static TextAttributes getAttributesFor(final Range range) {
65     return new TextAttributes() {
66       @Override
67       public Color getErrorStripeColor() {
68         return getDiffColor(range);
69       }
70     };
71   }
72
73   private static void paintGutterFragment(final Editor editor, final Graphics g, final Rectangle r, final Range range) {
74     final EditorGutterComponentEx gutter = ((EditorEx)editor).getGutterComponentEx();
75     Color gutterColor = getDiffGutterColor(range);
76     Color borderColor = getDiffGutterBorderColor();
77
78     final int x = r.x + r.width - 3;
79     final int endX = gutter.getWhitespaceSeparatorOffset();
80
81     if (range.getInnerRanges() == null) { // actual painter
82       if (r.height > 0) {
83         paintRect(g, gutterColor, borderColor, x, r.y, endX, r.y + r.height);
84       }
85       else {
86         paintTriangle(g, gutterColor, borderColor, x, endX, r.y);
87       }
88     }
89     else { // registry: diff.status.tracker.smart
90       if (range.getType() == Range.DELETED) {
91         final int y = lineToY(editor, range.getLine1());
92         paintTriangle(g, gutterColor, borderColor, x, endX, y);
93       }
94       else {
95         final int y = lineToY(editor, range.getLine1());
96         int endY = lineToY(editor, range.getLine2());
97
98         List<Range.InnerRange> innerRanges = range.getInnerRanges();
99         for (Range.InnerRange innerRange : innerRanges) {
100           if (innerRange.getType() == Range.DELETED) continue;
101
102           int start = lineToY(editor, innerRange.getLine1());
103           int end = lineToY(editor, innerRange.getLine2());
104
105           paintRect(g, getDiffColor(innerRange), null, x, start, endX, end);
106         }
107
108         for (int i = 0; i < innerRanges.size(); i++) {
109           Range.InnerRange innerRange = innerRanges.get(i);
110           if (innerRange.getType() != Range.DELETED) continue;
111
112           int start;
113           int end;
114
115           if (i == 0) {
116             start = lineToY(editor, innerRange.getLine1());
117             end = lineToY(editor, innerRange.getLine2()) + 5;
118           }
119           else if (i == innerRanges.size() - 1) {
120             start = lineToY(editor, innerRange.getLine1()) - 5;
121             end = lineToY(editor, innerRange.getLine2());
122           }
123           else {
124             start = lineToY(editor, innerRange.getLine1()) - 3;
125             end = lineToY(editor, innerRange.getLine2()) + 3;
126           }
127
128           paintRect(g, getDiffColor(innerRange), null, x, start, endX, end);
129         }
130
131         paintRect(g, null, borderColor, x, y, endX, endY);
132       }
133     }
134   }
135
136   private static int lineToY(@NotNull Editor editor, int line) {
137     Document document = editor.getDocument();
138     if (line >= document.getLineCount()) {
139       int y = lineToY(editor, document.getLineCount() - 1);
140       return y + editor.getLineHeight() * (line - document.getLineCount() + 1);
141     }
142     return editor.logicalPositionToXY(editor.offsetToLogicalPosition(document.getLineStartOffset(line))).y;
143   }
144
145   private static void paintRect(@NotNull Graphics g, @Nullable Color color, @Nullable Color borderColor, int x1, int y1, int x2, int y2) {
146     if (color != null) {
147       g.setColor(color);
148       g.fillRect(x1, y1, x2 - x1, y2 - y1);
149     }
150     if (borderColor != null) {
151       g.setColor(borderColor);
152       UIUtil.drawLine(g, x1, y1, x2 - 1, y1);
153       UIUtil.drawLine(g, x1, y1, x1, y2 - 1);
154       UIUtil.drawLine(g, x1, y2 - 1, x2 - 1, y2 - 1);
155     }
156   }
157
158   private static void paintTriangle(@NotNull Graphics g, @Nullable Color color, @Nullable Color borderColor, int x1, int x2, int y) {
159     int size = 4;
160
161     final int[] xPoints = new int[]{x1, x1, x2};
162     final int[] yPoints = new int[]{y - size, y + size, y};
163
164     if (color != null) {
165       g.setColor(color);
166       g.fillPolygon(xPoints, yPoints, xPoints.length);
167     }
168     if (borderColor != null) {
169       g.setColor(borderColor);
170       g.drawPolygon(xPoints, yPoints, xPoints.length);
171     }
172   }
173
174   public static LineMarkerRenderer createRenderer(final Range range, final LineStatusTracker tracker) {
175     return new ActiveGutterRenderer() {
176       public void paint(final Editor editor, final Graphics g, final Rectangle r) {
177         paintGutterFragment(editor, g, r, range);
178       }
179
180       public void doAction(final Editor editor, final MouseEvent e) {
181         e.consume();
182         final JComponent comp = (JComponent)e.getComponent(); // shall be EditorGutterComponent, cast is safe.
183         final JLayeredPane layeredPane = comp.getRootPane().getLayeredPane();
184         final Point point = SwingUtilities.convertPoint(comp, ((EditorEx)editor).getGutterComponentEx().getWidth(), e.getY(), layeredPane);
185         showActiveHint(range, editor, point, tracker);
186       }
187
188       public boolean canDoAction(final MouseEvent e) {
189         final EditorGutterComponentEx gutter = (EditorGutterComponentEx)e.getComponent();
190         return e.getX() > gutter.getLineMarkerAreaOffset() + gutter.getIconsAreaWidth();
191       }
192     };
193   }
194
195   public static void showActiveHint(final Range range, final Editor editor, final Point point, final LineStatusTracker tracker) {
196     final DefaultActionGroup group = new DefaultActionGroup();
197
198     final ShowPrevChangeMarkerAction localShowPrevAction = new ShowPrevChangeMarkerAction(tracker.getPrevRange(range), tracker, editor);
199     final ShowNextChangeMarkerAction localShowNextAction = new ShowNextChangeMarkerAction(tracker.getNextRange(range), tracker, editor);
200     final RollbackLineStatusRangeAction rollback = new RollbackLineStatusRangeAction(tracker, range, editor);
201     final ShowLineStatusRangeDiffAction showDiff = new ShowLineStatusRangeDiffAction(tracker, range, editor);
202     final CopyLineStatusRangeAction copyRange = new CopyLineStatusRangeAction(tracker, range);
203
204     group.add(localShowPrevAction);
205     group.add(localShowNextAction);
206     group.add(rollback);
207     group.add(showDiff);
208     group.add(copyRange);
209
210
211     final JComponent editorComponent = editor.getComponent();
212     EmptyAction.setupAction(localShowPrevAction, "VcsShowPrevChangeMarker", editorComponent);
213     EmptyAction.setupAction(localShowNextAction, "VcsShowNextChangeMarker", editorComponent);
214     EmptyAction.setupAction(rollback, IdeActions.SELECTED_CHANGES_ROLLBACK, editorComponent);
215     EmptyAction.setupAction(showDiff, "ChangesView.Diff", editorComponent);
216     EmptyAction.setupAction(copyRange, IdeActions.ACTION_COPY, editorComponent);
217
218
219     final JComponent toolbar =
220       ActionManager.getInstance().createActionToolbar(ActionPlaces.FILEHISTORY_VIEW_TOOLBAR, group, true).getComponent();
221
222     final Color background = ((EditorEx)editor).getBackgroundColor();
223     final Color foreground = editor.getColorsScheme().getColor(EditorColors.CARET_COLOR);
224     toolbar.setBackground(background);
225
226     toolbar
227       .setBorder(new ColoredSideBorder(foreground, foreground, range.getType() != Range.INSERTED ? null : foreground, foreground, 1));
228
229     final JPanel component = new JPanel(new BorderLayout());
230     component.setOpaque(false);
231
232     final JPanel toolbarPanel = new JPanel(new BorderLayout());
233     toolbarPanel.setOpaque(false);
234     toolbarPanel.add(toolbar, BorderLayout.WEST);
235     JPanel emptyPanel = new JPanel();
236     emptyPanel.setOpaque(false);
237     toolbarPanel.add(emptyPanel, BorderLayout.CENTER);
238     MouseAdapter listener = new MouseAdapter() {
239       @Override
240       public void mousePressed(MouseEvent e) {
241         editor.getContentComponent().dispatchEvent(SwingUtilities.convertMouseEvent(e.getComponent(), e, editor.getContentComponent()));
242       }
243
244       @Override
245       public void mouseClicked(MouseEvent e) {
246         editor.getContentComponent().dispatchEvent(SwingUtilities.convertMouseEvent(e.getComponent(), e, editor.getContentComponent()));
247       }
248
249       public void mouseReleased(final MouseEvent e) {
250         editor.getContentComponent().dispatchEvent(SwingUtilities.convertMouseEvent(e.getComponent(), e, editor.getContentComponent()));
251       }
252     };
253     emptyPanel.addMouseListener(listener);
254
255     component.add(toolbarPanel, BorderLayout.NORTH);
256
257
258     if (range.getType() != Range.INSERTED) {
259       final DocumentEx doc = (DocumentEx)tracker.getVcsDocument();
260       final EditorEx uEditor = (EditorEx)EditorFactory.getInstance().createViewer(doc, tracker.getProject());
261       final EditorHighlighter highlighter =
262         EditorHighlighterFactory.getInstance().createEditorHighlighter(tracker.getProject(), getFileName(tracker.getDocument()));
263       uEditor.setHighlighter(highlighter);
264
265       final EditorFragmentComponent editorFragmentComponent =
266         EditorFragmentComponent.createEditorFragmentComponent(uEditor, range.getVcsLine1(), range.getVcsLine2(), false, false);
267
268       component.add(editorFragmentComponent, BorderLayout.CENTER);
269
270       EditorFactory.getInstance().releaseEditor(uEditor);
271     }
272
273
274     final List<AnAction> actionList = ActionUtil.getActions(editorComponent);
275     final LightweightHint lightweightHint = new LightweightHint(component);
276     HintListener closeListener = new HintListener() {
277       public void hintHidden(final EventObject event) {
278         actionList.remove(rollback);
279         actionList.remove(showDiff);
280         actionList.remove(copyRange);
281         actionList.remove(localShowPrevAction);
282         actionList.remove(localShowNextAction);
283       }
284     };
285     lightweightHint.addHintListener(closeListener);
286
287     HintManagerImpl.getInstanceImpl().showEditorHint(lightweightHint, editor, point,
288                                                      HintManagerImpl.HIDE_BY_ANY_KEY | HintManagerImpl.HIDE_BY_TEXT_CHANGE |
289                                                      HintManagerImpl.HIDE_BY_SCROLLING,
290                                                      -1, false, new HintHint(editor, point));
291
292     if (!lightweightHint.isVisible()) {
293       closeListener.hintHidden(null);
294     }
295   }
296
297   private static String getFileName(final Document document) {
298     final VirtualFile file = FileDocumentManager.getInstance().getFile(document);
299     if (file == null) return "";
300     return file.getName();
301   }
302
303   public static void moveToRange(final Range range, final Editor editor, final LineStatusTracker tracker) {
304     final Document document = tracker.getDocument();
305     final int lastOffset = document.getLineStartOffset(Math.min(range.getLine2(), document.getLineCount() - 1));
306     editor.getCaretModel().moveToOffset(lastOffset);
307     editor.getScrollingModel().scrollToCaret(ScrollType.CENTER);
308
309     showHint(range, editor, tracker);
310   }
311
312   public static void showHint(final Range range, final Editor editor, final LineStatusTracker tracker) {
313     editor.getScrollingModel().runActionOnScrollingFinished(new Runnable() {
314       public void run() {
315         Point p = editor.visualPositionToXY(editor.getCaretModel().getVisualPosition());
316         final JComponent editorComponent = editor.getContentComponent();
317         final JLayeredPane layeredPane = editorComponent.getRootPane().getLayeredPane();
318         p = SwingUtilities.convertPoint(editorComponent, 0, p.y, layeredPane);
319         showActiveHint(range, editor, p, tracker);
320       }
321     });
322   }
323
324   @Nullable
325   private static Color getDiffColor(@NotNull Range.InnerRange range) {
326     // TODO: we should move color settings from Colors-General to Colors-Diff
327     final EditorColorsScheme globalScheme = EditorColorsManager.getInstance().getGlobalScheme();
328     switch (range.getType()) {
329       case Range.INSERTED:
330         return globalScheme.getColor(EditorColors.ADDED_LINES_COLOR);
331       case Range.DELETED:
332         return globalScheme.getColor(EditorColors.DELETED_LINES_COLOR);
333       case Range.MODIFIED:
334         return globalScheme.getColor(EditorColors.MODIFIED_LINES_COLOR);
335       case Range.EQUAL:
336         return globalScheme.getColor(EditorColors.WHITESPACES_MODIFIED_LINES_COLOR);
337       default:
338         assert false;
339         return null;
340     }
341   }
342
343   @NotNull
344   private static Color getDiffColor(@NotNull Range range) {
345     final EditorColorsScheme globalScheme = EditorColorsManager.getInstance().getGlobalScheme();
346     switch (range.getType()) {
347       case Range.INSERTED:
348         return globalScheme.getAttributes(DiffColors.DIFF_INSERTED).getErrorStripeColor();
349       case Range.DELETED:
350         return globalScheme.getAttributes(DiffColors.DIFF_DELETED).getErrorStripeColor();
351       case Range.MODIFIED:
352         return globalScheme.getAttributes(DiffColors.DIFF_MODIFIED).getErrorStripeColor();
353       default:
354         assert false;
355         return null;
356     }
357   }
358
359   @Nullable
360   private static Color getDiffGutterColor(@NotNull Range range) {
361     final EditorColorsScheme globalScheme = EditorColorsManager.getInstance().getGlobalScheme();
362     switch (range.getType()) {
363       case Range.INSERTED:
364         return globalScheme.getColor(EditorColors.ADDED_LINES_COLOR);
365       case Range.DELETED:
366         return globalScheme.getColor(EditorColors.DELETED_LINES_COLOR);
367       case Range.MODIFIED:
368         return globalScheme.getColor(EditorColors.MODIFIED_LINES_COLOR);
369       default:
370         assert false;
371         return null;
372     }
373   }
374
375   @Nullable
376   private static Color getDiffGutterBorderColor() {
377     final EditorColorsScheme globalScheme = EditorColorsManager.getInstance().getGlobalScheme();
378     return globalScheme.getColor(EditorColors.BORDER_LINES_COLOR);
379   }
380 }