@@ -1,13 +1,22 @@
package org.python.pydev.overview_ruler;

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;
@@ -18,36 +27,18 @@
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 org.python.pydev.core.uiutils.RunInUiThread;

public class MinimapOverviewRuler extends CopiedOverviewRuler {

private boolean mousePressed = false;

/**
* @param annotationAccess
* @param width
* @param sharedColors
* @param discolorTemporaryAnnotation
*/
public MinimapOverviewRuler(IAnnotationAccess annotationAccess, ISharedTextColors sharedColors) {
super(annotationAccess, 120, sharedColors);

}

@Override
protected void doubleBufferPaint(GC dest) {
if (fTextViewer != null) {
fCanvas.setBackground(fTextViewer.getTextWidget().getBackground());
fCanvas.setForeground(fTextViewer.getTextWidget().getForeground());
}
super.doubleBufferPaint(dest);
}

/**
* Removes whitespaces and tabs at the end of the string.
*/
@@ -62,6 +53,9 @@ public static String rightTrim(final String input) {
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;
@@ -78,14 +72,185 @@ public static int getFirstCharPosition(String src) {
}
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[]>();

/**
* 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);

}

@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);
@@ -97,117 +262,142 @@ public void mouseMove(MouseEvent event) {
onMouseMove(event);
}
});

fCanvas.addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent event) {
onDispose();
}
});

return ret;
}

private void onMouseDown(MouseEvent event) {
mousePressed = true;
}

private void onMouseUp(MouseEvent event) {
mousePressed = false;
}

private void onMouseMove(MouseEvent event) {
if(mousePressed){
if (mousePressed) {
event.button = 1;
super.handleMouseDown(event);
}
}



private void onDispose() {
if (baseImage != null && !baseImage.isDisposed()) {
baseImage.dispose();
baseImage = null;
}
if (lastImage != null && !lastImage.isDisposed()) {
lastImage.dispose();
lastImage = null;
}
redrawJob.cancel();
redrawJob.disposeStackedParameters();
}

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) {
StyledText styledText = fTextViewer.getTextWidget();
Point size = fCanvas.getSize();
if (size.x != 0 && size.y != 0) {

StyledTextContent content = styledText.getContent();
int lineCount = content.getLineCount();
IPreferenceStore preferenceStore = EditorsUI.getPreferenceStore();
int marginCols = preferenceStore.getInt(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_PRINT_MARGIN_COLUMN);
String strColor = preferenceStore.getString(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_PRINT_MARGIN_COLOR);
RGB asRGB = StringConverter.asRGB(strColor);
Color color = new Color(Display.getCurrent(), asRGB);
Color black = new Color(Display.getCurrent(), new RGB(0, 0, 0));

int maxChars = (int) (marginCols + (marginCols * 0.1));
int spacing = 1;
int imageHeight = lineCount * spacing;
int imageWidth = maxChars;

Image image = new Image(Display.getCurrent(), size.x, size.y);

try {
GC gc = new GC(image);
gc.setAdvanced(true);
gc.setAntialias(SWT.ON);
gc.setBackground(styledText.getBackground());
gc.setForeground(styledText.getBackground());
gc.fillRectangle(0, 0, size.x, size.y);

gc.setForeground(styledText.getForeground());


//int newImageHeight = imageHeight;
//if (newImageHeight > size.y) {
// newImageHeight = size.y;
//}
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;

Transform transform = new Transform(Display.getCurrent());
final Transform transform = new Transform(Display.getCurrent());
transform.scale((float) scaleX, (float) scaleY);
gc.setTransform(transform);

try {
int x1 = 0, y1 = 0, x2 = 0, y2 = 0;
if (baseImage == null || !Arrays.equals(this.cacheKey, currCacheKey)) {
this.cacheKey = currCacheKey;

for (int i = 0; i < lineCount; i++) {
String line = rightTrim(content.getLine(i));
x1 = getFirstCharPosition(line);
x2 = line.length();
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);

if (x2 > 0) {
gc.drawLine(x1, y1, x2, y2);
}
y1 = y2 = y1 + spacing;
}
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);

gc.setForeground(color);
gc.setBackground(color);
gc.drawLine(marginCols, 0, marginCols, imageHeight);
redrawJob.schedule();
}

try {
if (baseImage != null && !baseImage.isDisposed()) {
if (lastImage != null && !lastImage.isDisposed()) {
lastImage.dispose();
}

Rectangle clientArea = styledText.getClientArea();
int top = styledText.getLineIndex(0);
int bottom = styledText.getLineIndex(clientArea.height) + 1;
Image image = new Image(Display.getCurrent(), size.x, size.y);
GC gc2 = new GC(image);
try {
gc2.drawImage(baseImage, 0, 0);

float rect[] = new float[] { 0, top * spacing, imageWidth, (bottom * spacing) - (top * spacing) };
transform.transform(rect);
Rectangle clientArea = styledText.getClientArea();
int top = styledText.getLineIndex(0);
int bottom = styledText.getLineIndex(clientArea.height) + 1;

gc.setTransform(null);
float rect[] = new float[] { 0, top * spacing, imageWidth, (bottom * spacing) - (top * spacing) };
transform.transform(rect);

gc.setLineWidth(3);
gc.setAlpha(150);
gc.fillRectangle(Math.round(rect[0]), Math.round(rect[1]), Math.round(rect[2]), Math.round(rect[3]));
gc.setAlpha(255);
gc.drawRectangle(Math.round(rect[0]), Math.round(rect[1]), Math.round(rect[2]), Math.round(rect[3]));
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]));


gc.setForeground(black);
gc.drawRectangle(0, 0, size.x, size.y);
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 {
gc.dispose();
marginColor.dispose();
black.dispose();
}
paintGc.drawImage(image, 0, 0);
} finally {
image.dispose();
color.dispose();
black.dispose();
}

}
}
}
super.doPaint1(paintGc);