view dwtx/draw2d/text/LineRoot.d @ 98:95307ad235d9

Added Draw2d code, still work in progress
author Frank Benoit <benoit@tionex.de>
date Sun, 03 Aug 2008 00:52:14 +0200
parents
children
line wrap: on
line source

/*******************************************************************************
 * 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
 * Port to the D programming language:
 *     Frank Benoit <benoit@tionex.de>
 *******************************************************************************/

module dwtx.draw2d.text.LineRoot;

import dwt.dwthelper.utils;
import dwtx.dwtxhelper.Collection;

import dwtx.draw2d.geometry.Rectangle;
import dwtx.draw2d.text.LineBox;
import dwtx.draw2d.text.FlowBox;
import dwtx.draw2d.text.NestedLine;
import dwtx.draw2d.text.ContentBox;
import dwtx.draw2d.text.InlineFlow;

/**
 * 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 dwtx.draw2d.text.ContentBox fragments} know of the LineRoot they belong
 * to.
 * @author Randy Hudson
 * @author Pratik Shah
 * @since 3.1
 */
public class LineRoot
    : LineBox
{

private int baseline;
private bool isMirrored;

/**
 * Constructor
 * @param isMirrored <code>true</code> if the line is to be displayed in a mirrored control
 */
public this(bool isMirrored) {
    this.isMirrored = isMirrored;
}

/**
 * @see dwtx.draw2d.text.CompositeBox#add(dwtx.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 = cast(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 ( auto lb = cast(LineBox)box ) {
        List children = lb.getFragments();
        for (int i = 0; i < children.size(); i++)
            buildBidiTree(cast(FlowBox)children.get(i), node, branches);
        if (box !is this)
            branches.add(box);
    } else {
        ContentBox leafBox = cast(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 dwtx.draw2d.text.FlowBox#containsPoint(int, int)
 */
public bool 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 (auto lb = cast(LineBox)box ) {
        List fragments = lb.getFragments();
        int i = isMirrored ? fragments.size() - 1 : 0;
        while (i >= 0 && i < fragments.size()) {
            FlowBox child = cast(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 = cast(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 dwtx.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 = cast(NestedLine)branches.get(i);
        FlowBox prevChild = null;
        Rectangle bounds = null;
        List frags = parent.getFragments();
        for (int j = 0; j < frags.size(); j++) {
            FlowBox child = cast(FlowBox) frags.get(j);
            if (prevChild !is null && prevChild.getX() + prevChild.width !is child.getX()
                    && child.getX() + child.width !is 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 is 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 dwtx.draw2d.text.CompositeBox#setLineTop(int)
 */
public void setLineTop(int top) {
    this.baseline = top + getAscent();
}

private static class BidiLevelNode : ArrayList
{
    int level;
    final BidiLevelNode parent;

    this() {
        this(null, 0);
    }

    this(BidiLevelNode parent, int level) {
        this.parent = parent;
        this.level = level;
    }

    void emit(List list) {
        if (level % 2 is 1) {
            for (int i = size() - 1; i >= 0; i--) {
                Object child = get(i);
                if (auto bln = cast(BidiLevelNode)child )
                    bln.emit(list);
                else
                    list.add(child);
            }
        } else {
            for (int i = 0; i < size(); i++) {
                Object child = get(i);
                if (auto bln = cast(BidiLevelNode)child )
                    bln.emit(list);
                else
                    list.add(child);
            }
        }
    }

    BidiLevelNode pop() {
        return parent;
    }

    BidiLevelNode push() {
        if (!isEmpty()) {
            Object last = get(size() - 1);
            if (null !is cast(BidiLevelNode)last && (cast(BidiLevelNode)last).level is level + 1)
                return cast(BidiLevelNode)last;
        }
        BidiLevelNode child = new BidiLevelNode(this, level + 1);
        add(child);
        return child;
    }
}

private static class Result {
    private int index;
    private LineBox parent;
    private this(LineBox box, int i) {
        parent = box;
        index = i;
    }
}

}