Index: javax/swing/text/FlowView.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/text/FlowView.java,v
retrieving revision 1.1
diff -u -r1.1 FlowView.java
--- javax/swing/text/FlowView.java 29 Jul 2005 09:56:10 -0000 1.1
+++ javax/swing/text/FlowView.java 25 Aug 2005 19:08:06 -0000
@@ -38,19 +38,580 @@
package javax.swing.text;
-// TODO: Implement this class.
-public class FlowView
- extends BoxView
+import java.awt.Container;
+import java.awt.Graphics;
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.util.Vector;
+
+import javax.swing.event.DocumentEvent;
+
+/**
+ * A View
that can flows it's children into it's layout space.
+ *
+ * The FlowView
manages a set of logical views (that are
+ * the children of the address@hidden #layoutPool} field). These are translated
+ * at layout time into a set of physical views. These are the views that
+ * are managed as the real child views. Each of these child views represents
+ * a row and are laid out within a box using the superclasses behaviour.
+ * The concrete implementation of the rows must be provided by subclasses.
+ *
+ * @author Roman Kennke (address@hidden)
+ */
+public abstract class FlowView extends BoxView
{
+ /**
+ * A strategy for translating the logical views of a FlowView
+ * into the real views.
+ */
+ public static class FlowStrategy
+ {
+ /**
+ * Creates a new instance of FlowStragegy
.
+ */
+ public FlowStrategy()
+ {
+ }
+
+ /**
+ * Receives notification from a FlowView
that some content
+ * has been inserted into the document at a location that the
+ * FlowView
is responsible for.
+ *
+ * The default implementation simply calls address@hidden #layout}.
+ *
+ * @param fv the flow view that sends the notification
+ * @param e the document event describing the change
+ * @param alloc the current allocation of the flow view
+ */
+ public void insertUpdate(FlowView fv, DocumentEvent e, Rectangle alloc)
+ {
+ layout(fv);
+ }
+
+ /**
+ * Receives notification from a FlowView
that some content
+ * has been removed from the document at a location that the
+ * FlowView
is responsible for.
+ *
+ * The default implementation simply calls address@hidden #layout}.
+ *
+ * @param fv the flow view that sends the notification
+ * @param e the document event describing the change
+ * @param alloc the current allocation of the flow view
+ */
+ public void removeUpdate(FlowView fv, DocumentEvent e, Rectangle alloc)
+ {
+ layout(fv);
+ }
+
+ /**
+ * Receives notification from a FlowView
that some attributes
+ * have changed in the document at a location that the
+ * FlowView
is responsible for.
+ *
+ * The default implementation simply calls address@hidden #layout}.
+ *
+ * @param fv the flow view that sends the notification
+ * @param e the document event describing the change
+ * @param alloc the current allocation of the flow view
+ */
+ public void changedUpdate(FlowView fv, DocumentEvent e, Rectangle alloc)
+ {
+ layout(fv);
+ }
+
+ /**
+ * Returns the logical view of the managed FlowView
.
+ *
+ * @param fv the flow view for which to return the logical view
+ *
+ * @return the logical view of the managed FlowView
+ */
+ public View getLogicalView(FlowView fv)
+ {
+ return fv.layoutPool;
+ }
+
+ /**
+ * Performs the layout for the whole view. By default this rebuilds
+ * all the physical views from the logical views of the managed FlowView.
+ *
+ * This is called by address@hidden FlowLayout#layout} to update the layout of
+ * the view.
+ *
+ * @param fv the flow view for which we perform the layout
+ */
+ public void layout(FlowView fv)
+ {
+ fv.removeAll();
+ Element el = fv.getElement();
+
+ int rowStart = el.getStartOffset();
+ int end = el.getEndOffset();
+ int rowIndex = 0;
+ while (rowStart >= 0 && rowStart < end)
+ {
+ View row = fv.createRow();
+ fv.append(row);
+ rowStart = layoutRow(fv, rowIndex, rowStart);
+ rowIndex++;
+ }
+ }
+
+ /**
+ * Lays out one row of the flow view. This is called by address@hidden #layout}
+ * to fill one row with child views until the available span is exhausted.
+ *
+ * @param fv the flow view for which we perform the layout
+ * @param rowIndex the index of the row
+ * @param pos the start position for the row
+ *
+ * @return the start position of the next row
+ */
+ protected int layoutRow(FlowView fv, int rowIndex, int pos)
+ {
+ int spanLeft = fv.getFlowSpan(rowIndex);
+ if (spanLeft <= 0)
+ return -1;
+
+ int offset = pos;
+ View row = fv.getView(rowIndex);
+ int flowAxis = fv.getFlowAxis();
+
+ while (spanLeft > 0)
+ {
+ View child = createView(fv, offset, spanLeft, rowIndex);
+ if (child == null)
+ break;
+
+ int span = (int) child.getPreferredSpan(flowAxis);
+ if (span > spanLeft)
+ break;
+
+ row.append(child);
+ spanLeft -= span;
+ offset = child.getEndOffset();
+ }
+ return offset;
+ }
+
+ /**
+ * Creates physical views that form the rows of the flow view. This
+ * can be an entire view from the logical view (if it fits within the
+ * available span), a fragment of such a view (if it doesn't fit in the
+ * available span and can be broken down) or null
(if it does
+ * not fit in the available span and also cannot be broken down).
+ *
+ * @param fv the flow view
+ * @param startOffset the start offset for the view to be created
+ * @param spanLeft the available span
+ * @param rowIndex the index of the row
+ *
+ * @return a view to fill the row with, or null
if there
+ * is no view or view fragment that fits in the available span
+ */
+ protected View createView(FlowView fv, int offset, int spanLeft,
+ int rowIndex)
+ {
+ // Find the logical element for the given offset.
+ View logicalView = getLogicalView(fv);
+
+ int viewIndex = logicalView.getViewIndex(offset, Position.Bias.Forward);
+ View child = logicalView.getView(viewIndex);
+ int flowAxis = fv.getFlowAxis();
+ int span = (int) child.getPreferredSpan(flowAxis);
+
+ if (span <= spanLeft)
+ return child;
+
+ else if (child.getBreakWeight(flowAxis, offset, spanLeft)
+ > BadBreakWeight)
+ // FIXME: What to do with the pos parameter here?
+ return child.breakView(flowAxis, offset, 0, spanLeft);
+ else
+ return null;
+ }
+ }
+
+ /**
+ * This special subclass of View
is used to represent
+ * the logical representation of this view. It does not support any
+ * visual representation, this is handled by the physical view implemented
+ * in the FlowView
.
+ */
+ class LogicalView extends View
+ {
+ /**
+ * The child views of this logical view.
+ */
+ Vector children;
+
+ /**
+ * Creates a new LogicalView instance.
+ */
+ LogicalView(Element el)
+ {
+ super(el);
+ children = new Vector();
+ }
+
+ /**
+ * Returns the container that holds this view. The logical view returns
+ * the enclosing FlowView's container here.
+ *
+ * @return the container that holds this view
+ */
+ public Container getContainer()
+ {
+ return FlowView.this.getContainer();
+ }
+
+ /**
+ * Returns the number of child views of this logical view.
+ *
+ * @return the number of child views of this logical view
+ */
+ public int getViewCount()
+ {
+ return children.size();
+ }
+
+ /**
+ * Returns the child view at the specified index.
+ *
+ * @param index the index
+ *
+ * @return the child view at the specified index
+ */
+ public View getView(int index)
+ {
+ return (View) children.get(index);
+ }
+
+ /**
+ * Replaces some child views with other child views.
+ *
+ * @param offset the offset at which to replace child views
+ * @param length the number of children to remove
+ * @param views the views to be inserted
+ */
+ public void replace(int offset, int length, View[] views)
+ {
+ if (length > 0)
+ {
+ for (int count = 0; count < length; ++count)
+ children.remove(offset);
+ }
+
+ int endOffset = offset + views.length;
+ for (int i = offset; i < endOffset; ++i)
+ {
+ children.add(i, views[i - offset]);
+ // Set the parent of the child views to the flow view itself so
+ // it has something to resolve.
+ views[i - offset].setParent(FlowView.this);
+ }
+ }
+
+ /**
+ * Returns the index of the child view that contains the specified
+ * position in the document model.
+ *
+ * @param pos the position for which we are searching the child view
+ * @param b the bias
+ *
+ * @return the index of the child view that contains the specified
+ * position in the document model
+ */
+ public int getViewIndex(int pos, Position.Bias b)
+ {
+ return getElement().getElementIndex(pos);
+ }
+
+ /**
+ * Throws an AssertionError because it must never be called. LogicalView
+ * only serves as a holder for child views and has no visual
+ * representation.
+ */
+ public float getPreferredSpan(int axis)
+ {
+ throw new AssertionError("This method must not be called in "
+ + "LogicalView.");
+ }
+
+ /**
+ * Throws an AssertionError because it must never be called. LogicalView
+ * only serves as a holder for child views and has no visual
+ * representation.
+ */
+ public Shape modelToView(int pos, Shape a, Position.Bias b)
+ throws BadLocationException
+ {
+ throw new AssertionError("This method must not be called in "
+ + "LogicalView.");
+ }
+
+ /**
+ * Throws an AssertionError because it must never be called. LogicalView
+ * only serves as a holder for child views and has no visual
+ * representation.
+ */
+ public void paint(Graphics g, Shape s)
+ {
+ throw new AssertionError("This method must not be called in "
+ + "LogicalView.");
+ }
+
+ /**
+ * Throws an AssertionError because it must never be called. LogicalView
+ * only serves as a holder for child views and has no visual
+ * representation.
+ */
+ public int viewToModel(float x, float y, Shape a, Position.Bias[] b)
+ {
+ throw new AssertionError("This method must not be called in "
+ + "LogicalView.");
+ }
+ }
+
+ /**
+ * The shared instance of FlowStrategy.
+ */
+ static final FlowStrategy sharedStrategy = new FlowStrategy();
+
+ /**
+ * The span of the FlowView
that should be flowed.
+ */
+ protected int layoutSpan;
+
+ /**
+ * Represents the logical child elements of this view, encapsulated within
+ * one parent view (an instance of a package private LogicalView
+ * class). These will be translated to a set of real views that are then
+ * displayed on screen. This translation is performed by the inner class
+ * address@hidden FlowStrategy}.
+ */
+ protected View layoutPool;
+
+ /**
+ * The FlowStrategy
to use for translating between the
+ * logical and physical view.
+ */
+ protected FlowStrategy strategy;
/**
* Creates a new FlowView
for the given
- * Element
.
+ * Element
and axis
.
*
* @param element the element that is rendered by this FlowView
+ * @param axis the axis along which the view is tiled, either
+ * View.X_AXIS
or View.Y_AXIS
, the flow
+ * axis is orthogonal to this one
+ */
+ public FlowView(Element element, int axis)
+ {
+ super(element, axis);
+ strategy = sharedStrategy;
+ }
+
+ /**
+ * Returns the axis along which the view should be flowed. This is
+ * orthogonal to the axis along which the boxes are tiled.
+ *
+ * @return the axis along which the view should be flowed
+ */
+ public int getFlowAxis()
+ {
+ int axis = getAxis();
+ int flowAxis;
+
+ if (axis == X_AXIS)
+ flowAxis = Y_AXIS;
+ else
+ flowAxis = X_AXIS;
+
+ return flowAxis;
+
+ }
+
+ /**
+ * Returns the span of the flow for the specified child view. A flow
+ * layout can be shaped by providing different span values for different
+ * child indices. The default implementation returns the entire available
+ * span inside the view.
+ *
+ * @param index the index of the child for which to return the span
+ *
+ * @return the span of the flow for the specified child view
+ */
+ public int getFlowSpan(int index)
+ {
+ return layoutSpan;
+ }
+
+ /**
+ * Returns the location along the flow axis where the flow span starts
+ * given a child view index. The flow can be shaped by providing
+ * different values here.
+ *
+ * @param index the index of the child for which to return the flow location
+ *
+ * @return the location along the flow axis where the flow span starts
+ */
+ public int getFlowStart(int index)
+ {
+ return getLeftInset(); // TODO: Is this correct?
+ }
+
+ /**
+ * Creates a new view that represents a row within a flow.
+ *
+ * @return a view for a new row
+ */
+ protected abstract View createRow();
+
+ /**
+ * Loads the children of this view. The FlowView
does not
+ * directly load its children. Instead it creates a logical view
+ * (@{link #layoutPool}) which is filled by the logical child views.
+ * The real children are created at layout time and each represent one
+ * row.
+ *
+ * This method is called by address@hidden #setParent} in order to initialize
+ * the view.
+ *
+ * @param vf the view factory to use for creating the child views
*/
- public FlowView(Element element)
+ protected void loadChildren(ViewFactory vf)
{
- super(element);
+ if (layoutPool == null)
+ {
+ layoutPool = new LogicalView(getElement());
+
+ Element el = getElement();
+ int count = el.getElementCount();
+ for (int i = 0; i < count; ++i)
+ {
+ Element childEl = el.getElement(i);
+ View childView = vf.create(childEl);
+ layoutPool.append(childView);
+ }
+ }
+ }
+
+ /**
+ * Performs the layout of this view. If the span along the flow axis changed,
+ * this first calls address@hidden FlowStrategy.layout} in order to rebuild the
+ * rows of this view. Then the superclass's behaviour is called to arrange
+ * the rows within the box.
+ *
+ * @param width the width of the view
+ * @param height the height of the view
+ */
+ protected void layout(int width, int height)
+ {
+ boolean rebuild = false;
+
+ int flowAxis = getFlowAxis();
+ if (flowAxis == X_AXIS)
+ {
+ rebuild = !(width == layoutSpan);
+ layoutSpan = width;
+ }
+ else
+ {
+ rebuild = !(height == layoutSpan);
+ layoutSpan = height;
+ }
+
+ if (rebuild)
+ strategy.layout(this);
+
+ // TODO: If the span along the box axis has changed in the process of
+ // relayouting the rows (that is, if rows have been added or removed),
+ // call preferenceChanged in order to throw away cached layout information
+ // of the surrounding BoxView.
+
+ super.layout(width, height);
+ }
+
+ /**
+ * Receice notification that some content has been inserted in the region
+ * that this view is responsible for. This calls
+ * address@hidden FlowStrategy#insertUpdate}.
+ *
+ * @param changes the document event describing the changes
+ * @param a the current allocation of the view
+ * @param vf the view factory that is used for creating new child views
+ */
+ public void insertUpdate(DocumentEvent changes, Shape a, ViewFactory vf)
+ {
+ strategy.insertUpdate(this, changes, getInsideAllocation(a));
+ }
+
+ /**
+ * Receice notification that some content has been removed from the region
+ * that this view is responsible for. This calls
+ * address@hidden FlowStrategy#removeUpdate}.
+ *
+ * @param changes the document event describing the changes
+ * @param a the current allocation of the view
+ * @param vf the view factory that is used for creating new child views
+ */
+ public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory vf)
+ {
+ strategy.removeUpdate(this, changes, getInsideAllocation(a));
+ }
+
+ /**
+ * Receice notification that some attributes changed in the region
+ * that this view is responsible for. This calls
+ * address@hidden FlowStrategy#changedUpdate}.
+ *
+ * @param changes the document event describing the changes
+ * @param a the current allocation of the view
+ * @param vf the view factory that is used for creating new child views
+ */
+ public void changedUpdate(DocumentEvent changes, Shape a, ViewFactory vf)
+ {
+ strategy.changedUpdate(this, changes, getInsideAllocation(a));
+ }
+
+ /**
+ * Returns the index of the child View
for the given model
+ * position.
+ *
+ * This is implemented to iterate over the children of this
+ * view (the rows) and return the index of the first view that contains
+ * the given position.
+ *
+ * @param pos the model position for whicht the child View
is
+ * queried
+ *
+ * @return the index of the child View
for the given model
+ * position
+ */
+ protected int getViewIndexAtPosition(int pos)
+ {
+ // First make sure we have a valid layout.
+ if (!isAllocationValid())
+ layout(getWidth(), getHeight());
+
+ int count = getViewCount();
+ int result = -1;
+
+ for (int i = 0; i < count; ++i)
+ {
+ View child = getView(i);
+ int start = child.getStartOffset();
+ int end = child.getEndOffset();
+ if (start <= pos && end > pos)
+ {
+ result = i;
+ break;
+ }
+ }
+ return result;
}
}