You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
896 lines
32 KiB
896 lines
32 KiB
/*
|
|
* Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved.
|
|
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*
|
|
*/
|
|
package javax.swing.text;
|
|
|
|
import java.awt.*;
|
|
import java.util.Vector;
|
|
import javax.swing.event.*;
|
|
import javax.swing.SizeRequirements;
|
|
|
|
/**
|
|
* A View that tries to flow it's children into some
|
|
* partially constrained space. This can be used to
|
|
* build things like paragraphs, pages, etc. The
|
|
* flow is made up of the following pieces of functionality.
|
|
* <ul>
|
|
* <li>A logical set of child views, which as used as a
|
|
* layout pool from which a physical view is formed.
|
|
* <li>A strategy for translating the logical view to
|
|
* a physical (flowed) view.
|
|
* <li>Constraints for the strategy to work against.
|
|
* <li>A physical structure, that represents the flow.
|
|
* The children of this view are where the pieces of
|
|
* of the logical views are placed to create the flow.
|
|
* </ul>
|
|
*
|
|
* @author Timothy Prinzing
|
|
* @see View
|
|
* @since 1.3
|
|
*/
|
|
public abstract class FlowView extends BoxView {
|
|
|
|
/**
|
|
* Constructs a FlowView for the given element.
|
|
*
|
|
* @param elem the element that this view is responsible for
|
|
* @param axis may be either View.X_AXIS or View.Y_AXIS
|
|
*/
|
|
public FlowView(Element elem, int axis) {
|
|
super(elem, axis);
|
|
layoutSpan = Integer.MAX_VALUE;
|
|
strategy = new FlowStrategy();
|
|
}
|
|
|
|
/**
|
|
* Fetches the axis along which views should be
|
|
* flowed. By default, this will be the axis
|
|
* orthogonal to the axis along which the flow
|
|
* rows are tiled (the axis of the default flow
|
|
* rows themselves). This is typically used
|
|
* by the <code>FlowStrategy</code>.
|
|
*/
|
|
public int getFlowAxis() {
|
|
if (getAxis() == Y_AXIS) {
|
|
return X_AXIS;
|
|
}
|
|
return Y_AXIS;
|
|
}
|
|
|
|
/**
|
|
* Fetch the constraining span to flow against for
|
|
* the given child index. This is called by the
|
|
* FlowStrategy while it is updating the flow.
|
|
* A flow can be shaped by providing different values
|
|
* for the row constraints. By default, the entire
|
|
* span inside of the insets along the flow axis
|
|
* is returned.
|
|
*
|
|
* @param index the index of the row being updated.
|
|
* This should be a value >= 0 and < getViewCount().
|
|
* @see #getFlowStart
|
|
*/
|
|
public int getFlowSpan(int index) {
|
|
return layoutSpan;
|
|
}
|
|
|
|
/**
|
|
* Fetch the location along the flow axis that the
|
|
* flow span will start at. This is called by the
|
|
* FlowStrategy while it is updating the flow.
|
|
* A flow can be shaped by providing different values
|
|
* for the row constraints.
|
|
|
|
* @param index the index of the row being updated.
|
|
* This should be a value >= 0 and < getViewCount().
|
|
* @see #getFlowSpan
|
|
*/
|
|
public int getFlowStart(int index) {
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Create a View that should be used to hold a
|
|
* a rows worth of children in a flow. This is
|
|
* called by the FlowStrategy when new children
|
|
* are added or removed (i.e. rows are added or
|
|
* removed) in the process of updating the flow.
|
|
*/
|
|
protected abstract View createRow();
|
|
|
|
// ---- BoxView methods -------------------------------------
|
|
|
|
/**
|
|
* Loads all of the children to initialize the view.
|
|
* This is called by the <code>setParent</code> method.
|
|
* This is reimplemented to not load any children directly
|
|
* (as they are created in the process of formatting).
|
|
* If the layoutPool variable is null, an instance of
|
|
* LogicalView is created to represent the logical view
|
|
* that is used in the process of formatting.
|
|
*
|
|
* @param f the view factory
|
|
*/
|
|
protected void loadChildren(ViewFactory f) {
|
|
if (layoutPool == null) {
|
|
layoutPool = new LogicalView(getElement());
|
|
}
|
|
layoutPool.setParent(this);
|
|
|
|
// This synthetic insertUpdate call gives the strategy a chance
|
|
// to initialize.
|
|
strategy.insertUpdate(this, null, null);
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
if (pos >= getStartOffset() && (pos < getEndOffset())) {
|
|
for (int counter = 0; counter < getViewCount(); counter++) {
|
|
View v = getView(counter);
|
|
if(pos >= v.getStartOffset() &&
|
|
pos < v.getEndOffset()) {
|
|
return counter;
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Lays out the children. If the span along the flow
|
|
* axis has changed, layout is marked as invalid which
|
|
* which will cause the superclass behavior to recalculate
|
|
* the layout along the box axis. The FlowStrategy.layout
|
|
* method will be called to rebuild the flow rows as
|
|
* appropriate. If the height of this view changes
|
|
* (determined by the preferred size along the box axis),
|
|
* a preferenceChanged is called. Following all of that,
|
|
* the normal box layout of the superclass is performed.
|
|
*
|
|
* @param width the width to lay out against >= 0. This is
|
|
* the width inside of the inset area.
|
|
* @param height the height to lay out against >= 0 This
|
|
* is the height inside of the inset area.
|
|
*/
|
|
protected void layout(int width, int height) {
|
|
final int faxis = getFlowAxis();
|
|
int newSpan;
|
|
if (faxis == X_AXIS) {
|
|
newSpan = width;
|
|
} else {
|
|
newSpan = height;
|
|
}
|
|
if (layoutSpan != newSpan) {
|
|
layoutChanged(faxis);
|
|
layoutChanged(getAxis());
|
|
layoutSpan = newSpan;
|
|
}
|
|
|
|
// repair the flow if necessary
|
|
if (! isLayoutValid(faxis)) {
|
|
final int heightAxis = getAxis();
|
|
int oldFlowHeight = (heightAxis == X_AXIS)? getWidth() : getHeight();
|
|
strategy.layout(this);
|
|
int newFlowHeight = (int) getPreferredSpan(heightAxis);
|
|
if (oldFlowHeight != newFlowHeight) {
|
|
View p = getParent();
|
|
if (p != null) {
|
|
p.preferenceChanged(this, (heightAxis == X_AXIS), (heightAxis == Y_AXIS));
|
|
}
|
|
|
|
// PENDING(shannonh)
|
|
// Temporary fix for 4250847
|
|
// Can be removed when TraversalContext is added
|
|
Component host = getContainer();
|
|
if (host != null) {
|
|
//nb idk 12/12/2001 host should not be equal to null. We need to add assertion here
|
|
host.repaint();
|
|
}
|
|
}
|
|
}
|
|
|
|
super.layout(width, height);
|
|
}
|
|
|
|
/**
|
|
* Calculate requirements along the minor axis. This
|
|
* is implemented to forward the request to the logical
|
|
* view by calling getMinimumSpan, getPreferredSpan, and
|
|
* getMaximumSpan on it.
|
|
*/
|
|
protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r) {
|
|
if (r == null) {
|
|
r = new SizeRequirements();
|
|
}
|
|
float pref = layoutPool.getPreferredSpan(axis);
|
|
float min = layoutPool.getMinimumSpan(axis);
|
|
// Don't include insets, Box.getXXXSpan will include them.
|
|
r.minimum = (int)min;
|
|
r.preferred = Math.max(r.minimum, (int) pref);
|
|
r.maximum = Integer.MAX_VALUE;
|
|
r.alignment = 0.5f;
|
|
return r;
|
|
}
|
|
|
|
// ---- View methods ----------------------------------------------------
|
|
|
|
/**
|
|
* Gives notification that something was inserted into the document
|
|
* in a location that this view is responsible for.
|
|
*
|
|
* @param changes the change information from the associated document
|
|
* @param a the current allocation of the view
|
|
* @param f the factory to use to rebuild if the view has children
|
|
* @see View#insertUpdate
|
|
*/
|
|
public void insertUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
|
|
layoutPool.insertUpdate(changes, a, f);
|
|
strategy.insertUpdate(this, changes, getInsideAllocation(a));
|
|
}
|
|
|
|
/**
|
|
* Gives notification that something was removed from the document
|
|
* in a location that this view is responsible for.
|
|
*
|
|
* @param changes the change information from the associated document
|
|
* @param a the current allocation of the view
|
|
* @param f the factory to use to rebuild if the view has children
|
|
* @see View#removeUpdate
|
|
*/
|
|
public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
|
|
layoutPool.removeUpdate(changes, a, f);
|
|
strategy.removeUpdate(this, changes, getInsideAllocation(a));
|
|
}
|
|
|
|
/**
|
|
* Gives notification from the document that attributes were changed
|
|
* in a location that this view is responsible for.
|
|
*
|
|
* @param changes the change information from the associated document
|
|
* @param a the current allocation of the view
|
|
* @param f the factory to use to rebuild if the view has children
|
|
* @see View#changedUpdate
|
|
*/
|
|
public void changedUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
|
|
layoutPool.changedUpdate(changes, a, f);
|
|
strategy.changedUpdate(this, changes, getInsideAllocation(a));
|
|
}
|
|
|
|
/** {@inheritDoc} */
|
|
public void setParent(View parent) {
|
|
super.setParent(parent);
|
|
if (parent == null
|
|
&& layoutPool != null ) {
|
|
layoutPool.setParent(null);
|
|
}
|
|
}
|
|
|
|
// --- variables -----------------------------------------------
|
|
|
|
/**
|
|
* Default constraint against which the flow is
|
|
* created against.
|
|
*/
|
|
protected int layoutSpan;
|
|
|
|
/**
|
|
* These are the views that represent the child elements
|
|
* of the element this view represents (The logical view
|
|
* to translate to a physical view). These are not
|
|
* directly children of this view. These are either
|
|
* placed into the rows directly or used for the purpose
|
|
* of breaking into smaller chunks, to form the physical
|
|
* view.
|
|
*/
|
|
protected View layoutPool;
|
|
|
|
/**
|
|
* The behavior for keeping the flow updated. By
|
|
* default this is a singleton shared by all instances
|
|
* of FlowView (FlowStrategy is stateless). Subclasses
|
|
* can create an alternative strategy, which might keep
|
|
* state.
|
|
*/
|
|
protected FlowStrategy strategy;
|
|
|
|
/**
|
|
* Strategy for maintaining the physical form
|
|
* of the flow. The default implementation is
|
|
* completely stateless, and recalculates the
|
|
* entire flow if the layout is invalid on the
|
|
* given FlowView. Alternative strategies can
|
|
* be implemented by subclassing, and might
|
|
* perform incremental repair to the layout
|
|
* or alternative breaking behavior.
|
|
* @since 1.3
|
|
*/
|
|
public static class FlowStrategy {
|
|
Position damageStart = null;
|
|
Vector<View> viewBuffer;
|
|
|
|
void addDamage(FlowView fv, int offset) {
|
|
if (offset >= fv.getStartOffset() && offset < fv.getEndOffset()) {
|
|
if (damageStart == null || offset < damageStart.getOffset()) {
|
|
try {
|
|
damageStart = fv.getDocument().createPosition(offset);
|
|
} catch (BadLocationException e) {
|
|
// shouldn't happen since offset is inside view bounds
|
|
assert(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void unsetDamage() {
|
|
damageStart = null;
|
|
}
|
|
|
|
/**
|
|
* Gives notification that something was inserted into the document
|
|
* in a location that the given flow view is responsible for. The
|
|
* strategy should update the appropriate changed region (which
|
|
* depends upon the strategy used for repair).
|
|
*
|
|
* @param e the change information from the associated document
|
|
* @param alloc the current allocation of the view inside of the insets.
|
|
* This value will be null if the view has not yet been displayed.
|
|
* @see View#insertUpdate
|
|
*/
|
|
public void insertUpdate(FlowView fv, DocumentEvent e, Rectangle alloc) {
|
|
// FlowView.loadChildren() makes a synthetic call into this,
|
|
// passing null as e
|
|
if (e != null) {
|
|
addDamage(fv, e.getOffset());
|
|
}
|
|
|
|
if (alloc != null) {
|
|
Component host = fv.getContainer();
|
|
if (host != null) {
|
|
host.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
|
|
}
|
|
} else {
|
|
fv.preferenceChanged(null, true, true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gives notification that something was removed from the document
|
|
* in a location that the given flow view is responsible for.
|
|
*
|
|
* @param e the change information from the associated document
|
|
* @param alloc the current allocation of the view inside of the insets.
|
|
* @see View#removeUpdate
|
|
*/
|
|
public void removeUpdate(FlowView fv, DocumentEvent e, Rectangle alloc) {
|
|
addDamage(fv, e.getOffset());
|
|
if (alloc != null) {
|
|
Component host = fv.getContainer();
|
|
if (host != null) {
|
|
host.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
|
|
}
|
|
} else {
|
|
fv.preferenceChanged(null, true, true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gives notification from the document that attributes were changed
|
|
* in a location that this view is responsible for.
|
|
*
|
|
* @param fv the <code>FlowView</code> containing the changes
|
|
* @param e the <code>DocumentEvent</code> describing the changes
|
|
* done to the Document
|
|
* @param alloc Bounds of the View
|
|
* @see View#changedUpdate
|
|
*/
|
|
public void changedUpdate(FlowView fv, DocumentEvent e, Rectangle alloc) {
|
|
addDamage(fv, e.getOffset());
|
|
if (alloc != null) {
|
|
Component host = fv.getContainer();
|
|
if (host != null) {
|
|
host.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
|
|
}
|
|
} else {
|
|
fv.preferenceChanged(null, true, true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This method gives flow strategies access to the logical
|
|
* view of the FlowView.
|
|
*/
|
|
protected View getLogicalView(FlowView fv) {
|
|
return fv.layoutPool;
|
|
}
|
|
|
|
/**
|
|
* Update the flow on the given FlowView. By default, this causes
|
|
* all of the rows (child views) to be rebuilt to match the given
|
|
* constraints for each row. This is called by a FlowView.layout
|
|
* to update the child views in the flow.
|
|
*
|
|
* @param fv the view to reflow
|
|
*/
|
|
public void layout(FlowView fv) {
|
|
View pool = getLogicalView(fv);
|
|
int rowIndex, p0;
|
|
int p1 = fv.getEndOffset();
|
|
|
|
if (fv.majorAllocValid) {
|
|
if (damageStart == null) {
|
|
return;
|
|
}
|
|
// In some cases there's no view at position damageStart, so
|
|
// step back and search again. See 6452106 for details.
|
|
int offset = damageStart.getOffset();
|
|
while ((rowIndex = fv.getViewIndexAtPosition(offset)) < 0) {
|
|
offset--;
|
|
}
|
|
if (rowIndex > 0) {
|
|
rowIndex--;
|
|
}
|
|
p0 = fv.getView(rowIndex).getStartOffset();
|
|
} else {
|
|
rowIndex = 0;
|
|
p0 = fv.getStartOffset();
|
|
}
|
|
reparentViews(pool, p0);
|
|
|
|
viewBuffer = new Vector<View>(10, 10);
|
|
int rowCount = fv.getViewCount();
|
|
while (p0 < p1) {
|
|
View row;
|
|
if (rowIndex >= rowCount) {
|
|
row = fv.createRow();
|
|
fv.append(row);
|
|
} else {
|
|
row = fv.getView(rowIndex);
|
|
}
|
|
p0 = layoutRow(fv, rowIndex, p0);
|
|
rowIndex++;
|
|
}
|
|
viewBuffer = null;
|
|
|
|
if (rowIndex < rowCount) {
|
|
fv.replace(rowIndex, rowCount - rowIndex, null);
|
|
}
|
|
unsetDamage();
|
|
}
|
|
|
|
/**
|
|
* Creates a row of views that will fit within the
|
|
* layout span of the row. This is called by the layout method.
|
|
* This is implemented to fill the row by repeatedly calling
|
|
* the createView method until the available span has been
|
|
* exhausted, a forced break was encountered, or the createView
|
|
* method returned null. If the remaining span was exhausted,
|
|
* the adjustRow method will be called to perform adjustments
|
|
* to the row to try and make it fit into the given span.
|
|
*
|
|
* @param rowIndex the index of the row to fill in with views. The
|
|
* row is assumed to be empty on entry.
|
|
* @param pos The current position in the children of
|
|
* this views element from which to start.
|
|
* @return the position to start the next row
|
|
*/
|
|
protected int layoutRow(FlowView fv, int rowIndex, int pos) {
|
|
View row = fv.getView(rowIndex);
|
|
float x = fv.getFlowStart(rowIndex);
|
|
float spanLeft = fv.getFlowSpan(rowIndex);
|
|
int end = fv.getEndOffset();
|
|
TabExpander te = (fv instanceof TabExpander) ? (TabExpander)fv : null;
|
|
final int flowAxis = fv.getFlowAxis();
|
|
|
|
int breakWeight = BadBreakWeight;
|
|
float breakX = 0f;
|
|
float breakSpan = 0f;
|
|
int breakIndex = -1;
|
|
int n = 0;
|
|
|
|
viewBuffer.clear();
|
|
while (pos < end && spanLeft >= 0) {
|
|
View v = createView(fv, pos, (int)spanLeft, rowIndex);
|
|
if (v == null) {
|
|
break;
|
|
}
|
|
|
|
int bw = v.getBreakWeight(flowAxis, x, spanLeft);
|
|
if (bw >= ForcedBreakWeight) {
|
|
View w = v.breakView(flowAxis, pos, x, spanLeft);
|
|
if (w != null) {
|
|
viewBuffer.add(w);
|
|
} else if (n == 0) {
|
|
// if the view does not break, and it is the only view
|
|
// in a row, use the whole view
|
|
viewBuffer.add(v);
|
|
}
|
|
break;
|
|
} else if (bw >= breakWeight && bw > BadBreakWeight) {
|
|
breakWeight = bw;
|
|
breakX = x;
|
|
breakSpan = spanLeft;
|
|
breakIndex = n;
|
|
}
|
|
|
|
float chunkSpan;
|
|
if (flowAxis == X_AXIS && v instanceof TabableView) {
|
|
chunkSpan = ((TabableView)v).getTabbedSpan(x, te);
|
|
} else {
|
|
chunkSpan = v.getPreferredSpan(flowAxis);
|
|
}
|
|
|
|
if (chunkSpan > spanLeft && breakIndex >= 0) {
|
|
// row is too long, and we may break
|
|
if (breakIndex < n) {
|
|
v = viewBuffer.get(breakIndex);
|
|
}
|
|
for (int i = n - 1; i >= breakIndex; i--) {
|
|
viewBuffer.remove(i);
|
|
}
|
|
v = v.breakView(flowAxis, v.getStartOffset(), breakX, breakSpan);
|
|
}
|
|
|
|
spanLeft -= chunkSpan;
|
|
x += chunkSpan;
|
|
viewBuffer.add(v);
|
|
pos = v.getEndOffset();
|
|
n++;
|
|
}
|
|
|
|
View[] views = new View[viewBuffer.size()];
|
|
viewBuffer.toArray(views);
|
|
row.replace(0, row.getViewCount(), views);
|
|
return (views.length > 0 ? row.getEndOffset() : pos);
|
|
}
|
|
|
|
/**
|
|
* Adjusts the given row if possible to fit within the
|
|
* layout span. By default this will try to find the
|
|
* highest break weight possible nearest the end of
|
|
* the row. If a forced break is encountered, the
|
|
* break will be positioned there.
|
|
*
|
|
* @param rowIndex the row to adjust to the current layout
|
|
* span.
|
|
* @param desiredSpan the current layout span >= 0
|
|
* @param x the location r starts at.
|
|
*/
|
|
protected void adjustRow(FlowView fv, int rowIndex, int desiredSpan, int x) {
|
|
final int flowAxis = fv.getFlowAxis();
|
|
View r = fv.getView(rowIndex);
|
|
int n = r.getViewCount();
|
|
int span = 0;
|
|
int bestWeight = BadBreakWeight;
|
|
int bestSpan = 0;
|
|
int bestIndex = -1;
|
|
View v;
|
|
for (int i = 0; i < n; i++) {
|
|
v = r.getView(i);
|
|
int spanLeft = desiredSpan - span;
|
|
|
|
int w = v.getBreakWeight(flowAxis, x + span, spanLeft);
|
|
if ((w >= bestWeight) && (w > BadBreakWeight)) {
|
|
bestWeight = w;
|
|
bestIndex = i;
|
|
bestSpan = span;
|
|
if (w >= ForcedBreakWeight) {
|
|
// it's a forced break, so there is
|
|
// no point in searching further.
|
|
break;
|
|
}
|
|
}
|
|
span += v.getPreferredSpan(flowAxis);
|
|
}
|
|
if (bestIndex < 0) {
|
|
// there is nothing that can be broken, leave
|
|
// it in it's current state.
|
|
return;
|
|
}
|
|
|
|
// Break the best candidate view, and patch up the row.
|
|
int spanLeft = desiredSpan - bestSpan;
|
|
v = r.getView(bestIndex);
|
|
v = v.breakView(flowAxis, v.getStartOffset(), x + bestSpan, spanLeft);
|
|
View[] va = new View[1];
|
|
va[0] = v;
|
|
View lv = getLogicalView(fv);
|
|
int p0 = r.getView(bestIndex).getStartOffset();
|
|
int p1 = r.getEndOffset();
|
|
for (int i = 0; i < lv.getViewCount(); i++) {
|
|
View tmpView = lv.getView(i);
|
|
if (tmpView.getEndOffset() > p1) {
|
|
break;
|
|
}
|
|
if (tmpView.getStartOffset() >= p0) {
|
|
tmpView.setParent(lv);
|
|
}
|
|
}
|
|
r.replace(bestIndex, n - bestIndex, va);
|
|
}
|
|
|
|
void reparentViews(View pool, int startPos) {
|
|
int n = pool.getViewIndex(startPos, Position.Bias.Forward);
|
|
if (n >= 0) {
|
|
for (int i = n; i < pool.getViewCount(); i++) {
|
|
pool.getView(i).setParent(pool);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates a view that can be used to represent the current piece
|
|
* of the flow. This can be either an entire view from the
|
|
* logical view, or a fragment of the logical view.
|
|
*
|
|
* @param fv the view holding the flow
|
|
* @param startOffset the start location for the view being created
|
|
* @param spanLeft the about of span left to fill in the row
|
|
* @param rowIndex the row the view will be placed into
|
|
*/
|
|
protected View createView(FlowView fv, int startOffset, int spanLeft, int rowIndex) {
|
|
// Get the child view that contains the given starting position
|
|
View lv = getLogicalView(fv);
|
|
int childIndex = lv.getViewIndex(startOffset, Position.Bias.Forward);
|
|
View v = lv.getView(childIndex);
|
|
if (startOffset==v.getStartOffset()) {
|
|
// return the entire view
|
|
return v;
|
|
}
|
|
|
|
// return a fragment.
|
|
v = v.createFragment(startOffset, v.getEndOffset());
|
|
return v;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This class can be used to represent a logical view for
|
|
* a flow. It keeps the children updated to reflect the state
|
|
* of the model, gives the logical child views access to the
|
|
* view hierarchy, and calculates a preferred span. It doesn't
|
|
* do any rendering, layout, or model/view translation.
|
|
*/
|
|
static class LogicalView extends CompositeView {
|
|
|
|
LogicalView(Element elem) {
|
|
super(elem);
|
|
}
|
|
|
|
protected int getViewIndexAtPosition(int pos) {
|
|
Element elem = getElement();
|
|
if (elem.isLeaf()) {
|
|
return 0;
|
|
}
|
|
return super.getViewIndexAtPosition(pos);
|
|
}
|
|
|
|
protected void loadChildren(ViewFactory f) {
|
|
Element elem = getElement();
|
|
if (elem.isLeaf()) {
|
|
View v = new LabelView(elem);
|
|
append(v);
|
|
} else {
|
|
super.loadChildren(f);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetches the attributes to use when rendering. This view
|
|
* isn't directly responsible for an element so it returns
|
|
* the outer classes attributes.
|
|
*/
|
|
public AttributeSet getAttributes() {
|
|
View p = getParent();
|
|
return (p != null) ? p.getAttributes() : null;
|
|
}
|
|
|
|
/**
|
|
* Determines the preferred span for this view along an
|
|
* axis.
|
|
*
|
|
* @param axis may be either View.X_AXIS or View.Y_AXIS
|
|
* @return the span the view would like to be rendered into.
|
|
* Typically the view is told to render into the span
|
|
* that is returned, although there is no guarantee.
|
|
* The parent may choose to resize or break the view.
|
|
* @see View#getPreferredSpan
|
|
*/
|
|
public float getPreferredSpan(int axis) {
|
|
float maxpref = 0;
|
|
float pref = 0;
|
|
int n = getViewCount();
|
|
for (int i = 0; i < n; i++) {
|
|
View v = getView(i);
|
|
pref += v.getPreferredSpan(axis);
|
|
if (v.getBreakWeight(axis, 0, Integer.MAX_VALUE) >= ForcedBreakWeight) {
|
|
maxpref = Math.max(maxpref, pref);
|
|
pref = 0;
|
|
}
|
|
}
|
|
maxpref = Math.max(maxpref, pref);
|
|
return maxpref;
|
|
}
|
|
|
|
/**
|
|
* Determines the minimum span for this view along an
|
|
* axis. The is implemented to find the minimum unbreakable
|
|
* span.
|
|
*
|
|
* @param axis may be either View.X_AXIS or View.Y_AXIS
|
|
* @return the span the view would like to be rendered into.
|
|
* Typically the view is told to render into the span
|
|
* that is returned, although there is no guarantee.
|
|
* The parent may choose to resize or break the view.
|
|
* @see View#getPreferredSpan
|
|
*/
|
|
public float getMinimumSpan(int axis) {
|
|
float maxmin = 0;
|
|
float min = 0;
|
|
boolean nowrap = false;
|
|
int n = getViewCount();
|
|
for (int i = 0; i < n; i++) {
|
|
View v = getView(i);
|
|
if (v.getBreakWeight(axis, 0, Integer.MAX_VALUE) == BadBreakWeight) {
|
|
min += v.getPreferredSpan(axis);
|
|
nowrap = true;
|
|
} else if (nowrap) {
|
|
maxmin = Math.max(min, maxmin);
|
|
nowrap = false;
|
|
min = 0;
|
|
}
|
|
if (v instanceof ComponentView) {
|
|
maxmin = Math.max(maxmin, v.getMinimumSpan(axis));
|
|
}
|
|
}
|
|
maxmin = Math.max(maxmin, min);
|
|
return maxmin;
|
|
}
|
|
|
|
/**
|
|
* Forward the DocumentEvent to the given child view. This
|
|
* is implemented to reparent the child to the logical view
|
|
* (the children may have been parented by a row in the flow
|
|
* if they fit without breaking) and then execute the superclass
|
|
* behavior.
|
|
*
|
|
* @param v the child view to forward the event to.
|
|
* @param e the change information from the associated document
|
|
* @param a the current allocation of the view
|
|
* @param f the factory to use to rebuild if the view has children
|
|
* @see #forwardUpdate
|
|
* @since 1.3
|
|
*/
|
|
protected void forwardUpdateToView(View v, DocumentEvent e,
|
|
Shape a, ViewFactory f) {
|
|
View parent = v.getParent();
|
|
v.setParent(this);
|
|
super.forwardUpdateToView(v, e, a, f);
|
|
v.setParent(parent);
|
|
}
|
|
|
|
/** {@inheritDoc} */
|
|
@Override
|
|
protected void forwardUpdate(DocumentEvent.ElementChange ec,
|
|
DocumentEvent e, Shape a, ViewFactory f) {
|
|
// Update the view responsible for the changed element by invocation of
|
|
// super method.
|
|
super.forwardUpdate(ec, e, a, f);
|
|
// Re-calculate the update indexes and update the views followed by
|
|
// the changed place. Note: we update the views only when insertion or
|
|
// removal takes place.
|
|
DocumentEvent.EventType type = e.getType();
|
|
if (type == DocumentEvent.EventType.INSERT ||
|
|
type == DocumentEvent.EventType.REMOVE) {
|
|
firstUpdateIndex = Math.min((lastUpdateIndex + 1), (getViewCount() - 1));
|
|
lastUpdateIndex = Math.max((getViewCount() - 1), 0);
|
|
for (int i = firstUpdateIndex; i <= lastUpdateIndex; i++) {
|
|
View v = getView(i);
|
|
if (v != null) {
|
|
v.updateAfterChange();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// The following methods don't do anything useful, they
|
|
// simply keep the class from being abstract.
|
|
|
|
/**
|
|
* Renders using the given rendering surface and area on that
|
|
* surface. This is implemented to do nothing, the logical
|
|
* view is never visible.
|
|
*
|
|
* @param g the rendering surface to use
|
|
* @param allocation the allocated region to render into
|
|
* @see View#paint
|
|
*/
|
|
public void paint(Graphics g, Shape allocation) {
|
|
}
|
|
|
|
/**
|
|
* Tests whether a point lies before the rectangle range.
|
|
* Implemented to return false, as hit detection is not
|
|
* performed on the logical view.
|
|
*
|
|
* @param x the X coordinate >= 0
|
|
* @param y the Y coordinate >= 0
|
|
* @param alloc the rectangle
|
|
* @return true if the point is before the specified range
|
|
*/
|
|
protected boolean isBefore(int x, int y, Rectangle alloc) {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Tests whether a point lies after the rectangle range.
|
|
* Implemented to return false, as hit detection is not
|
|
* performed on the logical view.
|
|
*
|
|
* @param x the X coordinate >= 0
|
|
* @param y the Y coordinate >= 0
|
|
* @param alloc the rectangle
|
|
* @return true if the point is after the specified range
|
|
*/
|
|
protected boolean isAfter(int x, int y, Rectangle alloc) {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Fetches the child view at the given point.
|
|
* Implemented to return null, as hit detection is not
|
|
* performed on the logical view.
|
|
*
|
|
* @param x the X coordinate >= 0
|
|
* @param y the Y coordinate >= 0
|
|
* @param alloc the parent's allocation on entry, which should
|
|
* be changed to the child's allocation on exit
|
|
* @return the child view
|
|
*/
|
|
protected View getViewAtPoint(int x, int y, Rectangle alloc) {
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Returns the allocation for a given child.
|
|
* Implemented to do nothing, as the logical view doesn't
|
|
* perform layout on the children.
|
|
*
|
|
* @param index the index of the child, >= 0 && < getViewCount()
|
|
* @param a the allocation to the interior of the box on entry,
|
|
* and the allocation of the child view at the index on exit.
|
|
*/
|
|
protected void childAllocation(int index, Rectangle a) {
|
|
}
|
|
}
|
|
|
|
|
|
}
|