/*******************************************************************************
* Copyright (c) 2004, 2008 John Krasnay and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* John Krasnay - initial API and implementation
*******************************************************************************/
package net.sf.vex.layout;
import net.sf.vex.core.ColorResource;
import net.sf.vex.core.FontResource;
import net.sf.vex.core.Graphics;
import net.sf.vex.css.Styles;
import net.sf.vex.dom.Document;
import net.sf.vex.dom.Element;
import net.sf.vex.dom.Text;
/**
* A TextBox that gets its text from the document.
* Represents text which is editable within the VexWidget.
*/
public class DocumentTextBox extends TextBox {
private int startRelative;
private int endRelative;
/**
* Class constructor, accepting a Text object.
* @param context LayoutContext in use
* @param element Element being used
* @param text
*/
public DocumentTextBox(LayoutContext context, Element element, Text text) {
this(context, element, text.getStartOffset(), text.getEndOffset());
}
/**
* Class constructor.
*
* @param context LayoutContext used to calculate the box's size.
* @param element Element that directly contains the text.
* @param startOffset start offset of the text
* @param endOffset end offset of the text
*/
public DocumentTextBox(LayoutContext context, Element element, int startOffset, int endOffset) {
super(element);
if (startOffset >= endOffset) {
throw new IllegalStateException("DocumentTextBox: startOffset (" + startOffset + ") >= endOffset (" + endOffset + ")");
}
this.startRelative = startOffset - element.getStartOffset();
this.endRelative = endOffset - element.getStartOffset();
this.calculateSize(context);
if (this.getText().length() < (endOffset - startOffset)) {
throw new IllegalStateException();
}
}
/**
* @see net.sf.vex.layout.Box#getEndOffset()
*/
public int getEndOffset() {
if (this.endRelative == -1) {
return -1;
} else {
return this.getElement().getStartOffset() + this.endRelative - 1;
}
}
/**
* @see net.sf.vex.layout.Box#getStartOffset()
*/
public int getStartOffset() {
if (this.startRelative == -1) {
return -1;
} else {
return this.getElement().getStartOffset() + this.startRelative;
}
}
/**
* @see net.sf.vex.layout.TextBox#getText()
*/
public String getText() {
Document doc = this.getElement().getDocument();
return doc.getText(this.getStartOffset(), this.getEndOffset() + 1);
}
/**
* @see net.sf.vex.layout.Box#hasContent()
*/
public boolean hasContent() {
return true;
}
/**
* @see net.sf.vex.layout.Box#paint(net.sf.vex.layout.LayoutContext, int, int)
*/
public void paint(LayoutContext context, int x, int y) {
Styles styles = context.getStyleSheet().getStyles(this.getElement());
Graphics g = context.getGraphics();
FontResource font = g.createFont(styles.getFont());
FontResource oldFont = g.setFont(font);
ColorResource foreground = g.createColor(styles.getColor());
ColorResource oldForeground = g.setColor(foreground);
// ColorResource background = g.createColor(styles.getBackgroundColor());
// ColorResource oldBackground = g.setBackgroundColor(background);
char[] chars = this.getText().toCharArray();
if (chars.length < this.getEndOffset() - this.getStartOffset()) {
throw new IllegalStateException();
}
if (chars.length == 0) {
throw new IllegalStateException();
}
int start = 0;
int end = chars.length;
if (chars[end - 1] == NEWLINE_CHAR) {
end--;
}
int selStart = context.getSelectionStart() - this.getStartOffset();
selStart = Math.min(Math.max(selStart, start), end);
int selEnd = context.getSelectionEnd() - this.getStartOffset();
selEnd = Math.min(Math.max(selEnd, start), end);
// text before selection
if (start < selStart) {
g.drawChars(chars, start, selStart - start, x, y);
String s = new String(chars, start, selStart - start);
paintTextDecoration(context, styles, s, x, y);
}
// text after selection
if (selEnd < end) {
int x1 = x + g.charsWidth(chars, 0, selEnd);
g.drawChars(chars, selEnd, end - selEnd, x1, y);
String s = new String(chars, selEnd, end - selEnd);
paintTextDecoration(context, styles, s, x1, y);
}
// text within selection
if (selStart < selEnd) {
String s = new String(chars, selStart, selEnd - selStart);
int x1 = x + g.charsWidth(chars, 0, selStart);
this.paintSelectedText(context, s, x1, y);
paintTextDecoration(context, styles, s, x1, y);
}
g.setFont(oldFont);
g.setColor(oldForeground);
// g.setBackgroundColor(oldBackground);
font.dispose();
foreground.dispose();
// background.dispose();
}
/**
* @see net.sf.vex.layout.TextBox#splitAt(int)
*/
public Pair splitAt(LayoutContext context, int offset) {
if (offset < 0 || offset > (this.endRelative - this.startRelative)) {
throw new IllegalStateException();
}
int split = this.getStartOffset() + offset;
DocumentTextBox left;
if (offset == 0) {
left = null;
} else {
left = new DocumentTextBox(context, this.getElement(), this.getStartOffset(), split);
}
InlineBox right;
if (split == this.getEndOffset() + 1) {
right = null;
} else {
right = new DocumentTextBox(context, this.getElement(), split, this.getEndOffset() + 1);
}
return new Pair(left, right);
}
/**
* @see net.sf.vex.layout.Box#viewToModel(net.sf.vex.layout.LayoutContext, int, int)
*/
public int viewToModel(LayoutContext context, int x, int y) {
Graphics g = context.getGraphics();
Styles styles = context.getStyleSheet().getStyles(this.getElement());
FontResource font = g.createFont(styles.getFont());
FontResource oldFont = g.setFont(font);
char[] chars = this.getText().toCharArray();
if (this.getWidth() <= 0) {
return this.getStartOffset();
}
// first, get an estimate based on x / width
int offset = (x / this.getWidth()) * chars.length;
offset = Math.max(0, offset);
offset = Math.min(chars.length, offset);
int delta = Math.abs(x - g.charsWidth(chars, 0, offset));
// Search backwards
while (offset > 0) {
int newDelta = Math.abs(x - g.charsWidth(chars, 0, offset-1));
if (newDelta > delta) {
break;
}
delta = newDelta;
offset--;
}
// Search forwards
while (offset < chars.length-1) {
int newDelta = Math.abs(x - g.charsWidth(chars, 0, offset+1));
if (newDelta > delta) {
break;
}
delta = newDelta;
offset++;
}
g.setFont(oldFont);
font.dispose();
return this.getStartOffset() + offset;
}
}