/******************************************************************************* * Copyright (c) 2004, 2008 John Krasnay 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: * John Krasnay - initial API and implementation *******************************************************************************/ package net.sf.vex.layout; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import net.sf.vex.core.Caret; import net.sf.vex.core.FontMetrics; import net.sf.vex.core.FontResource; import net.sf.vex.core.Graphics; import net.sf.vex.css.Styles; /** * InlineBox consisting of several children. This is the parent class * of InlineElementBox and LineBox, and implements the split method. */ public abstract class CompositeInlineBox extends AbstractBox implements InlineBox { /** * Returns true if any of the children have content. */ public boolean hasContent() { Box[] children = this.getChildren(); for (int i = 0; i < children.length; i++) { if (children[i].hasContent()) { return true; } } return false; } public boolean isEOL() { Box[] children = this.getChildren(); return children.length > 0 && ((InlineBox) children[children.length-1]).isEOL(); } /** * @see net.sf.vex.layout.Box#getCaret(net.sf.vex.layout.LayoutContext, int) */ public Caret getCaret(LayoutContext context, int offset) { int x = 0; Box[] children = this.getChildren(); // we want the caret to be to the right of any leading static boxes... int start = 0; while (start < children.length && !children[start].hasContent()) { x += children[start].getWidth(); start++; } // ...and to the left of any trailing static boxes int end = children.length; while (end < 0 && !children[end - 1].hasContent()) { end--; } for (int i = start; i < end; i++) { Box child = children[i]; if (child.hasContent()) { if (offset < child.getStartOffset()) { break; } else if (offset <= child.getEndOffset()) { Caret caret = child.getCaret(context, offset); caret.translate(child.getX(), child.getY()); return caret; } } x += child.getWidth(); } Graphics g = context.getGraphics(); Styles styles = context.getStyleSheet().getStyles(this.getElement()); FontResource font = g.createFont(styles.getFont()); FontResource oldFont = g.setFont(font); FontMetrics fm = g.getFontMetrics(); int height = fm.getAscent() + fm.getDescent(); g.setFont(oldFont); font.dispose(); int lineHeight = styles.getLineHeight(); int y = (lineHeight - height) / 2; return new TextCaret(x, y, height); } /** * @see net.sf.vex.layout.InlineBox#split(net.sf.vex.layout.LayoutContext, int, boolean) */ public Pair split(LayoutContext context, int maxWidth, boolean force) { // list of children that have yet to be added to the left side LinkedList rights = new LinkedList(Arrays.asList(this.getChildren())); // pending is a list of inlines we are trying to add to the left side // but which cannot end at a split List pending = new ArrayList(); // list of inlines that make up the left side List lefts = new ArrayList(); int remaining = maxWidth; boolean eol = false; while (!rights.isEmpty() && remaining >= 0) { InlineBox inline = (InlineBox) rights.removeFirst(); InlineBox.Pair pair = inline.split(context, remaining, force && lefts.isEmpty()); if (pair.getLeft() != null) { lefts.addAll(pending); pending.clear(); lefts.add(pair.getLeft()); remaining -= pair.getLeft().getWidth(); } if (pair.getRight() != null) { pending.add(pair.getRight()); remaining -= pair.getRight().getWidth(); } if (pair.getLeft() != null && pair.getLeft().isEOL()) { eol = true; break; } } if (((force && lefts.isEmpty()) || remaining >= 0) && !eol) { lefts.addAll(pending); } else { rights.addAll(0, pending); } InlineBox[] leftKids = (InlineBox[]) lefts.toArray(new InlineBox[lefts.size()]); InlineBox[] rightKids = (InlineBox[]) rights.toArray(new InlineBox[rights.size()]); return this.split(context, leftKids, rightKids); } /** * Creates a Pair of InlineBoxes, each with its own set of children. * @param context LayoutContext used for this layout. * @param lefts Child boxes to be given to the left box. * @param rights Child boxes to be given to the right box. * @return */ protected abstract Pair split(LayoutContext context, InlineBox[] lefts, InlineBox[] rights); /** * @see net.sf.vex.layout.Box#viewToModel(net.sf.vex.layout.LayoutContext, int, int) */ public int viewToModel(LayoutContext context, int x, int y) { if (!this.hasContent()) { throw new RuntimeException("Oops. Calling viewToModel on a line with no content"); } Box closestContentChild = null; int delta = Integer.MAX_VALUE; Box[] children = this.getChildren(); for (int i = 0; i < children.length; i++) { Box child = children[i]; if (child.hasContent()) { int newDelta = 0; if (x < child.getX()) { newDelta = child.getX() - x; } else if (x > child.getX() + child.getWidth()) { newDelta = x - (child.getX() + child.getWidth()); } if (newDelta < delta) { delta = newDelta; closestContentChild = child; } } } return closestContentChild.viewToModel( context, x - closestContentChild.getX(), y - closestContentChild.getY()); } }