package net.jeeeyul.eclipse.themes.css.internal;
import java.util.HashSet;
import net.jeeeyul.swtend.SWTExtensions;
import net.jeeeyul.swtend.ui.HSB;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.ILock;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.ui.progress.UIJob;
/**
* Renders text underlines for {@link StyledText}. Client can customize line
* color by {@link #setLineColor(HSB)} and customize line style through
* {@link #setLineStyle(int)}.
*
* @since 2.1
* @author Jeeeyul
*/
public class EditorLineSupport {
private static final SWTExtensions swtToolkit = SWTExtensions.INSTANCE;
private static ILock GLOBAL_LOCK = Job.getJobManager().newLock();
private static HashSet<EditorLineSupport> INSTANCES = new HashSet<EditorLineSupport>();
/**
* Dispose all {@link EditorLineSupport} instances.
*/
public static void disposeAll() {
GLOBAL_LOCK.acquire();
EditorLineSupport[] array = INSTANCES.toArray(new EditorLineSupport[INSTANCES.size()]);
for (EditorLineSupport each : array) {
each.dispose();
}
INSTANCES.clear();
GLOBAL_LOCK.release();
}
/**
* Creates a new {@link EditorLineSupport}.
*
* @param client
* A {@link StyledText} widget to draw lines.
* @return {@link EditorLineSupport} instance for given {@link StyledText}
* widget.
*/
public static EditorLineSupport get(StyledText client) {
GLOBAL_LOCK.acquire();
EditorLineSupport liner = (EditorLineSupport) client.getData(EditorLineSupport.class.getCanonicalName());
if (liner == null) {
liner = new EditorLineSupport(client);
}
GLOBAL_LOCK.release();
return liner;
}
private StyledText client;
private HSB lineColor = HSB.BLACK;
private int lineStyle = SWT.NONE;
private Image backgroundImage;
private UIJob refreshJob;
private boolean isDisposed = false;
private Listener listener = new Listener() {
@Override
public void handleEvent(Event event) {
if (!isDisposed) {
doHandle(event);
}
}
};
private EditorLineSupport(StyledText client) {
this.client = client;
INSTANCES.add(this);
client.setData(EditorLineSupport.class.getCanonicalName(), this);
hook();
}
private Image createNewBackgroundImage() {
int width = 24;
int height = client.getLineHeight();
int offset = height - 1;
if (client.getVerticalBar() != null) {
offset = height - (client.getVerticalBar().getSelection() % height) - 1;
}
Image image = new Image(client.getDisplay(), width, height);
GC gc = new GC(image);
gc.setBackground(client.getBackground());
gc.fillRectangle(0, 0, width, height);
gc.setForeground(SWTExtensions.INSTANCE.toAutoDisposeColor(lineColor));
gc.setAntialias(SWT.OFF);
switch (lineStyle) {
case SWT.LINE_DASH:
gc.setLineStyle(SWT.LINE_DASH);
gc.setLineDash(new int[] { 2, 1 });
break;
case SWT.LINE_DOT:
gc.setLineStyle(SWT.LINE_DOT);
gc.setLineDash(new int[] { 1, 2 });
break;
case SWT.LINE_SOLID:
break;
default:
break;
}
gc.drawLine(0, offset, width, offset);
gc.dispose();
return image;
}
/**
* Dispose {@link EditorLineSupport} instance and stop to draw underlines
* for it's {@link StyledText}.
*/
public void dispose() {
if (isDisposed) {
return;
}
GLOBAL_LOCK.acquire();
if (client != null && !client.isDisposed()) {
client.setBackgroundImage(null);
client.setData(EditorLineSupport.class.getCanonicalName(), null);
unhook();
}
swtToolkit.safeDispose(backgroundImage);
INSTANCES.remove(this);
isDisposed = true;
GLOBAL_LOCK.release();
}
private void doHandle(Event event) {
switch (event.type) {
case SWT.Dispose:
dispose();
break;
case SWT.Selection:
invalidate();
break;
}
}
private void doRefresh() {
if (client.isDisposed()) {
return;
}
swtToolkit.safeDispose(backgroundImage);
if (lineStyle == SWT.NONE) {
client.setBackgroundImage(null);
dispose();
} else {
backgroundImage = createNewBackgroundImage();
client.setBackgroundImage(backgroundImage);
}
}
/**
*
* @return Color of underline.
*/
public HSB getLineColor() {
return lineColor;
}
/**
*
* @return line style flag.
*
* @see SWT#LINE_DASH
* @see SWT#LINE_SOLID
* @see SWT#LINE_DOT
* @see SWT#NONE
*/
public int getLineStyle() {
return lineStyle;
}
private UIJob getRefreshJob() {
if (refreshJob == null) {
refreshJob = new UIJob(Display.getDefault(), "Refersh Editor Line") {
@Override
public IStatus runInUIThread(IProgressMonitor monitor) {
doRefresh();
return Status.OK_STATUS;
}
};
refreshJob.setUser(false);
refreshJob.setSystem(true);
}
return refreshJob;
}
private void hook() {
client.addListener(SWT.Dispose, listener);
if (client.getVerticalBar() != null)
client.getVerticalBar().addListener(SWT.Selection, listener);
}
private void invalidate() {
getRefreshJob().schedule();
}
/**
* Sets color of underline.
*
* @param lineColor
* color for under line.
*/
public void setLineColor(HSB lineColor) {
if (this.lineColor == lineColor) {
return;
} else if (this.lineColor != null) {
if (this.lineColor.equals(lineColor)) {
return;
}
}
this.lineColor = lineColor;
invalidate();
}
/**
*
* @param lineStyle
*
* @see SWT#LINE_DASH
* @see SWT#LINE_SOLID
* @see SWT#LINE_DOT
* @see SWT#NONE
*/
public void setLineStyle(int lineStyle) {
if (this.lineStyle == lineStyle) {
return;
}
this.lineStyle = lineStyle;
invalidate();
}
private void unhook() {
client.removeListener(SWT.Dispose, listener);
if (client.getVerticalBar() != null)
client.getVerticalBar().removeListener(SWT.Selection, listener);
}
}