
com.globalmentor.swing.text.xml.XMLParagraphView Maven / Gradle / Ivy
Show all versions of globalmentor-swing Show documentation
/*
* Copyright © 1996-2009 GlobalMentor, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.globalmentor.swing.text.xml;
import java.awt.*;
import javax.swing.SizeRequirements;
import javax.swing.event.DocumentEvent;
import javax.swing.text.*;
import static com.globalmentor.swing.text.SwingText.*;
import static com.globalmentor.swing.text.Views.*;
import com.globalmentor.swing.text.ContainerBoxView;
import com.globalmentor.swing.text.ContainerView;
import com.globalmentor.swing.text.FragmentViewFactory;
import com.globalmentor.swing.text.ViewBreakStrategy;
import com.globalmentor.swing.text.xml.css.XMLCSSStyles;
import com.globalmentor.swing.text.xml.css.XMLCSSView;
import com.globalmentor.swing.text.xml.css.XMLCSSViewPainter;
import com.globalmentor.w3c.spec.CSS;
import com.globalmentor.awt.Inset;
import com.globalmentor.log.Log;
/**
* A paragraph that understands CSS styles and knows how to be broken into fragments.
* @author Garret Wilson
*/
public class XMLParagraphView extends ParagraphView implements Inset, XMLCSSView, FragmentViewFactory //TODO del if not needed, ViewReleasable
{
/** The shared default break strategy for container views. */
protected static final ViewBreakStrategy DEFAULT_BREAK_STRATEGY = ContainerBoxView.DEFAULT_BREAK_STRATEGY;
/** The stategy for breaking this view into fragments. */
private ViewBreakStrategy breakStrategy = DEFAULT_BREAK_STRATEGY;
/** @return The stategy for breaking this view into fragments. */
protected ViewBreakStrategy getBreakStrategy() {
return breakStrategy;
}
/**
* Sets the stategy for breaking this view into fragments.
* @param strategy The strategy to use for creating view fragments.
*/
protected void setBreakStrategy(final ViewBreakStrategy strategy) {
breakStrategy = strategy;
}
/**
* The justification of the paragraph. This is an override of the ParagraphView
version because ParagraphView provides no accessor functions.
*/
private int justification;
/**
* Gets the justification of the paragraph. This is present because ParagraphView
ParagraphView provides no accessor functions for justification.
* @preturn The justification of the paragraph.
*/
public int getJustification() {
return justification;
}
/**
* Sets the justification of the paragraph. This is present because ParagraphView
ParagraphView provides no accessor functions for justification.
* @param justification The justification of the paragraph.
*/
protected void setJustification(final int justification) {
this.justification = justification;
}
/**
* The line spacing of the paragraph. This is an override of the ParagraphView
version because ParagraphView provides no accessor functions.
*/
private float lineSpacing;
/**
* Gets the line spacing of the paragraph. This is present because ParagraphView
ParagraphView provides no accessor functions for line spacing.
* @preturn The line spacing of the paragraph.
*/
public float getLineSpacing() {
return lineSpacing;
}
/**
* Sets the line spacing of the paragraph. This is present because ParagraphView
ParagraphView provides no accessor functions for line spacing.
* @param lineSpacing The line spacing of the paragraph.
*/
protected void setLineSpacing(final float lineSpacing) {
this.lineSpacing = lineSpacing;
}
/**
* Whether the first line should be indented according to the specified indentation. A value of false
will override any specified setting.
*/
private boolean firstLineIndented = true;
/**
* @return Whether the first line should be indented, which overrides andy specified setting.
*/
protected boolean isFirstLineIndented() {
return firstLineIndented;
}
/**
* Sets whether the first line should be indented. If this is set to false
(if, for example, there is an image and no text), any specified
* first-line indent value will be ignored.
* @param newFirstLineIndented true
if the first line should be indented according to the indention value, false
if that value
* should be ignored.
*/
protected void setFirstLineIndented(final boolean newFirstLineIndented) {
firstLineIndented = newFirstLineIndented;
}
/** @return The indent amount of the first line. */
protected float getFirstLineIndent() {
return firstLineIndent;
}
/**
* Constructs a paragraph view for the given element.
* @param element The element for which this view is responsible.
*/
public XMLParagraphView(final Element element) {
super(element); //construct the parent class
//TODO del Log.trace("Creating XMLParagraphView, i18n property: ", getDocument().getProperty("i18n")); //TODO testing
Log.trace(); //TODO testing
//TODO fix antialias and fonts strategy=new com.globalmentor.swing.text.TextLayoutStrategy(); //TODO fix; testing i18n
}
/** @return The insets of the view. */
public Insets getInsets() {
return new Insets(getTopInset(), getLeftInset(), getBottomInset(), getRightInset()); //return the insets of the view
}
/**
* Releases any cached information such as pooled views and flows. This version releases the entire pooled view.
*/
/*TODO del if not needed
public void release()
{
layoutPool=null; //release the pool of views
strategy=null; //TODO testing ViewReleasable
//TODO later, maybe release the strategy and the child views as well
}
*/
/**
* Sets the cached properties from the attributes. This overrides the version in ParagraphView
to work with CSS attributes.
*/
protected void setPropertiesFromAttributes() {
final AttributeSet attributeSet = getAttributes(); //get our attributes
if(attributeSet != null) { //if we have attributes
setBackgroundColor(XMLCSSStyles.getBackgroundColor(attributeSet)); //set the background color from the attributes
setParagraphInsets(attributeSet); //TODO fix
setJustification(StyleConstants.getAlignment(attributeSet)); //TODO fix
//TODO del setJustification(StyleConstants.ALIGN_JUSTIFIED); //TODO fix
//TODO fix setLineSpacing(StyleConstants.getLineSpacing(attributeSet)); //TODO fix
//TODO fix setLineSpacing(2); //TODO fix; testing
//TODO del setLineSpacing(1.5f); //TODO fix; testing
setLineSpacing(XMLCSSStyles.getLineHeight(attributeSet)); //set the line height amount from our CSS attributes TODO fix this to correctly use the number
setVisible(!CSS.CSS_DISPLAY_NONE.equals(XMLCSSStyles.getDisplay(attributeSet))); //the paragraph is visible only if it doesn't have a display of "none"
//TODO del LineSpacing=3; //TODO fix; testing
final Document document = getDocument(); //get our document
if(document instanceof StyledDocument) { //if this is a styled document
final StyledDocument styledDocument = (StyledDocument)document; //cast the document to a styled document
final Font font = styledDocument.getFont(attributeSet); //let the document get the font from the attributes
//TODO find some way to cache the font in the attributes
setFirstLineIndent(XMLCSSStyles.getTextIndent(attributeSet, font)); //set the text indent amount from the CSS property in the attributes, providing a font in case the length is in ems
//TODO fix firstLineIndent = (int)StyleConstants.getFirstLineIndent(attributeSet); //TODO fix
//TODO we may want to set the insets in setPropertiesFromAttributes(); for
//percentages, getPreferredeSpan(), etc. will have to look at the preferred
//span and make calculations based upon the percentages
//TODO probably have some other exernal helper class that sets the margins based upon the attributes
final short marginTop = (short)Math.round(XMLCSSStyles.getMarginTop(attributeSet)); //get the top margin from the attributes
final short marginLeft = (short)Math.round(XMLCSSStyles.getMarginLeft(attributeSet, font)); //get the left margin from the attributes
final short marginBottom = (short)Math.round(XMLCSSStyles.getMarginBottom(attributeSet)); //get the bottom margin from the attributes
final short marginRight = (short)Math.round(XMLCSSStyles.getMarginRight(attributeSet, font)); //get the right margin from the attributes
setInsets(marginTop, marginLeft, marginBottom, marginRight); //TODO fix; testing
}
}
}
/**
* Returns the constraining span to flow against for the given child index. This is called by the FlowStrategy
while it is updating the flow.
* This method overrides the standard version to allow first-line indentions.
* @param index The index of the row being updated (>=0 and 0 && isFirstLineIndented()) //if there is at least one row, and the first line should be indented
offsets[0] += getFirstLineIndent(); //add the correct indentation amount to the first rowcan
}
/**
* Renders using the given rendering surface and area on that surface. This version paings CSS-specific attributes
* @param graphics The rendering surface to use.
* @param allocation The allocated region to render into.
*/
public void paint(final Graphics graphics, final Shape allocation) {
if(isVisible()) { //if this view is visible
//TODO testing fractional metrics; should this be in XMLInlineView?
final Graphics2D graphics2D = (Graphics2D)graphics; //cast to the 2D version of graphics
//turn on fractional metrics TODO probably do this conditionally, based on some sort of flag
//TODO del; moved to XMLCSSViewPainter graphics2D.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
//TODO del; moved to XMLCSSViewPainter graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
XMLCSSViewPainter.paint(graphics, allocation, this, getAttributes()); //paint our CSS-specific parts
super.paint(graphics, allocation); //let the super class paint the rest of the paragraph
}
}
/**
* Determines the preferred span for this view. Returns 0 if the view is not visible, otherwise it calls the superclass method to get the preferred span.
* @param axis The axis (View.X_AXIS
or View.Y_AXIS).
* @return The span the view would like to be rendered into.
* @see javax.swing.text.ParagraphView#getPreferredSpan
*/
public float getPreferredSpan(int axis) {
return isVisible() ? super.getPreferredSpan(axis) : 0; //return 0 if the view isn't visible
}
/**
* Determines the minimum span for this view along an axis. Returns 0 if the view is not visible, otherwise it calls the superclass method to get the minimum
* span.
* @param axis The axis (View.X_AXIS
or View.Y_AXIS).
* @return The minimum span the view can be rendered into.
* @see javax.swing.text.ParagraphView#getMinimumSpan
*/
public float getMinimumSpan(int axis) {
return isVisible() ? super.getMinimumSpan(axis) : 0; //return 0 if the view isn't visible
}
/**
* Determines the maximum span for this view along an axis. Returns 0 if the view is not visible, otherwise it calls the superclass method to get the maximum
* span.
* @param axis The axis (View.X_AXIS
or View.Y_AXIS).
* @return The maximum span the view can be rendered into.
* @see javax.swing.text.ParagraphView#getMaximumSpan
*/
public float getMaximumSpan(int axis) {
return isVisible() ? super.getMaximumSpan(axis) : 0; //return 0 if the view isn't visible
}
/**
* Loads all of the children to initialize the view. This is called by the setParent
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 LinePoolView
is
* created to represent the logical view that is used in the process of formatting. This version of loadChildren
is implemented to return
* specifically a XMLParagraphView.LinePoolView
, so that that view can implement its own special version of child view construction.
* @param viewFactory The view factory to use for child view creation.
*/
protected void loadChildren(final ViewFactory viewFactory) {
if(layoutPool == null) { //if there is no layout pool
layoutPool = new LinePoolView(getElement()); //create our own brand of logical view that will correctly create paragraph child views
}
super.loadChildren(viewFactory); //load the pool children normally
final int poolViewCount = layoutPool.getViewCount(); //find out how many views are in the pool
if(poolViewCount > 0 && getEndOffset() > getStartOffset()) { //if we have at least some content, see if there are only object views present, how many inline views there are, etc.
boolean onlyObjectsPresent = true; //assume there are only objects present in this paragraph
int inlineViewCount = 0; //we'll keep a record of how many inline views there are
//TODO fix boolean firstInlineViewHasMultipleWords=false; //assume the first inline view has multiple words
for(int i = 0; i < poolViewCount; ++i) { //look at each view in the pool
final View pooledView = layoutPool.getView(i); //get a reference to this pooled view
if(!(pooledView instanceof XMLObjectView)) { //if this isn't an object
onlyObjectsPresent = false; //show that there are other things besides objects present
} else if(pooledView instanceof XMLInlineView) {
//TODO fix if(inlineViweCount==0) //if this is the first inline view we've found
// TODO fix {
// TODO fix final Segment segment=getText(pooledView.getStartOffset(), pooledView.getEndOffset()); //get the segment of text the inline view represents
// TODO fix pooledView.getElement().get
// TODO fix }
++inlineViewCount; //show that we've found another inline view
}
}
final View parentView = getParent(); //get our parent view
final String parentDisplay = XMLCSSStyles.getDisplay(parentView.getAttributes()); //see what kind of parent we have
final boolean isInTableCell = CSS.CSS_DISPLAY_TABLE_CELL.equals(parentDisplay); //see if we're inside a table cell
setFirstLineIndented(!onlyObjectsPresent && (inlineViewCount > 1 || !isInTableCell)); //if there are only objects present in this paragraph, or if there's only one inline view in a table cell, we won't indent
} else { //if there is no content
setVisible(false); //make the entire paragraph invisible
}
}
/**
* Determines how attractive a break opportunity in this view is. This is implemented to forward to the superclass for axis perpendicular to the flow axis.
* Along the flow axis the following values may be returned:
*
* View.BadBreakWeight
* - If the desired break location is less than the first child is comfortable with (i.e. not even one child view can fit), or there are no child views
* View.GoodBreakWeight
* - If the other conditions don't occur.
*
* This will result in the view being broken with the maximum number of child views that can fit within the required span.
* @param axis The breaking axis, either View.X_AXIS or View.Y_AXIS.
* @param pos The potential location of the start of the broken view (>=0). This may be useful for calculating tab positions.
* @param len Specifies the relative length from pos where a potential break is desired (>=0).
* @return The weight, which should be a value between View.ForcedBreakWeight
and View.BadBreakWeight.
*/
public int getBreakWeight(int axis, float pos, float len) { //TODO add support for requiring a certain number of child views (lines)
if(axis == getAxis()) { //if they want to break along our tiling axis
final String pageBreakAfter = XMLCSSStyles.getPageBreakAfter(getAttributes()); //see how the view considers breaking after it
//if we should avoid breaking after this view, and the provided length is more than we need (i.e. we aren't being asked to break in our middle)
if(CSS.CSS_PAGE_BREAK_AFTER_AVOID.equals(pageBreakAfter) && len > getPreferredSpan(axis)) {
return BadBreakWeight; //don't allow breaking
} else { //if we aren't break-averse, get the highest break weight available; this has the advantage of allowing invisible views to return their low break weight
final float marginSpan = (axis == X_AXIS) ? getLeftInset() + getRightInset() : getTopInset() + getBottomInset(); //see how much margin we have to allow for
int bestBreakWeight = BadBreakWeight; //start out assuming we can't break
float spanLeft = len - marginSpan; //find out how much span we have left (the margins will always be there)
final int viewCount = getViewCount(); //find out how many child views there are
for(int i = 0; i < viewCount && spanLeft > 0; ++i) { //look at each child view until we run out of span
final View view = getView(i); //get this child view
final int breakWeight = view.getBreakWeight(axis, pos, spanLeft); //get the break weight of this view
if(breakWeight > bestBreakWeight) { //if this break weight is better than the one we already have
bestBreakWeight = breakWeight; //update our best break weight
}
spanLeft -= view.getPreferredSpan(axis); //update the amount of span we've used by this point
}
return bestBreakWeight; //return the best break weight we found
}
} else { //if they want to break along another axis
return super.getBreakWeight(axis, pos, len); //return the default break weight
}
}
/**
* Breaks this view on the given axis at the given length. This implementation delegates to the view break strategy.
* @param axis The axis to break along, either View.X_AXIS or View.Y_AXIS.
* @param offset The location in the model where the fragment should start its representation (>=0).
* @param pos The position along the axis that the broken view would occupy (>=0).
* @param length Specifies the distance along the axis where a potential break is desired (>=0).
* @return The fragment of the view that represents the given span, or the view itself if it cannot be broken
* @see ViewBreakStrategy#breakView()
*/
public View breakView(final int axis, final int offset, final float pos, final float length) {
final float marginSpan = (axis == X_AXIS) ? getLeftInset() + getRightInset() : getTopInset() + getBottomInset(); //see how much margin we have to allow for
return getBreakStrategy().breakView(this, axis, offset, pos, length - marginSpan, this); //ask the view break strategy to break our view, using this view as the view fragment factory
}
/**
* Creates a view that represents a portion of the element. This implementation delegates to the view break strategy.
* @param p0 The starting offset (>=0). This should be a value greater or equal to the element starting offset and less than the element ending offset.
* @param p1 The ending offset (>p0). This should be a value less than or equal to the elements end offset and greater than the elements starting offset.
* @return The view fragment, or itself if the view doesn't support breaking into fragments.
* @see ViewBreakStrategy#createFragment()
*/
public View createFragment(int p0, int p1) {
return getBreakStrategy().createFragment(this, p0, p1, this); //ask the view break strategy to break our view, using this view as the view fragment factory
}
/**
* Creates a fragment view into which pieces of this view will be placed.
* @param isFirstFragment Whether this fragment holds the first part of the original view.
* @param isLastFragment Whether this fragment holds the last part of the original view.
*/
public View createFragmentView(final boolean isFirstFragment, final boolean isLastFragment) {
return new XMLParagraphFragmentView(getElement(), getAxis(), this, isFirstFragment, isLastFragment); //create a fragment of this view
}
/**
* Internally created view that holds the views representing a paragraph line.
* @author Garret Wilson
*/
protected class Line extends ContainerBoxView {
/**
* Constructor.
* @param element The paragraph element.
*/
public Line(final Element element) {
super(element, XMLParagraphView.this.getAxis() == X_AXIS ? Y_AXIS : X_AXIS); //tile the line orthogonally to the paragraph
}
/**
* Each line does not need to fill its children, since its parent paragraph will load its children with the views it created. This function therefore does
* nothing.
*/
protected void loadChildren(ViewFactory f) {
}
/**
* Returns the attributes to use for this container view. Because this view does not directly represent its underlying element, the attributes of the parent
* view is returned, if there is a parent.
* @return The attributes of the parent view, or the attributes of the underlying element if there is no parent.
*/
public AttributeSet getAttributes() {
final View parentView = getParent(); //get the parent view
return parentView != null ? parentView.getAttributes() : super.getAttributes(); //return the parent's attributes, if we have a parent, or the default attributes if we have no parent
}
//TODO comment
public float getAlignment(int axis) {
if(axis == View.X_AXIS) {
switch(getJustification()) {
case StyleConstants.ALIGN_LEFT:
return 0;
case StyleConstants.ALIGN_RIGHT:
return 1;
case StyleConstants.ALIGN_CENTER:
case StyleConstants.ALIGN_JUSTIFIED:
return 0.5f;
}
}
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
* @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 { //TODO recomment; improve
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;
}
/**
* 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 BoxView.baselineLayout.
*
* @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.
* @returns the offset and span for each child view in the offsets and spans parameters.
*/
protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans) { //TODO verify this method
baselineLayout(targetSpan, axis, offsets, spans); //do a baseline layout of the row
for(int i = 0; i < offsets.length; ++i) { //look at each of the offsets
final String verticalAlign; //we'll get the vertical alignment value from the view
final View childView = getView(i); //get a reference to this child view
if(childView instanceof XMLInlineView) //if this is an XML inline view
verticalAlign = ((XMLInlineView)childView).getVerticalAlign(); //get the cached vertical alignment value directly from the view
else
//if the view is another type
verticalAlign = XMLCSSStyles.getVerticalAlign(childView.getAttributes()); //get the vertical alignment specified by the inline view
final int relativeIndex = i > 0 ? i - 1 : (offsets.length > 1 ? i + 1 : i); //we'll move the offset relative to the offset before us or, if this is the first offset, the one after us; if there is only one offset, use ourselves
//TODO del Log.trace("Relative index: ", relativeIndex); //TODO del
//TODO this currently doesn't work for subscripts of subscripts or superscripts of superscripts or a superscript followed by a subscript, etc.
final int relativeOffset = offsets[relativeIndex]; //get the span to which we're relative
final int relativeSpan = spans[relativeIndex]; //get the span to which we're relative
if(CSS.CSS_VERTICAL_ALIGN_SUPER.equals(verticalAlign)) { //if this is superscript
offsets[i] = relativeOffset - Math.round(relativeSpan * .20f); //move the box 20% above the relative box of the relative box's height
} else if(CSS.CSS_VERTICAL_ALIGN_SUB.equals(verticalAlign)) { //if this is subscript
//TODO fix; right now, it doesn't compensate for the linespacing
offsets[i] = relativeOffset + Math.round(relativeSpan * .60f); //move the box 20% below the relative box of the relative box's height
}
}
}
protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r) {
return baselineRequirements(axis, r);
}
public float getPreferredSpan(int axis) {
return axis == Y_AXIS ? super.getPreferredSpan(axis) * getLineSpacing() : super.getPreferredSpan(axis); //TODO testing
}
public float getMaximumSpan(int axis) {
return axis == Y_AXIS ? super.getMaximumSpan(axis) * getLineSpacing() : super.getMaximumSpan(axis); //TODO testing
}
public float getMinimumSpan(int axis) {
return axis == Y_AXIS ? super.getMinimumSpan(axis) * getLineSpacing() : super.getMinimumSpan(axis); //TODO testing
}
}
/**
* The class that serves as a pool of views representing the logical view of the paragraph. 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. This class replicates functionality of FlowView.LogicalView
with special view loading for paragraph children to accomodate nested
* style tags. This class does not descend from FlowView.LogicalView
because that class has package visibility.
* @see FlowView#LogicalView
*/
protected class LinePoolView extends ContainerView {
/**
* Logical view constructor.
* @param element The element this logical view represents.
*/
public LinePoolView(Element element) {
super(element, XMLParagraphView.this.getAxis() == X_AXIS ? Y_AXIS : X_AXIS); //tile the line orthogonally to the paragraph
}
/**
* Returns the attributes to use for this container view. Because this view does not directly represent its underlying element, the attributes of the parent
* view is returned, if there is a parent.
* @return The attributes of the parent view, or the attributes of the underlying element if there is no parent.
*/
public AttributeSet getAttributes() {
final View parentView = getParent(); //get the parent view
return parentView != null ? parentView.getAttributes() : super.getAttributes(); //return the parent's attributes, if we have a parent, or the default attributes if we have no parent
}
/**
* Loads all of the children to initialize the view. This is called by the setParent method. This version initially attempts to
* create a view for each child element, as normal. If the view factory returns null
for an element, however, the element's children will be
* iterated and the same process will take place. This allows nested inline elements to be correctly turned into inline views by a view factory which
* recognizes this procedure, with the view factory determining which element hierarchies should be iterated (such as XHTML <em>
) and
* which should not (such as XHTML &img;
). View factories which do not support this procedure will function as normal.
* @param viewFactory The view factory.
* @see #setParent
*/
protected void loadChildren(ViewFactory viewFactory) {
if(viewFactory == null) //if there is no view factory, the parent view has somehow changed
return; //don't create children
final Element[] childElements = getChildElements(getElement()); //put our child elements into an array
final View[] views = createViews(childElements, viewFactory); //create views for the child elements
replace(0, getViewCount(), views); //add the views to our view
}
/**
* Updates the child views in response to receiving notification that the model changed, and there is change record for the element this view is responsible
* for.
*
* This version correctly collapses the child inline view hierarchy.
*
* @param elementChange The change information for the element this view is responsible for. This should not be null
if this method gets
* called.
* @param documentEvent The change information from the associated document.
* @param viewFactory The factory to use to build child views.
* @return Whether or not the child views represent the child elements of the element this view is responsible for.
*/
protected boolean updateChildren(final DocumentEvent.ElementChange elementChange, final DocumentEvent documentEvent, final ViewFactory viewFactory) {
final Element[] removedElems = elementChange.getChildrenRemoved(); //TODO comment and verify
final Element[] addedElems = elementChange.getChildrenAdded();
View[] added = null;
if(addedElems != null) {
added = createViews(addedElems, viewFactory); //create views for the added elements
}
int nremoved = 0;
int index = elementChange.getIndex();
if(removedElems != null) {
nremoved = removedElems.length;
}
replace(index, nremoved, added);
return true;
}
/**
* Forward the document event to the given child view. This implementation first reparents the child to the logical view, as the child may have been given a
* parent line if the child could fit without breaking.
* @param view The child view to forward the event to.
* @param event The change information from the associated document.
* @param allocation The current allocation of the view.
* @param factory The factory to use to rebuild if the view has children.
*/
protected void forwardUpdateToView(final View view, final DocumentEvent event, final Shape allocation, final ViewFactory factory) {
view.setParent(this); //reparent the view to the pool TODO check about hierarchy reparenting; see XMLPagedView.PagePoolView
super.forwardUpdateToView(view, event, allocation, factory); //forward the update normally
}
}
/**
* The class that serves as a fragment if the paragraph is broken.
* @author Garret Wilson
*/
protected class XMLParagraphFragmentView extends XMLFragmentBlockView {
/**
* Constructs a fragment view for the paragraph.
* @param element The element this view is responsible for.
* @param axis The tiling axis, either View.X_AXIS or View.Y_AXIS.
* @param wholeView The original, unfragmented view from which this fragment (or one or more intermediate fragments) was broken.
* @param firstFragment Whether this is the first fragment of the original view.
* @param lastFragment Whether this is the last fragment of the original view.
*/
public XMLParagraphFragmentView(final Element element, final int axis, final View wholeView, final boolean firstFragment, final boolean lastFragment) {
super(element, axis, wholeView, firstFragment, lastFragment); //do the default construction
}
/**
* Creates a fragment view into which pieces of this view will be placed.
* @param isFirstFragment Whether this fragment holds the first part of the original view.
* @param isLastFragment Whether this fragment holds the last part of the original view.
*/
public View createFragmentView(final boolean isFirstFragment, final boolean isLastFragment) {
return new XMLParagraphFragmentView(getElement(), getAxis(), getWholeView(), isFirstFragment, isLastFragment); //create a fragment of this view, indicating the original view
}
/**
* 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 version performs the default layout and then adds the correct
* indentation amount to the first row.
* @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.
* @returns the offset and span for each child view in the offsets and spans parameters.
*/
protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans) { //TODO testing
super.layoutMinorAxis(targetSpan, axis, offsets, spans); //do the default layout
//if we're the first fragment, there is at least one row, and the first line should be indented
if(isFirstFragment() && offsets.length > 0 && isFirstLineIndented())
//TODO del when works if(offsets.length>0) //if there is at least one row, and the first line should be indented
offsets[0] += firstLineIndent; //add the correct indentation amount to the first row TODO use an accessor function here for firstLineIndent, if we can
}
}
/**
* Child views can call this on the parent to indicate that the preference has changed and should be reconsidered for layout. By default this just propagates
* upward to the next parent. The root view will call revalidate
on the associated text component.
*
* @param child the child view
* @param width true if the width preference has changed
* @param height true if the height preference has changed
* @see javax.swing.JComponent#revalidate
*/
public void preferenceChanged(View child, boolean width, boolean height) {
//TODO del removeAll(); //TODO testing
super.preferenceChanged(child, width, height);
}
//newswing
/** Whether this view is visible. */
private boolean visible;
/** @return Whether this view should be visible. */
public boolean isVisible() {
return visible;
}
/**
* Sets whether this view is visible.
* @param newVisible Whether the view should be visible.
*/
protected void setVisible(final boolean newVisible) {
visible = newVisible;
}
/** The background color of the block view. */
private Color backgroundColor;
/**
* Gets the background color of the block view.
* @return The background color of the block view.
*/
public Color getBackgroundColor() {
//TODO del; ParagraphView doesn't seem to do the same type of caching synchronize(); //make sure we have the correct cached property values
return backgroundColor;
}
/**
* Sets the background color of the block view.
* @param newBackgroundColor The color of the background.
*/
protected void setBackgroundColor(final Color newBackgroundColor) {
backgroundColor = newBackgroundColor;
}
}