blob: ced980a4ab48c29960376ce936e021983e966a7a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2010 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.draw2d.text;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.draw2d.geometry.Rectangle;
/**
* LineRoot is the top-most container on a line of text displayed in Draw2d.
* Hence, a LineRoot can tell you of things like the highest ascent or descent
* on a line, which is required to display selection and such. All
* {@link org.eclipse.draw2d.text.ContentBox fragments} know of the LineRoot
* they belong to.
*
* @author Randy Hudson
* @author Pratik Shah
* @since 3.1
*/
public class LineRoot extends LineBox {
private int baseline;
private boolean isMirrored;
/**
* Constructor
*
* @param isMirrored
* <code>true</code> if the line is to be displayed in a mirrored
* control
*/
public LineRoot(boolean isMirrored) {
this.isMirrored = isMirrored;
}
/**
* @see org.eclipse.draw2d.text.CompositeBox#add(org.eclipse.draw2d.text.FlowBox)
*/
public void add(FlowBox child) {
super.add(child);
child.setLineRoot(this);
}
private void bidiCommit() {
int xLocation = getX();
BidiLevelNode root = new BidiLevelNode();
List branches = new ArrayList();
// branches does not include this LineRoot; all the non-leaf child
// fragments of a
// parent will be listed before the parent itself in this list
buildBidiTree(this, root, branches);
List result = new ArrayList();
root.emit(result);
int i = isMirrored ? result.size() - 1 : 0;
while (i >= 0 && i < result.size()) {
FlowBox box = (FlowBox) result.get(i);
box.setX(xLocation);
xLocation += box.getWidth();
i += isMirrored ? -1 : 1;
}
// set the bounds of the composite boxes, and break overlapping ones
// into two
layoutNestedLines(branches);
}
private void buildBidiTree(FlowBox box, BidiLevelNode node, List branches) {
if (box instanceof LineBox) {
List children = ((LineBox) box).getFragments();
for (int i = 0; i < children.size(); i++)
buildBidiTree((FlowBox) children.get(i), node, branches);
if (box != this)
branches.add(box);
} else {
ContentBox leafBox = (ContentBox) box;
while (leafBox.getBidiLevel() < node.level)
node = node.pop();
while (leafBox.getBidiLevel() > node.level)
node = node.push();
node.add(leafBox);
}
}
/**
* Committing a LineRoot will position its children correctly. All children
* boxes are made to have the same baseline, and are laid out according to
* the Unicode BiDi Algorithm, or left-to-right if Bidi is not necessary.
*/
public void commit() {
if (requiresBidi())
bidiCommit();
else
contiguousCommit(this, getX());
}
/**
* A LineRoot cannot be targetted.
*
* @see org.eclipse.draw2d.text.FlowBox#containsPoint(int, int)
*/
public boolean containsPoint(int x, int y) {
return false;
}
/*
* Simply lays out all fragments from left-to-right in the order in which
* they're contained.
*/
private void contiguousCommit(FlowBox box, int x) {
box.setX(x);
if (box instanceof LineBox) {
List fragments = ((LineBox) box).getFragments();
int i = isMirrored ? fragments.size() - 1 : 0;
while (i >= 0 && i < fragments.size()) {
FlowBox child = (FlowBox) fragments.get(i);
contiguousCommit(child, x);
x += child.getWidth();
i += isMirrored ? -1 : 1;
}
}
}
private Result findParent(NestedLine line, List branches, int afterIndex) {
for (int i = afterIndex + 1; i < branches.size(); i++) {
NestedLine box = (NestedLine) branches.get(i);
int index = box.getFragments().indexOf(line);
if (index >= 0)
return new Result(box, index);
}
return new Result(this, getFragments().indexOf(line));
}
/**
* @see org.eclipse.draw2d.text.FlowBox#getBaseline()
*/
public int getBaseline() {
return baseline;
}
LineRoot getLineRoot() {
return this;
}
int getVisibleBottom() {
return baseline + contentDescent;
}
int getVisibleTop() {
return baseline - contentAscent;
}
private void layoutNestedLines(List branches) {
for (int i = 0; i < branches.size(); i++) {
NestedLine parent = (NestedLine) branches.get(i);
FlowBox prevChild = null;
Rectangle bounds = null;
List frags = parent.getFragments();
for (int j = 0; j < frags.size(); j++) {
FlowBox child = (FlowBox) frags.get(j);
if (prevChild != null
&& prevChild.getX() + prevChild.width != child.getX()
&& child.getX() + child.width != prevChild.getX()) {
// the boxes are not adjacent, and hence the parent box
// needs to
// be broken up
InlineFlow parentFig = parent.owner;
// Create and initialize a new line box
NestedLine newBox = new NestedLine(parentFig);
newBox.setLineRoot(this);
// Add all remaining fragments from the current line box to
// the new one
for (int k = j; k < frags.size();)
newBox.fragments.add(frags.remove(k));
// Add the new line box to the parent box's list of
// fragments
Result result = findParent(parent, branches, i);
result.parent.getFragments().add(result.index + 1, newBox);
// Add the new line box to the flow figure's list of
// fragments
parentFig.fragments.add(
parentFig.fragments.indexOf(parent) + 1, newBox);
branches.add(i + 1, newBox);
break;
}
if (bounds == null)
bounds = new Rectangle(child.getX(), 1, child.getWidth(), 1);
else
bounds.union(child.getX(), 1, child.getWidth(), 1);
prevChild = child;
}
parent.setX(bounds.x);
parent.setWidth(bounds.width);
}
}
/**
* Positions the line vertically by settings its baseline.
*
* @param baseline
* the baseline
*/
public void setBaseline(int baseline) {
this.baseline = baseline;
}
/**
* @see org.eclipse.draw2d.text.CompositeBox#setLineTop(int)
*/
public void setLineTop(int top) {
this.baseline = top + getAscent();
}
private static class BidiLevelNode extends ArrayList {
int level;
final BidiLevelNode parent;
BidiLevelNode() {
this(null, 0);
}
BidiLevelNode(BidiLevelNode parent, int level) {
this.parent = parent;
this.level = level;
}
void emit(List list) {
if (level % 2 == 1) {
for (int i = size() - 1; i >= 0; i--) {
Object child = get(i);
if (child instanceof BidiLevelNode)
((BidiLevelNode) child).emit(list);
else
list.add(child);
}
} else {
for (int i = 0; i < size(); i++) {
Object child = get(i);
if (child instanceof BidiLevelNode)
((BidiLevelNode) child).emit(list);
else
list.add(child);
}
}
}
BidiLevelNode pop() {
return parent;
}
BidiLevelNode push() {
if (!isEmpty()) {
Object last = get(size() - 1);
if (last instanceof BidiLevelNode
&& ((BidiLevelNode) last).level == level + 1)
return (BidiLevelNode) last;
}
BidiLevelNode child = new BidiLevelNode(this, level + 1);
add(child);
return child;
}
}
private static class Result {
private int index;
private LineBox parent;
private Result(LineBox box, int i) {
parent = box;
index = i;
}
}
}