/*
* @(#)InlineView.java 1.27 06/05/12
*
* Copyright 2006 Sun Microsystems, Inc. All rights reserved.
* SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package javax.swing.text.html;
import java.awt.*;
import java.text.BreakIterator;
import javax.swing.event.DocumentEvent;
import javax.swing.text.*;
/**
* Displays the <dfn>inline element</dfn> styles
* based upon css attributes.
*
* @author Timothy Prinzing
* @version 1.27 05/12/06
*/
public class InlineView extends LabelView {
/**
* Constructs a new view wrapped on an element.
*
* @param elem the element
*/
public InlineView(Element elem) {
super(elem);
StyleSheet sheet = getStyleSheet();
attr = sheet.getViewAttributes(this);
}
/**
* Gives notification that something was inserted into
* the document in a location that this view is responsible for.
* If either parameter is <code>null</code>, behavior of this method is
* implementation dependent.
*
* @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
* @since 1.5
* @see View#insertUpdate
*/
public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) {
super.insertUpdate(e, a, f);
longestWordSpan = -1.0f;
}
/**
* Gives notification that something was removed from the document
* in a location that this view is responsible for.
* If either parameter is <code>null</code>, behavior of this method is
* implementation dependent.
*
* @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
* @since 1.5
* @see View#removeUpdate
*/
public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) {
super.removeUpdate(e, a, f);
longestWordSpan = -1.0f;
}
/**
* Gives notification from the document that attributes were changed
* in a location that this view is responsible for.
*
* @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 View#changedUpdate
*/
public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
super.changedUpdate(e, a, f);
StyleSheet sheet = getStyleSheet();
attr = sheet.getViewAttributes(this);
longestWordSpan = -1.0f;
preferenceChanged(null, true, true);
}
/**
* Fetches the attributes to use when rendering. This is
* implemented to multiplex the attributes specified in the
* model with a StyleSheet.
*/
public AttributeSet getAttributes() {
return attr;
}
/**
* Determines how attractive a break opportunity in
* this view is. This can be used for determining which
* view is the most attractive to call <code>breakView</code>
* on in the process of formatting. A view that represents
* text that has whitespace in it might be more attractive
* than a view that has no whitespace, for example. The
* higher the weight, the more attractive the break. A
* value equal to or lower than <code>BadBreakWeight</code>
* should not be considered for a break. A value greater
* than or equal to <code>ForcedBreakWeight</code> should
* be broken.
* <p>
* This is implemented to provide the default behavior
* of returning <code>BadBreakWeight</code> unless the length
* is greater than the length of the view in which case the
* entire view represents the fragment. Unless a view has
* been written to support breaking behavior, it is not
* attractive to try and break the view. An example of
* a view that does support breaking is <code>LabelView</code>.
* An example of a view that uses break weight is
* <code>ParagraphView</code>.
*
* @param axis may be 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 <em>pos</em>
* where a potential break is desired >= 0.
* @return the weight, which should be a value between
* ForcedBreakWeight and BadBreakWeight.
* @see LabelView
* @see ParagraphView
* @see javax.swing.text.View#BadBreakWeight
* @see javax.swing.text.View#GoodBreakWeight
* @see javax.swing.text.View#ExcellentBreakWeight
* @see javax.swing.text.View#ForcedBreakWeight
*/
public int getBreakWeight(int axis, float pos, float len) {
if (nowrap) {
return BadBreakWeight;
}
return super.getBreakWeight(axis, pos, len);
}
/**
* Tries to break this view on the given axis. Refer to
* {@link javax.swing.text.View#breakView} for a complete
* description of this method.
* <p>Behavior of this method is unspecified in case <code>axis</code>
* is neither <code>View.X_AXIS</code> nor <code>View.Y_AXIS</code>, and
* in case <code>offset</code>, <code>pos</code>, or <code>len</code>
* is null.
*
* @param axis may be either <code>View.X_AXIS</code> or
* <code>View.Y_AXIS</code>
* @param offset the location in the document model
* that a broken fragment would occupy >= 0. This
* would be the starting offset of the fragment
* returned
* @param pos the position along the axis that the
* broken view would occupy >= 0. This may be useful for
* things like tab calculations
* @param len specifies the distance along the axis
* where a potential break is desired >= 0
* @return the fragment of the view that represents the
* given span.
* @since 1.5
* @see javax.swing.text.View#breakView
*/
public View breakView(int axis, int offset, float pos, float len) {
InlineView view = (InlineView)super.breakView(axis, offset, pos, len);
if (view != this) {
view.longestWordSpan = -1;
}
return view;
}
/**
* Fetch the span of the longest word in the view.
*/
float getLongestWordSpan() {
if (longestWordSpan < 0.0f) {
longestWordSpan = calculateLongestWordSpan();
}
return longestWordSpan;
}
float calculateLongestWordSpan() {
float rv = 0f;
Document doc = getDocument();
//AbstractDocument.MultiByteProperty
final Object MultiByteProperty = "multiByte";
if (doc != null &&
Boolean.TRUE.equals(doc.getProperty(MultiByteProperty))) {
rv = calculateLongestWordSpanUseBreakIterator();
} else {
rv = calculateLongestWordSpanUseWhitespace();
}
return rv;
}
float calculateLongestWordSpanUseBreakIterator() {
float span = 0;
Document doc = getDocument();
int p0 = getStartOffset();
int p1 = getEndOffset();
if (p1 > p0) {
try {
FontMetrics metrics = getFontMetrics();
Segment segment = new Segment();
doc.getText(p0, p1 - p0, segment);
Container c = getContainer();
BreakIterator line;
if (c != null) {
line = BreakIterator.getLineInstance(c.getLocale());
} else {
line = BreakIterator.getLineInstance();
}
line.setText(segment);
int start = line.first();
for (int end = line.next();
end != BreakIterator.DONE;
start = end, end = line.next()) {
if (end > start) {
span = Math.max(span,
metrics.charsWidth(segment.array, start,
end - start));
}
}
} catch (BadLocationException ble) {
// If the text can't be retrieved, it can't influence the size.
}
}
return span;
}
float calculateLongestWordSpanUseWhitespace() {
float span = 0;
Document doc = getDocument();
int p0 = getStartOffset();
int p1 = getEndOffset();
if (p1 > p0) {
try {
Segment segment = new Segment();
doc.getText(p0, p1 - p0, segment);
final int CONTENT = 0;
final int SPACES = 1;
int state = CONTENT;
int start = segment.offset;
int end = start;
FontMetrics metrics = getFontMetrics();
final int lastIndex = segment.offset + segment.count - 1;
for (int i = segment.offset; i <= lastIndex; i++) {
boolean updateSpan = false;
if (Character.isWhitespace(segment.array[i])) {
if (state == CONTENT) {
//we got a word
updateSpan = true;
state = SPACES;
}
} else {
if (state == SPACES) {
//first non space
start = i;
end = start;
state = CONTENT;
} else {
end = i;
}
//handle last word
if (i == lastIndex) {
updateSpan = true;
}
}
if (updateSpan) {
if (end > start) {
span = Math.max(span,
metrics.charsWidth(segment.array, start,
end - start + 1));
}
}
}
} catch (BadLocationException ble) {
// If the text can't be retrieved, it can't influence the size.
}
}
return span;
}
/**
* Set the cached properties from the attributes.
*/
protected void setPropertiesFromAttributes() {
super.setPropertiesFromAttributes();
AttributeSet a = getAttributes();
Object decor = a.getAttribute(CSS.Attribute.TEXT_DECORATION);
boolean u = (decor != null) ?
(decor.toString().indexOf("underline") >= 0) : false;
setUnderline(u);
boolean s = (decor != null) ?
(decor.toString().indexOf("line-through") >= 0) : false;
setStrikeThrough(s);
Object vAlign = a.getAttribute(CSS.Attribute.VERTICAL_ALIGN);
s = (vAlign != null) ? (vAlign.toString().indexOf("sup") >= 0) : false;
setSuperscript(s);
s = (vAlign != null) ? (vAlign.toString().indexOf("sub") >= 0) : false;
setSubscript(s);
Object whitespace = a.getAttribute(CSS.Attribute.WHITE_SPACE);
if ((whitespace != null) && whitespace.equals("nowrap")) {
nowrap = true;
} else {
nowrap = false;
}
HTMLDocument doc = (HTMLDocument)getDocument();
// fetches background color from stylesheet if specified
Color bg = doc.getBackground(a);
if (bg != null) {
setBackground(bg);
}
}
protected StyleSheet getStyleSheet() {
HTMLDocument doc = (HTMLDocument) getDocument();
return doc.getStyleSheet();
}
private boolean nowrap;
private AttributeSet attr;
private float longestWordSpan = -1.0f;
}