/******************************************************************************* * 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.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; } } }