2 Copyright (C) 2004, 2005, 2006 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
39 package javax
.swing
.text
;
41 import java
.awt
.Color
;
42 import java
.awt
.Component
;
44 import java
.awt
.FontMetrics
;
45 import java
.awt
.Graphics
;
46 import java
.awt
.Rectangle
;
47 import java
.awt
.Shape
;
49 import javax
.swing
.SwingUtilities
;
50 import javax
.swing
.event
.DocumentEvent
;
51 import javax
.swing
.event
.DocumentEvent
.ElementChange
;
53 public class PlainView
extends View
implements TabExpander
56 Color unselectedColor
;
59 * The color that is used to draw disabled text fields.
65 /** The length of the longest line in the Document **/
66 float maxLineLength
= -1;
68 /** The longest line in the Document **/
69 Element longestLine
= null;
71 protected FontMetrics metrics
;
74 * The instance returned by {@link #getLineBuffer()}.
76 private transient Segment lineBuffer
;
78 public PlainView(Element elem
)
86 protected void updateMetrics()
88 Component component
= getContainer();
89 Font font
= component
.getFont();
91 if (this.font
!= font
)
94 metrics
= component
.getFontMetrics(font
);
101 protected Rectangle
lineToRect(Shape a
, int line
)
103 // Ensure metrics are up-to-date.
106 Rectangle rect
= a
.getBounds();
107 int fontHeight
= metrics
.getHeight();
108 return new Rectangle(rect
.x
, rect
.y
+ (line
* fontHeight
),
109 rect
.width
, fontHeight
);
112 public Shape
modelToView(int position
, Shape a
, Position
.Bias b
)
113 throws BadLocationException
115 // Ensure metrics are up-to-date.
118 Document document
= getDocument();
120 // Get rectangle of the line containing position.
121 int lineIndex
= getElement().getElementIndex(position
);
122 Rectangle rect
= lineToRect(a
, lineIndex
);
124 // Get the rectangle for position.
125 Element line
= getElement().getElement(lineIndex
);
126 int lineStart
= line
.getStartOffset();
127 Segment segment
= getLineBuffer();
128 document
.getText(lineStart
, position
- lineStart
, segment
);
129 int xoffset
= Utilities
.getTabbedTextWidth(segment
, metrics
, rect
.x
,
132 // Calc the real rectangle.
135 rect
.height
= metrics
.getHeight();
141 * Draws a line of text. The X and Y coordinates specify the start of
142 * the <em>baseline</em> of the line.
144 * @param lineIndex the index of the line
145 * @param g the graphics to use for drawing the text
146 * @param x the X coordinate of the baseline
147 * @param y the Y coordinate of the baseline
149 protected void drawLine(int lineIndex
, Graphics g
, int x
, int y
)
153 metrics
= g
.getFontMetrics();
154 // FIXME: Selected text are not drawn yet.
155 Element line
= getElement().getElement(lineIndex
);
156 drawUnselectedText(g
, x
, y
, line
.getStartOffset(),
157 line
.getEndOffset() - 1);
158 //drawSelectedText(g, , , , );
160 catch (BadLocationException e
)
162 AssertionError ae
= new AssertionError("Unexpected bad location");
168 protected int drawSelectedText(Graphics g
, int x
, int y
, int p0
, int p1
)
169 throws BadLocationException
171 g
.setColor(selectedColor
);
172 Segment segment
= getLineBuffer();
173 getDocument().getText(p0
, p1
- p0
, segment
);
174 return Utilities
.drawTabbedText(segment
, x
, y
, g
, this, 0);
178 * Draws a chunk of unselected text.
180 * @param g the graphics to use for drawing the text
181 * @param x the X coordinate of the baseline
182 * @param y the Y coordinate of the baseline
183 * @param p0 the start position in the text model
184 * @param p1 the end position in the text model
186 * @return the X location of the end of the range
188 * @throws BadLocationException if <code>p0</code> or <code>p1</code> are
191 protected int drawUnselectedText(Graphics g
, int x
, int y
, int p0
, int p1
)
192 throws BadLocationException
194 JTextComponent textComponent
= (JTextComponent
) getContainer();
195 if (textComponent
.isEnabled())
196 g
.setColor(unselectedColor
);
198 g
.setColor(disabledColor
);
200 Segment segment
= getLineBuffer();
201 getDocument().getText(p0
, p1
- p0
, segment
);
202 return Utilities
.drawTabbedText(segment
, x
, y
, g
, this, segment
.offset
);
205 public void paint(Graphics g
, Shape s
)
207 // Ensure metrics are up-to-date.
210 JTextComponent textComponent
= (JTextComponent
) getContainer();
212 selectedColor
= textComponent
.getSelectedTextColor();
213 unselectedColor
= textComponent
.getForeground();
214 disabledColor
= textComponent
.getDisabledTextColor();
216 Rectangle rect
= s
.getBounds();
218 // FIXME: Text may be scrolled.
219 Document document
= textComponent
.getDocument();
220 Element root
= document
.getDefaultRootElement();
221 int y
= rect
.y
+ metrics
.getAscent();
223 for (int i
= 0; i
< root
.getElementCount(); i
++)
225 drawLine(i
, g
, rect
.x
, y
);
226 y
+= metrics
.getHeight();
231 * Returns the tab size of a tab. Checks the Document's
232 * properties for PlainDocument.tabSizeAttribute and returns it if it is
233 * defined, otherwise returns 8.
235 * @return the tab size.
237 protected int getTabSize()
239 Object tabSize
= getDocument().getProperty(PlainDocument
.tabSizeAttribute
);
242 return ((Integer
)tabSize
).intValue();
246 * Returns the next tab stop position after a given reference position.
248 * This implementation ignores the <code>tabStop</code> argument.
250 * @param x the current x position in pixels
251 * @param tabStop the position within the text stream that the tab occured at
253 public float nextTabStop(float x
, int tabStop
)
255 float tabSizePixels
= getTabSize() * metrics
.charWidth('m');
256 return (float) (Math
.floor(x
/ tabSizePixels
) + 1) * tabSizePixels
;
260 * Returns the length of the longest line, used for getting the span
261 * @return the length of the longest line
263 float determineMaxLineLength()
265 // if the longest line is cached, return the cached value
266 if (maxLineLength
!= -1)
267 return maxLineLength
;
269 // otherwise we have to go through all the lines and find it
270 Element el
= getElement();
271 Segment seg
= getLineBuffer();
273 for (int i
= 0; i
< el
.getElementCount(); i
++)
275 Element child
= el
.getElement(i
);
276 int start
= child
.getStartOffset();
277 int end
= child
.getEndOffset();
280 el
.getDocument().getText(start
, end
- start
, seg
);
282 catch (BadLocationException ex
)
284 AssertionError ae
= new AssertionError("Unexpected bad location");
289 if (seg
== null || seg
.array
== null || seg
.count
== 0)
292 int width
= metrics
.charsWidth(seg
.array
, seg
.offset
, seg
.count
);
299 maxLineLength
= span
;
300 return maxLineLength
;
303 public float getPreferredSpan(int axis
)
305 if (axis
!= X_AXIS
&& axis
!= Y_AXIS
)
306 throw new IllegalArgumentException();
308 // make sure we have the metrics
311 Element el
= getElement();
317 span
= determineMaxLineLength();
321 span
= metrics
.getHeight() * el
.getElementCount();
329 * Maps coordinates from the <code>View</code>'s space into a position
330 * in the document model.
332 * @param x the x coordinate in the view space
333 * @param y the y coordinate in the view space
334 * @param a the allocation of this <code>View</code>
335 * @param b the bias to use
337 * @return the position in the document that corresponds to the screen
338 * coordinates <code>x, y</code>
340 public int viewToModel(float x
, float y
, Shape a
, Position
.Bias
[] b
)
342 Rectangle rec
= a
.getBounds();
343 Document doc
= getDocument();
344 Element root
= doc
.getDefaultRootElement();
346 // PlainView doesn't support line-wrapping so we can find out which
347 // Element was clicked on just by the y-position.
348 // Since the coordinates may be outside of the coordinate space
349 // of the allocation area (e.g. user dragged mouse outside
350 // the component) we have to limit the values.
351 // This has the nice effect that the user can drag the
352 // mouse above or below the component and it will still
353 // react to the x values (e.g. when selecting).
355 = Math
.min(Math
.max((int) (y
- rec
.y
) / metrics
.getHeight(), 0),
356 root
.getElementCount() - 1);
358 Element line
= root
.getElement(lineClicked
);
360 Segment s
= getLineBuffer();
361 int start
= line
.getStartOffset();
362 // We don't want the \n at the end of the line.
363 int end
= line
.getEndOffset() - 1;
366 doc
.getText(start
, end
- start
, s
);
368 catch (BadLocationException ble
)
370 AssertionError ae
= new AssertionError("Unexpected bad location");
375 int pos
= Utilities
.getTabbedTextOffset(s
, metrics
, rec
.x
, (int)x
, this, start
);
376 return Math
.max (0, pos
);
380 * Since insertUpdate and removeUpdate each deal with children
381 * Elements being both added and removed, they both have to perform
382 * the same checks. So they both simply call this method.
383 * @param changes the DocumentEvent for the changes to the Document.
384 * @param a the allocation of the View.
385 * @param f the ViewFactory to use for rebuilding.
387 protected void updateDamage(DocumentEvent changes
, Shape a
, ViewFactory f
)
389 float oldMaxLineLength
= maxLineLength
;
390 Rectangle alloc
= a
.getBounds();
391 Element el
= getElement();
392 ElementChange ec
= changes
.getChange(el
);
394 // If ec is null then no lines were added or removed, just
395 // repaint the changed line
398 int line
= el
.getElementIndex(changes
.getOffset());
400 // If characters have been removed from the current longest line
401 // we have to find out which one is the longest now otherwise
402 // the preferred x-axis span will not shrink.
403 if (changes
.getType() == DocumentEvent
.EventType
.REMOVE
404 && el
.getElement(line
) == longestLine
)
407 if (determineMaxLineLength() != alloc
.width
)
408 preferenceChanged(this, true, false);
411 damageLineRange(line
, line
, a
, getContainer());
415 Element
[] removed
= ec
.getChildrenRemoved();
416 Element
[] newElements
= ec
.getChildrenAdded();
418 // If no Elements were added or removed, we just want to repaint
419 // the area containing the line that was modified
420 if (removed
== null && newElements
== null)
422 int line
= getElement().getElementIndex(changes
.getOffset());
424 damageLineRange(line
, line
, a
, getContainer());
428 // Check to see if we removed the longest line, if so we have to
429 // search through all lines and find the longest one again.
432 for (int i
= 0; i
< removed
.length
; i
++)
433 if (removed
[i
].equals(longestLine
))
435 // reset maxLineLength and search through all lines for longest one
437 if (determineMaxLineLength() != alloc
.width
)
438 preferenceChanged(this, true, removed
.length
!= newElements
.length
);
440 ((JTextComponent
)getContainer()).repaint();
446 // If we've reached here, that means we haven't removed the longest line
447 if (newElements
== null)
449 // No lines were added, just repaint the container and exit
450 ((JTextComponent
)getContainer()).repaint();
455 // Make sure we have the metrics
458 // If we've reached here, that means we haven't removed the longest line
459 // and we have added at least one line, so we have to check if added lines
460 // are longer than the previous longest line
461 Segment seg
= getLineBuffer();
462 float longestNewLength
= 0;
463 Element longestNewLine
= null;
465 // Loop through the added lines to check their length
466 for (int i
= 0; i
< newElements
.length
; i
++)
468 Element child
= newElements
[i
];
469 int start
= child
.getStartOffset();
470 int end
= child
.getEndOffset();
473 el
.getDocument().getText(start
, end
- start
, seg
);
475 catch (BadLocationException ex
)
477 AssertionError ae
= new AssertionError("Unexpected bad location");
482 if (seg
== null || seg
.array
== null || seg
.count
== 0)
485 int width
= metrics
.charsWidth(seg
.array
, seg
.offset
, seg
.count
);
486 if (width
> longestNewLength
)
488 longestNewLine
= child
;
489 longestNewLength
= width
;
493 // Check if the longest of the new lines is longer than our previous
494 // longest line, and if so update our values
495 if (longestNewLength
> maxLineLength
)
497 maxLineLength
= longestNewLength
;
498 longestLine
= longestNewLine
;
501 // Report any changes to the preferred sizes of the view
502 // which may cause the underlying component to be revalidated.
503 boolean widthChanged
= oldMaxLineLength
!= maxLineLength
;
504 boolean heightChanged
= removed
.length
!= newElements
.length
;
505 if (widthChanged
|| heightChanged
)
506 preferenceChanged(this, widthChanged
, heightChanged
);
508 // Repaint the container
509 ((JTextComponent
)getContainer()).repaint();
513 * This method is called when something is inserted into the Document
514 * that this View is displaying.
516 * @param changes the DocumentEvent for the changes.
517 * @param a the allocation of the View
518 * @param f the ViewFactory used to rebuild
520 public void insertUpdate(DocumentEvent changes
, Shape a
, ViewFactory f
)
522 updateDamage(changes
, a
, f
);
526 * This method is called when something is removed from the Document
527 * that this View is displaying.
529 * @param changes the DocumentEvent for the changes.
530 * @param a the allocation of the View
531 * @param f the ViewFactory used to rebuild
533 public void removeUpdate(DocumentEvent changes
, Shape a
, ViewFactory f
)
535 updateDamage(changes
, a
, f
);
539 * This method is called when attributes were changed in the
540 * Document in a location that this view is responsible for.
542 public void changedUpdate (DocumentEvent changes
, Shape a
, ViewFactory f
)
544 updateDamage(changes
, a
, f
);
548 * Repaint the given line range. This is called from insertUpdate,
549 * changedUpdate, and removeUpdate when no new lines were added
550 * and no lines were removed, to repaint the line that was
553 * @param line0 the start of the range
554 * @param line1 the end of the range
555 * @param a the rendering region of the host
556 * @param host the Component that uses this View (used to call repaint
561 protected void damageLineRange (int line0
, int line1
, Shape a
, Component host
)
566 Rectangle rec0
= lineToRect(a
, line0
);
567 Rectangle rec1
= lineToRect(a
, line1
);
569 if (rec0
== null || rec1
== null)
570 // something went wrong, repaint the entire host to be safe
574 Rectangle repaintRec
= SwingUtilities
.computeUnion(rec0
.x
, rec0
.y
,
577 host
.repaint(repaintRec
.x
, repaintRec
.y
, repaintRec
.width
,
583 * Provides a {@link Segment} object, that can be used to fetch text from
586 * @returna {@link Segment} object, that can be used to fetch text from
589 protected Segment
getLineBuffer()
591 if (lineBuffer
== null)
592 lineBuffer
= new Segment();