summaryrefslogtreecommitdiff
path: root/libjava/classpath/javax/swing/text/PlainView.java
diff options
context:
space:
mode:
Diffstat (limited to 'libjava/classpath/javax/swing/text/PlainView.java')
-rw-r--r--libjava/classpath/javax/swing/text/PlainView.java724
1 files changed, 724 insertions, 0 deletions
diff --git a/libjava/classpath/javax/swing/text/PlainView.java b/libjava/classpath/javax/swing/text/PlainView.java
new file mode 100644
index 000000000..16112fdb1
--- /dev/null
+++ b/libjava/classpath/javax/swing/text/PlainView.java
@@ -0,0 +1,724 @@
+/* PlainView.java --
+ Copyright (C) 2004, 2005, 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath 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 for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing.text;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Rectangle;
+import java.awt.Shape;
+
+import javax.swing.SwingUtilities;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentEvent.ElementChange;
+
+public class PlainView extends View implements TabExpander
+{
+ Color selectedColor;
+ Color unselectedColor;
+
+ /**
+ * The color that is used to draw disabled text fields.
+ */
+ Color disabledColor;
+
+ /**
+ * While painting this is the textcomponent's current start index
+ * of the selection.
+ */
+ int selectionStart;
+
+ /**
+ * While painting this is the textcomponent's current end index
+ * of the selection.
+ */
+ int selectionEnd;
+
+ Font font;
+
+ /** The length of the longest line in the Document **/
+ float maxLineLength = -1;
+
+ /** The longest line in the Document **/
+ Element longestLine = null;
+
+ protected FontMetrics metrics;
+
+ /**
+ * The instance returned by {@link #getLineBuffer()}.
+ */
+ private transient Segment lineBuffer;
+
+ /**
+ * The base offset for tab calculations.
+ */
+ private int tabBase;
+
+ /**
+ * The tab size.
+ */
+ private int tabSize;
+
+ public PlainView(Element elem)
+ {
+ super(elem);
+ }
+
+ /**
+ * @since 1.4
+ */
+ protected void updateMetrics()
+ {
+ Component component = getContainer();
+ Font font = component.getFont();
+
+ if (this.font != font)
+ {
+ this.font = font;
+ metrics = component.getFontMetrics(font);
+ tabSize = getTabSize() * metrics.charWidth('m');
+ }
+ }
+
+ /**
+ * @since 1.4
+ */
+ protected Rectangle lineToRect(Shape a, int line)
+ {
+ // Ensure metrics are up-to-date.
+ updateMetrics();
+
+ Rectangle rect = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
+ int fontHeight = metrics.getHeight();
+ return new Rectangle(rect.x, rect.y + (line * fontHeight),
+ rect.width, fontHeight);
+ }
+
+ public Shape modelToView(int position, Shape a, Position.Bias b)
+ throws BadLocationException
+ {
+ // Ensure metrics are up-to-date.
+ updateMetrics();
+
+ Document document = getDocument();
+
+ // Get rectangle of the line containing position.
+ int lineIndex = getElement().getElementIndex(position);
+ Rectangle rect = lineToRect(a, lineIndex);
+ tabBase = rect.x;
+
+ // Get the rectangle for position.
+ Element line = getElement().getElement(lineIndex);
+ int lineStart = line.getStartOffset();
+ Segment segment = getLineBuffer();
+ document.getText(lineStart, position - lineStart, segment);
+ int xoffset = Utilities.getTabbedTextWidth(segment, metrics, tabBase,
+ this, lineStart);
+
+ // Calc the real rectangle.
+ rect.x += xoffset;
+ rect.width = 1;
+ rect.height = metrics.getHeight();
+
+ return rect;
+ }
+
+ /**
+ * Draws a line of text. The X and Y coordinates specify the start of
+ * the <em>baseline</em> of the line.
+ *
+ * @param lineIndex the index of the line
+ * @param g the graphics to use for drawing the text
+ * @param x the X coordinate of the baseline
+ * @param y the Y coordinate of the baseline
+ */
+ protected void drawLine(int lineIndex, Graphics g, int x, int y)
+ {
+ try
+ {
+ Element line = getElement().getElement(lineIndex);
+ int startOffset = line.getStartOffset();
+ int endOffset = line.getEndOffset() - 1;
+
+ if (selectionStart <= startOffset)
+ // Selection starts before the line ...
+ if (selectionEnd <= startOffset)
+ {
+ // end ends before the line: Draw completely unselected text.
+ drawUnselectedText(g, x, y, startOffset, endOffset);
+ }
+ else if (selectionEnd <= endOffset)
+ {
+ // and ends within the line: First part is selected,
+ // second is not.
+ x = drawSelectedText(g, x, y, startOffset, selectionEnd);
+ drawUnselectedText(g, x, y, selectionEnd, endOffset);
+ }
+ else
+ // and ends behind the line: Draw completely selected text.
+ drawSelectedText(g, x, y, startOffset, endOffset);
+ else if (selectionStart < endOffset)
+ // Selection starts within the line ..
+ if (selectionEnd < endOffset)
+ {
+ // and ends within it: First part unselected, second part
+ // selected, third part unselected.
+ x = drawUnselectedText(g, x, y, startOffset, selectionStart);
+ x = drawSelectedText(g, x, y, selectionStart, selectionEnd);
+ drawUnselectedText(g, x, y, selectionEnd, endOffset);
+ }
+ else
+ {
+ // and ends behind the line: First part unselected, second
+ // part selected.
+ x = drawUnselectedText(g, x, y, startOffset, selectionStart);
+ drawSelectedText(g, x, y, selectionStart, endOffset);
+ }
+ else
+ // Selection is behind this line: Draw completely unselected text.
+ drawUnselectedText(g, x, y, startOffset, endOffset);
+ }
+ catch (BadLocationException e)
+ {
+ AssertionError ae = new AssertionError("Unexpected bad location");
+ ae.initCause(e);
+ throw ae;
+ }
+ }
+
+ protected int drawSelectedText(Graphics g, int x, int y, int p0, int p1)
+ throws BadLocationException
+ {
+ g.setColor(selectedColor);
+ Segment segment = getLineBuffer();
+ getDocument().getText(p0, p1 - p0, segment);
+ return Utilities.drawTabbedText(segment, x, y, g, this, segment.offset);
+ }
+
+ /**
+ * Draws a chunk of unselected text.
+ *
+ * @param g the graphics to use for drawing the text
+ * @param x the X coordinate of the baseline
+ * @param y the Y coordinate of the baseline
+ * @param p0 the start position in the text model
+ * @param p1 the end position in the text model
+ *
+ * @return the X location of the end of the range
+ *
+ * @throws BadLocationException if <code>p0</code> or <code>p1</code> are
+ * invalid
+ */
+ protected int drawUnselectedText(Graphics g, int x, int y, int p0, int p1)
+ throws BadLocationException
+ {
+ JTextComponent textComponent = (JTextComponent) getContainer();
+ if (textComponent.isEnabled())
+ g.setColor(unselectedColor);
+ else
+ g.setColor(disabledColor);
+
+ Segment segment = getLineBuffer();
+ getDocument().getText(p0, p1 - p0, segment);
+ return Utilities.drawTabbedText(segment, x, y, g, this, segment.offset);
+ }
+
+ public void paint(Graphics g, Shape s)
+ {
+ // Ensure metrics are up-to-date.
+ updateMetrics();
+
+ JTextComponent textComponent = (JTextComponent) getContainer();
+
+ selectedColor = textComponent.getSelectedTextColor();
+ unselectedColor = textComponent.getForeground();
+ disabledColor = textComponent.getDisabledTextColor();
+ selectionStart = textComponent.getSelectionStart();
+ selectionEnd = textComponent.getSelectionEnd();
+
+ Rectangle rect = s instanceof Rectangle ? (Rectangle) s : s.getBounds();
+ tabBase = rect.x;
+
+ // FIXME: Text may be scrolled.
+ Document document = textComponent.getDocument();
+ Element root = getElement();
+ int height = metrics.getHeight();
+
+ // For layered highlighters we need to paint the layered highlights
+ // before painting any text.
+ LayeredHighlighter hl = null;
+ Highlighter h = textComponent.getHighlighter();
+ if (h instanceof LayeredHighlighter)
+ hl = (LayeredHighlighter) h;
+
+ int count = root.getElementCount();
+
+ // Determine first and last line inside the clip.
+ Rectangle clip = g.getClipBounds();
+ SwingUtilities.computeIntersection(rect.x, rect.y, rect.width, rect.height,
+ clip);
+ int line0 = (clip.y - rect.y) / height;
+ line0 = Math.max(0, Math.min(line0, count - 1));
+ int line1 = (clip.y + clip.height - rect.y) / height;
+ line1 = Math.max(0, Math.min(line1, count - 1));
+ int y = rect.y + metrics.getAscent() + height * line0;
+ for (int i = line0; i <= line1; i++)
+ {
+ if (hl != null)
+ {
+ Element lineEl = root.getElement(i);
+ // Exclude the trailing newline from beeing highlighted.
+ if (i == count)
+ hl.paintLayeredHighlights(g, lineEl.getStartOffset(),
+ lineEl.getEndOffset(), s, textComponent,
+ this);
+ else
+ hl.paintLayeredHighlights(g, lineEl.getStartOffset(),
+ lineEl.getEndOffset() - 1, s,
+ textComponent, this);
+ }
+ drawLine(i, g, rect.x, y);
+ y += height;
+ }
+ }
+
+ /**
+ * Returns the tab size of a tab. Checks the Document's
+ * properties for PlainDocument.tabSizeAttribute and returns it if it is
+ * defined, otherwise returns 8.
+ *
+ * @return the tab size.
+ */
+ protected int getTabSize()
+ {
+ Object tabSize = getDocument().getProperty(PlainDocument.tabSizeAttribute);
+ if (tabSize == null)
+ return 8;
+ return ((Integer)tabSize).intValue();
+ }
+
+ /**
+ * Returns the next tab stop position after a given reference position.
+ *
+ * This implementation ignores the <code>tabStop</code> argument.
+ *
+ * @param x the current x position in pixels
+ * @param tabStop the position within the text stream that the tab occured at
+ */
+ public float nextTabStop(float x, int tabStop)
+ {
+ float next = x;
+ if (tabSize != 0)
+ {
+ int numTabs = (((int) x) - tabBase) / tabSize;
+ next = tabBase + (numTabs + 1) * tabSize;
+ }
+ return next;
+ }
+
+ /**
+ * Returns the length of the longest line, used for getting the span
+ * @return the length of the longest line
+ */
+ float determineMaxLineLength()
+ {
+ // if the longest line is cached, return the cached value
+ if (maxLineLength != -1)
+ return maxLineLength;
+
+ // otherwise we have to go through all the lines and find it
+ Element el = getElement();
+ Segment seg = getLineBuffer();
+ float span = 0;
+ for (int i = 0; i < el.getElementCount(); i++)
+ {
+ Element child = el.getElement(i);
+ int start = child.getStartOffset();
+ int end = child.getEndOffset() - 1;
+ try
+ {
+ el.getDocument().getText(start, end - start, seg);
+ }
+ catch (BadLocationException ex)
+ {
+ AssertionError ae = new AssertionError("Unexpected bad location");
+ ae.initCause(ex);
+ throw ae;
+ }
+
+ if (seg == null || seg.array == null || seg.count == 0)
+ continue;
+
+ int width = metrics.charsWidth(seg.array, seg.offset, seg.count);
+ if (width > span)
+ {
+ longestLine = child;
+ span = width;
+ }
+ }
+ maxLineLength = span;
+ return maxLineLength;
+ }
+
+ public float getPreferredSpan(int axis)
+ {
+ if (axis != X_AXIS && axis != Y_AXIS)
+ throw new IllegalArgumentException();
+
+ // make sure we have the metrics
+ updateMetrics();
+
+ Element el = getElement();
+ float span;
+
+ switch (axis)
+ {
+ case X_AXIS:
+ span = determineMaxLineLength();
+ break;
+ case Y_AXIS:
+ default:
+ span = metrics.getHeight() * el.getElementCount();
+ break;
+ }
+
+ return span;
+ }
+
+ /**
+ * Maps coordinates from the <code>View</code>'s space into a position
+ * in the document model.
+ *
+ * @param x the x coordinate in the view space
+ * @param y the y coordinate in the view space
+ * @param a the allocation of this <code>View</code>
+ * @param b the bias to use
+ *
+ * @return the position in the document that corresponds to the screen
+ * coordinates <code>x, y</code>
+ */
+ public int viewToModel(float x, float y, Shape a, Position.Bias[] b)
+ {
+ Rectangle rec = a instanceof Rectangle ? (Rectangle) a : a.getBounds();
+ tabBase = rec.x;
+
+ int pos;
+ if ((int) y < rec.y)
+ // Above our area vertically. Return start offset.
+ pos = getStartOffset();
+ else if ((int) y > rec.y + rec.height)
+ // Below our area vertically. Return end offset.
+ pos = getEndOffset() - 1;
+ else
+ {
+ // Inside the allocation vertically. Determine line and X offset.
+ Document doc = getDocument();
+ Element root = doc.getDefaultRootElement();
+ int line = Math.abs(((int) y - rec.y) / metrics.getHeight());
+ if (line >= root.getElementCount())
+ pos = getEndOffset() - 1;
+ else
+ {
+ Element lineEl = root.getElement(line);
+ if (x < rec.x)
+ // To the left of the allocation area.
+ pos = lineEl.getStartOffset();
+ else if (x > rec.x + rec.width)
+ // To the right of the allocation area.
+ pos = lineEl.getEndOffset() - 1;
+ else
+ {
+ try
+ {
+ int p0 = lineEl.getStartOffset();
+ int p1 = lineEl.getEndOffset();
+ Segment s = new Segment();
+ doc.getText(p0, p1 - p0, s);
+ tabBase = rec.x;
+ pos = p0 + Utilities.getTabbedTextOffset(s, metrics,
+ tabBase, (int) x,
+ this, p0);
+ }
+ catch (BadLocationException ex)
+ {
+ // Should not happen.
+ pos = -1;
+ }
+ }
+
+ }
+ }
+ // Bias is always forward.
+ b[0] = Position.Bias.Forward;
+ return pos;
+ }
+
+ /**
+ * Since insertUpdate and removeUpdate each deal with children
+ * Elements being both added and removed, they both have to perform
+ * the same checks. So they both simply call this method.
+ * @param changes the DocumentEvent for the changes to the Document.
+ * @param a the allocation of the View.
+ * @param f the ViewFactory to use for rebuilding.
+ */
+ protected void updateDamage(DocumentEvent changes, Shape a, ViewFactory f)
+ {
+ // This happens during initialization.
+ if (metrics == null)
+ {
+ updateMetrics();
+ preferenceChanged(null, true, true);
+ return;
+ }
+
+ Element element = getElement();
+
+ // Find longest line if it hasn't been initialized yet.
+ if (longestLine == null)
+ findLongestLine(0, element.getElementCount() - 1);
+
+ ElementChange change = changes.getChange(element);
+ if (changes.getType() == DocumentEvent.EventType.INSERT)
+ {
+ // Handles character/line insertion.
+
+ // Determine if lines have been added. In this case we repaint
+ // differently.
+ boolean linesAdded = true;
+ if (change == null)
+ linesAdded = false;
+
+ // Determine the start line.
+ int start;
+ if (linesAdded)
+ start = change.getIndex();
+ else
+ start = element.getElementIndex(changes.getOffset());
+
+ // Determine the length of the updated region.
+ int length = 0;
+ if (linesAdded)
+ length = change.getChildrenAdded().length - 1;
+
+ // Update the longest line and length.
+ int oldMaxLength = (int) maxLineLength;
+ if (longestLine.getEndOffset() < changes.getOffset()
+ || longestLine.getStartOffset() > changes.getOffset()
+ + changes.getLength())
+ {
+ findLongestLine(start, start + length);
+ }
+ else
+ {
+ findLongestLine(0, element.getElementCount() - 1);
+ }
+
+ // Trigger a preference change so that the layout gets updated
+ // correctly.
+ preferenceChanged(null, maxLineLength != oldMaxLength, linesAdded);
+
+ // Damage the updated line range.
+ int endLine = start;
+ if (linesAdded)
+ endLine = element.getElementCount() - 1;
+ damageLineRange(start, endLine, a, getContainer());
+
+ }
+ else
+ {
+ // Handles character/lines removals.
+
+ // Update the longest line and length and trigger preference changed.
+ int oldMaxLength = (int) maxLineLength;
+ if (change != null)
+ {
+ // Line(s) have been removed.
+ findLongestLine(0, element.getElementCount() - 1);
+ preferenceChanged(null, maxLineLength != oldMaxLength, true);
+ }
+ else
+ {
+ // No line has been removed.
+ int lineNo = getElement().getElementIndex(changes.getOffset());
+ Element line = getElement().getElement(lineNo);
+ if (longestLine == line)
+ {
+ findLongestLine(0, element.getElementCount() - 1);
+ preferenceChanged(null, maxLineLength != oldMaxLength, false);
+ }
+ damageLineRange(lineNo, lineNo, a, getContainer());
+ }
+ }
+ }
+
+ /**
+ * This method is called when something is inserted into the Document
+ * that this View is displaying.
+ *
+ * @param changes the DocumentEvent for the changes.
+ * @param a the allocation of the View
+ * @param f the ViewFactory used to rebuild
+ */
+ public void insertUpdate(DocumentEvent changes, Shape a, ViewFactory f)
+ {
+ updateDamage(changes, a, f);
+ }
+
+ /**
+ * This method is called when something is removed from the Document
+ * that this View is displaying.
+ *
+ * @param changes the DocumentEvent for the changes.
+ * @param a the allocation of the View
+ * @param f the ViewFactory used to rebuild
+ */
+ public void removeUpdate(DocumentEvent changes, Shape a, ViewFactory f)
+ {
+ updateDamage(changes, a, f);
+ }
+
+ /**
+ * This method is called when attributes were changed in the
+ * Document in a location that this view is responsible for.
+ */
+ public void changedUpdate (DocumentEvent changes, Shape a, ViewFactory f)
+ {
+ updateDamage(changes, a, f);
+ }
+
+ /**
+ * Repaint the given line range. This is called from insertUpdate,
+ * changedUpdate, and removeUpdate when no new lines were added
+ * and no lines were removed, to repaint the line that was
+ * modified.
+ *
+ * @param line0 the start of the range
+ * @param line1 the end of the range
+ * @param a the rendering region of the host
+ * @param host the Component that uses this View (used to call repaint
+ * on that Component)
+ *
+ * @since 1.4
+ */
+ protected void damageLineRange (int line0, int line1, Shape a, Component host)
+ {
+ if (a == null)
+ return;
+
+ Rectangle rec0 = lineToRect(a, line0);
+ Rectangle rec1 = lineToRect(a, line1);
+
+ if (rec0 == null || rec1 == null)
+ // something went wrong, repaint the entire host to be safe
+ host.repaint();
+ else
+ {
+ Rectangle repaintRec = SwingUtilities.computeUnion(rec0.x, rec0.y,
+ rec0.width,
+ rec0.height, rec1);
+ host.repaint(repaintRec.x, repaintRec.y, repaintRec.width,
+ repaintRec.height);
+ }
+ }
+
+ /**
+ * Provides a {@link Segment} object, that can be used to fetch text from
+ * the document.
+ *
+ * @returna {@link Segment} object, that can be used to fetch text from
+ * the document
+ */
+ protected final Segment getLineBuffer()
+ {
+ if (lineBuffer == null)
+ lineBuffer = new Segment();
+ return lineBuffer;
+ }
+
+ /**
+ * Finds and updates the longest line in the view inside an interval of
+ * lines.
+ *
+ * @param start the start of the search interval
+ * @param end the end of the search interval
+ */
+ private void findLongestLine(int start, int end)
+ {
+ for (int i = start; i <= end; i++)
+ {
+ int w = getLineLength(i);
+ if (w > maxLineLength)
+ {
+ maxLineLength = w;
+ longestLine = getElement().getElement(i);
+ }
+ }
+ }
+
+ /**
+ * Determines the length of the specified line.
+ *
+ * @param line the number of the line
+ *
+ * @return the length of the line in pixels
+ */
+ private int getLineLength(int line)
+ {
+ Element lineEl = getElement().getElement(line);
+ Segment buffer = getLineBuffer();
+ try
+ {
+ Document doc = getDocument();
+ doc.getText(lineEl.getStartOffset(),
+ lineEl.getEndOffset() - lineEl.getStartOffset() - 1,
+ buffer);
+ }
+ catch (BadLocationException ex)
+ {
+ AssertionError err = new AssertionError("Unexpected bad location");
+ err.initCause(ex);
+ throw err;
+ }
+
+ return Utilities.getTabbedTextWidth(buffer, metrics, tabBase, this,
+ lineEl.getStartOffset());
+ }
+}