22.5 Views
In our
discussion of how Swing represents styled text, we
haven't mentioned how it is actually drawn on the
screen. That's where the View
classes come in. They form most of the V part of the MVC architecture
for text components and are responsible for rendering the text.
The way this works is a tree of View objects is
created from the Document's
Element tree. (Examples of this are shown in Figure 22-11.) For
DefaultStyledDocuments in a
JTextPane, the View tree is
modeled closely on the structure of the Element
tree, with almost a one-to-one mapping from
Elements to Views. A
PlainDocument in a JTextArea is handled
more simply, with a single View object that paints
the entire Element tree.
Notice that the View trees have a root
View above what could be considered the
"natural" root. This was done to
ease the implementation of the other View classes,
which can now all assume that they have a non-null
parent in the View tree. So that each child
doesn't have to register as a
DocumentListener, the root View
also takes care of dispatching DocumentEvents to
its children. The actual type of the root View is
a package-private inner class. (You are free to create a
View tree with any root you like, so this does not
always need to be the case. The implementations of the
TextUI.getRootView( ) method that Swing provides
do return a package-private View class.)
Creation of the View tree is often handled by the
text component's TextUI, but if
its EditorKit returns a
non-null value from its getViewFactory( ) method, this task is delegated to the factory. This gives
the more complex text components (such as
JEditorPane, discussed in Chapter 23) a powerful tool for customizing their
appearance.
22.5.1 The ViewFactory Interface
Implementations of the
ViewFactory interface determine the kind of
View object that needs to be created for each
Element passed in to the factory.
We'll see more of the ViewFactory
interface in the next chapter.
22.5.1.1 Method
- public View create(Element elem)
-
Return a View for the given
Element.
22.5.2 The View Class
This abstract class is the base class
for a score of View types (see Figure 22-12) and provides basic functionality for drawing
part of a Document's content.
Before we cover the properties and methods of
View, we should note a few potentially confusing
details.
22.5.2.1 Float coordinates
The View classes follow the lead of the 2D API and
use float values to specify
x and y coordinates. It is
usually safe to convert these to ints if you
prefer.
22.5.2.2 Shape versus Rectangle
Many View methods take arguments of type
java.awt.Shape. The Shape interface is
implemented by many classes that represent geometric shapes,
including java.awt.Rectangle. Theoretically, a
View class can take advantage of nonrectangular
rendering areas, but the existing View classes
treat Shape objects as
Rectangles. You can convert a
Shape into a Rectangle
(int coordinates) by calling
someShape.getBounds( ), or you can convert a
Shape into a Rectangle2D
(float coordinates) by calling
someShape.getBounds2D( ).
22.5.2.3 Span and allocation
The term
span
refers to a distance (typically a float) in a
particular direction while
allocation
refers to the area (a Shape) in which a
View is drawn. These terms are used frequently in
the View source code and documentation, so
we'll use them too.
22.5.2.4 Axis
An
axis
value should be either X_AXIS or
Y_AXIS (int constants).
View has several indexed properties that expect
the index to be one of these two values. (The exception is the
view property. getView(n)
requests the nth child of the
View.)
22.5.2.5 Bias
Many View methods have a parameter of type
Position.Bias
(see the previous discussion). There are only two values for an
object of this type: Bias.Forward and
Bias.Backward. The idea is that the addition of a
Bias refines a Document offset
somewhat. Without a Bias, if a user clicks to an
offset of 5, then you know the click was either on the right half of
the 4th character or on the left half of the 5th. The
Bias parameter distinguishes between the two
cases.
That said, Swing's handling of
Position.Bias is spotty. Many method
implementations simply ignore the Bias parameter,
and many callers blindly pass in Bias.Forward.
Should you decide to do the same, you would be in good company.
22.5.2.6 Properties
The View class defines the properties shown in
Table 22-34.
Table 22-34. View properties
alignmenti
|
float
|
·
|
|
|
0.5
|
attributes
|
AttributeSet
|
·
|
|
|
From element
|
container
|
Container
|
·
|
|
|
From parent
|
document
|
Document
|
·
|
|
|
From element
|
element
|
Element
|
·
|
|
|
From constructor
|
endOffset
|
int
|
·
|
|
|
From element
|
graphics1.3
|
Graphics
|
·
|
|
|
|
maximumSpani
|
float
|
·
|
|
|
(resizeWeight<=0 ? preferredSpan :
Integer.MAX_VALUE)
|
minimumSpani
|
float
|
·
|
|
|
(resizeWeight<=0 ? preferredSpan : 0)
|
parent
|
View
|
·
|
|
·
|
null
|
preferredSpani
|
float
|
·
|
|
|
Abstract
|
resizeWeighti
|
int
|
·
|
|
|
0
|
startOffset
|
int
|
·
|
|
|
From element
|
viewi
|
View
|
·
|
|
|
null
|
viewCount
|
int
|
·
|
|
|
0
|
viewFactory
|
ViewFactory
|
·
|
|
|
From parent
|
visible
|
boolean
|
|
·
|
|
true
|
iindexed, 1.3since 1.3
|
The properties are defined as follows:
- attributes
-
This property defines the AttributeSet used by the
View to render an Element. By
default, the Element's attributes
are used. However, View objects should access the
attributes through the getAttributes( ) accessor
to allow View-specific attributes to be added to
the Element's attributes or to
allow the attributes to be converted in some way by the
View subclasses.
- container
-
This property is the Container ultimately
responsible for this View. This
View uses the
container's coordinate system as
its own, without performing any translation for child
Views. We refer to this coordinate system
as
view coordinates.
- element
-
This property is the Element this
View is responsible for rendering. The
document,
startOffset
, and endOffset
properties are taken directly from the element
property.
- viewCount
-
This property defines the number of children this
View has, and the indexed view
property gives access to specific children. Typically, only
subclasses of CompositeView (plus root
Views) have any children.
- viewFactory
-
This property is the factory that was used to create this
View and can be used to create child
View objects.
- graphics
-
This property is a java.awt.Graphics object that
can be used to determine font characteristics. (The paint( ) method is passed a Graphics object for
drawing, so it doesn't need to use this one.)
- visible
-
This property reflects whether the View should be
drawn on the screen..
- alignment
-
This property specifies the desired alignment for the
View for each axis. 0 indicates
origin alignment, 1 indicates alignment to the
full span away from the origin, and 0.5 (the
default) indicates centered alignment.
The other four properties help determine the minimum, maximum, and
preferred size of the component using the View.
preferredSpan reflects the
View's preferred size along a
particular axis.
resizeWeight is
used to determine the minimum and maximum size of the
View. A value of 0 (or less)
indicates that the view should not be resized. The
minimumSpan and
maximumSpan of a View with a
resizeWeight of 0 are equal to
the preferredSpan. Otherwise (if
resizeWeight > 0), the
minimumSpan is considered to be
0 while the maximumSpan is
Integer.MAX_VALUE.
22.5.2.7 Constructor
- public View(Element elem)
-
Instantiate a View for the given
Element. Since the View class
is abstract, this is called only from the subclass constructors.
22.5.2.8 Constants
View defines the constants shown in Table 22-35. The first four are used in the
getBreakWeight( ) method, which is described
later. The other two apply to the three properties that are indexed
based on an axis.
Table 22-35. View constants
BadBreakWeight
|
int
|
The View should not be broken into fragments for
formatting purposes.
|
ExcellentBreakWeight
|
int
|
The View supports splitting, and this is a very
good place to break.
|
ForcedBreakWeight
|
int
|
The View supports splitting, and it must be broken
here to be displayed properly.
|
GoodBreakWeight
|
int
|
The View supports splitting, but there is probably
a better break location.
|
X_AXIS
|
int
|
Used to specify the x-axis (another name for
SwingConstants.HORIZONTAL).
|
Y_AXIS
|
int
|
Used to specify the y-axis (another name for
SwingConstants.VERTICAL).
|
22.5.2.9 Abstract methods
These three key methods must be implemented by all concrete
subclasses of View:
- public abstract void paint(Graphics g, Shape allocation)
-
Lay out and paint the View within the bounds of
the given Shape with the given
Graphics. The allocation may be different than the
last time the View was painted, so the
View must be prepared to reorganize its layout
appropriately. For efficiency, there is usually no clipping region
set on the Graphics object. It's
the View's responsibility to stay
within the allocation (or explicitly call
g.setClip(allocation)). Also, you
can't be sure that the Graphics
object has any other settings (foreground color, background color,
etc.).
- public abstract Shape modelToView(int offset, Shape allocation, Position.Bias b) throws BadLocationException
-
Convert from a Document offset to view
coordinates. The return value should be a small
Rectangle (or another Shape)
that designates the region in which the character at that offset is
drawn. Be prepared for the Position.Bias argument
to be null, in which case it should be treated as
Position.Bias.Forward.
- public abstract int viewToModel(float x, float y, Shape allocation, Position.Bias biasReturn[])
-
Convert from view coordinates to a Document
offset. The return value should be the Document
offset closest to point (x, y).
You should set the value of biasReturn[0] to
Bias.Forward or Bias.Backward
to indicate whether the given point is closer to the next character
or the previous one. (If you're less scrupulous and
always set it to Bias.Forward, you
won't be alone, but set it correctly if you can.)
In the method descriptions that follow, we describe the functions of
the methods for a generic subclass of View. These
functions are not what the methods usually do in the
View class itself. The View
class does provide implementations, but they are often empty or
minimal (i.e., they always return null or always
return this).
22.5.2.10 Translation methods
These
methods
(with the addition of two of the above abstract methods) translate
between Document offsets and view coordinates.
Views use the same coordinate system as the text
component in which they are drawn.
- public Shape modelToView(int p0, Position.Bias b0,int p1, Position.Bias b1, Shape allocation) throws BadLocationException
-
Like the abstract modelToView( ) method, but
returns a Shape that binds a range of characters
instead of a single character. (p0 and
p1 are Document offsets.)
- public int getNextVisualPositionFrom(int offset, PositionBias bias, Shape a, int direction, Position.Bias[] biasReturn) throws BadLocationException
-
This method determines where the caret goes when the user presses one
of the arrow keys. The direction parameter is one of
SwingConstants.NORTH, SOUTH,
EAST, or WEST. The input and
output are both Document offsets, so handling
EAST and WEST is easy.
NORTH and SOUTH might need to
be converted into view coordinates and then reconverted. The
View class has a good implementation that tries to
respect magicCaretPosition (if any) and calls
Utilities.getPostionAbove( ) and
getPositionBelow( ) for NORTH
and SOUTH. You should set the value of
biasReturn[0] to Bias.Forward
or Bias.Backward as appropriate.
- public int getViewIndex(int offset, Shape allocation) throws BadLocationException
-
Return the index of the child View that
corresponds to the given Document offset, or
-1 if no such child exists. This method was
introduced in SDK 1.3.
- public int getViewIndex(float x, float y, Shape allocation) throws BadLocationException
-
Return the index of the child View that
corresponds to the given point, or -1 if no such
child exists. This method was introduced in SDK 1.4.
- public String getToolTipText(float x, float y, Shape allocation)
-
Return the tooltip for the given point, or null if
there is none. This method was introduced in SDK 1.4 to support the
display of ALT text from HTML IMG tags in
JEditorPane.
22.5.2.11 Break methods
-
public View breakView(int axis, int offset, float pos, float length)
-
Return a new View for painting part of this
View's content. The new
View should start at the given
offset into the Document, which
can be anywhere in the range defined by the
View's
startOffset and endOffset
properties. Ideally, the new View should have a
span of length along the given
axis. The pos argument
designates the starting position (along the same axis) for the new
View's allocation. Most
Views ignore this argument except for things such
as calculating the positions of tab stops. Views
that support breaking typically implement this by calling the
createFragment( ) method after determining an
offset for the far side of the break. Views that
don't support breaking return themselves unbroken
(via return this).
Suppose we're trying to draw lines of text that are
400 pixels wide and we've already drawn 280 pixels
worth of text on the current line. We could call
breakView(X_AXIS, offset,
280, 120) in an attempt to fill
out the line. The View returned may or may not
have the horizontal span we asked for.
- public int getBreakWeight(int axis, float pos, float length)
-
Return a "score" indicating how
appropriate it would be to break this View into a
piece with the given length along the given
axis (from the start of the
View, i.e., from the Document
offset designated by its startOffset property).
The return value should be one of the constants from Table 22-35. The pos argument
designates the starting position (along the same axis) of the
View's allocation. Most
Views ignore this argument except for things such
as calculating the positions of tab stops.
- public View createFragment(int p0, int p1)
-
Return a new View for painting part of this
View's content. The new
View should cover the Document
from offset p0 to offset p1,
which are in the range defined by the
View's
startOffset and endOffset
properties. Views that don't
support breaking return themselves unbroken (via return
this).
22.5.2.12 Tree management methods
These
methods
(introduced in SDK 1.3) allow the structure of the
View tree to be modified. In
Views that don't support
children, these methods do nothing.
- public void insert(int index, View v)
-
Insert a child View at the given index.
- public void append(View v)
-
Add a child View to the end; equivalent to
insert(getViewCount( ), v).
- public void remove(int index)
-
Remove the child View at the given
index.
- public void removeAll( )
-
Remove all of this View's
children.
- public void replace(int index, int length, View[] views)
-
Replace some child Views (starting at
index) with some other Views.
The length argument may be 0,
resulting in a pure insertion. The views array may
be null, resulting in a pure deletion.
22.5.2.13 Layout methods
The
way
that Views are laid out is based on how
Components are laid out, except that each axis is
handled separately. Both View and
Component have preferredSize,
minimumSize, and maximumSize
properties. View's
setSize( ) method resembles a combination of
Component's doLayout( ) and setSize( ) methods.
View's
preferenceChanged( ) method is analogous to
invalidate( ) in Component.
- public void setSize(float width, float height)
-
Set the size of the View and lay out any children.
This method may be called with the same arguments as the previous
call to ensure that the layout is up-to-date.
- public void preferenceChanged(View child, boolean width, boolean height)
-
A child View calls this method to indicate to its
parent that its preferredSpan has changed,
identifying itself and the axis (one or both) of the change. Any
cached size information for the child should be discarded, and
preferenceChanged( ) should be called on this
View's parent (grandparent of the
child). A preferenceChanged( ) on the root
View triggers a revalidate( )
on the text component.
- public Shape getChildAllocation(int index, Shape a)
-
The parameters are the Shape that is the
allocation for this View (the parent) and a child
index, and the Shape returned
is the allocation for the child. (The child's
allocation should be entirely inside the parent's.)
This is an important method because many of the
View methods require an allocation argument. So
you need the value returned by this method to call them.
22.5.2.14 Update methods
If
every
View object registers as a
DocumentListener, there is a flurry of redundant
activity each time the document changes. To ameliorate this, only the
root View registers itself and uses these methods
to forward DocumentEvents down the tree. A child
View is notified only if the event applies to that
child.
- public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f )
- public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f )
- public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f )
-
These methods have the same names and purposes as those defined in
the DocumentListener interface, but have extra
arguments to pass the View its current allocation
and a ViewFactory that can be used to rebuild its
children if necessary.
22.5.3 The View Classes
Swing provides an almost overwhelming number of
View classes. Figure 22-12 shows
them and the relationships between them. (Note that the
View types used by JTextArea
and JTextPane are depicted in Figure 22-11.)
It's not worth discussing every
View class in detail, but here's
a brief description of each:
- PlainView
-
Displays one or more nonwrapped lines of text in a single font and
color.
- FieldView
-
A subclass of PlainView used by
JTextField. It knows how to perform alignment on a
single line of text.
- PasswordView
-
A subclass of FieldView used by
JPasswordField.
- GlyphView
-
Displays a block of styled text, but all in the same style (such as
the text represented by a leaf in the Element
tree). This View supports breaking, so the text
may wrap across several lines.
- LabelView
-
A subclass of GlyphView that
doesn't behave any differently except that it caches
character attributes to speed up painting. (It has nothing to do with
JLabel.)
- ComponentView
-
Knows how to display embedded Components.
- IconView
-
Knows how to display embedded Icons.
- CompositeView
-
An abstract class customized to handle children. There is no
requirement that all Views that use child
Views must extend this class, but they do in
practice.
- BoxView
-
A subclass of CompositeView that tiles its child
Views along an axis (as when paragraphs are tiled
down a page, for example).
- WrappedPlainView
-
A subclass of BoxView that displays one or more
paragraphs of text in a single font and color. Each paragraph is
managed by a child View (an instance of a
package-private inner class) and is wrapped if necessary.
- FlowView
-
An abstract subclass of BoxView that defines a
"flow strategy" for laying out
children amid constraints.
- ParagraphView
-
An subclass of FlowView that knows how to display
an indented paragraph of text that may have a mix of styles.
- ZoneView
-
A subclass of BoxView that attempts to be
memory-efficient by deferring the creation of child
Views until they are needed. This can be useful
for displaying (parts of) very large Element
trees, but this class is unused in Swing.
- AsyncBoxView
-
Behaves like BoxView but performs layout on a
low-priority thread in an attempt to free up the GUI thread for other
things. The thread and pending layout tasks are managed by the
LayoutQueue class.
- TableView
-
An abstract class in which each child View
represents a row, and each grandchild View
represents a cell.
In addition to these, there are several View
classes in the javax.swing.text.html package that
are used by JEditorPane to display HTML. These are
listed in Table 22-36.
(JEditorPane is the topic of the next chapter.)
Table 22-36. View classes in the javax.swing.text.html package
BlockView
|
<blockquote>,
<pre>, <center>,
<div>, <li>,
<dl>, <dd>, parent
View for others
|
FormView
|
<input>, <select>,
<textarea> (HTML forms)
|
ImageView
|
<img>
|
InlineView
|
Inline text
|
ListView
|
<ul>, <ol>,
<dir>, <menu>
|
ObjectView
|
Component specified in <object> tag
|
ParagraphView
|
<p> (including implied
<p> tags), <dt>,
<h1>, <h2>,
<h3>, <h4>,
<h5>, <h6>
|
As of SDK 1.4, ObjectView supports an interesting
feature. If you include a tag like this:
<object classid='javax.swing.JButton'>
<param name='text' value='push me'>
<param name='toolTipText' value='go ahead and push me...'>
</object>
in your HTML, ObjectView instantiates the
Object and (if it is a
Component, including
JComponent) renders it with the rest of the HTML.
This works for any Object type, but (at least for
now) the <param> tag works only for
properties of type String.
22.5.4 The TabExpander Interface
TabExpander is a
simple interface implemented by View classes that
know how to expand tabs.
22.5.4.1 Method
- public abstract float nextTabStop( float x, int tabOffset)
-
Return the next tab stop position after the specified position.
Values are expressed in points. For example, a
View with tab stops every 80 points, starting at
0, would return 240.0 for an input of 227.0. The second parameter is
the Document offset of the tab, which can be used
to look up the kind of tab when multiple tab types are supported.
22.5.5 The TabableView Interface
This
interface is implemented by
View classes that need to take tab settings into
account when calculating their preferredSpan. In
general, a View instance that implements this
interface functions correctly with respect to tabs only if its parent
View implements the TabExpander
interface.
22.5.5.1 Methods
- public abstract float getTabbedSpan( float x, TabExpander e)
-
Return a value indicating the span of the View,
starting at the specified point and using the supplied
TabExpander to expand tabs.
- public abstract float getPartialSpan(int p0, int p1)
-
Return a value indicating the span needed to cover the range
delimited by the specified document offsets. Implementations may
assume that there are no tabs in the given range.
22.5.6 The Utilities Class
This class defines a number of static
methods used by the text package. Some of these methods are key to
the View classes. Many of the method signatures
clash with the rest of the Swing text API. For example, parameters in
the view coordinate system are declared to be
ints, not floats.
22.5.6.1 Public static methods
- public static final int getWordStart(JTextComponent c, int offset) throws BadLocationException
- public static final int getWordEnd(JTextComponent c, int offset) throws BadLocationException
-
Return the Document offset at the beginning (or
one position past the end) of the word at the specified
Document offset.
- public static final int getPreviousWord(JTextComponent c, int offset) throws BadLocationException
- public static final int getNextWord(JTextComponent c, int offset) throws BadLocationException
-
Return the Document offset at the beginning of the
word that precedes or follows the specified offset.
- public static final int getRowStart(JTextComponent c, int offset) throws BadLocationException
- public static final int getRowEnd(JTextComponent c, int offset) throws BadLocationException
-
Return the Document offset corresponding to the
start or end of the row containing the specified offset.
- public static final int getPositionAbove(JTextComponent c, int offset, int x) throws BadLocationException
- public static int getPositionBelow(JTextComponent c, int offset, int x) throws BadLocationException
-
Return the document position closest to the given
x view coordinate in the row above or below the
row containing the specified offset.
- public static final Element getParagraphElement(JTextComponent c, int offset)
-
Return the paragraph Element corresponding to the
specified Document offset. If the
component's model is a
StyledDocument, it delegates to the
model's getParagraphElement( )
method. Otherwise, the Document's
defaultRootElement is queried for the child
containing the given offset.
- public static final int getBreakLocation(Segment s, FontMetrics metrics, int x0, int x, TabExpander e, int startOffset)
-
Try to find a suitable line-break location by looking for whitespace
in the supplied Segment. x0 and
x (both in view coordinates) are the start and
intended end of the span. The return value is a
Document offset. The
TabExpander may be null, but
then tabs are counted as spaces.
- public static final int drawTabbedText(Segment s, int x, int y, Graphics g, TabExpander e, int startOffset)
-
Draw the Segment's text, starting
at startOffset, to the supplied
Graphics at the specified point
(x, y) in view coordinates. The
TabExpander may be null, but
then tabs are counted as spaces. The return value is the x-coordinate
at the end of the drawn text.
- public static final int getTabbedTextWidth(Segment s, FontMetrics metrics, int x, TabExpander e, int startOffset)
-
Return the width (in view coordinates) of the
Segment's text, starting at
startOffset, with tabs expanded. The
x argument is the starting position of the text in
view coordinates. The TabExpander may be
null, but then tabs are counted as spaces.
- public static final int getTabbedTextOffset(Segment s, FontMetrics metrics, int x0, int x, TabExpander e, int startOffset)
-
Return the offset into the given Segment
corresponding to the view coordinate x. The
x0 coordinate specifies the
View location of the start of the text. The
TabExpander may be null, but
then tabs are counted as spaces.
- public static final int getTabbedTextOffset(Segment s, FontMetrics metrics, int x0, int x, TabExpander e, int startOffset, boolean round)
-
This is the same as the previous method if round
is true. If round is
false, when x falls between two
characters, it returns the offset of the left one even if the right
one is
closer.
|