// BlogBridge -- RSS feed reader, manager, and web based service // Copyright (C) 2002-2006 by R. Pito Salas // // This program is free software; you can redistribute it and/or modify it under // the terms of the GNU General Public License as published by the Free Software Foundation; // either version 2 of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; // without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License along with this program; // if not, write to the Free Software Foundation, Inc., 59 Temple Place, // Suite 330, Boston, MA 02111-1307 USA // // Contact: R. Pito Salas // mailto:pitosalas@users.sourceforge.net // More information: about BlogBridge // http://www.blogbridge.com // http://sourceforge.net/projects/blogbridge // // $Id: FloatParagraphView.java,v 1.14 2006/06/08 04:44:38 kyank Exp $ // package com.salas.bb.utils.uif.html; import javax.swing.text.*; import javax.swing.text.html.ParagraphView; import javax.swing.*; import java.awt.*; import java.util.*; /** * Customized version of paragraph view. Enhanced to detect and paint left/right-aligned * (floated) images in the margins. * * Author of original class taken as a base from Sun SDK is Timothy Prinzing. */ public class FloatParagraphView extends UnjustifiedParagraphView { /** * Flag to indicate that this paragraph contains floated images that * protrude below its bottom edge. */ private boolean passForwardFloats = false; private FloatMarginManager fmm = new FloatMarginManager(); /** * The set of <code>FloatableImageView</code>s that are positioned in the margin of this * paragraph. These are painted whenever this paragraph is painted. */ private Set intrudingFloats = new HashSet(); private int justification; private float lineSpacing; /** * Creates paragraph view with support for floated images. * @param elem paragraph element */ public FloatParagraphView(Element elem) { super(elem); strategy = new FloatFlowStrategy(); } /** * Renders using the given rendering surface and area on that * surface. This is implemented to delgate to the superclass * for most painting, then paint any floating images in the * paragraph. * * @param g the rendering surface to use * @param a the allocated region to render into * @see javax.swing.text.View#paint */ public void paint(Graphics g, Shape a) { super.paint(g, a); paintFloatingImages(g, a); } /** * Paints floating images in the margins of the paragraph. * @param g the rendering surface to use * @param a the allocated region to render into */ protected void paintFloatingImages(Graphics g, Shape a) { // Paint intruding floated images Iterator it = intrudingFloats.iterator(); while (it.hasNext()) { FloatableImageView fiv = (FloatableImageView)it.next(); paintFloatingImage(g, a, fiv, this); } } /** * Paints a floated image. * @param g the rendering surface to use * @param a the allocated area of the current <code>FloatParagraphView</code> * @param iv the view for the floated image * @param fpv the current <code>FloatParagraphView</code>, which is initiating this paint */ static void paintFloatingImage(Graphics g, Shape a, FloatableImageView iv, FloatParagraphView fpv) { Rectangle r = a.getBounds(); // Obtain index of fpv in parent View parent = fpv.getParent(); int n = parent.getViewCount(); int thisIndex; for (thisIndex = 0; thisIndex < n; thisIndex++) { if (parent.getView(thisIndex) == fpv) { break; } } // Convert allocated area of fpv to allocated area of host paragraph // Assumes X_AXIS allocation remains constant across subsequent FloatParagraphViews FloatParagraphView hostFpv = (FloatParagraphView)iv.getParent().getParent(); while (fpv != hostFpv) { fpv = (FloatParagraphView)parent.getView(--thisIndex); r.y -= fpv.getPreferredSpan(Y_AXIS); } r.height = (int)fpv.getPreferredSpan(Y_AXIS); // Adjust rendering area for vertical offset within host paragraph Shape rowAllocation = hostFpv.getChildAllocation( hostFpv.getViewIndexAtPosition(iv.getStartOffset()), r); r.y = rowAllocation.getBounds().y; // Adjust rendering area for assigned float position r.y += iv.vFloatPos; if (iv.getLayoutType() == FloatableImageView.FLOAT_LEFT) { r.x += iv.hFloatPos; } else { r.x += r.width - iv.hFloatPos - iv.getPreferredSpan(X_AXIS, true); } r.width = (int)iv.getPreferredSpan(X_AXIS, true); r.height = (int)iv.getPreferredSpan(Y_AXIS, true); iv.paint(g, r, true); } /** * Create a <code>View</code> that should be used to hold a row's worth of children in a flow. * Adjusts the left and right margins for any floated images present. * * @return a new <code>FloatRow</code> */ protected View createRow() { return new FloatRow(getElement()); } /** * Fetch the constraining span to flow against for * the given child index. This has been re-implemented * because <code>Row</code> was also re-implemented, so the superclass' * version of this method does not use the right <code>Row</code> class. * * @param index the child view (row) to fetch * * @return the span in pixels */ public int getFlowSpan(int index) { View child = getView(index); int adjust = 0; if (child instanceof FloatRow) { FloatRow row = (FloatRow) child; adjust = row.getLeftInset() + row.getRightInset(); } return Math.max(0, layoutSpan - adjust); } /** * Fetch the location along the flow axis that the * flow span will start at. This has been re-implemented * because <code>Row</code> was also re-implemented, so the superclass' * version of this method does not use the right <code>Row</code> class. * * @param index the child view (row) to fetch * * @return the start position in pixels */ public int getFlowStart(int index) { View child = getView(index); int adjust = 0; if (child instanceof FloatRow) { FloatRow row = (FloatRow) child; adjust = row.getLeftInset(); } return (int)getTabBase() + adjust; } /** * Sets the amount of line spacing. * @param ls the line spacing as a factor of line height */ protected void setLineSpacing(float ls) { super.setLineSpacing(ls); lineSpacing = ls; } /** * Gets the amount of line spacing. * @return the line spacing as a factor of line height */ protected float getLineSpacing() { return lineSpacing; } /** * Set the type of justification. * @param j one of <code>StyleConstants.ALIGN_LEFT</code>, * <code>StyleConstants.ALIGN_RIGHT</code>, etc. * * @see javax.swing.text.StyleConstants */ protected void setJustification(int j) { super.setJustification(j); justification = j; } /** * Gets the type of justification. * @return one of <code>StyleConstants.ALIGN_LEFT</code>, * <code>StyleConstants.ALIGN_RIGHT</code>, etc. * * @see javax.swing.text.StyleConstants */ protected int getJustification() { return justification; } /** * Gets the index of the child view (<code>FloatRow</code>) at the given document position. * Must be implemented here to give access to classes in this package that need it, like * <code>FloatableImageView</code>. * * @param pos The document position. * @return The child view (<code>FloatRow</code>) for the given document position. */ protected int getViewIndexAtPosition(int pos) { return super.getViewIndexAtPosition(pos); } /** * Strategy for maintaining the physical form of the flow. * Takes into account floated images in the margins when performing layout. */ public static class FloatFlowStrategy extends FlowStrategy { /** * Creates all <code>Row</code>s and their contents from scratch whenever the paragraph's * layout is invalidated. * @param fv the <code>FlowView</code> to lay out */ public void layout(FlowView fv) { if (fv instanceof FloatParagraphView) { FloatParagraphView pv = (FloatParagraphView)fv; // Get previous and next siblings View parent = pv.getParent(); int n = parent.getViewCount(); View prevSibling = null; View nextSibling = null; int thisIndex; for (thisIndex = 0; thisIndex < n; thisIndex++) { if (parent.getView(thisIndex) == pv) { if (thisIndex > 0) prevSibling = parent.getView(thisIndex - 1); if (thisIndex < n - 1) nextSibling = parent.getView(thisIndex + 1); break; } } // If the previous sibling is a FloatParagraphView, copy its float manager // to obtain any floats intruding from above. pv.intrudingFloats.clear(); if (prevSibling != null && prevSibling instanceof FloatParagraphView) { FloatParagraphView prevPara = (FloatParagraphView)prevSibling; pv.fmm = prevPara.fmm.copy(); // Take responsibility for painting any active floating images Iterator it = pv.fmm.floating.iterator(); while (it.hasNext()) { FloatMarginManager.FloatSpec spec = (FloatMarginManager.FloatSpec)it.next(); pv.intrudingFloats.add(spec.iv); } // Advance the float state to allow for top margin of paragraph pv.fmm.moveDown(pv.getTopInset(), pv); } else { // Set up an empty float state pv.fmm.clear(); // Assumption: the span only changes at the start of a paragraph with no // floats intruding on it from above. We may have to change this so that span // is re-evaluated whenever a margin is cleared of floats by fmm.moveDown(), // especially if we implement support for floats through lists and blockquotes. pv.fmm.setSpan(pv.layoutSpan); } // Perform the layout super.layout(fv); // Advance the float state to allow for bottom margin of paragraph pv.fmm.moveDown(pv.getBottomInset(), pv); // Check if this paragraph contained or now contains floats that intruded on // following paragraph or not. boolean wasPassForward = pv.passForwardFloats; pv.passForwardFloats = !pv.fmm.isEmpty(); // Invalidate following paragraph if appropriate if (nextSibling != null && nextSibling instanceof FloatParagraphView && (wasPassForward || pv.passForwardFloats)) { ((ParagraphView)nextSibling).layoutChanged(X_AXIS); ((ParagraphView)nextSibling).layoutChanged(Y_AXIS); } // If last paragraph, expand bottom margin of this paragraph to allow for floating // images to paint fully if (nextSibling == null || !(nextSibling instanceof FloatParagraphView) && pv.passForwardFloats) { FloatRow additionalRow = (FloatRow)pv.createRow(); // Reset insets to allow bottom margin short bottom = 0; while (!pv.fmm.isEmpty()) { short dist = (short)pv.fmm.getRemainingActiveLength(); pv.fmm.moveDown(dist, pv); bottom += dist; } additionalRow.setInsets((short)0, (short)0, bottom, (short)0); pv.append(additionalRow); } } else super.layout(fv); } /** * Fills a row with as many sub-views as will fit. Updates the floating * image indentation state if neccessary. * * @param fv the <code>FlowView</code> for which to lay out a row. * @param rowIndex the index of the row view to lay out. * @param pos the document position to begin the row. * @return The document position at the end of the row. */ protected int layoutRow(FlowView fv, int rowIndex, int pos) { FloatRow row = (FloatRow)fv.getView(rowIndex); // Get margins for floating images // Normally we would get the default insets using getLeftInset, etc., but // implementation of these in Row adds to the default values for line height // and first line indent without removing these again on setInset. To work around // this, we simply assume zero insets on all sides. short left = (short)((FloatParagraphView)fv).fmm.usedLeft; short right = (short)((FloatParagraphView)fv).fmm.usedRight; row.setInsets((short)0, left, (short)0, right); // Perform layout tasks in superclass int endPos = super.layoutRow(fv, rowIndex, pos); FloatParagraphView fpv = (FloatParagraphView)fv; // Process any floating images in the new row processFloatingImages(fpv, row); // Add bottom margin to row if it ends with a clearing break int bottom = 0; int dist; if (row.isClearLeft()) { while (!fpv.fmm.isEmpty(LEFT)) { dist = fpv.fmm.getRemainingActiveLength(LEFT); if (dist == 0) break; fpv.fmm.moveDown(dist, fpv); bottom += dist; } } if (row.isClearRight()) { while (!fpv.fmm.isEmpty(RIGHT)) { dist = fpv.fmm.getRemainingActiveLength(RIGHT); if (dist == 0) break; fpv.fmm.moveDown(dist, fpv); bottom += dist; } } row.setInsets((short)0, left, (short)bottom, right); return endPos; } /** * Adjusts the margins of the parent paragraph view in response to any floated images * in the current row. Floated images at the very start of the row intrude on the * margins of this row. Other floated images intrude beginning with the next row. * This distinction contravenes CSS 2 and CSS2.1, but it's also what every browser out * there does. * @param fv the paragraph view * @param row the row view */ private void processFloatingImages(FloatParagraphView fv, Row row) { int n = row.getViewCount(); boolean isLeadingImage = true; int rowHeight = row.calculateMinorAxisRequirements(Y_AXIS, null).preferred; for (int i = 0; i < n; i++) { View v = row.getView(i); if (v instanceof FloatableImageView && ((FloatableImageView)v).isFloat()) { FloatableImageView iv = (FloatableImageView)v; int margin = iv.getLayoutType() == FloatableImageView.FLOAT_LEFT ? LEFT : RIGHT; // Update float state fv.fmm.addFloat( (int)iv.getPreferredSpan(X_AXIS, true), (int)iv.getPreferredSpan(Y_AXIS, true), margin, !isLeadingImage, iv, fv); } else if (isLeadingImage) { isLeadingImage = false; } } fv.fmm.moveDown(rowHeight, fv); } } /** * Data structure used to track images floating in the margins of paragraphs. * Allows you to advance down the major axis and have floats drop out of significance. * Will find the best position for a new floating image given the other floating images * currently in effect. */ protected static class FloatMarginManager { /** * Active floats. */ private LinkedList floating; /** * Floats waiting for space to free up. */ private LinkedList waiting; /** * The available span, when no floats are active. */ private int span = Integer.MAX_VALUE; /** * The amount of span currently occupied by left floats. */ private int usedLeft = 0; /** * The amount of span currently occupied by right floats. */ private int usedRight = 0; /** * Create an empty <code>FloatMarginManager</code>. */ protected FloatMarginManager() { floating = new LinkedList(); waiting = new LinkedList(); } /** * Sets the available span when no floats are intruding. * @param newSpan The available span in pixels. */ protected void setSpan(int newSpan) { span = newSpan; } /** * Adds a floating image block, either to begin at the current position, or * to wait for space to be positioned. * * @param size amount of the margin the floating block consumes. * @param length length of the floating block (i.e. image height). * @param alignment margin to float in (<code>RIGHT</code> or <code>LEFT</code>) * @param mustWait if <code>true</code>, this image will not be assigned a space in the * margin until at least the next row. * @param iv the <code>FloatableImageView</code> for the image wanting to be floated * @param fpv the <code>FloatParagraphView</code> that will be responsible for painting * the <code>FloatableImageView</code> if it is added to the margin immediately. * This may be <code>null</code> if <code>mustWait</code> is <code>true</code>. */ protected void addFloat(int size, int length, int alignment, boolean mustWait, FloatableImageView iv, FloatParagraphView fpv) { FloatSpec fs = new FloatSpec(size, length, alignment, iv); iv.vFloatPos = 0; if (!mustWait && getRemainingSpan() >= size) { if (alignment == LEFT) { iv.hFloatPos = usedLeft; usedLeft += size; } else if (alignment == RIGHT) { iv.hFloatPos = usedRight; usedRight += size; } // Added first because floats expire like a LIFO queue floating.addFirst(fs); fpv.intrudingFloats.add(iv); } else { // Added last because floats become active like a FIFO queue waiting.addLast(fs); } } /** * Gets the amount of span still available between current floats. * @return The available span in pixels. */ protected int getRemainingSpan() { return span - usedLeft - usedRight; } /** * Gets the current amount of the margin occupied by floating image blocks. * * @param margin The margin to check (<code>LEFT</code> or <code>RIGHT</code>) * @return the size of the margin due to floating blocks in pixels */ protected int getMarginSize(int margin) { int m = 0; if (margin == LEFT) { m = usedLeft; } else if (margin == RIGHT) { m = usedRight; } return m; } /** * Calculates the remaining document length before all currently active floating blocks end. * This does not take into account floats that are waiting for space, so effectively this * gives the maximum distance before a new float comes into effect, or all floats are * finished. * * @return remaining document length with objects in the margin */ protected int getRemainingActiveLength() { int maxLength = 0; Iterator it = floating.iterator(); while (it.hasNext()) { FloatSpec fs = (FloatSpec)it.next(); maxLength = Math.max(maxLength, fs.length); } return maxLength; } /** * Calculates the remaining document length before all currently active floating blocks in * the specified margin end. * This does not take into account floats that are waiting for space, so effectively this * gives the maximum distance before a new float comes into effect, or all floats are * finished. * * @param margin the margin to measure the remaining distance in * @return remaining document length with objects in the margin */ protected int getRemainingActiveLength(int margin) { int maxLength = 0; Iterator it = floating.iterator(); while (it.hasNext()) { FloatSpec fs = (FloatSpec)it.next(); if (fs.alignment == margin) { maxLength = Math.max(maxLength, fs.length); } } return maxLength; } /** * Advances the major axis by the specified distance. Some active floats may * expire as they no longer intrude at this position, while elements waiting for float * space may become active floats. * * @param distance distance of document to advance * @param fpv the <code>FloatParagraphView</code> that will be responsible for painting any * floating images that take effect as a result of this call. */ protected void moveDown(int distance, FloatParagraphView fpv) { Iterator it; FloatSpec currentSpec; do { // If there are no floats to expire, we'll just move the requested distance int minDistance = distance; // Find out if we can move a shorter distance and get an innermost float to expire it = floating.iterator(); boolean foundInnermostLeftFloat = false; boolean foundInnerMostRightFloat = false; while (it.hasNext() && !(foundInnermostLeftFloat && foundInnerMostRightFloat)) { currentSpec = (FloatSpec)it.next(); if (currentSpec.alignment == LEFT && !foundInnermostLeftFloat) { foundInnermostLeftFloat = true; if (currentSpec.length < minDistance) { minDistance = currentSpec.length; } } else if (currentSpec.alignment == RIGHT && !foundInnerMostRightFloat) { foundInnerMostRightFloat = true; if (currentSpec.length < minDistance) { minDistance = currentSpec.length; } } } // Move the discovered distance it = floating.iterator(); boolean leftExpiring = true; boolean rightExpiring = true; while (it.hasNext()) { currentSpec = (FloatSpec)it.next(); currentSpec.length -= minDistance; // Identify floats eligible for expiration if (currentSpec.length <= 0) { // Expire only innermost floats if (currentSpec.alignment == LEFT && leftExpiring) { usedLeft -= currentSpec.size; it.remove(); } else if (currentSpec.alignment == RIGHT && rightExpiring) { usedRight -= currentSpec.size; it.remove(); } } else { if (currentSpec.alignment == LEFT) { leftExpiring = false; } else if (currentSpec.alignment == RIGHT) { rightExpiring = false; } } } // Activate any eligible waiting floats it = waiting.iterator(); while (it.hasNext()) { currentSpec = (FloatSpec)it.next(); // Increment vertical positions of all waiting floats currentSpec.iv.vFloatPos += minDistance; // If the waiting float will fit, or if there are no active floats if (currentSpec.size <= getRemainingSpan() || floating.size() == 0) { if (currentSpec.alignment == LEFT) { currentSpec.iv.hFloatPos = usedLeft; usedLeft += currentSpec.size; } else if (currentSpec.alignment == RIGHT) { currentSpec.iv.hFloatPos = usedRight; usedRight += currentSpec.size; } // Added first because floats expire like a LIFO queue floating.addFirst(currentSpec); fpv.intrudingFloats.add(currentSpec.iv); it.remove(); } } // Decrease distance remaining by amount moved distance -= minDistance; } while (distance > 0); } /** * Resets this object to its initial (empty) state, but keeps the same <code>span</code> * value. */ protected void clear() { floating.clear(); waiting.clear(); usedLeft = 0; usedRight = 0; } /** * Checks if there are currently any active (or waiting) images. * @return True if there are no active or waiting floated images. */ protected boolean isEmpty() { // If none floating, then we can assume none waiting return floating.isEmpty(); } /** * Checks if there are currently any active (or waiting) images in the specified margin. * @param margin The margin to check. <code>LEFT</code> or <code>RIGHT</code>. * @return True if there are no active or waiting floated images. */ protected boolean isEmpty(int margin) { return isEmpty(margin, false); } /** * Checks if there are currently any active (or waiting) images in the specified margin. * @param margin The margin to check. <code>LEFT</code> or <code>RIGHT</code>. * @return True if there are no active or waiting floated images. */ protected boolean isEmpty(int margin, boolean onlyFloating) { Iterator it = floating.iterator(); while (it.hasNext()) { if (((FloatSpec)it.next()).alignment == margin) { return false; } } if (onlyFloating) return true; it = waiting.iterator(); while (it.hasNext()) { if (((FloatSpec)it.next()).alignment == margin) { return false; } } return true; } /** * Performs a deep copy of this object. * @return A copy of this object, containing copies of its elements. */ public FloatMarginManager copy() { FloatMarginManager clone = new FloatMarginManager(); clone.span = span; clone.usedLeft = usedLeft; clone.usedRight = usedRight; Iterator it = floating.iterator(); while (it.hasNext()) { clone.floating.addLast(((FloatSpec)it.next()).copy()); } it = waiting.iterator(); while (it.hasNext()) { clone.waiting.addLast(((FloatSpec)it.next()).copy()); } return clone; } /** * A record of a single floated element's requested space. */ static class FloatSpec implements Cloneable { /** * The amount of the margin this float will consume. */ private int size; /** * The vertical distance along the margin this float will consume. */ private int length; /** * Which margin this float will consume. <code>LEFT</code> or <code>RIGHT</code>. */ private int alignment; /** * The <code>FloatableImageView</code> responsible for painting the floated image. */ private FloatableImageView iv; /** * Creates a new <code>FloatSpec</code> with the specified parameters. * @param size the amount of margin this float will consume * @param length the vertical distance along the margin this float will consume * @param alignment which margin this float will consume (<code>LEFT</code> or * <code>RIGHT</code>) * @param iv the <code>FloatableImageView</code> responsible for painting the floated * image */ FloatSpec(int size, int length, int alignment, FloatableImageView iv) { this.size = size; this.length = length; this.alignment = alignment; this.iv = iv; } /** * Performs a shallow copy of this <code>FloatSpec</code>. * @return A shallow copy of this object (i.e. the <code>iv</code> property will point * to the same instance). */ public Object copy() { Object clone = null; try { clone = super.clone(); } catch (CloneNotSupportedException ex) { // Should never happen } return clone; } } } /** * A <code>Row</code> that may have a floated image in its margin. */ class FloatRow extends Row { /** * Creates a <code>FloatRow</code>. * @param elem the paragraph element responsible for this row */ FloatRow(Element elem) { super(elem); } /** * Performs layout for the major axis of the box (i.e. the * axis that it represents). Zeroes the spans of right-aligned images at the * start of the row, adjusting subsequent offsets. * * @param targetSpan the total span given to the view, which * would be used to layout the children * @param axis the axis being layed out * @param offsets the offsets from the origin of the view for * each of the child views; this is a return value and is * filled in by the implementation of this method * @param spans the span of each child view; this is a return * value and is filled in by the implementation of this method */ protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets, int[] spans) { super.layoutMajorAxis(targetSpan, axis, offsets, spans); // Find right-floated floated images at the start of the row int n = getViewCount(); for (int i = 0; i < n; i++) { View v = getView(i); if (v instanceof FloatableImageView) { FloatableImageView fiv = (FloatableImageView)v; if (!fiv.isFloat()) break; if (fiv.getLayoutType() == FloatableImageView.FLOAT_RIGHT) { // Reduce the offset of subsequent views in the row for (int j = i + 1; j < n; j++) { offsets[j] -= spans[i]; } // Set the span of the right-floated image to zero spans[i] = 0; } } else break; } } /** * Checks if we should clear the left margin after this row. * * @return true if following rows should begin after any left floats */ boolean isClearLeft() { FloatBrView brView = getLastFloatBrView(); return brView != null && brView.isClearLeft(); } /** * Checks if we should clear the right margin after this row. * * @return true if following rows should begin after any right floats */ boolean isClearRight() { FloatBrView brView = getLastFloatBrView(); return brView != null && brView.isClearRight(); } /** * Returns the <code>FloatBrView</code> that terminates this row, if any. * * @return the last view in this row if it's a <code>FloatBrView</code>, or null if it's not */ FloatBrView getLastFloatBrView() { int n = getViewCount(); if (n > 0) { View lastView = getView(getViewCount() - 1); if (lastView instanceof FloatBrView) { return (FloatBrView)lastView; } } return null; } /** * Determines if a point falls before an allocated region. Takes into * account that this row may begin with a left-floated image. * @param x the X coordinate >= 0 * @param y the Y coordinate >= 0 * @param innerAlloc the allocated region; this is the area inside of the insets * @return true if the point lies before the region else false */ protected boolean isBefore(int x, int y, Rectangle innerAlloc) { if (getViewCount() > 0 && getView(0) instanceof FloatableImageView) { FloatableImageView iv = (FloatableImageView)getView(0); int imgWidth = (int)iv.getPreferredSpan(getAxis(), true); if (iv.getLayoutType() == FloatableImageView.FLOAT_LEFT) { x -= imgWidth; } } return super.isBefore(x, y, innerAlloc); } /** * Determines if a point falls after an allocated region. Takes into * account that this row may begin with a left-floated image. * @param x the X coordinate >= 0 * @param y the Y coordinate >= 0 * @param innerAlloc the allocated region; this is the area inside of the insets * @return true if the point lies after the region else false */ protected boolean isAfter(int x, int y, Rectangle innerAlloc) { if (getViewCount() > 0 && getView(0) instanceof FloatableImageView) { FloatableImageView iv = (FloatableImageView)getView(0); int imgWidth = (int) iv.getPreferredSpan(getAxis(), true); if (iv.getLayoutType() == FloatableImageView.FLOAT_LEFT) { x -= imgWidth; } } return super.isAfter(x, y, innerAlloc); } /** * Gets the top inset. Redeclared so that it's accessible to containing class. * * @return the top inset * @see FloatParagraphView#createRow */ protected short getTopInset() { return super.getTopInset(); } /** * Gets the left inset. Redeclared so that it's accessible to containing class. * * @return the left inset * @see FloatParagraphView#createRow */ protected short getLeftInset() { return super.getLeftInset(); } /** * Gets the right inset. Redeclared so that it's accessible to containing class. * * @return the right inset * @see FloatParagraphView#createRow */ protected short getRightInset() { return super.getRightInset(); } /** * Gets the bottom inset. Redeclared so that it's accessible to containing class. * * @return the bottom inset * @see FloatParagraphView#createRow */ protected short getBottomInset() { return super.getBottomInset(); } /** * Sets the insets. Redeclared so that it's accessible to containing class. * * @see FloatParagraphView#createRow */ protected void setInsets(short top, short left, short bottom, short right) { super.setInsets(top, left, bottom, right); } } /** * Internally created view that has the purpose of holding * the views that represent the children of the paragraph * that have been arranged in rows. * * Reimplemention of package-private inner class in * javax.swing.text.ParagraphView. * * @see javax.swing.text.ParagraphView.Row */ class Row extends BoxView { /** * Creates a row. * @param elem the paragraph element responsible for this row */ Row(Element elem) { super(elem, View.X_AXIS); } /** * This is reimplemented to do nothing since the * paragraph fills in the row with its needed * children. * * @param f the <code>ViewFactory</code> to (not) use */ protected void loadChildren(ViewFactory f) { } /** * Fetches the attributes to use when rendering. This view * isn't directly responsible for an element so it returns * the outer classes attributes. * * @return the <code>AttributeSet</code> from the parent view */ public AttributeSet getAttributes() { View p = getParent(); return (p != null) ? p.getAttributes() : null; } /** * Determines the desired alignment for this view along an axis. This is implemented to * give the total alignment needed to position the children with the alignment points * lined up along the axis orthoginal to the axis that is being tiled. The axis being * tiled will request to be centered (i.e. 0.5f). * @param axis may be either <code>View.X_AXIS</code> or <code>View.Y_AXIS</code> * @return the desired alignment >= 0.0f && <= 1.0f; this should be a value between 0.0 * and 1.0 where 0 indicates alignment at the origin and 1.0 indicates alignment to the * full span away from the origin; an alignment of 0.5 would be the center of the view */ public float getAlignment(int axis) { if (axis == View.X_AXIS) { switch (justification) { case StyleConstants.ALIGN_LEFT: return 0; case StyleConstants.ALIGN_RIGHT: return 1; case StyleConstants.ALIGN_CENTER: case StyleConstants.ALIGN_JUSTIFIED: return 0.5f; default: } } return super.getAlignment(axis); } /** * Provides a mapping from the document model coordinate space * to the coordinate space of the view mapped to it. This is * implemented to let the superclass find the position along * the major axis and the allocation of the row is used * along the minor axis, so that even though the children * are different heights they all get the same caret height. * * @param pos the position to convert * @param a the allocated region to render into * @param b the bias * @return the bounding box of the given position * @throws BadLocationException if the given position does not represent a * valid location in the associated document * @see View#modelToView */ public Shape modelToView(int pos, Shape a, Position.Bias b) throws BadLocationException { Rectangle r = a.getBounds(); View v = getViewAtPosition(pos, r); if ((v != null) && (!v.getElement().isLeaf())) { // Don't adjust the height if the view represents a branch. return super.modelToView(pos, a, b); } r = a.getBounds(); int height = r.height; int y = r.y; Shape loc = super.modelToView(pos, a, b); r = loc.getBounds(); r.height = height; r.y = y; return r; } /** * Fetches the portion of the model for which this view is responsible. * * @return the starting offset into the model >= 0 */ public int getStartOffset() { int offs = Integer.MAX_VALUE; int n = getViewCount(); for (int i = 0; i < n; i++) { View v = getView(i); offs = Math.min(offs, v.getStartOffset()); } return offs; } /** * Fetches the portion of the model for which this view is responsible. * * @return the ending offset into the model >= 0 */ public int getEndOffset() { int offs = 0; int n = getViewCount(); for (int i = 0; i < n; i++) { View v = getView(i); offs = Math.max(offs, v.getEndOffset()); } return offs; } /** * Perform layout for the minor axis of the box (i.e. the * axis orthoginal to the axis that it represents). The results * of the layout should be placed in the given arrays which represent * the allocations to the children along the minor axis. * * This is implemented to do a baseline layout of the children * by calling <code>BoxView.baselineLayout</code>. * * @param targetSpan the total span given to the view, which * whould be used to layout the children. * @param axis the axis being layed out. * @param offsets the offsets from the origin of the view for * each of the child views. This is a return value and is * filled in by the implementation of this method. * @param spans the span of each child view. This is a return * value and is filled in by the implementation of this method. */ protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans) { baselineLayout(targetSpan, axis, offsets, spans); } /** * Calculates the size requirements for the minor axis <code>axis</code>. * @param axis The minor axis (<code>Y_AXIS</code> in LTR/RTL text). * @param r The <code>SizeRequirements</code> object to return. If null, a new object will * be created. * @return The size requirements for the minor axis. */ protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r) { return baselineRequirements(axis, r); } /** * Fetches the child view index representing the given position in * the model. * * @param pos the position >= 0 * @return index of the view representing the given position, or * -1 if no view represents that position */ protected int getViewIndexAtPosition(int pos) { // This is expensive, but are views are not necessarily layed // out in model order. if (pos < getStartOffset() || pos >= getEndOffset()) return -1; for (int counter = getViewCount() - 1; counter >= 0; counter--) { View v = getView(counter); if (pos >= v.getStartOffset() && pos < v.getEndOffset()) { return counter; } } return -1; } /** * Gets the left inset. * * @return the inset */ protected short getLeftInset() { View parentView; int adjustment = 0; if ((parentView = getParent()) != null) { //use firstLineIdent for the first row if (this == parentView.getView(0)) { adjustment = firstLineIndent; } } return (short)(super.getLeftInset() + adjustment); } /** * Gets the bottom inset. * * @return the inset */ protected short getBottomInset() { SizeRequirements minorRequest = calculateMinorAxisRequirements(Y_AXIS, null); return (short)(super.getBottomInset() + ((minorRequest != null) ? minorRequest.preferred : 0) * lineSpacing); } } }