/*
* Copyright 2010-2013, Sikuli.org
* Released under the MIT License.
*
* modified RaiMan 2013
*/
package org.sikuli.ide;
import java.awt.*;
import java.util.*;
import java.util.prefs.PreferenceChangeEvent;
import java.util.prefs.PreferenceChangeListener;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.JComponent;
import javax.swing.text.*;
import org.sikuli.script.Settings;
import org.sikuli.script.Debug;
public class EditorViewFactory implements ViewFactory {
@Override
public View create(Element elem) {
String kind = elem.getName();
Debug.log(6, "ViewCreate: " + kind);
if (kind != null) {
if (kind.equals(AbstractDocument.ContentElementName)) {
return new HighlightLabelView(elem);
} else if (kind.equals(AbstractDocument.ParagraphElementName)) {
return new LineBoxView(elem, View.X_AXIS);
} else if (kind.equals(AbstractDocument.SectionElementName)) {
return new SectionBoxView(elem, View.Y_AXIS);
} else if (kind.equals(StyleConstants.ComponentElementName)) {
return new ButtonView(elem);
} else if (kind.equals(StyleConstants.IconElementName)) {
return new IconView(elem);
}
}
// default to text display
return new LabelView(elem);
}
}
//<editor-fold defaultstate="collapsed" desc="Section">
class SectionBoxView extends BoxView {
public SectionBoxView(Element elem, int axis) {
super(elem, axis);
}
@Override
protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
super.layoutMajorAxis(targetSpan, axis, offsets, spans);
int count = getViewCount();
if (count == 0) {
return;
}
int offset = 0;
offsets[0] = 0;
spans[0] = (int) getView(0).getMinimumSpan(View.Y_AXIS);
for (int i = 1; i < count; i++) {
View view = getView(i);
spans[i] = (int) view.getMinimumSpan(View.Y_AXIS);
offset += spans[i - 1];
offsets[i] = offset;
}
return;
}
@Override
protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
super.layoutMinorAxis(targetSpan, axis, offsets, spans);
int count = getViewCount();
for (int i = 0; i < count; i++) {
offsets[i] = 0;
}
}
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="Line">
class LineBoxView extends BoxView {
public LineBoxView(Element elem, int axis) {
super(elem, axis);
}
// for Utilities.getRowStart
@Override
public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
Rectangle r = a.getBounds();
View v = getViewAtPosition(pos, r);
if ((v != null) && (!v.getElement().isLeaf())) {
// Don't adjust the height if the view represents a branch.
return super.modelToView(pos, a, b);
}
int height = r.height;
int y = r.y;
Shape loc = super.modelToView(pos, a, b);
r = loc.getBounds();
r.height = height;
r.y = y;
return r;
}
@Override
protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
super.layoutMinorAxis(targetSpan, axis, offsets, spans);
int maxH = 0;
for (int i = 0; i < spans.length; i++) {
if (spans[i] > maxH) {
maxH = spans[i];
}
}
for (int i = 0; i < offsets.length; i++) {
offsets[i] = (maxH - spans[i]) / 2;
}
}
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="Highlight">
class HighlightLabelView extends LabelView {
static FontMetrics _fMetrics = null;
static String tabStr = nSpaces(PreferencesUser.getInstance().getTabWidth());
private static Map<Pattern, Color> patternColors;
private static Font fontParenthesis;
//<editor-fold defaultstate="collapsed" desc="keyword lists">
private static String[] keywords = {
"and", "del", "for", "is", "raise",
"assert", "elif", "from", "lambda", "return",
"break", "else", "global", "not", "try",
"class", "except", "if", "or", "while",
"continue", "exec", "import", "pass", "yield",
"def", "finally", "in", "print", "with"
};
private static String[] keywordsSikuliClass = {
"Region", "Screen", "Match", "Pattern",
"Location", "VDict", "Env", "Key", "Button", "Finder",
"App", "KeyModifier", "Vision"
};
private static String[] keywordsSikuli = {
"find", "wait", "findAll", "waitVanish", "exists",
"click", "doubleClick", "rightClick", "hover", "wheel",
"type", "paste",
"dragDrop", "drag", "dropAt",
"mouseMove", "mouseDown", "mouseUp",
"keyDown", "keyUp",
"onAppear", "onVanish", "onChange", "observe", "stopObserver",
"popup", "capture", "input", "sleep", "run",
"switchApp", "openApp", "closeApp",
"assertExist", "assertNotExist",
"selectRegion",
"getOS", "getMouseLocation", "exit",
//Region
"right", "left", "above", "below", "nearby", "inside",
"getScreen", "getCenter",
"setX", "setY", "setW", "setH", "setRect", "setROI",
"getX", "getY", "getW", "getH", "getRect", "getROI",
"highlight",
"getNumberScreens", "getBounds",
//Pattern
"similar", "targetOffset", "getLastMatch", "getLastMatches",
"getTargetOffset", "getFilename",
//global
"setAutoWaitTimeout", "setBundlePath", "setShowActions",
"setThrowException",
"hasNext", "next", "destroy", "exact", "offset",
"getOSVersion", "getScore", "getTarget",
"getBundlePath", "getAutoWaitTimeout", "getThrowException",
"getClipboard",
"addImagePath", "removeImagePath", "getImagePath",
//App class
"open", "close", "focus", "window", "focusedWindow",};
private static String[] constantsSikuli = {
"FOREVER",
"KEY_SHIFT", "KEY_CTRL", "KEY_META", "KEY_ALT", "KEY_CMD", "KEY_WIN",
"ENTER", "BACKSPACE", "TAB", "ESC", "UP", "RIGHT", "DOWN", "LEFT",
"PAGE_UP", "PAGE_DOWN", "DELETE", "END", "HOME", "INSERT", "F1",
"F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12",
"F13", "F14", "F15", "SHIFT", "CTRL", "ALT", "META", "CMD", "WIN",
"SCREEN", "MIDDLE",
"WHEEL_UP", "WHEEL_DOWN",
"PRINTSCREEN", "SCROLL_LOCK", "PAUSE", "CAPS_LOCK", "NUM0",
"NUM1", "NUM2", "NUM3", "NUM4", "NUM5", "NUM6", "NUM7", "NUM8", "NUM9",
"SEPARATOR", "NUM_LOCK", "ADD", "MINUS", "MULTIPLY", "DIVIDE"
};
//</editor-fold>
static {
PreferencesUser.getInstance().addPreferenceChangeListener(
new PreferenceChangeListener() {
@Override
public void preferenceChange(PreferenceChangeEvent event) {
//TODO: need to reposition images
if (event.getKey().equals("TAB_WIDTH")) {
tabStr = nSpaces(Integer.parseInt(event.getNewValue()));
}
}
});
fontParenthesis = new Font("Osaka-Mono", Font.PLAIN, 30);
// NOTE: the order is important!
patternColors = new HashMap<Pattern, Color>();
patternColors.put(Pattern.compile("(#:.*$)"), new Color(220, 220, 220));
patternColors.put(Pattern.compile("(#.*$)"), new Color(138, 140, 193));
patternColors.put(Pattern.compile("(\"[^\"]*\"?)"), new Color(128, 0, 0));
patternColors.put(Pattern.compile("(\'[^\']*\'?)"), new Color(128, 0, 0));
patternColors.put(Pattern.compile("\\b([0-9]+)\\b"), new Color(128, 64, 0));
for (int i = 0; i < keywords.length; i++) { patternColors.put(Pattern.compile(
"\\b(" + keywords[i] + ")\\b"), Color.blue);
}
for (int i = 0; i < keywordsSikuli.length; i++) { patternColors.put(Pattern.compile(
"\\b(" + keywordsSikuli[i] + ")\\b"), new Color(63, 127, 127));
}
for (int i = 0; i < keywordsSikuliClass.length; i++) { patternColors.put(Pattern.compile(
"\\b(" + keywordsSikuliClass[i] + ")\\b"), new Color(215, 41, 56));
}
for (int i = 0; i < constantsSikuli.length; i++) { patternColors.put(Pattern.compile(
"\\b(" + constantsSikuli[i] + ")\\b"), new Color(128, 64, 0));
}
}
public HighlightLabelView(Element elm) {
super(elm);
}
private static String nSpaces(int n) {
char[] s = new char[n];
Arrays.fill(s, ' ');
return new String(s);
}
//<editor-fold defaultstate="collapsed" desc="length of line in view">
@Override
public float getMinimumSpan(int axis) {
float f = super.getMinimumSpan(axis);
if (axis == View.X_AXIS) {
f = tabbedWidth();
}
return f;
}
@Override
public float getMaximumSpan(int axis) {
float f = super.getMaximumSpan(axis);
if (axis == View.X_AXIS) {
f = tabbedWidth();
}
return f;
}
@Override
public float getPreferredSpan(int axis) {
float f = super.getPreferredSpan(axis);
if (axis == View.X_AXIS) {
f = tabbedWidth();
}
return f;
}
private float tabbedWidth() {
String str = getText(getStartOffset(), getEndOffset()).toString();
int tab = countTab(str);
if (Settings.isMac()) {
return stringWidth(str) + getRealTabWidth() * tab;
} else {
return stringWidth(str) + getTabWidth() * tab;
}
}
private int countTab(String str) {
int pos = -1;
int count = 0;
while ((pos = str.indexOf('\t', pos + 1)) != -1) {
count++;
}
return count;
}
private int stringWidth(String str) {
if (_fMetrics == null) {
_fMetrics = getGraphics().getFontMetrics();
}
return _fMetrics.stringWidth(str);
}
private float getRealTabWidth() {
final int tabCharWidth;
if (Settings.isMac()) {
tabCharWidth = stringWidth("\t");
} else {
tabCharWidth = stringWidth(" ");
}
return getTabWidth() - tabCharWidth /* + 1f */; //still buggy
}
private int getTabWidth() {
return stringWidth(tabStr);
}
//</editor-fold>
@Override
public int viewToModel(float fx, float fy, Shape a, Position.Bias[] bias) {
bias[0] = Position.Bias.Forward;
Debug.log(9, "viewToModel: " + fx + " " + fy);
String str = getText(getStartOffset(), getEndOffset()).toString();
int left = getStartOffset(), right = getEndOffset();
int pos = 0;
while (left < right) {
Debug.log(9, "viewToModel: " + left + " " + right + " " + pos);
pos = (left + right) / 2;
try {
Shape s = modelToView(pos, a, bias[0]);
float sx = s.getBounds().x;
if (sx > fx) {
right = pos;
} else if (sx < fx) {
left = pos + 1;
} else {
break;
}
} catch (BadLocationException ble) {
break;
}
}
pos = left - 1 >= getStartOffset() ? left - 1 : getStartOffset();
try {
Debug.log(9, "viewToModel: try " + pos);
Shape s1 = modelToView(pos, a, bias[0]);
Shape s2 = modelToView(pos + 1, a, bias[0]);
if (Math.abs(s1.getBounds().x - fx) <= Math.abs(s2.getBounds().x - fx)) {
return pos;
} else {
return pos + 1;
}
} catch (BadLocationException ble) {
}
return pos;
}
@Override
public Shape modelToView(int pos, Shape a, Position.Bias b)
throws BadLocationException {
int start = getStartOffset(), end = getEndOffset();
Debug.log(9, "[modelToView] start: " + start
+ " end: " + end + " pos:" + pos);
String strHead = getText(start, pos).toString();
String strTail = getText(pos, end).toString();
Debug.log(9, "[modelToView] [" + strHead + "]-pos-[" + strTail + "]");
int tabHead = countTab(strHead), tabTail = countTab(strTail);
Debug.log(9, "[modelToView] " + tabHead + " " + tabTail);
Shape s = super.modelToView(pos, a, b);
Rectangle ret = s.getBounds();
Debug.log(9, "[modelToView] super.bounds: " + ret);
if (pos != end) {
ret.x += tabHead * getRealTabWidth();
}
//ret.width += tabTail*tabWidth;
Debug.log(9, "[modelToView] new bounds: " + ret);
return ret;
}
//<editor-fold defaultstate="collapsed" desc="overwritten paint">
@Override
public void paint(Graphics g, Shape shape) {
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
//super.paint(g, shape); // for drawing selection
String text = getText(getStartOffset(), getEndOffset()).toString();
//System.out.println("draw " + text);
SortedMap<Integer, Integer> posMap = new TreeMap<Integer, Integer>();
SortedMap<Integer, Color> colorMap = new TreeMap<Integer, Color>();
buildColorMaps(text, posMap, colorMap);
if (_fMetrics == null) {
_fMetrics = g2d.getFontMetrics();
}
Rectangle alloc = (shape instanceof Rectangle)
? (Rectangle) shape : shape.getBounds();
int sx = alloc.x;
int sy = alloc.y + alloc.height - _fMetrics.getDescent();
int i = 0;
for (Map.Entry<Integer, Integer> entry : posMap.entrySet()) {
int start = entry.getKey();
int end = entry.getValue();
if (i <= start) {
g2d.setColor(Color.black);
String str = text.substring(i, start);
sx = drawString(g2d, str, sx, sy);
} else {
break;
}
g2d.setColor(colorMap.get(start));
i = end;
String str = text.substring(start, i);
/*
* if( str.equals("(") || str.equals(")") )
* sx = drawParenthesis(g2d, str, sx, sy);
* else
*/
sx = drawString(g2d, str, sx, sy);
}
// Paint possible remaining text black
if (i < text.length()) {
g2d.setColor(Color.black);
String str = text.substring(i, text.length());
drawString(g2d, str, sx, sy);
}
}
int drawString(Graphics2D g2d, String str, int x, int y) {
if (str.length() == 0) {
return x;
}
int tabPos = str.indexOf('\t');
if (tabPos != -1) {
x = drawString(g2d, str.substring(0, tabPos), x, y);
x = drawTab(g2d, x, y);
x = drawString(g2d, str.substring(tabPos + 1), x, y);
} else {
g2d.drawString(str, x, y);
x += stringWidth(str);
}
return x;
}
int drawTab(Graphics2D g2d, int x, int y) {
return drawString(g2d, tabStr, x, y);
}
int drawParenthesis(Graphics2D g2d, String str, int x, int y) {
Font origFont = g2d.getFont();
g2d.setFont(fontParenthesis);
g2d.drawString(str, x, y);
x += g2d.getFontMetrics().stringWidth(str);
g2d.setFont(origFont);
return x;
}
void buildColorMaps(String text, Map<Integer, Integer> posMap,
Map<Integer, Color> colorMap) {
// Match all regexes on this snippet, store positions
for (Map.Entry<Pattern, Color> entry : patternColors.entrySet()) {
Matcher matcher = entry.getKey().matcher(text);
while (matcher.find()) {
posMap.put(matcher.start(1), matcher.end());
colorMap.put(matcher.start(1), entry.getValue());
}
}
}
//</editor-fold>
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="Button">
class ButtonView extends ComponentView {
public ButtonView(Element elem) {
super(elem);
Debug.log(6,"ViewCreate: Button");
}
@Override
public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException {
return super.modelToView(pos, a, b);
}
@Override
public void paint(Graphics g, Shape shape) {
JComponent comp = (JComponent) getComponent();
Rectangle alloc = (shape instanceof Rectangle) ? (Rectangle) shape : shape.getBounds();
return;
}
}
//</editor-fold>