/*******************************************************************************
* Copyright (c) 2004, 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.gef.examples.text.edit;
import java.beans.PropertyChangeEvent;
import com.ibm.icu.text.BreakIterator;
import org.eclipse.swt.graphics.Font;
import org.eclipse.jface.util.Assert;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.draw2d.text.CaretInfo;
import org.eclipse.draw2d.text.SimpleTextLayout;
import org.eclipse.draw2d.text.TextFlow;
import org.eclipse.gef.examples.text.TextLocation;
import org.eclipse.gef.examples.text.model.Style;
import org.eclipse.gef.examples.text.model.TextRun;
import org.eclipse.gef.examples.text.requests.CaretRequest;
import org.eclipse.gef.examples.text.requests.SearchResult;
/**
* @since 3.1
*/
public class TextFlowPart
extends AbstractTextPart
{
private static BreakIterator wordIterator = null;
private Font localFont;
public TextFlowPart(Object model) {
setModel(model);
}
protected void createEditPolicies() {}
protected IFigure createFigure() {
TextFlow flow = new TextFlow();
if (((TextRun)getModel()).getType() == TextRun.TYPE_CODE)
flow.setLayoutManager(new SimpleTextLayout(flow));
return flow;
}
public void deactivate() {
super.deactivate();
if (localFont != null)
FontCache.checkIn(localFont);
}
public CaretInfo getCaretPlacement(int offset, boolean trailing) {
Assert.isTrue(offset <= getLength());
if (trailing)
if (offset > 0)
offset--;
else {
// @TODO:Pratik this should never happen
trailing = false;
new RuntimeException("unexpected condition").printStackTrace();
}
return getTextFlow().getCaretPlacement(offset, trailing);
}
public int getLength() {
return getTextFlow().getText().length();
}
TextFlow getTextFlow() {
return (TextFlow)getFigure();
}
public void getTextLocation(CaretRequest search, SearchResult result) {
if (search.getType() == CaretRequest.LINE_BOUNDARY)
searchLineBoundary(search, result);
else if (search.getType() == CaretRequest.COLUMN)
searchColumn(search, result);
else if (search.getType() == CaretRequest.ROW)
searchRow(search, result);
else if (search.getType() == CaretRequest.WORD_BOUNDARY)
searchWordBoundary(search, result);
else if (search.getType() == CaretRequest.LOCATION)
searchLocation(search, result);
else if (getParent() instanceof TextEditPart)
getTextParent().getTextLocation(search, result);
}
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals("text"))
refreshVisuals();
}
protected void refreshVisuals() {
TextRun textRun = (TextRun)getModel();
Style style = textRun.getContainer().getStyle();
Font font = FontCache.checkOut(style.getFontFamily(), style.getFontHeight(),
style.isBold(), style.isItalic());
if (font != localFont) {
if (localFont != null)
FontCache.checkIn(localFont);
localFont = font;
getFigure().setFont(font);
} else
FontCache.checkIn(font);
getTextFlow().setText(textRun.getText());
}
protected void searchColumn(CaretRequest search, SearchResult result) {
TextFlow flow = getTextFlow();
result.trailing = search.isForward;
if (search.isRecursive || !(getParent() instanceof TextEditPart)) {
if (search.isInto) {
result.trailing = !search.isForward;
if (search.isForward)
result.location =
new TextLocation(this, flow.getNextVisibleOffset(-1));
else
result.location =
new TextLocation(this, flow.getPreviousVisibleOffset(-1));
} else {
if (getLength() > 0) {
if (search.isForward)
result.location =
new TextLocation(this, flow.getNextVisibleOffset(0));
else
result.location = new TextLocation(this, flow
.getPreviousVisibleOffset(flow
.getPreviousVisibleOffset(-1)));
}
}
} else if (search.isForward && search.where.offset < getLength())
result.location = new TextLocation(this,
flow.getNextVisibleOffset(search.where.offset));
else if (!search.isForward && search.where.offset > 0)
result.location = new TextLocation(this,
flow.getPreviousVisibleOffset(search.where.offset));
else
getTextParent().getTextLocation(search, result);
}
protected void searchLineBoundary(CaretRequest search, SearchResult result) {
if (search.isRecursive || !(getParent() instanceof TextEditPart)) {
Point where = search.getLocation().getCopy();
TextFlow flow = getTextFlow();
flow.translateToRelative(where);
int offset;
result.trailing = search.isForward;
if (search.isForward) {
offset = flow.getLastOffsetForLine(where.y);
if (offset != -1)
offset++;
} else
offset = flow.getFirstOffsetForLine(where.y);
if (offset != -1)
result.location = new TextLocation(this, offset);
} else
getTextParent().getTextLocation(search, result);
}
// also used for PGUP and PGDN
protected void searchLocation(CaretRequest search, SearchResult result) {
Point pt = search.getLocation().getCopy();
getTextFlow().translateToRelative(pt);
// This detects the case where you've gone past page up or down
if (result.location != null && vDistanceBetween(getTextFlow().getBounds(), pt.y)
> result.proximity.height) {
result.bestMatchFound = true;
return;
}
int[] trailing = new int[1];
int offset = getTextFlow().getOffset(pt, trailing, result.proximity) + trailing[0];
if (offset != -1) {
result.trailing = trailing[0] == 1;
result.location = new TextLocation(this, offset);
result.bestMatchFound = result.proximity.width == 0
&& result.proximity.height == 0;
if (result.bestMatchFound)
return;
}
if (!search.isRecursive && getParent() instanceof TextEditPart) {
search.setReferenceTextLocation(this, search.isForward ? getLength() : 0);
getTextParent().getTextLocation(search, result);
}
}
protected void searchRow(CaretRequest search, SearchResult result) {
if (search.isRecursive || !(getParent() instanceof TextEditPart)) {
Point where = search.getLocation().getCopy();
TextFlow flow = getTextFlow();
flow.translateToRelative(where);
int[] trailing = new int[1];
int offset = flow.getNextOffset(where, search.isForward, trailing)
+ trailing[0];
if (offset != -1) {
CaretInfo info = getCaretPlacement(offset, trailing[0] == 1);
int vDistance = Math.abs(info.getBaseline() - search.getLocation().y);
if (vDistance > result.proximity.height)
result.bestMatchFound = true;
else {
int hDistance = Math.abs(info.getX() - search.getLocation().x);
if (vDistance < result.proximity.height
|| (vDistance == result.proximity.height
&& hDistance < result.proximity.width)) {
result.trailing = trailing[0] == 1;
result.location = new TextLocation(this, offset);
result.proximity.width = hDistance;
result.proximity.height = vDistance;
}
}
} else {
// @TODO:Pratik should go to the end of the current line if offset == -1 (as is
// the case when on the last line and going down, or the first line and going up)
}
} else
getTextParent().getTextLocation(search, result);
}
protected void searchWordBoundary(CaretRequest search, SearchResult result) {
String text = getTextFlow().getText();
if (text.trim().length() > 0) {
int length = text.length();
int offset = BreakIterator.DONE;
if (wordIterator == null)
wordIterator = BreakIterator.getWordInstance();
wordIterator.setText(text);
if (search.isRecursive)
offset = search.isForward ? 0 : length;
else {
if (search.isForward)
offset = search.where.offset == length ? BreakIterator.DONE
: wordIterator.following(search.where.offset);
else
offset = wordIterator.preceding(Math.min(search.where.offset, length - 1));
}
int index = Math.min(offset, length - 1);
if (offset != BreakIterator.DONE
&& Character.isWhitespace(text.charAt(index)))
offset = search.isForward ? wordIterator.following(index)
: wordIterator.preceding(index);
if (offset != BreakIterator.DONE) {
result.location = new TextLocation(this, offset);
result.trailing = offset == length;
// this is the case where you're at the beginning or the end of the text flow
result.bestMatchFound = !Character.isWhitespace(
text.charAt(Math.min(offset, length - 1)));
}
}
if (!result.bestMatchFound && !search.isRecursive
&& getParent() instanceof TextEditPart)
getTextParent().getTextLocation(search, result);
}
public void setSelection(int start, int end) {
if (start == end)
getTextFlow().setSelection(-1, -1);
else
getTextFlow().setSelection(start, end);
}
private int vDistanceBetween(Rectangle rect, int y) {
if (y < rect.y)
return rect.y - y;
return Math.max(0, y - rect.bottom());
}
}