package com.yoursway.swt.styledtext.extended;
import org.eclipse.swt.custom.ExtendedModifyEvent;
import org.eclipse.swt.custom.ExtendedModifyListener;
import org.eclipse.swt.custom.ST;
import org.eclipse.swt.custom.VerifyKeyListener;
import org.eclipse.swt.custom.YourSwayStyledTextInternal;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Widget;
import com.yoursway.utils.annotations.CallFromAnyThread_NonReentrant;
import com.yoursway.utils.annotations.Reentrant_CallFromAnyThread;
import com.yoursway.utils.annotations.UseFromUIThread;
@UseFromUIThread
public class ExtendedStyledText {
private final YourSwayStyledTextInternal internal; //? rename to ExtendedTextWidget widget
public ExtendedStyledText(Composite parent, int style) {
internal = new YourSwayStyledTextInternal(parent, style);
}
public void addExtendedModifyListener(final ExtendedModifyListener listener) {
//! several listeners
internal.addExtendedModifyListener(new ExtendedModifyListener() {
public void modifyText(ExtendedModifyEvent e) {
Widget _widget = e.widget;
int _length = e.length;
String _replacedText = e.replacedText;
int _start = e.start;
e.widget = null;
e.length = textWithoutInsertions(_start, _length).length();
e.replacedText = replacedTextWithoutInsertions(_replacedText, _start, _length);
e.start = externalOffset(_start);
if (e.length != 0 || e.replacedText.length() != 0) {
listener.modifyText(e);
}
//? check e fields
e.widget = _widget;
e.length = _length;
e.replacedText = _replacedText;
e.start = _start;
}
private String textWithoutInsertions(int _start, int _length) {
if (_length == 0)
return "";
int end = _start + _length - 1;
if (insertionAtEdge(_start, _length))
end--;
return withoutInsets(internal.getText(_start, end));
}
private String replacedTextWithoutInsertions(String _replacedText, int _start, int _length) {
String text = _replacedText;
if (insertionAtEdge(_start, _length))
text = text.substring(0, text.length() - 1);
return withoutInsets(text);
}
private boolean insertionAtEdge(int _start, int _length) {
int i = _start + _length;
return (internal.getCharCount() > i)
&& (internal.getText(i, i).equals(internal.insetPlaceholder()));
}
});
}
public void addVerifyKeyListener(final VerifyKeyListener listener) {
//! several listeners
internal.addVerifyKeyListener(new VerifyKeyListener() {
public void verifyKey(VerifyEvent e) {
//? change fields
listener.verifyKey(e);
//? change fields
}
});
}
private int externalLineIndex(int internalLineIndex) {
return internalLineIndex - insetLinesAbove(internalLineIndex, false);
}
private int internalLineIndex(int externalLineIndex) {
int internalLineIndex = externalLineIndex + insetLinesAbove(externalLineIndex, true);
if (internal.isInsetLine(internalLineIndex))
throw new AssertionError("External line can't be insertion line.");
return internalLineIndex;
}
private int insetLinesAbove(int lineIndex, boolean external) {
int workingLineIndex = lineIndex;
int count = 0;
for (int i = 0; i <= workingLineIndex; i++) {
if (internal.isInsetLine(i)) {
count++;
if (external)
workingLineIndex++;
}
}
return count;
}
private int internalOffset(int externalOffset) {
//? ineffective //> by lines
int workingOffset = externalOffset;
String text = internal.getText();
char p = internal.insetPlaceholder().charAt(0);
for (int i = 0; i <= workingOffset; i++) {
if (text.length() > i && text.charAt(i) == p) {
workingOffset += 2;
//! magic 2 == ("\n" + insertionPlaceholder).length()
}
}
return workingOffset;
}
private int externalOffset(int internalOffset) {
//? ineffective
int workingOffset = internalOffset;
String text = internal.getText();
char p = internal.insetPlaceholder().charAt(0);
if (text.length() > internalOffset && text.charAt(internalOffset) == p)
workingOffset--;
return withoutInsets(text.substring(0, workingOffset)).length();
}
public int caretLine() {
return externalLineIndex(internal.caretLine());
}
public String getLine(int lineIndex) {
return internal.getLine(internalLineIndex(lineIndex));
}
public Point selectedLines() {
Point internalLines = internal.selectedLines();
int x = externalLineIndex(internalLines.x);
int y = externalLineIndex(internalLines.y);
return new Point(x, y);
}
public void setFont(Font font) {
internal.setFont(font);
}
public void addInset(int lineIndex, Inset inset) {
internal.addInset(internalLineIndex(lineIndex), inset);
}
public void append(String string) {
internal.append(string);
}
public Inset existingInset(int lineIndex) {
return internal.existingInset(internalLineIndex(lineIndex));
}
public int getCharCount() {
return externalOffset(internal.getCharCount()); //! it works :) change name?
}
public int getLineAtOffset(int offset) {
return externalLineIndex(internal.getLineAtOffset(internalOffset(offset)));
}
public boolean inLastLine() {
return caretLine() == getLineCount() - 1;
}
public boolean lineHasInset(int lineIndex) {
return internal.lineHasInset(internalLineIndex(lineIndex));
}
public boolean removeInset(int lineIndex) {
return internal.removeInset(internalLineIndex(lineIndex));
}
public String getText(int start, int end) {
return internal.getText(internalOffset(start), internalOffset(end));
}
public String getSelectionText() {
return withoutInsets(internal.getSelectionText());
}
@Reentrant_CallFromAnyThread
private String withoutInsets(String text) {
return text.replace("\n" + internal.insetPlaceholder(), "");
}
public void setSelection(int start) {
internal.setSelection(internalOffset(start));
}
public int getLineCount() {
return externalLineIndex(internal.getLineCount()); //! it works :) change name?
}
public void lineDown() {
internal.invokeAction(ST.LINE_DOWN);
if (internal.inInsetLine())
internal.invokeAction(ST.LINE_DOWN);
}
public Color getBackground() {
return internal.getBackground();
}
public Rectangle getClientArea() {
return internal.getClientArea();
}
public Shell getShell() {
return internal.getShell();
}
@CallFromAnyThread_NonReentrant
public Display getDisplay() {
return internal.getDisplay();
}
}