blob: a194905c1a05f62cc6c8fbe53907747def0219e2 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2005 IBM Corporation 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.ui.internal.forms.widgets;
import java.text.BreakIterator;
import java.util.*;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.*;
/**
* @version 1.0
* @author
*/
public class TextSegment extends ParagraphSegment {
private String colorId;
private String fontId;
private String text;
protected boolean underline;
private boolean wrapAllowed = true;
protected Vector areaRectangles = new Vector();
private TextFragment[] textFragments;
class AreaRectangle {
Rectangle rect;
int from, to;
public AreaRectangle(Rectangle rect, int from, int to) {
this.rect = rect;
this.from = from;
this.to = to;
}
public boolean contains(int x, int y) {
return rect.contains(x, y);
}
public boolean intersects(Rectangle region) {
return rect.intersects(region);
}
public String getText() {
if (from == 0 && to == -1)
return TextSegment.this.getText();
if (from > 0 && to == -1)
return TextSegment.this.getText().substring(from);
return TextSegment.this.getText().substring(from, to);
}
}
static class SelectionRange {
public int start;
public int stop;
public SelectionRange() {
reset();
}
public void reset() {
start = -1;
stop = -1;
}
}
static class TextFragment {
short index;
short length;
public TextFragment(short index, short length) {
this.index = index;
this.length = length;
}
}
public TextSegment(String text, String fontId) {
this(text, fontId, null);
}
public TextSegment(String text, String fontId, String colorId) {
this.text = cleanup(text);
this.fontId = fontId;
this.colorId = colorId;
}
private String cleanup(String text) {
StringBuffer buf = new StringBuffer();
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
if (c == '\n' || c == '\r' || c == '\f') {
if (i > 0)
buf.append(' ');
} else
buf.append(c);
}
return buf.toString();
}
public void setWordWrapAllowed(boolean value) {
wrapAllowed = value;
}
public boolean isWordWrapAllowed() {
return wrapAllowed;
}
public boolean isSelectable() {
return false;
}
public String getColorId() {
return colorId;
}
public String getText() {
return text;
}
void setText(String text) {
this.text = cleanup(text);
textFragments = null;
}
void setColorId(String colorId) {
this.colorId = colorId;
}
void setFontId(String fontId) {
this.fontId = fontId;
textFragments = null;
}
public boolean contains(int x, int y) {
for (int i = 0; i < areaRectangles.size(); i++) {
AreaRectangle ar = (AreaRectangle) areaRectangles.get(i);
if (ar.contains(x, y))
return true;
if (i<areaRectangles.size()-1) {
// test the gap
Rectangle top = ar.rect;
Rectangle bot = ((AreaRectangle)areaRectangles.get(i+1)).rect;
if (y >= top.y+top.height && y < bot.y) {
// in the gap
int left = Math.max(top.x, bot.x);
int right = Math.min(top.x+top.width, bot.x+bot.width);
if (x>=left && x<=right) {
return true;
}
}
}
}
return false;
}
public boolean intersects(Rectangle rect) {
for (int i = 0; i < areaRectangles.size(); i++) {
AreaRectangle ar = (AreaRectangle) areaRectangles.get(i);
if (ar.intersects(rect))
return true;
if (i<areaRectangles.size()-1) {
// test the gap
Rectangle top = ar.rect;
Rectangle bot = ((AreaRectangle)areaRectangles.get(i+1)).rect;
if (top.y+top.height < bot.y) {
int y = top.y+top.height;
int height = bot.y-y;
int left = Math.max(top.x, bot.x);
int right = Math.min(top.x+top.width, bot.x+bot.width);
Rectangle gap = new Rectangle(left, y, right-left, height);
if (gap.intersects(rect))
return true;
}
}
}
return false;
}
public Rectangle getBounds() {
int x = 0, y = 0;
int width = 0, height = 0;
for (int i = 0; i < areaRectangles.size(); i++) {
AreaRectangle ar = (AreaRectangle) areaRectangles.get(i);
if (i == 0) {
x = ar.rect.x;
y = ar.rect.y;
} else
x = Math.min(ar.rect.x, x);
width = Math.max(ar.rect.width, width);
height += ar.rect.height;
}
return new Rectangle(x, y, width, height);
}
public boolean advanceLocator(GC gc, int wHint, Locator locator,
Hashtable objectTable, boolean computeHeightOnly) {
Font oldFont = null;
if (fontId != null) {
oldFont = gc.getFont();
Font newFont = (Font) objectTable.get(fontId);
if (newFont != null)
gc.setFont(newFont);
}
FontMetrics fm = gc.getFontMetrics();
int lineHeight = fm.getHeight();
boolean newLine = false;
if (wHint == SWT.DEFAULT || !wrapAllowed) {
Point extent = gc.textExtent(text);
int totalExtent = locator.x+extent.x;
if (isSelectable())
totalExtent+=1;
if (wHint != SWT.DEFAULT && totalExtent > wHint) {
// new line
locator.x = locator.indent;
locator.y += locator.rowHeight;
if (computeHeightOnly)
locator.collectHeights();
locator.rowHeight = 0;
locator.leading = 0;
newLine = true;
}
int width = extent.x;
if (isSelectable())
width += 1;
locator.x += width;
locator.width = locator.indent + width;
locator.rowHeight = Math.max(locator.rowHeight, extent.y);
locator.leading = Math.max(locator.leading, fm.getLeading());
return newLine;
}
computeTextFragments(gc);
int width = 0;
Point lineExtent = new Point(0, 0);
for (int i = 0; i < textFragments.length; i++) {
TextFragment textFragment = textFragments[i];
int currentExtent = locator.x + lineExtent.x;
if (isSelectable())
currentExtent += 1;
if (currentExtent + textFragment.length > wHint) {
// overflow
int lineWidth = currentExtent;
locator.rowHeight = Math.max(locator.rowHeight, lineExtent.y);
locator.leading = Math.max(locator.leading, fm.getLeading());
if (computeHeightOnly)
locator.collectHeights();
locator.x = locator.indent;
locator.y += locator.rowHeight;
locator.rowHeight = 0;
locator.leading = 0;
lineExtent.x = 0;
lineExtent.y = 0;
width = Math.max(width, lineWidth);
newLine = true;
}
lineExtent.x += textFragment.length;
lineExtent.y = Math.max(lineHeight, lineExtent.y);
}
int lineWidth = lineExtent.x;
if (isSelectable())
lineWidth += 1;
locator.x += lineWidth;
locator.width = width;
locator.rowHeight = Math.max(locator.rowHeight, lineExtent.y);
locator.leading = Math.max(locator.leading, fm.getLeading());
if (oldFont != null) {
gc.setFont(oldFont);
}
return newLine;
}
/**
* @param gc
* @param width
* @param locator
* @param selected
* @param selData
* @param color
* @param fm
* @param lineHeight
* @param descent
*/
private void layoutWithoutWrapping(GC gc, int width, Locator locator,
boolean selected, FontMetrics fm, int lineHeight, int descent) {
Point extent = gc.textExtent(text);
int ewidth = extent.x;
if (isSelectable())
ewidth += 1;
if (locator.x + ewidth > width-locator.marginWidth) {
// new line
locator.resetCaret();
locator.y += locator.rowHeight;
locator.rowHeight = 0;
locator.rowCounter++;
}
int ly = locator.getBaseline(fm.getHeight() - fm.getLeading());
//int lineY = ly + lineHeight - descent + 1;
Rectangle br = new Rectangle(locator.x, ly, ewidth,
lineHeight - descent + 3);
areaRectangles.add(new AreaRectangle(br, 0, -1));
locator.x += ewidth;
locator.width = ewidth;
locator.rowHeight = Math.max(locator.rowHeight, extent.y);
}
protected int convertOffsetToStringIndex(GC gc, String s, int x,
int swidth, int selOffset) {
int index = s.length();
while (index > 0 && x + swidth > selOffset) {
index--;
String ss = s.substring(0, index);
swidth = gc.textExtent(ss).x;
}
return index;
}
public void paintFocus(GC gc, Color bg, Color fg, boolean selected,
Rectangle repaintRegion) {
if (areaRectangles == null)
return;
for (int i = 0; i < areaRectangles.size(); i++) {
AreaRectangle areaRectangle = (AreaRectangle) areaRectangles.get(i);
Rectangle br = areaRectangle.rect;
int bx = br.x;
int by = br.y;
if (repaintRegion != null) {
bx -= repaintRegion.x;
by -= repaintRegion.y;
}
if (selected) {
gc.setBackground(bg);
gc.setForeground(fg);
gc.drawFocus(bx, by, br.width, br.height);
} else {
gc.setForeground(bg);
gc.drawRectangle(bx, by, br.width - 1, br.height - 1);
}
}
}
public void paint(GC gc, boolean hover, Hashtable resourceTable,
boolean selected, SelectionData selData, Rectangle repaintRegion) {
this.paint(gc, hover, resourceTable, selected, false, selData,
repaintRegion);
}
protected void paint(GC gc, boolean hover, Hashtable resourceTable,
boolean selected, boolean rollover, SelectionData selData,
Rectangle repaintRegion) {
Font oldFont = null;
Color oldColor = null;
Color oldBg = null;
// apply segment-specific font, color and background
if (fontId != null) {
oldFont = gc.getFont();
Font newFont = (Font) resourceTable.get(fontId);
if (newFont != null)
gc.setFont(newFont);
}
if (!hover && colorId != null) {
oldColor = gc.getForeground();
Color newColor = (Color) resourceTable.get(colorId);
if (newColor != null)
gc.setForeground(newColor);
}
oldBg = gc.getBackground();
FontMetrics fm = gc.getFontMetrics();
int lineHeight = fm.getHeight();
int descent = fm.getDescent();
// paint area rectangles of the segment
for (int i = 0; i < areaRectangles.size(); i++) {
AreaRectangle areaRectangle = (AreaRectangle) areaRectangles.get(i);
Rectangle rect = areaRectangle.rect;
String text = areaRectangle.getText();
Point extent = gc.textExtent(text);
int textX = rect.x + (isSelectable()?1:0);
int lineY = rect.y + lineHeight - descent + 1;
paintString(gc, text, extent.x, textX, rect.y, lineY, selData,
rect, hover, rollover, repaintRegion);
if (selected) {
int fx = rect.x;
int fy = rect.y;
if (repaintRegion != null) {
fx -= repaintRegion.x;
fy -= repaintRegion.y;
}
//To avoid partially cancelling the focus by painting over
//X-ORed pixels, first cancel it yourself
Color fg = gc.getForeground();
gc.setForeground(oldBg);
gc.drawRectangle(fx, fy, rect.width - 1, rect.height - 1);
gc.setForeground(fg);
gc.drawFocus(fx, fy, rect.width, rect.height);
}
}
// restore GC resources
if (oldFont != null) {
gc.setFont(oldFont);
}
if (oldColor != null) {
gc.setForeground(oldColor);
}
if (oldBg != null) {
gc.setBackground(oldBg);
}
}
public void computeSelection(GC gc, Hashtable resourceTable, SelectionData selData) {
Font oldFont = null;
if (fontId != null) {
oldFont = gc.getFont();
Font newFont = (Font) resourceTable.get(fontId);
if (newFont != null)
gc.setFont(newFont);
}
for (int i = 0; i < areaRectangles.size(); i++) {
AreaRectangle areaRectangle = (AreaRectangle) areaRectangles.get(i);
Rectangle rect = areaRectangle.rect;
String text = areaRectangle.getText();
Point extent = gc.textExtent(text);
computeSelection(gc, text, extent.x, selData,
rect);
}
// restore GC resources
if (oldFont != null) {
gc.setFont(oldFont);
}
}
private void paintString(GC gc, String s, int swidth, int x, int y,
int lineY, SelectionData selData, Rectangle bounds, boolean hover,
boolean rolloverMode, Rectangle repaintRegion) {
// repaints one area rectangle
if (selData != null && selData.isEnclosed()) {
Color savedBg = gc.getBackground();
Color savedFg = gc.getForeground();
int leftOffset = selData.getLeftOffset(bounds.height);
int rightOffset = selData.getRightOffset(bounds.height);
boolean firstRow = selData.isFirstSelectionRow(bounds.y,
bounds.height);
boolean lastRow = selData.isLastSelectionRow(bounds.y,
bounds.height);
boolean selectedRow = selData
.isSelectedRow(bounds.y, bounds.height);
int sstart = -1;
int sstop = -1;
if ((firstRow && x + swidth < leftOffset)
|| (lastRow && x > rightOffset)) {
paintStringSegment(gc, s, gc.textExtent(s).x, x, y, lineY,
hover, rolloverMode, repaintRegion);
return;
}
if (firstRow && bounds.x + swidth > leftOffset) {
sstart = convertOffsetToStringIndex(gc, s, bounds.x, swidth,
leftOffset);
}
if (lastRow && bounds.x + swidth > rightOffset) {
sstop = convertOffsetToStringIndex(gc, s, bounds.x, swidth,
rightOffset);
}
if (firstRow && sstart != -1) {
String left = s.substring(0, sstart);
int width = gc.textExtent(left).x;
paintStringSegment(gc, left, width, x, y, lineY, hover,
rolloverMode, repaintRegion);
x += width;
}
if (selectedRow) {
int lindex = sstart != -1 ? sstart : 0;
int rindex = sstop != -1 ? sstop : s.length();
String mid = s.substring(lindex, rindex);
Point extent = gc.textExtent(mid);
gc.setForeground(selData.fg);
gc.setBackground(selData.bg);
gc.fillRectangle(x, y, extent.x, extent.y);
paintStringSegment(gc, mid, extent.x, x, y, lineY, hover,
rolloverMode, repaintRegion);
x += extent.x;
gc.setForeground(savedFg);
gc.setBackground(savedBg);
} else {
paintStringSegment(gc, s, gc.textExtent(s).x, x, y, lineY,
hover, rolloverMode, repaintRegion);
}
if (lastRow && sstop != -1) {
String right = s.substring(sstop);
paintStringSegment(gc, right, gc.textExtent(right).x, x, y,
lineY, hover, rolloverMode, repaintRegion);
}
} else {
paintStringSegment(gc, s, gc.textExtent(s).x, x, y, lineY, hover,
rolloverMode, repaintRegion);
}
}
private void computeSelection(GC gc, String s, int swidth, SelectionData selData, Rectangle bounds) {
int leftOffset = selData.getLeftOffset(bounds.height);
int rightOffset = selData.getRightOffset(bounds.height);
boolean firstRow = selData.isFirstSelectionRow(bounds.y, bounds.height);
boolean lastRow = selData.isLastSelectionRow(bounds.y, bounds.height);
boolean selectedRow = selData.isSelectedRow(bounds.y, bounds.height);
int sstart = -1;
int sstop = -1;
if (firstRow && bounds.x + swidth > leftOffset) {
sstart = convertOffsetToStringIndex(gc, s, bounds.x, swidth,
leftOffset);
}
if (lastRow && bounds.x + swidth > rightOffset) {
sstop = convertOffsetToStringIndex(gc, s, bounds.x, swidth,
rightOffset);
}
if (selectedRow) {
int lindex = sstart != -1 ? sstart : 0;
int rindex = sstop != -1 ? sstop : s.length();
String mid = s.substring(lindex, rindex);
selData.addSegment(mid);
}
}
/**
* @param gc
* @param s
* @param x
* @param y
* @param lineY
* @param hover
* @param rolloverMode
*/
private void paintStringSegment(GC gc, String s, int swidth, int x, int y,
int lineY, boolean hover, boolean rolloverMode,
Rectangle repaintRegion) {
boolean reverse = false;
int clipX = x;
int clipY = y;
int clipLineY = lineY;
if (repaintRegion != null) {
clipX -= repaintRegion.x;
clipY -= repaintRegion.y;
clipLineY -= repaintRegion.y;
}
if (underline || hover || rolloverMode) {
if (rolloverMode && !hover)
reverse = true;
}
if (reverse) {
drawUnderline(gc, swidth, clipX, clipLineY, hover, rolloverMode);
gc.drawString(s, clipX, clipY, false);
} else {
gc.drawString(s, clipX, clipY, false);
drawUnderline(gc, swidth, clipX, clipLineY, hover, rolloverMode);
}
}
private void drawUnderline(GC gc, int swidth, int x, int y, boolean hover,
boolean rolloverMode) {
if (underline || hover || rolloverMode) {
Color saved = null;
if (rolloverMode && !hover) {
saved = gc.getForeground();
gc.setForeground(gc.getBackground());
}
gc.drawLine(x, y, x + swidth-1, y);
if (saved != null)
gc.setForeground(saved);
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.ui.internal.forms.widgets.ParagraphSegment#layout(org.eclipse.swt.graphics.GC,
* int, org.eclipse.ui.internal.forms.widgets.Locator,
* java.util.Hashtable, boolean,
* org.eclipse.ui.internal.forms.widgets.SelectionData)
*/
public void layout(GC gc, int width, Locator locator,
Hashtable resourceTable, boolean selected) {
Font oldFont = null;
areaRectangles.clear();
if (fontId != null) {
oldFont = gc.getFont();
Font newFont = (Font) resourceTable.get(fontId);
if (newFont != null)
gc.setFont(newFont);
}
FontMetrics fm = gc.getFontMetrics();
int lineHeight = fm.getHeight();
int descent = fm.getDescent();
if (!wrapAllowed) {
layoutWithoutWrapping(gc, width, locator, selected, fm, lineHeight,
descent);
} else {
int lineStart = 0;
int lastLoc = 0;
Point lineExtent = new Point(0, 0);
computeTextFragments(gc);
int rightEdge = width-locator.marginWidth;
for (int i = 0; i < textFragments.length; i++) {
TextFragment fragment = textFragments[i];
int breakLoc = fragment.index;
if (breakLoc == 0)
continue;
if (locator.x + lineExtent.x + fragment.length > rightEdge) {
// overflow
int lineWidth = locator.x + lineExtent.x;
if (isSelectable())
lineWidth += 1;
int ly = locator.getBaseline(lineHeight - fm.getLeading());
Rectangle br = new Rectangle(isSelectable()?
locator.x - 1:locator.x, ly,
isSelectable()?lineExtent.x + 1:lineExtent.x, lineHeight - descent + 3);
areaRectangles
.add(new AreaRectangle(br, lineStart, lastLoc));
locator.rowHeight = Math.max(locator.rowHeight,
lineExtent.y);
locator.resetCaret();
if (isSelectable())
locator.x += 1;
locator.y += locator.rowHeight;
locator.rowCounter++;
locator.rowHeight = 0;
lineStart = lastLoc;
lineExtent.x = 0;
lineExtent.y = 0;
}
lastLoc = breakLoc;
lineExtent.x += fragment.length;
lineExtent.y = Math.max(lineHeight, lineExtent.y);
}
//String lastLine = text.substring(lineStart, lastLoc);
int ly = locator.getBaseline(lineHeight - fm.getLeading());
int lastWidth = lineExtent.x;
if (isSelectable())
lastWidth += 1;
Rectangle br = new Rectangle(isSelectable()?locator.x - 1:locator.x, ly,
isSelectable()?lineExtent.x + 1:lineExtent.x,
lineHeight - descent + 3);
//int lineY = ly + lineHeight - descent + 1;
areaRectangles.add(new AreaRectangle(br, lineStart, lastLoc));
locator.x += lastWidth;
locator.rowHeight = Math.max(locator.rowHeight, lineExtent.y);
}
if (oldFont != null) {
gc.setFont(oldFont);
}
}
private void computeTextFragments(GC gc) {
if (textFragments != null)
return;
ArrayList list = new ArrayList();
BreakIterator wb = BreakIterator.getLineInstance();
wb.setText(getText());
int cursor = 0;
for (int loc = wb.first(); loc != BreakIterator.DONE; loc = wb.next()) {
if (loc == 0)
continue;
String word = text.substring(cursor, loc);
Point extent = gc.textExtent(word);
list.add(new TextFragment((short) loc, (short) extent.x));
cursor = loc;
}
textFragments = (TextFragment[]) list.toArray(new TextFragment[list
.size()]);
}
public void clearCache(String fontId) {
if (fontId==null && this.fontId==null)
textFragments = null;
else if (fontId!=null && this.fontId!=null && fontId.equals(this.fontId))
textFragments = null;
}
}