package org.python.pydev.overview_ruler; import java.lang.ref.WeakReference; import java.util.Arrays; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.resource.StringConverter; import org.eclipse.jface.text.IDocumentExtension4; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.source.IAnnotationAccess; import org.eclipse.jface.text.source.ISharedTextColors; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.custom.StyledTextContent; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseMoveListener; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.graphics.Transform; import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.editors.text.EditorsUI; import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants; import org.python.pydev.core.log.Log; import org.python.pydev.core.structure.FastStack; import com.aptana.shared_core.utils.RunInUiThread; public class MinimapOverviewRuler extends CopiedOverviewRuler { /** * Removes whitespaces and tabs at the end of the string. */ public static String rightTrim(final String input) { int len = input.length(); int st = 0; int off = 0; while ((st < len) && (input.charAt(off + len - 1) <= ' ')) { len--; } return input.substring(0, len); } /** * Helper to get the first char position in a string. */ public static int getFirstCharPosition(String src) { int i = 0; boolean breaked = false; while (i < src.length()) { if (Character.isWhitespace(src.charAt(i)) == false && src.charAt(i) != '\t') { i++; breaked = true; break; } i++; } if (!breaked) { i++; } return (i - 1); } /** * Lock to access the stacked parameters. */ private final static Object lockStackedParameters = new Object(); /** * Redraws a temporary image in the background and after that's finished, replaces the new base image and asks * for a new redraw. */ private final class RedrawJob extends Job { private RedrawJob(String name) { super(name); this.setPriority(Job.SHORT); this.setSystem(true); } private FastStack<Object[]> stackedParameters = new FastStack<Object[]>(20); /** * Note: the GC and marginColor need to be disposed after they're used. */ private void setParameters(GC gc, Color styledTextForeground, Point size, StyledTextContent content, int lineCount, int marginCols, Color marginColor, int spacing, int imageHeight, Transform transform, Image tmpImage) { synchronized (lockStackedParameters) { stackedParameters.push(new Object[] { gc, styledTextForeground, size, content, lineCount, marginCols, marginColor, spacing, imageHeight, transform, tmpImage }); } } /** * Redraws the base image (i.e.: lines) */ private void redrawBaseImage(GC gc, Color styledTextForeground, Point size, StyledTextContent content, int lineCount, int marginCols, Color marginColor, int spacing, int imageHeight, Transform transform, IProgressMonitor monitor) { gc.setForeground(styledTextForeground); gc.setAlpha(200); gc.setTransform(transform); int x1 = 0, y1 = 0, x2 = 0, y2 = 0; for (int i = 0; i < lineCount; i++) { if (monitor.isCanceled()) { return; } String line = rightTrim(content.getLine(i)); //if(lineCount > 5000){ // if(!PySelection.matchesClassLine(line) && !PySelection.matchesFunctionLine(line)){ // y1 = y2 = y1 + spacing; // continue; //Only print lines related to classes/functions // } //} x1 = getFirstCharPosition(line); x2 = line.length(); if (x2 > 0) { gc.drawLine(x1, y1, x2, y2); } y1 = y2 = y1 + spacing; } if (monitor.isCanceled()) { return; } gc.setForeground(marginColor); gc.setBackground(marginColor); gc.drawLine(marginCols, 0, marginCols, imageHeight); } /** * Calls the method to draw image and later replaces the base image to be used and calls a new redraw. */ @Override protected IStatus run(IProgressMonitor monitor) { Object[] parameters; synchronized (lockStackedParameters) { parameters = stackedParameters.pop(); disposeStackedParameters(); } GC gc = (GC) parameters[0]; Color styledTextForeground = (Color) parameters[1]; Point size = (Point) parameters[2]; StyledTextContent content = (StyledTextContent) parameters[3]; int lineCount = (Integer) parameters[4]; int marginCols = (Integer) parameters[5]; Color marginColor = (Color) parameters[6]; int spacing = (Integer) parameters[7]; int imageHeight = (Integer) parameters[8]; Transform transform = (Transform) parameters[9]; final Image image = (Image) parameters[10]; try { redrawBaseImage(gc, styledTextForeground, size, content, lineCount, marginCols, marginColor, spacing, imageHeight, transform, monitor); } catch (Throwable e) { Log.log(e); } finally { gc.dispose(); marginColor.dispose(); } boolean disposeOfImage = true; try { if (!monitor.isCanceled()) { final Canvas c = fCanvas; if (c != null && !c.isDisposed()) { disposeOfImage = false; RunInUiThread.async(new Runnable() { public void run() { //The baseImage should only be disposed in the UI thread (so, no locks are needed to //replace/dispose the image) if (baseImage != null && !baseImage.isDisposed()) { baseImage.dispose(); } if (c != null && !c.isDisposed()) { baseImage = image; MinimapOverviewRuler.this.redraw(); } else { image.dispose(); } } }); } } } finally { if (disposeOfImage) { image.dispose(); } } return Status.OK_STATUS; } /** * Disposes of any parameters in the stack that need an explicit dispose(). */ public void disposeStackedParameters() { synchronized (lockStackedParameters) { while (stackedParameters.size() > 0) { Object[] disposeOfParameters = stackedParameters.pop(); GC gc = (GC) disposeOfParameters[0]; Color marginColor = (Color) disposeOfParameters[6]; gc.dispose(); marginColor.dispose(); } } } } /** * Helper to deal with the drag. */ private boolean mousePressed = false; public MinimapOverviewRuler(IAnnotationAccess annotationAccess, ISharedTextColors sharedColors) { super(annotationAccess, 120, sharedColors); } private WeakReference<StyledText> styledText; private final PaintListener paintListener = new PaintListener() { public void paintControl(PaintEvent e) { if (!fCanvas.isDisposed()) { MinimapOverviewRuler.this.redraw(); } } }; @Override protected void doubleBufferPaint(GC dest) { if (fTextViewer != null) { fCanvas.setBackground(fTextViewer.getTextWidget().getBackground()); fCanvas.setForeground(fTextViewer.getTextWidget().getForeground()); } super.doubleBufferPaint(dest); } @Override public Control createControl(Composite parent, ITextViewer textViewer) { Control ret = super.createControl(parent, textViewer); fCanvas.addMouseListener(new MouseAdapter() { public void mouseDown(MouseEvent event) { onMouseDown(event); } @Override public void mouseUp(MouseEvent event) { onMouseUp(event); } }); fCanvas.addMouseMoveListener(new MouseMoveListener() { public void mouseMove(MouseEvent event) { onMouseMove(event); } }); fCanvas.addDisposeListener(new DisposeListener() { public void widgetDisposed(DisposeEvent event) { onDispose(); } }); StyledText textWidget = textViewer.getTextWidget(); if (!textWidget.isDisposed()) { styledText = new WeakReference<StyledText>(textWidget); textWidget.addPaintListener(paintListener); } return ret; } private void onMouseDown(MouseEvent event) { mousePressed = true; } private void onMouseUp(MouseEvent event) { mousePressed = false; } private void onMouseMove(MouseEvent event) { if (mousePressed) { event.button = 1; super.handleMouseDown(event); } } private void onDispose() { try { if (baseImage != null && !baseImage.isDisposed()) { baseImage.dispose(); baseImage = null; } if (lastImage != null && !lastImage.isDisposed()) { lastImage.dispose(); lastImage = null; } if (styledText != null) { StyledText textWidget = styledText.get(); if (textWidget != null && !textWidget.isDisposed()) { textWidget.removePaintListener(paintListener); } } } catch (Throwable e) { Log.log(e); } try { redrawJob.cancel(); redrawJob.disposeStackedParameters(); } catch (Throwable e) { Log.log(e); } } private volatile Image baseImage; private volatile Image lastImage; private Object[] cacheKey; private final RedrawJob redrawJob = new RedrawJob("Redraw overview ruler"); @Override protected void doPaint1(GC paintGc) { //Draw the minimap if (fTextViewer != null) { IDocumentExtension4 document = (IDocumentExtension4) fTextViewer.getDocument(); if (document != null) { final StyledText styledText = fTextViewer.getTextWidget(); final Point size = fCanvas.getSize(); if (size.x != 0 && size.y != 0) { final StyledTextContent content = styledText.getContent(); final int lineCount = content.getLineCount(); IPreferenceStore preferenceStore = EditorsUI.getPreferenceStore(); final int marginCols = preferenceStore .getInt(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_PRINT_MARGIN_COLUMN); String strColor = preferenceStore .getString(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_PRINT_MARGIN_COLOR); RGB marginRgb = StringConverter.asRGB(strColor); Color marginColor = new Color(Display.getCurrent(), marginRgb); Color black = new Color(Display.getCurrent(), new RGB(0, 0, 0)); int maxChars = (int) (marginCols + (marginCols * 0.1)); final int spacing = 1; final int imageHeight = lineCount * spacing; int imageWidth = maxChars; Object[] currCacheKey = new Object[] { document.getModificationStamp(), size.x, size.y, styledText.getForeground(), styledText.getBackground(), marginCols, marginRgb }; double scaleX = size.x / (double) imageWidth; double scaleY = size.y / (double) imageHeight; final Transform transform = new Transform(Display.getCurrent()); transform.scale((float) scaleX, (float) scaleY); if (baseImage == null || !Arrays.equals(this.cacheKey, currCacheKey)) { this.cacheKey = currCacheKey; Image tmpImage = new Image(Display.getCurrent(), size.x, size.y); final GC gc = new GC(tmpImage); gc.setAdvanced(true); gc.setAntialias(SWT.ON); gc.setBackground(styledText.getBackground()); gc.setForeground(styledText.getBackground()); gc.fillRectangle(0, 0, size.x, size.y); final Color styledTextForeground = styledText.getForeground(); final Color marginColor2 = new Color(Display.getCurrent(), marginRgb); redrawJob.cancel(); redrawJob.setParameters(gc, styledTextForeground, size, content, lineCount, marginCols, marginColor2, spacing, imageHeight, transform, tmpImage); redrawJob.schedule(); } try { if (baseImage != null && !baseImage.isDisposed()) { if (lastImage != null && !lastImage.isDisposed()) { lastImage.dispose(); } Image image = new Image(Display.getCurrent(), size.x, size.y); GC gc2 = new GC(image); try { gc2.drawImage(baseImage, 0, 0); Rectangle clientArea = styledText.getClientArea(); int top = styledText.getLineIndex(0); int bottom = styledText.getLineIndex(clientArea.height) + 1; float rect[] = new float[] { 0, top * spacing, imageWidth, (bottom * spacing) - (top * spacing) }; transform.transform(rect); gc2.setLineWidth(3); gc2.setAlpha(150); gc2.fillRectangle(Math.round(rect[0]), Math.round(rect[1]), Math.round(rect[2]), Math.round(rect[3])); gc2.setAlpha(255); gc2.drawRectangle(Math.round(rect[0]), Math.round(rect[1]), Math.round(rect[2]), Math.round(rect[3])); gc2.setForeground(black); gc2.drawRectangle(0, 0, size.x, size.y); } finally { gc2.dispose(); } lastImage = image; } if (lastImage != null && !lastImage.isDisposed()) { paintGc.drawImage(lastImage, 0, 0); } } finally { marginColor.dispose(); black.dispose(); } } } } super.doPaint1(paintGc); } }