View Javadoc

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(); // shall be EditorGutterComponent, cast is safe.
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 }