/*
* Copyright (c) 1999, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package javax.swing.text;
import java.awt.*;
import java.text.BreakIterator;
import javax.swing.event.*;
import java.util.BitSet;
import java.util.Locale;
import javax.swing.UIManager;
import sun.swing.SwingUtilities2;
import static sun.swing.SwingUtilities2.IMPLIED_CR;
A GlyphView is a styled chunk of text that represents a view
mapped over an element in the text model. This view is generally
responsible for displaying text glyphs using character level
attributes in some way.
An implementation of the GlyphPainter class is used to do the
actual rendering and model/view translations. This separates
rendering from layout and management of the association with
the model.
The view supports breaking for the purpose of formatting.
The fragments produced by breaking share the view that has
primary responsibility for the element (i.e. they are nested
classes and carry only a small amount of state of their own)
so they can share its resources.
Since this view
represents text that may have tabs embedded in it, it implements the
TabableView
interface. Tabs will only be
expanded if this view is embedded in a container that does
tab expansion. ParagraphView is an example of a container
that does tab expansion.
Author: Timothy Prinzing Since: 1.3
/**
* A GlyphView is a styled chunk of text that represents a view
* mapped over an element in the text model. This view is generally
* responsible for displaying text glyphs using character level
* attributes in some way.
* An implementation of the GlyphPainter class is used to do the
* actual rendering and model/view translations. This separates
* rendering from layout and management of the association with
* the model.
* <p>
* The view supports breaking for the purpose of formatting.
* The fragments produced by breaking share the view that has
* primary responsibility for the element (i.e. they are nested
* classes and carry only a small amount of state of their own)
* so they can share its resources.
* <p>
* Since this view
* represents text that may have tabs embedded in it, it implements the
* <code>TabableView</code> interface. Tabs will only be
* expanded if this view is embedded in a container that does
* tab expansion. ParagraphView is an example of a container
* that does tab expansion.
*
* @since 1.3
*
* @author Timothy Prinzing
*/
public class GlyphView extends View implements TabableView, Cloneable {
Constructs a new view wrapped on an element.
Params: - elem – the element
/**
* Constructs a new view wrapped on an element.
*
* @param elem the element
*/
public GlyphView(Element elem) {
super(elem);
offset = 0;
length = 0;
Element parent = elem.getParentElement();
AttributeSet attr = elem.getAttributes();
// if there was an implied CR
impliedCR = (attr != null && attr.getAttribute(IMPLIED_CR) != null &&
// if this is non-empty paragraph
parent != null && parent.getElementCount() > 1);
skipWidth = elem.getName().equals("br");
}
Creates a shallow copy. This is used by the
createFragment and breakView methods.
Returns: the copy
/**
* Creates a shallow copy. This is used by the
* createFragment and breakView methods.
*
* @return the copy
*/
protected final Object clone() {
Object o;
try {
o = super.clone();
} catch (CloneNotSupportedException cnse) {
o = null;
}
return o;
}
Fetch the currently installed glyph painter.
If a painter has not yet been installed, and
a default was not yet needed, null is returned.
Returns: the currently installed glyph painter
/**
* Fetch the currently installed glyph painter.
* If a painter has not yet been installed, and
* a default was not yet needed, null is returned.
* @return the currently installed glyph painter
*/
public GlyphPainter getGlyphPainter() {
return painter;
}
Sets the painter to use for rendering glyphs.
Params: - p – the painter to use for rendering glyphs
/**
* Sets the painter to use for rendering glyphs.
* @param p the painter to use for rendering glyphs
*/
public void setGlyphPainter(GlyphPainter p) {
painter = p;
}
Fetch a reference to the text that occupies
the given range. This is normally used by
the GlyphPainter to determine what characters
it should render glyphs for.
Params: - p0 – the starting document offset >= 0
- p1 – the ending document offset >= p0
Returns: the Segment
containing the text
/**
* Fetch a reference to the text that occupies
* the given range. This is normally used by
* the GlyphPainter to determine what characters
* it should render glyphs for.
*
* @param p0 the starting document offset >= 0
* @param p1 the ending document offset >= p0
* @return the <code>Segment</code> containing the text
*/
public Segment getText(int p0, int p1) {
// When done with the returned Segment it should be released by
// invoking:
// SegmentCache.releaseSharedSegment(segment);
Segment text = SegmentCache.getSharedSegment();
try {
Document doc = getDocument();
doc.getText(p0, p1 - p0, text);
} catch (BadLocationException bl) {
throw new StateInvariantError("GlyphView: Stale view: " + bl);
}
return text;
}
Fetch the background color to use to render the
glyphs. If there is no background color, null should
be returned. This is implemented to call
StyledDocument.getBackground
if the associated
document is a styled document, otherwise it returns null.
Returns: the background color to use to render the glyphs
/**
* Fetch the background color to use to render the
* glyphs. If there is no background color, null should
* be returned. This is implemented to call
* <code>StyledDocument.getBackground</code> if the associated
* document is a styled document, otherwise it returns null.
* @return the background color to use to render the glyphs
*/
public Color getBackground() {
Document doc = getDocument();
if (doc instanceof StyledDocument) {
AttributeSet attr = getAttributes();
if (attr.isDefined(StyleConstants.Background)) {
return ((StyledDocument)doc).getBackground(attr);
}
}
return null;
}
Fetch the foreground color to use to render the
glyphs. If there is no foreground color, null should
be returned. This is implemented to call
StyledDocument.getBackground
if the associated
document is a StyledDocument. If the associated document
is not a StyledDocument, the associated components foreground
color is used. If there is no associated component, null
is returned.
Returns: the foreground color to use to render the glyphs
/**
* Fetch the foreground color to use to render the
* glyphs. If there is no foreground color, null should
* be returned. This is implemented to call
* <code>StyledDocument.getBackground</code> if the associated
* document is a StyledDocument. If the associated document
* is not a StyledDocument, the associated components foreground
* color is used. If there is no associated component, null
* is returned.
* @return the foreground color to use to render the glyphs
*/
public Color getForeground() {
Document doc = getDocument();
if (doc instanceof StyledDocument) {
AttributeSet attr = getAttributes();
return ((StyledDocument)doc).getForeground(attr);
}
Component c = getContainer();
if (c != null) {
return c.getForeground();
}
return null;
}
Fetch the font that the glyphs should be based
upon. This is implemented to call
StyledDocument.getFont
if the associated
document is a StyledDocument. If the associated document
is not a StyledDocument, the associated components font
is used. If there is no associated component, null
is returned.
Returns: the font that the glyphs should be based upon
/**
* Fetch the font that the glyphs should be based
* upon. This is implemented to call
* <code>StyledDocument.getFont</code> if the associated
* document is a StyledDocument. If the associated document
* is not a StyledDocument, the associated components font
* is used. If there is no associated component, null
* is returned.
* @return the font that the glyphs should be based upon
*/
public Font getFont() {
Document doc = getDocument();
if (doc instanceof StyledDocument) {
AttributeSet attr = getAttributes();
return ((StyledDocument)doc).getFont(attr);
}
Component c = getContainer();
if (c != null) {
return c.getFont();
}
return null;
}
Determine if the glyphs should be underlined. If true,
an underline should be drawn through the baseline.
Returns: if the glyphs should be underlined
/**
* Determine if the glyphs should be underlined. If true,
* an underline should be drawn through the baseline.
* @return if the glyphs should be underlined
*/
public boolean isUnderline() {
AttributeSet attr = getAttributes();
return StyleConstants.isUnderline(attr);
}
Determine if the glyphs should have a strikethrough
line. If true, a line should be drawn through the center
of the glyphs.
Returns: if the glyphs should have a strikethrough line
/**
* Determine if the glyphs should have a strikethrough
* line. If true, a line should be drawn through the center
* of the glyphs.
* @return if the glyphs should have a strikethrough line
*/
public boolean isStrikeThrough() {
AttributeSet attr = getAttributes();
return StyleConstants.isStrikeThrough(attr);
}
Determine if the glyphs should be rendered as superscript.
Returns: if the glyphs should be rendered as superscript
/**
* Determine if the glyphs should be rendered as superscript.
* @return if the glyphs should be rendered as superscript
*/
public boolean isSubscript() {
AttributeSet attr = getAttributes();
return StyleConstants.isSubscript(attr);
}
Determine if the glyphs should be rendered as subscript.
Returns: if the glyphs should be rendered as subscript
/**
* Determine if the glyphs should be rendered as subscript.
* @return if the glyphs should be rendered as subscript
*/
public boolean isSuperscript() {
AttributeSet attr = getAttributes();
return StyleConstants.isSuperscript(attr);
}
Fetch the TabExpander to use if tabs are present in this view.
Returns: the TabExpander to use if tabs are present in this view
/**
* Fetch the TabExpander to use if tabs are present in this view.
* @return the TabExpander to use if tabs are present in this view
*/
public TabExpander getTabExpander() {
return expander;
}
Check to see that a glyph painter exists. If a painter
doesn't exist, a default glyph painter will be installed.
/**
* Check to see that a glyph painter exists. If a painter
* doesn't exist, a default glyph painter will be installed.
*/
protected void checkPainter() {
if (painter == null) {
if (defaultPainter == null) {
// the classname should probably come from a property file.
defaultPainter = new GlyphPainter1();
}
setGlyphPainter(defaultPainter.getPainter(this, getStartOffset(),
getEndOffset()));
}
}
// --- TabableView methods --------------------------------------
Determines the desired span when using the given
tab expansion implementation.
Params: - x – the position the view would be located
at for the purpose of tab expansion >= 0.
- e – how to expand the tabs when encountered.
See Also: Returns: the desired span >= 0
/**
* Determines the desired span when using the given
* tab expansion implementation.
*
* @param x the position the view would be located
* at for the purpose of tab expansion >= 0.
* @param e how to expand the tabs when encountered.
* @return the desired span >= 0
* @see TabableView#getTabbedSpan
*/
public float getTabbedSpan(float x, TabExpander e) {
checkPainter();
TabExpander old = expander;
expander = e;
if (expander != old) {
// setting expander can change horizontal span of the view,
// so we have to call preferenceChanged()
preferenceChanged(null, true, false);
}
this.x = (int) x;
int p0 = getStartOffset();
int p1 = getEndOffset();
float width = painter.getSpan(this, p0, p1, expander, x);
return width;
}
Determines the span along the same axis as tab
expansion for a portion of the view. This is
intended for use by the TabExpander for cases
where the tab expansion involves aligning the
portion of text that doesn't have whitespace
relative to the tab stop. There is therefore
an assumption that the range given does not
contain tabs.
This method can be called while servicing the
getTabbedSpan or getPreferredSize. It has to
arrange for its own text buffer to make the
measurements.
Params: - p0 – the starting document offset >= 0
- p1 – the ending document offset >= p0
Returns: the span >= 0
/**
* Determines the span along the same axis as tab
* expansion for a portion of the view. This is
* intended for use by the TabExpander for cases
* where the tab expansion involves aligning the
* portion of text that doesn't have whitespace
* relative to the tab stop. There is therefore
* an assumption that the range given does not
* contain tabs.
* <p>
* This method can be called while servicing the
* getTabbedSpan or getPreferredSize. It has to
* arrange for its own text buffer to make the
* measurements.
*
* @param p0 the starting document offset >= 0
* @param p1 the ending document offset >= p0
* @return the span >= 0
*/
public float getPartialSpan(int p0, int p1) {
checkPainter();
float width = painter.getSpan(this, p0, p1, expander, x);
return width;
}
// --- View methods ---------------------------------------------
Fetches the portion of the model that this view is responsible for.
See Also: Returns: the starting offset into the model
/**
* Fetches the portion of the model that this view is responsible for.
*
* @return the starting offset into the model
* @see View#getStartOffset
*/
public int getStartOffset() {
Element e = getElement();
return (length > 0) ? e.getStartOffset() + offset : e.getStartOffset();
}
Fetches the portion of the model that this view is responsible for.
See Also: Returns: the ending offset into the model
/**
* Fetches the portion of the model that this view is responsible for.
*
* @return the ending offset into the model
* @see View#getEndOffset
*/
public int getEndOffset() {
Element e = getElement();
return (length > 0) ? e.getStartOffset() + offset + length : e.getEndOffset();
}
Lazily initializes the selections field
/**
* Lazily initializes the selections field
*/
private void initSelections(int p0, int p1) {
int viewPosCount = p1 - p0 + 1;
if (selections == null || viewPosCount > selections.length) {
selections = new byte[viewPosCount];
return;
}
for (int i = 0; i < viewPosCount; selections[i++] = 0);
}
Renders a portion of a text style run.
Params: - g – the rendering surface to use
- a – the allocated region to render into
/**
* Renders a portion of a text style run.
*
* @param g the rendering surface to use
* @param a the allocated region to render into
*/
public void paint(Graphics g, Shape a) {
checkPainter();
boolean paintedText = false;
Component c = getContainer();
int p0 = getStartOffset();
int p1 = getEndOffset();
Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a : a.getBounds();
Color bg = getBackground();
Color fg = getForeground();
if (c != null && ! c.isEnabled()) {
fg = (c instanceof JTextComponent ?
((JTextComponent)c).getDisabledTextColor() :
UIManager.getColor("textInactiveText"));
}
if (bg != null) {
g.setColor(bg);
g.fillRect(alloc.x, alloc.y, alloc.width, alloc.height);
}
if (c instanceof JTextComponent) {
JTextComponent tc = (JTextComponent) c;
Highlighter h = tc.getHighlighter();
if (h instanceof LayeredHighlighter) {
((LayeredHighlighter)h).paintLayeredHighlights
(g, p0, p1, a, tc, this);
}
}
if (Utilities.isComposedTextElement(getElement())) {
Utilities.paintComposedText(g, a.getBounds(), this);
paintedText = true;
} else if(c instanceof JTextComponent) {
JTextComponent tc = (JTextComponent) c;
Color selFG = tc.getSelectedTextColor();
if (// there's a highlighter (bug 4532590), and
(tc.getHighlighter() != null) &&
// selected text color is different from regular foreground
(selFG != null) && !selFG.equals(fg)) {
Highlighter.Highlight[] h = tc.getHighlighter().getHighlights();
if(h.length != 0) {
boolean initialized = false;
int viewSelectionCount = 0;
for (int i = 0; i < h.length; i++) {
Highlighter.Highlight highlight = h[i];
int hStart = highlight.getStartOffset();
int hEnd = highlight.getEndOffset();
if (hStart > p1 || hEnd < p0) {
// the selection is out of this view
continue;
}
if (!SwingUtilities2.useSelectedTextColor(highlight, tc)) {
continue;
}
if (hStart <= p0 && hEnd >= p1){
// the whole view is selected
paintTextUsingColor(g, a, selFG, p0, p1);
paintedText = true;
break;
}
// the array is lazily created only when the view
// is partially selected
if (!initialized) {
initSelections(p0, p1);
initialized = true;
}
hStart = Math.max(p0, hStart);
hEnd = Math.min(p1, hEnd);
paintTextUsingColor(g, a, selFG, hStart, hEnd);
// the array represents view positions [0, p1-p0+1]
// later will iterate this array and sum its
// elements. Positions with sum == 0 are not selected.
selections[hStart-p0]++;
selections[hEnd-p0]--;
viewSelectionCount++;
}
if (!paintedText && viewSelectionCount > 0) {
// the view is partially selected
int curPos = -1;
int startPos = 0;
int viewLen = p1 - p0;
while (curPos++ < viewLen) {
// searching for the next selection start
while(curPos < viewLen &&
selections[curPos] == 0) curPos++;
if (startPos != curPos) {
// paint unselected text
paintTextUsingColor(g, a, fg,
p0 + startPos, p0 + curPos);
}
int checkSum = 0;
// searching for next start position of unselected text
while (curPos < viewLen &&
(checkSum += selections[curPos]) != 0) curPos++;
startPos = curPos;
}
paintedText = true;
}
}
}
}
if(!paintedText)
paintTextUsingColor(g, a, fg, p0, p1);
}
Paints the specified region of text in the specified color.
/**
* Paints the specified region of text in the specified color.
*/
final void paintTextUsingColor(Graphics g, Shape a, Color c, int p0, int p1) {
// render the glyphs
g.setColor(c);
painter.paint(this, g, a, p0, p1);
// render underline or strikethrough if set.
boolean underline = isUnderline();
boolean strike = isStrikeThrough();
if (underline || strike) {
// calculate x coordinates
Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a : a.getBounds();
View parent = getParent();
if ((parent != null) && (parent.getEndOffset() == p1)) {
// strip whitespace on end
Segment s = getText(p0, p1);
while (Character.isWhitespace(s.last())) {
p1 -= 1;
s.count -= 1;
}
SegmentCache.releaseSharedSegment(s);
}
int x0 = alloc.x;
int p = getStartOffset();
if (p != p0) {
x0 += (int) painter.getSpan(this, p, p0, getTabExpander(), x0);
}
int x1 = x0 + (int) painter.getSpan(this, p0, p1, getTabExpander(), x0);
// calculate y coordinate
int y = alloc.y + (int)(painter.getHeight(this) - painter.getDescent(this));
if (underline) {
int yTmp = y + 1;
g.drawLine(x0, yTmp, x1, yTmp);
}
if (strike) {
// move y coordinate above baseline
int yTmp = y - (int) (painter.getAscent(this) * 0.3f);
g.drawLine(x0, yTmp, x1, yTmp);
}
}
}
Determines the minimum span for this view along an axis.
This implementation returns the longest non-breakable area within the view as a minimum span for View.X_AXIS
.
Params: - axis – may be either
View.X_AXIS
or View.Y_AXIS
Throws: - IllegalArgumentException – if the
axis
parameter is invalid
See Also: Returns: the minimum span the view can be rendered into
/**
* Determines the minimum span for this view along an axis.
*
* <p>This implementation returns the longest non-breakable area within
* the view as a minimum span for {@code View.X_AXIS}.</p>
*
* @param axis may be either {@code View.X_AXIS} or {@code View.Y_AXIS}
* @return the minimum span the view can be rendered into
* @throws IllegalArgumentException if the {@code axis} parameter is invalid
* @see javax.swing.text.View#getMinimumSpan
*/
@Override
public float getMinimumSpan(int axis) {
switch (axis) {
case View.X_AXIS:
if (minimumSpan < 0) {
minimumSpan = 0;
int p0 = getStartOffset();
int p1 = getEndOffset();
while (p1 > p0) {
int breakSpot = getBreakSpot(p0, p1);
if (breakSpot == BreakIterator.DONE) {
// the rest of the view is non-breakable
breakSpot = p0;
}
minimumSpan = Math.max(minimumSpan,
getPartialSpan(breakSpot, p1));
// Note: getBreakSpot returns the *last* breakspot
p1 = breakSpot - 1;
}
}
return minimumSpan;
case View.Y_AXIS:
return super.getMinimumSpan(axis);
default:
throw new IllegalArgumentException("Invalid axis: " + axis);
}
}
Determines the preferred span for this view along an
axis.
Params: - axis – may be either View.X_AXIS or View.Y_AXIS
Returns: the span the view would like to be rendered into >= 0.
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.
/**
* 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 >= 0.
* 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.
*/
public float getPreferredSpan(int axis) {
if (impliedCR) {
return 0;
}
checkPainter();
int p0 = getStartOffset();
int p1 = getEndOffset();
switch (axis) {
case View.X_AXIS:
if (skipWidth) {
return 0;
}
return painter.getSpan(this, p0, p1, expander, this.x);
case View.Y_AXIS:
float h = painter.getHeight(this);
if (isSuperscript()) {
h += h/3;
}
return h;
default:
throw new IllegalArgumentException("Invalid axis: " + axis);
}
}
Determines the desired alignment for this view along an
axis. For the label, the alignment is along the font
baseline for the y axis, and the superclasses alignment
along the x axis.
Params: - axis – may be either View.X_AXIS or View.Y_AXIS
Returns: the desired alignment. This should be a value
between 0.0 and 1.0 inclusive, 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.
/**
* Determines the desired alignment for this view along an
* axis. For the label, the alignment is along the font
* baseline for the y axis, and the superclasses alignment
* along the x axis.
*
* @param axis may be either View.X_AXIS or View.Y_AXIS
* @return the desired alignment. This should be a value
* between 0.0 and 1.0 inclusive, 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) {
checkPainter();
if (axis == View.Y_AXIS) {
boolean sup = isSuperscript();
boolean sub = isSubscript();
float h = painter.getHeight(this);
float d = painter.getDescent(this);
float a = painter.getAscent(this);
float align;
if (sup) {
align = 1.0f;
} else if (sub) {
align = (h > 0) ? (h - (d + (a / 2))) / h : 0;
} else {
align = (h > 0) ? (h - d) / h : 0;
}
return align;
}
return super.getAlignment(axis);
}
Provides a mapping from the document model coordinate space
to the coordinate space of the view mapped to it.
Params: - pos – the position to convert >= 0
- a – the allocated region to render into
- b – either
Position.Bias.Forward
or Position.Bias.Backward
Throws: - BadLocationException – if the given position does not represent a
valid location in the associated document
See Also: Returns: the bounding box of the given position
/**
* Provides a mapping from the document model coordinate space
* to the coordinate space of the view mapped to it.
*
* @param pos the position to convert >= 0
* @param a the allocated region to render into
* @param b either <code>Position.Bias.Forward</code>
* or <code>Position.Bias.Backward</code>
* @return the bounding box of the given position
* @exception 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 {
checkPainter();
return painter.modelToView(this, pos, b, a);
}
Provides a mapping from the view coordinate space to the logical
coordinate space of the model.
Params: - x – the X coordinate >= 0
- y – the Y coordinate >= 0
- a – the allocated region to render into
- biasReturn – either
Position.Bias.Forward
or Position.Bias.Backward
is returned as the
zero-th element of this array
See Also: Returns: the location within the model that best represents the
given point of view >= 0
/**
* Provides a mapping from the view coordinate space to the logical
* coordinate space of the model.
*
* @param x the X coordinate >= 0
* @param y the Y coordinate >= 0
* @param a the allocated region to render into
* @param biasReturn either <code>Position.Bias.Forward</code>
* or <code>Position.Bias.Backward</code> is returned as the
* zero-th element of this array
* @return the location within the model that best represents the
* given point of view >= 0
* @see View#viewToModel
*/
public int viewToModel(float x, float y, Shape a, Position.Bias[] biasReturn) {
checkPainter();
return painter.viewToModel(this, x, y, a, biasReturn);
}
Determines how attractive a break opportunity in
this view is. This can be used for determining which
view is the most attractive to call breakView
on in the process of formatting. The
higher the weight, the more attractive the break. A
value equal to or lower than View.BadBreakWeight
should not be considered for a break. A value greater
than or equal to View.ForcedBreakWeight
should
be broken.
This is implemented to forward to the superclass for
the Y_AXIS. Along the X_AXIS the following values
may be returned.
- View.ExcellentBreakWeight
- if there is whitespace proceeding the desired break
location.
- View.BadBreakWeight
- if the desired break location results in a break
location of the starting offset.
- View.GoodBreakWeight
- if the other conditions don't occur.
This will normally result in the behavior of breaking
on a whitespace location if one can be found, otherwise
breaking between characters.
Params: - axis – may be either View.X_AXIS or View.Y_AXIS
- pos – the potential location of the start of the
broken view >= 0. This may be useful for calculating tab
positions.
- len – specifies the relative length from pos
where a potential break is desired >= 0.
See Also: Returns: the weight, which should be a value between
View.ForcedBreakWeight and View.BadBreakWeight.
/**
* 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. The
* higher the weight, the more attractive the break. A
* value equal to or lower than <code>View.BadBreakWeight</code>
* should not be considered for a break. A value greater
* than or equal to <code>View.ForcedBreakWeight</code> should
* be broken.
* <p>
* This is implemented to forward to the superclass for
* the Y_AXIS. Along the X_AXIS the following values
* may be returned.
* <dl>
* <dt><b>View.ExcellentBreakWeight</b>
* <dd>if there is whitespace proceeding the desired break
* location.
* <dt><b>View.BadBreakWeight</b>
* <dd>if the desired break location results in a break
* location of the starting offset.
* <dt><b>View.GoodBreakWeight</b>
* <dd>if the other conditions don't occur.
* </dl>
* This will normally result in the behavior of breaking
* on a whitespace location if one can be found, otherwise
* breaking between characters.
*
* @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
* View.ForcedBreakWeight and View.BadBreakWeight.
* @see LabelView
* @see ParagraphView
* @see View#BadBreakWeight
* @see View#GoodBreakWeight
* @see View#ExcellentBreakWeight
* @see View#ForcedBreakWeight
*/
public int getBreakWeight(int axis, float pos, float len) {
if (axis == View.X_AXIS) {
checkPainter();
int p0 = getStartOffset();
int p1 = painter.getBoundedPosition(this, p0, pos, len);
return p1 == p0 ? View.BadBreakWeight :
getBreakSpot(p0, p1) != BreakIterator.DONE ?
View.ExcellentBreakWeight : View.GoodBreakWeight;
}
return super.getBreakWeight(axis, pos, len);
}
Breaks this view on the given axis at the given length.
This is implemented to attempt to break on a whitespace
location, and returns a fragment with the whitespace at
the end. If a whitespace location can't be found, the
nearest character is used.
Params: - axis – may be either View.X_AXIS or View.Y_AXIS
- p0 – the location in the model where the
fragment should start it's representation >= 0.
- pos – the position along the axis that the
broken view would occupy >= 0. This may be useful for
things like tab calculations.
- len – specifies the distance along the axis
where a potential break is desired >= 0.
See Also: Returns: the fragment of the view that represents the
given span, if the view can be broken. If the view
doesn't support breaking behavior, the view itself is
returned.
/**
* Breaks this view on the given axis at the given length.
* This is implemented to attempt to break on a whitespace
* location, and returns a fragment with the whitespace at
* the end. If a whitespace location can't be found, the
* nearest character is used.
*
* @param axis may be either View.X_AXIS or View.Y_AXIS
* @param p0 the location in the model where the
* fragment should start it's representation >= 0.
* @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, if the view can be broken. If the view
* doesn't support breaking behavior, the view itself is
* returned.
* @see View#breakView
*/
public View breakView(int axis, int p0, float pos, float len) {
if (axis == View.X_AXIS) {
checkPainter();
int p1 = painter.getBoundedPosition(this, p0, pos, len);
int breakSpot = getBreakSpot(p0, p1);
if (breakSpot != -1) {
p1 = breakSpot;
}
// else, no break in the region, return a fragment of the
// bounded region.
if (p0 == getStartOffset() && p1 == getEndOffset()) {
return this;
}
GlyphView v = (GlyphView) createFragment(p0, p1);
v.x = (int) pos;
return v;
}
return this;
}
Returns a location to break at in the passed in region, or
BreakIterator.DONE if there isn't a good location to break at
in the specified region.
/**
* Returns a location to break at in the passed in region, or
* BreakIterator.DONE if there isn't a good location to break at
* in the specified region.
*/
private int getBreakSpot(int p0, int p1) {
if (breakSpots == null) {
// Re-calculate breakpoints for the whole view
int start = getStartOffset();
int end = getEndOffset();
int[] bs = new int[end + 1 - start];
int ix = 0;
// Breaker should work on the parent element because there may be
// a valid breakpoint at the end edge of the view (space, etc.)
Element parent = getElement().getParentElement();
int pstart = (parent == null ? start : parent.getStartOffset());
int pend = (parent == null ? end : parent.getEndOffset());
Segment s = getText(pstart, pend);
s.first();
BreakIterator breaker = getBreaker();
breaker.setText(s);
// Backward search should start from end+1 unless there's NO end+1
int startFrom = end + (pend > end ? 1 : 0);
for (;;) {
startFrom = breaker.preceding(s.offset + (startFrom - pstart))
+ (pstart - s.offset);
if (startFrom > start) {
// The break spot is within the view
bs[ix++] = startFrom;
} else {
break;
}
}
SegmentCache.releaseSharedSegment(s);
breakSpots = new int[ix];
System.arraycopy(bs, 0, breakSpots, 0, ix);
}
int breakSpot = BreakIterator.DONE;
for (int i = 0; i < breakSpots.length; i++) {
int bsp = breakSpots[i];
if (bsp <= p1) {
if (bsp > p0) {
breakSpot = bsp;
}
break;
}
}
return breakSpot;
}
Return break iterator appropriate for the current document.
For non-i18n documents a fast whitespace-based break iterator is used.
/**
* Return break iterator appropriate for the current document.
*
* For non-i18n documents a fast whitespace-based break iterator is used.
*/
private BreakIterator getBreaker() {
Document doc = getDocument();
if ((doc != null) && Boolean.TRUE.equals(
doc.getProperty(AbstractDocument.MultiByteProperty))) {
Container c = getContainer();
Locale locale = (c == null ? Locale.getDefault() : c.getLocale());
return BreakIterator.getLineInstance(locale);
} else {
return new WhitespaceBasedBreakIterator();
}
}
Creates a view that represents a portion of the element.
This is potentially useful during formatting operations
for taking measurements of fragments of the view. If
the view doesn't support fragmenting (the default), it
should return itself.
This view does support fragmenting. It is implemented
to return a nested class that shares state in this view
representing only a portion of the view.
Params: - 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.
- 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.
See Also: Returns: the view fragment, or itself if the view doesn't
support breaking into fragments
/**
* Creates a view that represents a portion of the element.
* This is potentially useful during formatting operations
* for taking measurements of fragments of the view. If
* the view doesn't support fragmenting (the default), it
* should return itself.
* <p>
* This view does support fragmenting. It is implemented
* to return a nested class that shares state in this view
* representing only a portion of the view.
*
* @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 LabelView
*/
public View createFragment(int p0, int p1) {
checkPainter();
Element elem = getElement();
GlyphView v = (GlyphView) clone();
v.offset = p0 - elem.getStartOffset();
v.length = p1 - p0;
v.painter = painter.getPainter(v, p0, p1);
v.justificationInfo = null;
return v;
}
Provides a way to determine the next visually represented model location that one might place a caret. Some views may not be visible, they might not be in the same order found in the model, or they just might not allow access to some of the locations in the model. This method enables specifying a position to convert within the range of >=0. If the value is -1, a position will be calculated automatically. If the value < -1, the BadLocationException
will be thrown. Params: - pos – the position to convert
- a – the allocated region to render into
- direction – the direction from the current position that can
be thought of as the arrow keys typically found on a keyboard.
This may be SwingConstants.WEST, SwingConstants.EAST,
SwingConstants.NORTH, or SwingConstants.SOUTH.
Throws: - BadLocationException – the given position is not a valid
position within the document
- IllegalArgumentException – for an invalid direction
Returns: the location within the model that best represents the next
location visual position.
/**
* Provides a way to determine the next visually represented model
* location that one might place a caret. Some views may not be
* visible, they might not be in the same order found in the model, or
* they just might not allow access to some of the locations in the
* model.
* This method enables specifying a position to convert
* within the range of >=0. If the value is -1, a position
* will be calculated automatically. If the value < -1,
* the {@code BadLocationException} will be thrown.
*
* @param pos the position to convert
* @param a the allocated region to render into
* @param direction the direction from the current position that can
* be thought of as the arrow keys typically found on a keyboard.
* This may be SwingConstants.WEST, SwingConstants.EAST,
* SwingConstants.NORTH, or SwingConstants.SOUTH.
* @return the location within the model that best represents the next
* location visual position.
* @exception BadLocationException the given position is not a valid
* position within the document
* @exception IllegalArgumentException for an invalid direction
*/
public int getNextVisualPositionFrom(int pos, Position.Bias b, Shape a,
int direction,
Position.Bias[] biasRet)
throws BadLocationException {
if (pos < -1 || pos > getDocument().getLength()) {
throw new BadLocationException("invalid position", pos);
}
return painter.getNextVisualPositionFrom(this, pos, b, a, direction, biasRet);
}
Gives notification that something was inserted into
the document in a location that this view is responsible for.
This is implemented to call preferenceChanged along the
axis the glyphs are rendered.
Params: - e – the change information from the associated document
- a – the current allocation of the view
- f – the factory to use to rebuild if the view has children
See Also:
/**
* Gives notification that something was inserted into
* the document in a location that this view is responsible for.
* This is implemented to call preferenceChanged along the
* axis the glyphs are rendered.
*
* @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#insertUpdate
*/
public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) {
justificationInfo = null;
breakSpots = null;
minimumSpan = -1;
syncCR();
preferenceChanged(null, true, false);
}
Gives notification that something was removed from the document
in a location that this view is responsible for.
This is implemented to call preferenceChanged along the
axis the glyphs are rendered.
Params: - e – the change information from the associated document
- a – the current allocation of the view
- f – the factory to use to rebuild if the view has children
See Also:
/**
* Gives notification that something was removed from the document
* in a location that this view is responsible for.
* This is implemented to call preferenceChanged along the
* axis the glyphs are rendered.
*
* @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#removeUpdate
*/
public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) {
justificationInfo = null;
breakSpots = null;
minimumSpan = -1;
syncCR();
preferenceChanged(null, true, false);
}
Gives notification from the document that attributes were changed
in a location that this view is responsible for.
This is implemented to call preferenceChanged along both the
horizontal and vertical axis.
Params: - e – the change information from the associated document
- a – the current allocation of the view
- f – the factory to use to rebuild if the view has children
See Also:
/**
* Gives notification from the document that attributes were changed
* in a location that this view is responsible for.
* This is implemented to call preferenceChanged along both the
* horizontal and vertical axis.
*
* @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) {
minimumSpan = -1;
syncCR();
preferenceChanged(null, true, true);
}
// checks if the paragraph is empty and updates impliedCR flag
// accordingly
private void syncCR() {
if (impliedCR) {
Element parent = getElement().getParentElement();
impliedCR = (parent != null && parent.getElementCount() > 1);
}
}
{@inheritDoc} /** {@inheritDoc} */
@Override
void updateAfterChange() {
// Drop the break spots. They will be re-calculated during
// layout. It is necessary for proper line break calculation.
breakSpots = null;
}
Class to hold data needed to justify this GlyphView in a PargraphView.Row
/**
* Class to hold data needed to justify this GlyphView in a PargraphView.Row
*/
static class JustificationInfo {
//justifiable content start
final int start;
//justifiable content end
final int end;
final int leadingSpaces;
final int contentSpaces;
final int trailingSpaces;
final boolean hasTab;
final BitSet spaceMap;
JustificationInfo(int start, int end,
int leadingSpaces,
int contentSpaces,
int trailingSpaces,
boolean hasTab,
BitSet spaceMap) {
this.start = start;
this.end = end;
this.leadingSpaces = leadingSpaces;
this.contentSpaces = contentSpaces;
this.trailingSpaces = trailingSpaces;
this.hasTab = hasTab;
this.spaceMap = spaceMap;
}
}
JustificationInfo getJustificationInfo(int rowStartOffset) {
if (justificationInfo != null) {
return justificationInfo;
}
//states for the parsing
final int TRAILING = 0;
final int CONTENT = 1;
final int SPACES = 2;
int startOffset = getStartOffset();
int endOffset = getEndOffset();
Segment segment = getText(startOffset, endOffset);
int txtOffset = segment.offset;
int txtEnd = segment.offset + segment.count - 1;
int startContentPosition = txtEnd + 1;
int endContentPosition = txtOffset - 1;
int lastTabPosition = txtOffset - 1;
int trailingSpaces = 0;
int contentSpaces = 0;
int leadingSpaces = 0;
boolean hasTab = false;
BitSet spaceMap = new BitSet(endOffset - startOffset + 1);
//we parse conent to the right of the rightmost TAB only.
//we are looking for the trailing and leading spaces.
//position after the leading spaces (startContentPosition)
//position before the trailing spaces (endContentPosition)
for (int i = txtEnd, state = TRAILING; i >= txtOffset; i--) {
if (' ' == segment.array[i]) {
spaceMap.set(i - txtOffset);
if (state == TRAILING) {
trailingSpaces++;
} else if (state == CONTENT) {
state = SPACES;
leadingSpaces = 1;
} else if (state == SPACES) {
leadingSpaces++;
}
} else if ('\t' == segment.array[i]) {
hasTab = true;
break;
} else {
if (state == TRAILING) {
if ('\n' != segment.array[i]
&& '\r' != segment.array[i]) {
state = CONTENT;
endContentPosition = i;
}
} else if (state == CONTENT) {
//do nothing
} else if (state == SPACES) {
contentSpaces += leadingSpaces;
leadingSpaces = 0;
}
startContentPosition = i;
}
}
SegmentCache.releaseSharedSegment(segment);
int startJustifiableContent = -1;
if (startContentPosition < txtEnd) {
startJustifiableContent =
startContentPosition - txtOffset;
}
int endJustifiableContent = -1;
if (endContentPosition > txtOffset) {
endJustifiableContent =
endContentPosition - txtOffset;
}
justificationInfo =
new JustificationInfo(startJustifiableContent,
endJustifiableContent,
leadingSpaces,
contentSpaces,
trailingSpaces,
hasTab,
spaceMap);
return justificationInfo;
}
// --- variables ------------------------------------------------
Used by paint() to store highlighted view positions
/**
* Used by paint() to store highlighted view positions
*/
private byte[] selections = null;
int offset;
int length;
// if it is an implied newline character
boolean impliedCR;
boolean skipWidth;
how to expand tabs
/**
* how to expand tabs
*/
TabExpander expander;
Cached minimum x-span value /** Cached minimum x-span value */
private float minimumSpan = -1;
Cached breakpoints within the view /** Cached breakpoints within the view */
private int[] breakSpots = null;
location for determining tab expansion against.
/**
* location for determining tab expansion against.
*/
int x;
Glyph rendering functionality.
/**
* Glyph rendering functionality.
*/
GlyphPainter painter;
The prototype painter used by default.
/**
* The prototype painter used by default.
*/
static GlyphPainter defaultPainter;
private JustificationInfo justificationInfo = null;
A class to perform rendering of the glyphs.
This can be implemented to be stateless, or
to hold some information as a cache to
facilitate faster rendering and model/view
translation. At a minimum, the GlyphPainter
allows a View implementation to perform its
duties independent of a particular version
of JVM and selection of capabilities (i.e.
shaping for i18n, etc).
Since: 1.3
/**
* A class to perform rendering of the glyphs.
* This can be implemented to be stateless, or
* to hold some information as a cache to
* facilitate faster rendering and model/view
* translation. At a minimum, the GlyphPainter
* allows a View implementation to perform its
* duties independent of a particular version
* of JVM and selection of capabilities (i.e.
* shaping for i18n, etc).
*
* @since 1.3
*/
public abstract static class GlyphPainter {
Determine the span the glyphs given a start location
(for tab expansion).
Params: - v – the
GlyphView
- p0 – the beginning position
- p1 – the ending position
- e – how to expand the tabs when encountered
- x – the X coordinate
Returns: the span the glyphs given a start location
/**
* Determine the span the glyphs given a start location
* (for tab expansion).
* @param v the {@code GlyphView}
* @param p0 the beginning position
* @param p1 the ending position
* @param e how to expand the tabs when encountered
* @param x the X coordinate
* @return the span the glyphs given a start location
*/
public abstract float getSpan(GlyphView v, int p0, int p1, TabExpander e, float x);
Returns of the height.
Params: - v – the
GlyphView
Returns: of the height
/**
* Returns of the height.
* @param v the {@code GlyphView}
* @return of the height
*/
public abstract float getHeight(GlyphView v);
Returns of the ascent.
Params: - v – the
GlyphView
Returns: of the ascent
/**
* Returns of the ascent.
* @param v the {@code GlyphView}
* @return of the ascent
*/
public abstract float getAscent(GlyphView v);
Returns of the descent.
Params: - v – the
GlyphView
Returns: of the descent
/**
* Returns of the descent.
* @param v the {@code GlyphView}
* @return of the descent
*/
public abstract float getDescent(GlyphView v);
Paint the glyphs representing the given range.
Params: - v – the
GlyphView
- g – the graphics context
- a – the current allocation of the view
- p0 – the beginning position
- p1 – the ending position
/**
* Paint the glyphs representing the given range.
* @param v the {@code GlyphView}
* @param g the graphics context
* @param a the current allocation of the view
* @param p0 the beginning position
* @param p1 the ending position
*/
public abstract void paint(GlyphView v, Graphics g, Shape a, int p0, int p1);
Provides a mapping from the document model coordinate space
to the coordinate space of the view mapped to it.
This is shared by the broken views.
Params: - v – the
GlyphView
containing the
destination coordinate space - pos – the position to convert
- bias – either
Position.Bias.Forward
or Position.Bias.Backward
- a – Bounds of the View
Throws: - BadLocationException – if the given position does not represent a
valid location in the associated document
See Also: Returns: the bounding box of the given position
/**
* Provides a mapping from the document model coordinate space
* to the coordinate space of the view mapped to it.
* This is shared by the broken views.
*
* @param v the <code>GlyphView</code> containing the
* destination coordinate space
* @param pos the position to convert
* @param bias either <code>Position.Bias.Forward</code>
* or <code>Position.Bias.Backward</code>
* @param a Bounds of the View
* @return the bounding box of the given position
* @exception BadLocationException if the given position does not represent a
* valid location in the associated document
* @see View#modelToView
*/
public abstract Shape modelToView(GlyphView v,
int pos, Position.Bias bias,
Shape a) throws BadLocationException;
Provides a mapping from the view coordinate space to the logical
coordinate space of the model.
Params: - v – the
GlyphView
to provide a mapping for - x – the X coordinate
- y – the Y coordinate
- a – the allocated region to render into
- biasReturn – either
Position.Bias.Forward
or Position.Bias.Backward
is returned as the zero-th element of this array
See Also: Returns: the location within the model that best represents the
given point of view
/**
* Provides a mapping from the view coordinate space to the logical
* coordinate space of the model.
*
* @param v the <code>GlyphView</code> to provide a mapping for
* @param x the X coordinate
* @param y the Y coordinate
* @param a the allocated region to render into
* @param biasReturn either <code>Position.Bias.Forward</code>
* or <code>Position.Bias.Backward</code>
* is returned as the zero-th element of this array
* @return the location within the model that best represents the
* given point of view
* @see View#viewToModel
*/
public abstract int viewToModel(GlyphView v,
float x, float y, Shape a,
Position.Bias[] biasReturn);
Determines the model location that represents the
maximum advance that fits within the given span.
This could be used to break the given view. The result
should be a location just shy of the given advance. This
differs from viewToModel which returns the closest
position which might be proud of the maximum advance.
Params: - v – the view to find the model location to break at.
- p0 – the location in the model where the
fragment should start it's representation >= 0.
- x – the graphic location along the axis that the
broken view would occupy >= 0. This may be useful for
things like tab calculations.
- len – specifies the distance into the view
where a potential break is desired >= 0.
See Also: Returns: the maximum model location possible for a break.
/**
* Determines the model location that represents the
* maximum advance that fits within the given span.
* This could be used to break the given view. The result
* should be a location just shy of the given advance. This
* differs from viewToModel which returns the closest
* position which might be proud of the maximum advance.
*
* @param v the view to find the model location to break at.
* @param p0 the location in the model where the
* fragment should start it's representation >= 0.
* @param x the graphic location along the axis that the
* broken view would occupy >= 0. This may be useful for
* things like tab calculations.
* @param len specifies the distance into the view
* where a potential break is desired >= 0.
* @return the maximum model location possible for a break.
* @see View#breakView
*/
public abstract int getBoundedPosition(GlyphView v, int p0, float x, float len);
Create a painter to use for the given GlyphView. If
the painter carries state it can create another painter
to represent a new GlyphView that is being created. If
the painter doesn't hold any significant state, it can
return itself. The default behavior is to return itself.
Params: - v – the
GlyphView
to provide a painter for - p0 – the starting document offset >= 0
- p1 – the ending document offset >= p0
Returns: a painter to use for the given GlyphView
/**
* Create a painter to use for the given GlyphView. If
* the painter carries state it can create another painter
* to represent a new GlyphView that is being created. If
* the painter doesn't hold any significant state, it can
* return itself. The default behavior is to return itself.
* @param v the <code>GlyphView</code> to provide a painter for
* @param p0 the starting document offset >= 0
* @param p1 the ending document offset >= p0
* @return a painter to use for the given GlyphView
*/
public GlyphPainter getPainter(GlyphView v, int p0, int p1) {
return this;
}
Provides a way to determine the next visually represented model
location that one might place a caret. Some views may not be
visible, they might not be in the same order found in the model, or
they just might not allow access to some of the locations in the
model.
Params: - v – the view to use
- pos – the position to convert >= 0
- b – either
Position.Bias.Forward
or Position.Bias.Backward
- a – the allocated region to render into
- direction – the direction from the current position that can
be thought of as the arrow keys typically found on a keyboard.
This may be SwingConstants.WEST, SwingConstants.EAST,
SwingConstants.NORTH, or SwingConstants.SOUTH.
- biasRet – either
Position.Bias.Forward
or Position.Bias.Backward
is returned as the zero-th element of this array
Throws: - BadLocationException – for a bad location within a document model
- IllegalArgumentException – for an invalid direction
Returns: the location within the model that best represents the next
location visual position.
/**
* Provides a way to determine the next visually represented model
* location that one might place a caret. Some views may not be
* visible, they might not be in the same order found in the model, or
* they just might not allow access to some of the locations in the
* model.
*
* @param v the view to use
* @param pos the position to convert >= 0
* @param b either <code>Position.Bias.Forward</code>
* or <code>Position.Bias.Backward</code>
* @param a the allocated region to render into
* @param direction the direction from the current position that can
* be thought of as the arrow keys typically found on a keyboard.
* This may be SwingConstants.WEST, SwingConstants.EAST,
* SwingConstants.NORTH, or SwingConstants.SOUTH.
* @param biasRet either <code>Position.Bias.Forward</code>
* or <code>Position.Bias.Backward</code>
* is returned as the zero-th element of this array
* @return the location within the model that best represents the next
* location visual position.
* @exception BadLocationException for a bad location within a document model
* @exception IllegalArgumentException for an invalid direction
*/
public int getNextVisualPositionFrom(GlyphView v, int pos, Position.Bias b, Shape a,
int direction,
Position.Bias[] biasRet)
throws BadLocationException {
int startOffset = v.getStartOffset();
int endOffset = v.getEndOffset();
Segment text;
switch (direction) {
case View.NORTH:
case View.SOUTH:
if (pos != -1) {
// Presumably pos is between startOffset and endOffset,
// since GlyphView is only one line, we won't contain
// the position to the nort/south, therefore return -1.
return -1;
}
Container container = v.getContainer();
if (container instanceof JTextComponent) {
Caret c = ((JTextComponent)container).getCaret();
Point magicPoint;
magicPoint = (c != null) ? c.getMagicCaretPosition() :null;
if (magicPoint == null) {
biasRet[0] = Position.Bias.Forward;
return startOffset;
}
int value = v.viewToModel(magicPoint.x, 0f, a, biasRet);
return value;
}
break;
case View.EAST:
if(startOffset == v.getDocument().getLength()) {
if(pos == -1) {
biasRet[0] = Position.Bias.Forward;
return startOffset;
}
// End case for bidi text where newline is at beginning
// of line.
return -1;
}
if(pos == -1) {
biasRet[0] = Position.Bias.Forward;
return startOffset;
}
if(pos == endOffset) {
return -1;
}
if(++pos == endOffset) {
// Assumed not used in bidi text, GlyphPainter2 will
// override as necessary, therefore return -1.
return -1;
}
else {
biasRet[0] = Position.Bias.Forward;
}
return pos;
case View.WEST:
if(startOffset == v.getDocument().getLength()) {
if(pos == -1) {
biasRet[0] = Position.Bias.Forward;
return startOffset;
}
// End case for bidi text where newline is at beginning
// of line.
return -1;
}
if(pos == -1) {
// Assumed not used in bidi text, GlyphPainter2 will
// override as necessary, therefore return -1.
biasRet[0] = Position.Bias.Forward;
return endOffset - 1;
}
if(pos == startOffset) {
return -1;
}
biasRet[0] = Position.Bias.Forward;
return (pos - 1);
default:
throw new IllegalArgumentException("Bad direction: " + direction);
}
return pos;
}
}
}