/* * Copyright (c) 1999, 2010, 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.
*
*
* @since 1.3
*
* @author Timothy Prinzing
*/
public class GlyphView extends View implements TabableView, Cloneable {
/**
* 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.
*
* @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.
*/
public GlyphPainter getGlyphPainter() {
return painter;
}
/**
* Sets 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.
*
* @param p0 the starting document offset >= 0
* @param p1 the ending document offset >= p0
* @return the Segment
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.
*/
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.
*/
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.
*/
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.
*/
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.
*/
public boolean isStrikeThrough() {
AttributeSet attr = getAttributes();
return StyleConstants.isStrikeThrough(attr);
}
/**
* Determine 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.
*/
public boolean isSuperscript() {
AttributeSet attr = getAttributes();
return StyleConstants.isSuperscript(attr);
}
/**
* Fetch 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.
*/
protected void checkPainter() {
if (painter == null) {
if (defaultPainter == null) {
// the classname should probably come from a property file.
String classname = "javax.swing.text.GlyphPainter1";
try {
Class c;
ClassLoader loader = getClass().getClassLoader();
if (loader != null) {
c = loader.loadClass(classname);
} else {
c = Class.forName(classname);
}
Object o = c.newInstance();
if (o instanceof GlyphPainter) {
defaultPainter = (GlyphPainter) o;
}
} catch (Throwable e) {
throw new StateInvariantError("GlyphView: Can't load glyph painter: "
+ classname);
}
}
setGlyphPainter(defaultPainter.getPainter(this, getStartOffset(),
getEndOffset()));
}
}
// --- TabableView methods --------------------------------------
/**
* 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. * * @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. * * @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. * * @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 */ 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. * * @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. */ 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 + alloc.height - (int) 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 {@code View.X_AXIS}.
* * @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. * * @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. * * @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. * * @param pos the position to convert >= 0 * @param a the allocated region to render into * @param b eitherPosition.Bias.Forward
* or Position.Bias.Backward
* @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.
*
* @param x the X coordinate >= 0
* @param y the Y coordinate >= 0
* @param a the allocated region to render into
* @param biasReturn either Position.Bias.Forward
* or Position.Bias.Backward
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. *
* 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.
*
* @param pos the position to convert >= 0
* @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
* @exception IllegalArgumentException for an invalid direction
*/
public int getNextVisualPositionFrom(int pos, Position.Bias b, Shape a,
int direction,
Position.Bias[] biasRet)
throws BadLocationException {
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.
*
* @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.
*
* @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.
*
* @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);
}
}
/**
* 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
*/
private byte[] selections = null;
int offset;
int length;
// if it is an implied newline character
boolean impliedCR;
boolean skipWidth;
/**
* how to expand tabs
*/
TabExpander expander;
/** Cached minimum x-span value */
private float minimumSpan = -1;
/** Cached breakpoints within the view */
private int[] breakSpots = null;
/**
* location for determining tab expansion against.
*/
int x;
/**
* Glyph rendering functionality.
*/
GlyphPainter painter;
/**
* 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 independant of a particular version
* of JVM and selection of capabilities (i.e.
* shaping for i18n, etc).
*
* @since 1.3
*/
public static abstract class GlyphPainter {
/**
* Determine the span the glyphs given a start location
* (for tab expansion).
*/
public abstract float getSpan(GlyphView v, int p0, int p1, TabExpander e, float x);
public abstract float getHeight(GlyphView v);
public abstract float getAscent(GlyphView v);
public abstract float getDescent(GlyphView v);
/**
* Paint the glyphs representing the given range.
*/
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.
*
* @param v the GlyphView
containing the
* destination coordinate space
* @param pos the position to convert
* @param bias either Position.Bias.Forward
* or Position.Bias.Backward
* @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.
*
* @param v the GlyphView
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 Position.Bias.Forward
* or Position.Bias.Backward
* 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.
*
* @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.
* @param v the GlyphView
to provide a painter for
* @param p0 the starting document offset >= 0
* @param p1 the ending document offset >= p0
*/
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.
*
* @param v the view to use
* @param pos the position to convert >= 0
* @param b either Position.Bias.Forward
* or Position.Bias.Backward
* @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 Position.Bias.Forward
* or Position.Bias.Backward
* 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
* @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;
}
}
}