1 package com.atlassian.theplugin.idea.crucible;
2
3 import com.intellij.codeInsight.hint.EditorFragmentComponent;
4 import com.intellij.codeInsight.hint.HintManager;
5 import com.intellij.ide.highlighter.HighlighterFactory;
6 import com.intellij.openapi.actionSystem.*;
7 import com.intellij.openapi.diff.*;
8 import com.intellij.openapi.editor.Document;
9 import com.intellij.openapi.editor.Editor;
10 import com.intellij.openapi.editor.ScrollType;
11 import com.intellij.openapi.editor.colors.EditorColors;
12 import com.intellij.openapi.editor.colors.TextAttributesKey;
13 import com.intellij.openapi.editor.ex.DocumentEx;
14 import com.intellij.openapi.editor.ex.EditorEx;
15 import com.intellij.openapi.editor.ex.EditorGutterComponentEx;
16 import com.intellij.openapi.editor.highlighter.EditorHighlighter;
17 import com.intellij.openapi.editor.impl.EditorImpl;
18 import com.intellij.openapi.editor.markup.ActiveGutterRenderer;
19 import com.intellij.openapi.fileEditor.FileDocumentManager;
20 import com.intellij.openapi.project.Project;
21 import com.intellij.openapi.util.IconLoader;
22 import com.intellij.openapi.util.TextRange;
23 import com.intellij.openapi.vcs.VcsBundle;
24 import com.intellij.openapi.vcs.actions.AbstractVcsAction;
25 import com.intellij.openapi.vcs.actions.VcsContext;
26 import com.intellij.openapi.vcs.ex.Range;
27 import com.intellij.openapi.vfs.VirtualFile;
28 import com.intellij.ui.HintListener;
29 import com.intellij.ui.LightweightHint;
30 import com.intellij.ui.SideBorder2;
31 import com.intellij.util.ui.UIUtil;
32
33 import javax.swing.*;
34 import java.awt.*;
35 import java.awt.event.MouseEvent;
36 import java.util.EventObject;
37 import java.util.Iterator;
38 import java.util.List;
39 import java.util.ListIterator;
40
41
42 public class CrucibleDiffGutterRenderer implements ActiveGutterRenderer {
43 private static final int TWO_PIXEL = 2;
44 private static final int THREE_PIXEL = 3;
45 private static final int FOUR_PIXEL = 4;
46 private static final int INSERTED_RANGE = 2;
47 private static final int DELETED_RANGE = 3;
48 private static final int MODIFIED_RANGE = 1;
49
50 private static final int REFERENCE_TEXT_RANGE = 2;
51 private static final int DISPLAY_TEXT_RANGE = 3;
52
53 private final Range range;
54 private final Document referenceDocument;
55 private final Document displayDocument;
56 private final String fromRevision;
57 private final String toRevision;
58 private final Project project;
59 private List<Range> ranges;
60
61 public CrucibleDiffGutterRenderer(Project project, final List<Range> ranges, final Range range,
62 final Document referenceDocument, final Document displayDocument, final String fromRevision,
63 final String toRevision) {
64 this.project = project;
65 this.ranges = ranges;
66 this.range = range;
67 this.referenceDocument = referenceDocument;
68 this.displayDocument = displayDocument;
69 this.fromRevision = fromRevision;
70 this.toRevision = toRevision;
71 }
72
73 private TextAttributesKey getDiffColor(Range aRange) {
74 switch (aRange.getType()) {
75 case INSERTED_RANGE:
76 return DiffColors.DIFF_INSERTED;
77
78 case DELETED_RANGE:
79 return DiffColors.DIFF_DELETED;
80
81 case MODIFIED_RANGE:
82 return DiffColors.DIFF_MODIFIED;
83 default:
84 return null;
85 }
86 }
87
88 public void paint(Editor anEditor, Graphics g, Rectangle r) {
89 paintGutterFragment(anEditor, g, r, getDiffColor(range));
90
91 }
92
93 private void paintGutterFragment(Editor anEditor, Graphics g, Rectangle r, TextAttributesKey diffAttributeKey) {
94
95 EditorGutterComponentEx gutter = ((EditorEx) anEditor).getGutterComponentEx();
96 g.setColor(anEditor.getColorsScheme().getAttributes(diffAttributeKey).getBackgroundColor());
97 int endX = gutter.getWhitespaceSeparatorOffset();
98 int x = r.x + r.width - TWO_PIXEL;
99 int width = endX - x;
100 if (r.height > 0) {
101 g.fillRect(x, r.y + TWO_PIXEL, width, r.height - FOUR_PIXEL);
102 g.setColor(gutter.getFoldingColor(false));
103 UIUtil.drawLine(g, x, r.y + TWO_PIXEL, x + width, r.y + TWO_PIXEL);
104 UIUtil.drawLine(g, x, r.y + TWO_PIXEL, x, r.y + r.height - THREE_PIXEL);
105 UIUtil.drawLine(g, x, r.y + r.height - THREE_PIXEL, x + width, r.y + r.height - THREE_PIXEL);
106 } else {
107 int[] xPoints = new int[]{x,
108 x,
109 x + width - 1};
110 int[] yPoints = new int[]{r.y - FOUR_PIXEL,
111 r.y + FOUR_PIXEL,
112 r.y};
113 g.fillPolygon(xPoints, yPoints, THREE_PIXEL);
114
115 g.setColor(gutter.getFoldingColor(false));
116 g.drawPolygon(xPoints, yPoints, THREE_PIXEL);
117 }
118 }
119
120 public void doAction(Editor anEditor, MouseEvent e) {
121 e.consume();
122 JComponent comp = (JComponent) e.getComponent();
123 JLayeredPane layeredPane = comp.getRootPane().getLayeredPane();
124 Point point = SwingUtilities
125 .convertPoint(comp, ((EditorEx) anEditor).getGutterComponentEx().getWidth(), e.getY(), layeredPane);
126 showActiveHint(range, anEditor, point);
127 }
128
129
130 public Range getNextRange(Range aRange) {
131 int j = ranges.indexOf(aRange);
132 if (j == ranges.size() - 1) {
133 return null;
134 } else {
135 return ranges.get(j + 1);
136 }
137 }
138
139 public Range getPrevRange(Range aRange) {
140 int j = ranges.indexOf(aRange);
141 if (j == 0) {
142 return null;
143 } else {
144 return ranges.get(j - 1);
145 }
146 }
147
148 public Range getNextRange(int j) {
149 for (Iterator<Range> iterator = ranges.iterator(); iterator.hasNext();) {
150 Range myRange = iterator.next();
151 if (myRange.getOffset2() >= j) {
152 return myRange;
153 }
154 }
155
156 return null;
157 }
158
159 public Range getPrevRange(int j) {
160 for (ListIterator<Range> listiterator = ranges.listIterator(ranges.size()); listiterator.hasPrevious();) {
161 Range myRange = listiterator.previous();
162 if (myRange.getOffset1() <= j) {
163 return myRange;
164 }
165 }
166
167 return null;
168 }
169
170 public VirtualFile getReferenceVirtualFile() {
171 return FileDocumentManager.getInstance().getFile(referenceDocument);
172 }
173
174 public VirtualFile getDisplayVirtualFile() {
175 return FileDocumentManager.getInstance().getFile(displayDocument);
176 }
177
178
179 private TextRange getDisplayTextRange(Range aRange) {
180 return getTextRange(aRange.getType(), aRange.getOffset1(), aRange.getOffset2(), (byte) DISPLAY_TEXT_RANGE,
181 displayDocument);
182 }
183
184 private TextRange getReferenceTextRange(Range aRange) {
185 return getTextRange(aRange.getType(), aRange.getUOffset1(), aRange.getUOffset2(), (byte) REFERENCE_TEXT_RANGE,
186 referenceDocument);
187 }
188
189 private String getReferenceVirtualFileName() {
190 VirtualFile virtualfile = getReferenceVirtualFile();
191 if (virtualfile == null) {
192 return "";
193 } else {
194 return virtualfile.getName();
195 }
196 }
197
198 private static TextRange getTextRange(byte byte0, int j, int k, byte byte1, Document document) {
199 if (byte0 == byte1) {
200 int l;
201 if (j == 0) {
202 l = 0;
203 } else {
204 l = document.getLineEndOffset(j - 1);
205 }
206 return new TextRange(l, l);
207 }
208 int i1 = document.getLineStartOffset(j);
209 int j1 = document.getLineEndOffset(k - 1);
210 if (i1 > 0) {
211 i1--;
212 j1--;
213 }
214 return new TextRange(i1, j1);
215 }
216
217
218 public void moveToRange(final Range aRange, final Editor anEditor, final Document aDisplayDocument) {
219 final int firstOffset = aDisplayDocument
220 .getLineStartOffset(Math.min(aRange.getOffset1(), aDisplayDocument.getLineCount() - 1));
221 anEditor.getCaretModel().moveToOffset(firstOffset);
222 anEditor.getScrollingModel().scrollToCaret(ScrollType.CENTER);
223 anEditor.getScrollingModel().runActionOnScrollingFinished(new Runnable() {
224
225 public void run() {
226 java.awt.Point point = anEditor.visualPositionToXY(anEditor.offsetToVisualPosition(firstOffset));
227 JComponent jcomponent = anEditor.getContentComponent();
228 javax.swing.JLayeredPane jlayeredpane = jcomponent.getRootPane().getLayeredPane();
229 point = SwingUtilities.convertPoint(jcomponent, 0, point.y, jlayeredpane);
230 showActiveHint(aRange, anEditor, point);
231 }
232 });
233 }
234
235 private abstract class ShowChangeMarkerAction extends AbstractVcsAction {
236 protected Range range;
237 protected Editor editor;
238 protected Document document;
239
240 protected abstract Range extractRange(ChangeViewer aHighlighter, int i, Editor anEditor);
241
242 public ShowChangeMarkerAction(final Range range, final Editor anEditor, final Document document) {
243 this.range = range;
244
245 this.editor = anEditor;
246 this.document = document;
247 }
248
249 protected boolean forceSyncUpdate(AnActionEvent anactionevent) {
250 return true;
251 }
252
253 private boolean checkPosition(VcsContext vcscontext) {
254 return range != null;
255 }
256
257 protected void update(VcsContext vcscontext, Presentation presentation) {
258 presentation.setEnabled(checkPosition(vcscontext));
259 }
260
261 protected void actionPerformed(VcsContext vcscontext) {
262 moveToRange(range, editor, document);
263 }
264 }
265
266 private class ShowPrevChangeMarkerAction extends ShowChangeMarkerAction {
267
268 public ShowPrevChangeMarkerAction(Range range, Editor anEditor, Document document) {
269 super(range, anEditor, document);
270 }
271
272 protected Range extractRange(ChangeViewer aHighlighter, int i, Editor anEditor) {
273 return getPrevRange(i);
274 }
275 }
276
277 private class ShowNextChangeMarkerAction extends ShowChangeMarkerAction {
278
279 public ShowNextChangeMarkerAction(Range range, Editor editor, Document document) {
280 super(range, editor, document);
281 }
282
283 protected Range extractRange(ChangeViewer aHighlighter, int i, Editor anEditor) {
284 return getNextRange(i);
285 }
286 }
287
288
289 private class ShowDiffAction extends AnAction {
290 protected final Range myRange;
291 private final Project project;
292 private final Document referenceDocument;
293 private final Document displayDocument;
294
295 public void update(final AnActionEvent e) {
296 e.getPresentation().setEnabled(checkModified() || checkDeleted());
297 }
298
299 private boolean checkDeleted() {
300 return myRange.getType() == DELETED_RANGE;
301 }
302
303 private boolean checkModified() {
304 return myRange.getType() == MODIFIED_RANGE;
305 }
306
307 public void actionPerformed(AnActionEvent anactionevent) {
308 DiffManager.getInstance().getDiffTool().show(prepareDiffRequest());
309 }
310
311 private DiffRequest prepareDiffRequest() {
312 return new DiffRequest(project) {
313
314 public DiffContent[] getContents() {
315 return (new DiffContent[]{
316 getDiffContent(referenceDocument, getReferenceTextRange(myRange),
317 getReferenceVirtualFile()),
318 getDiffContent(displayDocument, getDisplayTextRange(myRange),
319 getDisplayVirtualFile())
320 });
321 }
322
323 public String[] getContentTitles() {
324 return (new String[]{
325 VcsBundle.message("diff.content.title.repository.version", new Object[]{fromRevision}),
326 VcsBundle.message("diff.content.title.repository.version", new Object[]{toRevision})
327 });
328 }
329
330 public String getWindowTitle() {
331 return VcsBundle.message("dialog.title.diff.for.range", new Object[0]);
332 }
333 };
334 }
335
336 private DiffContent getDiffContent(Document document, TextRange textrange, VirtualFile virtualfile) {
337 DocumentContent documentcontent = new DocumentContent(project, document);
338 return new FragmentContent(documentcontent, textrange, project, virtualfile);
339 }
340
341 public ShowDiffAction(Project project, Document referenceDocument, Document displayDocument, Range range) {
342 super(VcsBundle.message("action.name.show.difference"), null, IconLoader.getIcon("/actions/diff.png"));
343 this.project = project;
344 this.referenceDocument = referenceDocument;
345 this.displayDocument = displayDocument;
346 myRange = range;
347 }
348 }
349
350
351 public void showActiveHint(Range aRange, final Editor anEditor, Point point) {
352
353 DefaultActionGroup group = new DefaultActionGroup();
354
355 final AnAction globalShowNextAction = ActionManager.getInstance().getAction("VcsShowNextChangeMarker");
356 final AnAction globalShowPrevAction = ActionManager.getInstance().getAction("VcsShowPrevChangeMarker");
357
358 final ShowPrevChangeMarkerAction localShowPrevAction = new ShowPrevChangeMarkerAction(getPrevRange(aRange),
359 anEditor, displayDocument);
360 final ShowNextChangeMarkerAction localShowNextAction = new ShowNextChangeMarkerAction(getNextRange(aRange),
361 anEditor, displayDocument);
362
363 JComponent editorComponent = anEditor.getComponent();
364
365 localShowNextAction.registerCustomShortcutSet(localShowNextAction.getShortcutSet(), editorComponent);
366 localShowPrevAction.registerCustomShortcutSet(localShowPrevAction.getShortcutSet(), editorComponent);
367
368 group.add(localShowPrevAction);
369 group.add(localShowNextAction);
370
371 localShowNextAction.copyFrom(globalShowNextAction);
372 localShowPrevAction.copyFrom(globalShowPrevAction);
373
374 group.add(new ShowDiffAction(project, referenceDocument, displayDocument, aRange));
375
376 @SuppressWarnings("unchecked")
377 final java.util.List<AnAction> actionList = (java.util.List<AnAction>) editorComponent
378 .getClientProperty(AnAction.ourClientProperty);
379
380 actionList.remove(globalShowPrevAction);
381 actionList.remove(globalShowNextAction);
382
383 JComponent toolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.FILEHISTORY_VIEW_TOOLBAR, group, true)
384 .getComponent();
385
386 final Color background = ((EditorEx) anEditor).getBackroundColor();
387 final Color foreground = anEditor.getColorsScheme().getColor(EditorColors.CARET_COLOR);
388 toolbar.setBackground(background);
389
390 toolbar.setBorder(
391 new SideBorder2(foreground, foreground, aRange.getType() != Range.INSERTED ? null : foreground, foreground, 1));
392
393 JPanel component = new JPanel(new BorderLayout());
394 component.setOpaque(false);
395
396 JPanel toolbarPanel = new JPanel(new BorderLayout());
397 toolbarPanel.setOpaque(false);
398 toolbarPanel.add(toolbar, BorderLayout.WEST);
399 component.add(toolbarPanel, BorderLayout.NORTH);
400
401 if (aRange.getType() != Range.INSERTED) {
402 DocumentEx doc = (DocumentEx) referenceDocument;
403 EditorImpl uEditor = new EditorImpl(doc, true, project);
404 EditorHighlighter highlighter = HighlighterFactory
405 .createHighlighter(project, getReferenceVirtualFileName());
406 uEditor.setHighlighter(highlighter);
407
408 EditorFragmentComponent editorFragmentComponent =
409 EditorFragmentComponent
410 .createEditorFragmentComponent(uEditor, aRange.getUOffset1(), aRange.getUOffset2(), false, false);
411
412 component.add(editorFragmentComponent, BorderLayout.CENTER);
413 }
414
415 LightweightHint lightweightHint = new LightweightHint(component);
416 lightweightHint.addHintListener(new HintListener() {
417 public void hintHidden(EventObject event) {
418 actionList.remove(localShowPrevAction);
419 actionList.remove(localShowNextAction);
420 actionList.add(globalShowPrevAction);
421 actionList.add(globalShowNextAction);
422 }
423 });
424
425 HintManager.getInstance()
426 .showEditorHint(lightweightHint, anEditor, point, HintManager.HIDE_BY_ANY_KEY | HintManager.HIDE_BY_TEXT_CHANGE
427 | HintManager.HIDE_BY_OTHER_HINT | HintManager.HIDE_BY_SCROLLING,
428 -1, false);
429 }
430 }