You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pivot.apache.org by no...@apache.org on 2010/07/02 11:08:55 UTC
svn commit: r959922 [1/2] - /pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/
Author: noelgrandin
Date: Fri Jul 2 09:08:54 2010
New Revision: 959922
URL: http://svn.apache.org/viewvc?rev=959922&view=rev
Log:
move the NodeView classes in TextAreaSkin to their own top-level files
Added:
pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextAreaSkinComponentNodeView.java
pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextAreaSkinDocumentView.java
pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextAreaSkinElementView.java
pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextAreaSkinImageNodeView.java
pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextAreaSkinNodeView.java
pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextAreaSkinParagraphView.java
pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextAreaSkinSpanView.java
pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextAreaSkinTextNodeView.java
Modified:
pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextAreaSkin.java
Modified: pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextAreaSkin.java
URL: http://svn.apache.org/viewvc/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextAreaSkin.java?rev=959922&r1=959921&r2=959922&view=diff
==============================================================================
--- pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextAreaSkin.java (original)
+++ pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextAreaSkin.java Fri Jul 2 09:08:54 2010
@@ -20,23 +20,14 @@ import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Rectangle;
-import java.awt.Shape;
import java.awt.font.FontRenderContext;
-import java.awt.font.GlyphVector;
import java.awt.font.LineMetrics;
import java.awt.geom.Area;
-import java.awt.geom.Rectangle2D;
-import java.text.CharacterIterator;
-import java.util.Iterator;
-import org.apache.pivot.collections.ArrayList;
import org.apache.pivot.collections.Dictionary;
-import org.apache.pivot.collections.Sequence;
-import org.apache.pivot.util.ImmutableIterator;
import org.apache.pivot.wtk.ApplicationContext;
import org.apache.pivot.wtk.Bounds;
import org.apache.pivot.wtk.Component;
-import org.apache.pivot.wtk.ComponentListener;
import org.apache.pivot.wtk.Cursor;
import org.apache.pivot.wtk.Dimensions;
import org.apache.pivot.wtk.FocusTraversalDirection;
@@ -46,1671 +37,22 @@ import org.apache.pivot.wtk.Keyboard;
import org.apache.pivot.wtk.Mouse;
import org.apache.pivot.wtk.Orientation;
import org.apache.pivot.wtk.Platform;
-import org.apache.pivot.wtk.Point;
-import org.apache.pivot.wtk.Span;
import org.apache.pivot.wtk.TextArea;
import org.apache.pivot.wtk.TextAreaListener;
import org.apache.pivot.wtk.TextAreaSelectionListener;
import org.apache.pivot.wtk.Theme;
-import org.apache.pivot.wtk.Visual;
-import org.apache.pivot.wtk.media.Image;
-import org.apache.pivot.wtk.media.ImageListener;
import org.apache.pivot.wtk.text.ComponentNode;
-import org.apache.pivot.wtk.text.ComponentNodeListener;
import org.apache.pivot.wtk.text.Document;
-import org.apache.pivot.wtk.text.Element;
-import org.apache.pivot.wtk.text.ElementListener;
import org.apache.pivot.wtk.text.ImageNode;
-import org.apache.pivot.wtk.text.ImageNodeListener;
import org.apache.pivot.wtk.text.Node;
-import org.apache.pivot.wtk.text.NodeListener;
import org.apache.pivot.wtk.text.Paragraph;
import org.apache.pivot.wtk.text.TextNode;
-import org.apache.pivot.wtk.text.TextNodeListener;
/**
* Text area skin.
*/
public class TextAreaSkin extends ContainerSkin implements TextArea.Skin, TextAreaListener,
TextAreaSelectionListener {
- /**
- * Abstract base class for node views.
- */
- public abstract class NodeView implements Visual, NodeListener {
- private Node node = null;
- private ElementView parent = null;
-
- private int width = 0;
- private int height = 0;
- private int x = 0;
- private int y = 0;
-
- private int breakWidth = -1;
-
- private boolean valid = false;
-
- public NodeView(Node node) {
- this.node = node;
- }
-
- public Node getNode() {
- return node;
- }
-
- public ElementView getParent() {
- return parent;
- }
-
- protected void setParent(ElementView parent) {
- this.parent = parent;
- }
-
- protected void attach() {
- node.getNodeListeners().add(this);
- }
-
- protected void detach() {
- node.getNodeListeners().remove(this);
- }
-
- @Override
- public int getWidth() {
- return width;
- }
-
- @Override
- public int getHeight() {
- return height;
- }
-
- @Override
- public int getBaseline() {
- return -1;
- }
-
- public Dimensions getSize() {
- return new Dimensions(width, height);
- }
-
- protected void setSize(int width, int height) {
- assert(width >= 0);
- assert(height >= 0);
-
- // Redraw the region formerly occupied by this view
- repaint();
-
- this.width = width;
- this.height = height;
-
- // Redraw the region currently occupied by this view
- repaint();
- }
-
- public int getX() {
- return x;
- }
-
- public int getY() {
- return y;
- }
-
- public Point getLocation() {
- return new Point(x, y);
- }
-
- protected void setLocation(int x, int y) {
- // Redraw the region formerly occupied by this view
- repaint();
-
- this.x = x;
- this.y = y;
-
- // Redraw the region currently occupied by this view
- repaint();
- }
-
- /**
- * This is needed by the ComponentViewNode to correctly position child Component's.
- *
- * @param skinX the X coordinate in the skin's frame of reference
- * @param skinY the Y coordinate in the skin's frame of reference
- */
- protected abstract void setSkinLocation(int skinX, int skinY);
-
- public Bounds getBounds() {
- return new Bounds(x, y, width, height);
- }
-
- public void repaint() {
- repaint(0, 0, width, height);
- }
-
- public void repaint(int x, int y, int width, int height) {
- assert(width >= 0);
- assert(height >= 0);
-
- if (parent != null) {
- parent.repaint(x + this.x, y + this.y, width, height);
- }
- }
-
- public boolean isValid() {
- return valid;
- }
-
- public void invalidate() {
- valid = false;
-
- if (parent != null) {
- parent.invalidate();
- }
- }
-
- public void validate() {
- valid = true;
- }
-
- public int getBreakWidth() {
- return breakWidth;
- }
-
- public void setBreakWidth(int breakWidth) {
- int previousBreakWidth = this.breakWidth;
-
- if (previousBreakWidth != breakWidth) {
- this.breakWidth = breakWidth;
-
- // NOTE We can't call invalidate() here because it would ultimately
- // trigger a call to invalidateComponent(), which we don't want; this method
- // is called during preferred size calculations as well as layout, neither
- // of which should ever trigger an invalidate.
- valid = false;
- }
- }
-
- public int getOffset() {
- return node.getOffset();
- }
-
- public int getDocumentOffset() {
- return (parent == null) ? 0 : parent.getDocumentOffset() + getOffset();
- }
-
- public int getCharacterCount() {
- return node.getCharacterCount();
- }
-
- public abstract NodeView getNext();
- public abstract int getInsertionPoint(int x, int y);
- public abstract int getNextInsertionPoint(int x, int from, FocusTraversalDirection direction);
- public abstract int getRowIndex(int offset);
- public abstract int getRowCount();
- public abstract Bounds getCharacterBounds(int offset);
-
- @Override
- public void parentChanged(Node node, Element previousParent) {
- // No-op
- }
-
- @Override
- public void offsetChanged(Node node, int previousOffset) {
- // No-op
- }
-
- @Override
- public void rangeInserted(Node node, int offset, int span) {
- // No-op
- }
-
- @Override
- public void rangeRemoved(Node node, int offset, int characterCount) {
- // No-op
- }
-
- @Override
- public void nodesRemoved(Node node, Sequence<Node> removed, int offset) {
- // No-op
- }
-
- @Override
- public void nodeInserted(Node node, int offset) {
- // No-op
- }
- }
-
- /**
- * Abstract base class for element views.
- */
- public abstract class ElementView extends NodeView
- implements Sequence<NodeView>, Iterable<NodeView>, ElementListener {
- private ArrayList<NodeView> nodeViews = new ArrayList<NodeView>();
-
- public ElementView(Element element) {
- super(element);
- }
-
- @Override
- protected void attach() {
- super.attach();
-
- Element element = (Element)getNode();
- element.getElementListeners().add(this);
-
- // NOTE We don't attach child views here because this may not
- // be efficient for all subclasses (e.g. paragraph views need to
- // recreate child views when breaking across multiple lines)
- }
-
- @Override
- protected void detach() {
- Element element = (Element)getNode();
- element.getElementListeners().remove(this);
-
- // Detach child node views
- for (NodeView nodeView : this) {
- nodeView.detach();
- }
-
- super.detach();
- }
-
- @Override
- public int add(NodeView nodeView) {
- int index = getLength();
- insert(nodeView, index);
-
- return index;
- }
-
- @Override
- public void insert(NodeView nodeView, int index) {
- nodeView.setParent(this);
- nodeView.attach();
-
- nodeViews.insert(nodeView, index);
- }
-
- @Override
- public NodeView update(int index, NodeView nodeView) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int remove(NodeView nodeView) {
- int index = indexOf(nodeView);
- if (index != -1) {
- remove(index, 1);
- }
-
- return index;
- }
-
- @Override
- public Sequence<NodeView> remove(int index, int count) {
- Sequence<NodeView> removed = nodeViews.remove(index, count);
-
- for (int i = 0, n = removed.getLength(); i < n; i++) {
- NodeView nodeView = removed.get(i);
- nodeView.setParent(null);
- nodeView.detach();
- }
-
- return removed;
- }
-
- @Override
- public NodeView get(int index) {
- return nodeViews.get(index);
- }
-
- @Override
- public int indexOf(NodeView nodeView) {
- return nodeViews.indexOf(nodeView);
- }
-
- @Override
- public int getLength() {
- return nodeViews.getLength();
- }
-
- @Override
- public void paint(Graphics2D graphics) {
- // Determine the paint bounds
- Bounds paintBounds = new Bounds(0, 0, getWidth(), getHeight());
- Rectangle clipBounds = graphics.getClipBounds();
- if (clipBounds != null) {
- paintBounds = paintBounds.intersect(new Bounds(clipBounds));
- }
-
- for (NodeView nodeView : nodeViews) {
- Bounds nodeViewBounds = nodeView.getBounds();
-
- // Only paint node views that intersect the current clip rectangle
- if (nodeViewBounds.intersects(paintBounds)) {
- // Create a copy of the current graphics context and
- // translate to the node view's coordinate system
- Graphics2D nodeViewGraphics = (Graphics2D)graphics.create();
- nodeViewGraphics.translate(nodeViewBounds.x, nodeViewBounds.y);
-
- Color styledBackgroundColor = getStyledBackgroundColor();
- if (styledBackgroundColor != null) {
- nodeViewGraphics.setColor(styledBackgroundColor);
- nodeViewGraphics.fillRect(nodeViewBounds.x, nodeViewBounds.y, nodeViewBounds.width, nodeViewBounds.height);
- }
-
- // NOTE We don't clip here because views should generally
- // not overlap and clipping would impose an unnecessary
- // performance penalty
-
- // Paint the node view
- nodeView.paint(nodeViewGraphics);
-
- // Dispose of the node views's graphics
- nodeViewGraphics.dispose();
- }
- }
- }
-
- private Color getStyledBackgroundColor() {
- Color backgroundColor = null;
- Node node = getNode();
- // run up the tree until we find a Element's style to apply
- while (node != null) {
- if (node instanceof Element) {
- backgroundColor = ((Element) node).getBackgroundColor();
- if (backgroundColor != null) {
- break;
- }
- }
- node = node.getParent();
- }
- return backgroundColor;
- }
-
- @Override
- public Bounds getCharacterBounds(int offset) {
- Bounds characterBounds = null;
-
- for (int i = 0, n = nodeViews.getLength(); i < n; i++) {
- NodeView nodeView = nodeViews.get(i);
- int nodeViewOffset = nodeView.getOffset();
- int characterCount = nodeView.getCharacterCount();
-
- if (offset >= nodeViewOffset
- && offset < nodeViewOffset + characterCount) {
- characterBounds = nodeView.getCharacterBounds(offset - nodeViewOffset);
-
- if (characterBounds != null) {
- characterBounds = characterBounds.translate(nodeView.getX(), nodeView.getY());
- }
-
- break;
- }
- }
-
- if (characterBounds != null) {
- characterBounds = characterBounds.intersect(0, 0, getWidth(), getHeight());
- }
-
- return characterBounds;
- }
-
- @Override
- public void nodeInserted(Element element, int index) {
- invalidate();
- }
-
- @Override
- public void nodesRemoved(Element element, int index, Sequence<Node> nodes) {
- invalidate();
- }
-
- @Override
- public void fontChanged(Element element, Font previousFont) {
- invalidate();
- }
-
- @Override
- public void backgroundColorChanged(Element element, Color previousBackgroundColor) {
- invalidate();
- }
-
- @Override
- public void foregroundColorChanged(Element element, Color previousForegroundColor) {
- invalidate();
- }
-
- @Override
- public Iterator<NodeView> iterator() {
- return new ImmutableIterator<NodeView>(nodeViews.iterator());
- }
- }
-
- /**
- * Document view.
- */
- public class DocumentView extends ElementView {
- public DocumentView(Document document) {
- super(document);
- }
-
- @Override
- public void attach() {
- super.attach();
-
- // Attach child node views
- Document document = (Document)getNode();
- for (Node node : document) {
- add(createNodeView(node));
- }
- }
-
- @Override
- public void repaint(int x, int y, int width, int height) {
- super.repaint(x, y, width, height);
-
- repaintComponent(x, y, width, height);
- }
-
- @Override
- public void invalidate() {
- super.invalidate();
- invalidateComponent();
- }
-
- @Override
- public void validate() {
- // TODO At some point, we may want to optimize this method by deferring layout of
- // non-visible views. If so, we should not recycle views but rather recreate them
- // (as is done in ParagraphView). This way, we avoid thread contention over the
- // existing views (e.g. trying to paint one while modifying its size/location, etc.).
- // Any invalid node views are simply replaced (in the queued callback, when the
- // thread has finished processing the new ones). This allows the definition of
- // validate() to remain as-is. Of course, if we redefine NodeView to implement
- // ConstrainedVisual, this may no longer be an issue.
- // Note that, if anything happens to invalidate the existence of the new views before
- // they are added to the document view, we need to make sure they are disposed (i.e.
- // detached).
-
- if (!isValid()) {
- int breakWidth = getBreakWidth();
-
- int width = 0;
- int height = 0;
-
- int i = 0;
- int n = getLength();
-
- while (i < n) {
- NodeView nodeView = get(i++);
- nodeView.setBreakWidth(breakWidth);
- nodeView.validate();
-
- nodeView.setLocation(0, height);
-
- width = Math.max(width, nodeView.getWidth());
- height += nodeView.getHeight();
- }
-
- setSize(width, height);
-
- super.validate();
- }
- }
-
- @Override
- protected void setSkinLocation(int skinX, int skinY) {
- for (NodeView nodeView : this) {
- nodeView.setSkinLocation(skinX, skinY + nodeView.getY());
- }
- }
-
- @Override
- public int getInsertionPoint(int x, int y) {
- int offset = -1;
-
- for (int i = 0, n = getLength(); i < n; i++) {
- NodeView nodeView = get(i);
- Bounds nodeViewBounds = nodeView.getBounds();
-
- if (y >= nodeViewBounds.y
- && y < nodeViewBounds.y + nodeViewBounds.height) {
- offset = nodeView.getInsertionPoint(x - nodeView.getX(), y - nodeView.getY())
- + nodeView.getOffset();
- break;
- }
- }
-
- return offset;
- }
-
- @Override
- public int getNextInsertionPoint(int x, int from, FocusTraversalDirection direction) {
- int offset = -1;
-
- if (getLength() > 0) {
- if (from == -1) {
- int i = (direction == FocusTraversalDirection.FORWARD) ? 0 : getLength() - 1;
- NodeView nodeView = get(i);
- offset = nodeView.getNextInsertionPoint(x - nodeView.getX(), -1, direction);
-
- if (offset != -1) {
- offset += nodeView.getOffset();
- }
- } else {
- // Find the node view that contains the offset
- int n = getLength();
- int i = 0;
-
- while (i < n) {
- NodeView nodeView = get(i);
- int nodeViewOffset = nodeView.getOffset();
- int characterCount = nodeView.getCharacterCount();
-
- if (from >= nodeViewOffset
- && from < nodeViewOffset + characterCount) {
- break;
- }
-
- i++;
- }
-
- if (i < n) {
- NodeView nodeView = get(i);
- offset = nodeView.getNextInsertionPoint(x - nodeView.getX(),
- from - nodeView.getOffset(), direction);
-
- if (offset == -1) {
- // Move to the next or previous node view
- if (direction == FocusTraversalDirection.FORWARD) {
- nodeView = (i < n - 1) ? get(i + 1) : null;
- } else {
- nodeView = (i > 0) ? get(i - 1) : null;
- }
-
- if (nodeView != null) {
- offset = nodeView.getNextInsertionPoint(x - nodeView.getX(), -1, direction);
- }
- }
-
- if (offset != -1) {
- offset += nodeView.getOffset();
- }
- }
- }
- }
-
- return offset;
- }
-
- @Override
- public int getRowIndex(int offset) {
- int rowIndex = 0;
-
- for (NodeView nodeView : this) {
- int nodeViewOffset = nodeView.getOffset();
- int characterCount = nodeView.getCharacterCount();
-
- if (offset >= nodeViewOffset
- && offset < nodeViewOffset + characterCount) {
- rowIndex += nodeView.getRowIndex(offset - nodeView.getOffset());
- break;
- }
-
- rowIndex += nodeView.getRowCount();
- }
-
- return rowIndex;
- }
-
- @Override
- public int getRowCount() {
- int rowCount = 0;
-
- for (NodeView nodeView : this) {
- rowCount += nodeView.getRowCount();
- }
-
- return rowCount;
- }
-
- @Override
- public NodeView getNext() {
- return null;
- }
-
- @Override
- public void nodeInserted(Element element, int index) {
- super.nodeInserted(element, index);
-
- Document document = (Document)getNode();
- insert(createNodeView(document.get(index)), index);
- }
-
- @Override
- public void nodesRemoved(Element element, int index, Sequence<Node> nodes) {
- remove(index, nodes.getLength());
-
- super.nodesRemoved(element, index, nodes);
- }
- }
-
- public class ParagraphView extends ElementView {
- private class Row {
- public int x = 0;
- public int y = 0;
- public int width = 0;
- public int height = 0;
- public ArrayList<NodeView> nodeViews = new ArrayList<NodeView>();
- }
-
- private ArrayList<Row> rows = null;
- private Bounds terminatorBounds = new Bounds(0, 0, 0, 0);
-
- public ParagraphView(Paragraph paragraph) {
- super(paragraph);
- }
-
- @Override
- public void invalidate() {
- super.invalidate();
- terminatorBounds = null;
- }
-
- @Override
- public void validate() {
- if (!isValid()) {
- // Break the views into multiple rows
- int breakWidth = getBreakWidth();
-
- Paragraph paragraph = (Paragraph)getNode();
- rows = new ArrayList<Row>();
-
- Row row = new Row();
- for (Node node : paragraph) {
- NodeView nodeView = createNodeView(node);
-
- nodeView.setBreakWidth(Math.max(breakWidth - (row.width
- + PARAGRAPH_TERMINATOR_WIDTH), 0));
- nodeView.validate();
-
- int nodeViewWidth = nodeView.getWidth();
-
- if (row.width + nodeViewWidth > breakWidth
- && row.width > 0) {
- // The view is too big to fit in the remaining space,
- // and it is not the only view in this row
- rows.add(row);
- row = new Row();
- row.width = 0;
- }
-
- // Add the view to the row
- row.nodeViews.add(nodeView);
- row.width += nodeViewWidth;
-
- // If the view was split into multiple views, add them to
- // their own rows
- nodeView = nodeView.getNext();
- while (nodeView != null) {
- rows.add(row);
- row = new Row();
-
- nodeView.setBreakWidth(breakWidth);
- nodeView.validate();
-
- row.nodeViews.add(nodeView);
- row.width = nodeView.getWidth();
-
- nodeView = nodeView.getNext();
- }
- }
-
- // Add the last row
- if (row.nodeViews.getLength() > 0) {
- rows.add(row);
- }
-
- // Clear all existing views
- remove(0, getLength());
-
- // Add the row views to this view, lay out, and calculate height
- int x = 0;
- int width = 0;
- int height = 0;
- for (int i = 0, n = rows.getLength(); i < n; i++) {
- row = rows.get(i);
- row.y = height;
-
- width = Math.max(width, row.width);
-
- // Determine the row height
- for (NodeView nodeView : row.nodeViews) {
- row.height = Math.max(row.height, nodeView.getHeight());
- }
-
- // TODO Align horizontally when Elements support a horizontal
- // alignment property
- x = 0;
- for (NodeView nodeView : row.nodeViews) {
- // TODO Align to baseline
- int y = row.height - nodeView.getHeight();
-
- nodeView.setLocation(x, y + height);
- x += nodeView.getWidth();
-
- add(nodeView);
- }
-
- height += row.height;
- }
-
- // Recalculate terminator bounds
- FontRenderContext fontRenderContext = Platform.getFontRenderContext();
- LineMetrics lm = font.getLineMetrics("", 0, 0, fontRenderContext);
- int terminatorHeight = (int)Math.ceil(lm.getHeight());
-
- int terminatorY;
- if (getCharacterCount() == 1) {
- // The terminator is the only character in this paragraph
- terminatorY = 0;
- } else {
- terminatorY = height - terminatorHeight;
- }
-
- terminatorBounds = new Bounds(x, terminatorY,
- PARAGRAPH_TERMINATOR_WIDTH, terminatorHeight);
-
- // Ensure that the paragraph is visible even when empty
- width += terminatorBounds.width;
- height = Math.max(height, terminatorBounds.height);
-
- setSize(width, height);
- }
-
- super.validate();
- }
-
- @Override
- protected void setSkinLocation(int skinX, int skinY) {
- for (int i = 0, n = rows.getLength(); i < n; i++) {
- Row row = rows.get(i);
- for (NodeView nodeView : row.nodeViews) {
- nodeView.setSkinLocation(skinX + nodeView.getX(), skinY + nodeView.getY());
- }
- }
- }
-
- @Override
- public NodeView getNext() {
- return null;
- }
-
- @Override
- public int getInsertionPoint(int x, int y) {
- int offset = -1;
-
- int n = rows.getLength();
- if (n > 0) {
- for (int i = 0; i < n; i++) {
- Row row = rows.get(i);
-
- if (y >= row.y
- && y < row.y + row.height) {
- if (x < row.x) {
- NodeView firstNodeView = row.nodeViews.get(0);
- offset = firstNodeView.getOffset();
- } else if (x > row.x + row.width - 1) {
- NodeView lastNodeView = row.nodeViews.get(row.nodeViews.getLength() - 1);
- offset = lastNodeView.getOffset() + lastNodeView.getCharacterCount();
-
- if (offset < getCharacterCount() - 1) {
- offset--;
- }
- } else {
- for (NodeView nodeView : row.nodeViews) {
- Bounds nodeViewBounds = nodeView.getBounds();
-
- if (nodeViewBounds.contains(x, y)) {
- offset = nodeView.getInsertionPoint(x - nodeView.getX(), y - nodeView.getY())
- + nodeView.getOffset();
- break;
- }
- }
- }
- }
-
- if (offset != -1) {
- break;
- }
- }
- } else {
- offset = getCharacterCount() - 1;
- }
-
- return offset;
- }
-
- @Override
- public int getNextInsertionPoint(int x, int from, FocusTraversalDirection direction) {
- int offset = -1;
-
- int n = rows.getLength();
- if (n == 0
- && from == -1) {
- // There are no rows; select the terminator character
- offset = 0;
- } else {
- int i;
- if (from == -1) {
- i = (direction == FocusTraversalDirection.FORWARD) ? -1 : rows.getLength();
- } else {
- // Find the row that contains offset
- if (from == getCharacterCount() - 1) {
- i = rows.getLength() - 1;
- } else {
- i = 0;
- while (i < n) {
- Row row = rows.get(i);
- NodeView firstNodeView = row.nodeViews.get(0);
- NodeView lastNodeView = row.nodeViews.get(row.nodeViews.getLength() - 1);
- if (from >= firstNodeView.getOffset()
- && from < lastNodeView.getOffset() + lastNodeView.getCharacterCount()) {
- break;
- }
-
- i++;
- }
- }
- }
-
- // Move to the next or previous row
- if (direction == FocusTraversalDirection.FORWARD) {
- i++;
- } else {
- i--;
- }
-
- if (i >= 0
- && i < n) {
- // Find the node view that contains x and get the insertion point from it
- Row row = rows.get(i);
-
- for (NodeView nodeView : row.nodeViews) {
- Bounds bounds = nodeView.getBounds();
- if (x >= bounds.x
- && x < bounds.x + bounds.width) {
- offset = nodeView.getNextInsertionPoint(x - nodeView.getX(), -1, direction)
- + nodeView.getOffset();
- break;
- }
- }
-
- if (offset == -1) {
- // No node view contained the x position; move to the end of the row
- NodeView lastNodeView = row.nodeViews.get(row.nodeViews.getLength() - 1);
- offset = lastNodeView.getOffset() + lastNodeView.getCharacterCount();
-
- if (offset < getCharacterCount() - 1) {
- offset--;
- }
- }
- }
- }
-
- return offset;
- }
-
- @Override
- public int getRowIndex(int offset) {
- int rowIndex = -1;
-
- if (offset == getCharacterCount() - 1) {
- rowIndex = (rows.getLength() == 0) ? 0 : rows.getLength() - 1;
- } else {
- for (int i = 0, n = rows.getLength(); i < n; i++) {
- Row row = rows.get(i);
- NodeView firstNodeView = row.nodeViews.get(0);
- NodeView lastNodeView = row.nodeViews.get(row.nodeViews.getLength() - 1);
-
- if (offset >= firstNodeView.getOffset()
- && offset < lastNodeView.getOffset() + lastNodeView.getCharacterCount()) {
- rowIndex = i;
- break;
- }
- }
- }
-
- return rowIndex;
- }
-
- @Override
- public int getRowCount() {
- return Math.max(rows.getLength(), 1);
- }
-
- @Override
- public Bounds getCharacterBounds(int offset) {
- Bounds bounds;
-
- if (offset == getCharacterCount() - 1) {
- bounds = terminatorBounds;
- } else {
- bounds = super.getCharacterBounds(offset);
- }
-
- return bounds;
- }
- }
-
- public class SpanView extends ElementView {
-
- public SpanView(org.apache.pivot.wtk.text.Span span) {
- super(span);
- }
-
- @Override
- public void validate() {
- if (!isValid()) {
- // I have to re-create my children here instead of in attach(), because that is how ParagraphView works,
- // and ParagraphView is always my super-node.
-
- // Clear all existing views
- remove(0, getLength());
-
- // Attach child node views
- org.apache.pivot.wtk.text.Span span = (org.apache.pivot.wtk.text.Span)getNode();
- for (Node node : span) {
- add(createNodeView(node));
- }
-
- int breakWidth = getBreakWidth();
-
- int width = 0;
- int height = 0;
-
- int i = 0;
- int n = getLength();
-
- while (i < n) {
- NodeView nodeView = get(i++);
- nodeView.setBreakWidth(breakWidth);
- nodeView.validate();
-
- nodeView.setLocation(0, height);
-
- width = Math.max(width, nodeView.getWidth());
- height += nodeView.getHeight();
- }
-
- setSize(width, height);
-
- super.validate();
- }
- }
-
- @Override
- public int getInsertionPoint(int x, int y) {
- int offset = -1;
-
- for (int i = 0, n = getLength(); i < n; i++) {
- NodeView nodeView = get(i);
- Bounds nodeViewBounds = nodeView.getBounds();
-
- if (y >= nodeViewBounds.y
- && y < nodeViewBounds.y + nodeViewBounds.height) {
- offset = nodeView.getInsertionPoint(x - nodeView.getX(), y - nodeView.getY())
- + nodeView.getOffset();
- break;
- }
- }
-
- return offset;
- }
-
- @Override
- public NodeView getNext() {
- return null;
- }
-
- @Override
- public int getNextInsertionPoint(int x, int from, FocusTraversalDirection direction) {
- // TODO Auto-generated method stub
- return 0;
- }
-
- @Override
- public int getRowCount() {
- int rowCount = 0;
-
- for (NodeView nodeView : this) {
- rowCount += nodeView.getRowCount();
- }
-
- return rowCount;
- }
-
- @Override
- public int getRowIndex(int offset) {
- int rowIndex = 0;
-
- for (NodeView nodeView : this) {
- int nodeViewOffset = nodeView.getOffset();
- int characterCount = nodeView.getCharacterCount();
-
- if (offset >= nodeViewOffset
- && offset < nodeViewOffset + characterCount) {
- rowIndex += nodeView.getRowIndex(offset - nodeView.getOffset());
- break;
- }
-
- rowIndex += nodeView.getRowCount();
- }
-
- return rowIndex;
- }
-
- @Override
- protected void setSkinLocation(int skinX, int skinY) {
- for (NodeView nodeView : this) {
- nodeView.setSkinLocation(skinX, skinY + nodeView.getY());
- }
- }
-
- @Override
- public void nodeInserted(Element element, int index) {
- super.nodeInserted(element, index);
-
- org.apache.pivot.wtk.text.Span span = (org.apache.pivot.wtk.text.Span)getNode();
- insert(createNodeView(span.get(index)), index);
- }
-
- @Override
- public void nodesRemoved(Element element, int index, Sequence<Node> nodes) {
- remove(index, nodes.getLength());
-
- super.nodesRemoved(element, index, nodes);
- }
- }
-
- /**
- * Text node view.
- */
- public class TextNodeView extends NodeView implements TextNodeListener {
- private int start;
-
- private int length = 0;
- private GlyphVector glyphVector = null;
- private TextNodeView next = null;
-
- public TextNodeView(TextNode textNode) {
- this(textNode, 0);
- }
-
- public TextNodeView(TextNode textNode, int start) {
- super(textNode);
- this.start = start;
- }
-
- @Override
- protected void attach() {
- super.attach();
-
- TextNode textNode = (TextNode)getNode();
- textNode.getTextNodeListeners().add(this);
- }
-
- @Override
- protected void detach() {
- super.detach();
-
- TextNode textNode = (TextNode)getNode();
- textNode.getTextNodeListeners().remove(this);
- }
-
- @Override
- public void invalidate() {
- length = 0;
- next = null;
- glyphVector = null;
-
- super.invalidate();
- }
-
- @Override
- public void validate() {
- if (!isValid()) {
- TextNode textNode = (TextNode)getNode();
- FontRenderContext fontRenderContext = Platform.getFontRenderContext();
-
- int breakWidth = getBreakWidth();
- CharacterIterator ci = textNode.getCharacterIterator(start);
-
- float lineWidth = 0;
- int lastWhitespaceIndex = -1;
-
- Font effectiveFont = getEffectiveFont();
- char c = ci.first();
- while (c != CharacterIterator.DONE
- && lineWidth < breakWidth) {
- if (Character.isWhitespace(c)) {
- lastWhitespaceIndex = ci.getIndex();
- }
-
- int i = ci.getIndex();
- Rectangle2D characterBounds = effectiveFont.getStringBounds(ci, i, i + 1, fontRenderContext);
- lineWidth += characterBounds.getWidth();
-
- c = ci.current();
- }
-
- int end;
- if (wrapText) {
- if (textNode.getCharacterCount() == 0) {
- end = start;
- } else {
- if (lineWidth < breakWidth) {
- end = ci.getEndIndex();
- } else {
- if (lastWhitespaceIndex == -1) {
- end = ci.getIndex() - 1;
- if (end <= start) {
- end = start + 1;
- }
- } else {
- end = lastWhitespaceIndex + 1;
- }
- }
- }
- } else {
- end = ci.getEndIndex();
- }
-
- glyphVector = getEffectiveFont().createGlyphVector(fontRenderContext,
- textNode.getCharacterIterator(start, end));
-
- if (end < ci.getEndIndex()) {
- length = end - start;
- next = new TextNodeView(textNode, end);
- } else {
- length = ci.getEndIndex() - start;
- }
-
- Rectangle2D textBounds = glyphVector.getLogicalBounds();
- setSize((int)Math.ceil(textBounds.getWidth()),
- (int)Math.ceil(textBounds.getHeight()));
- }
-
- super.validate();
- }
-
- @Override
- protected void setSkinLocation(int skinX, int skinY) {
- }
-
- @Override
- public void paint(Graphics2D graphics) {
- if (glyphVector != null) {
- TextArea textArea = (TextArea)getComponent();
-
- FontRenderContext fontRenderContext = Platform.getFontRenderContext();
- LineMetrics lm = getEffectiveFont().getLineMetrics("", fontRenderContext);
- float ascent = lm.getAscent();
-
- graphics.setFont(getEffectiveFont());
-
- int selectionStart = textArea.getSelectionStart();
- int selectionLength = textArea.getSelectionLength();
- Span selectionRange = new Span(selectionStart, selectionStart + selectionLength - 1);
-
- int documentOffset = getDocumentOffset();
- Span characterRange = new Span(documentOffset, documentOffset + getCharacterCount() - 1);
-
- if (selectionLength > 0
- && characterRange.intersects(selectionRange)) {
- // Determine the selection bounds
- int width = getWidth();
- int height = getHeight();
-
- int x0;
- if (selectionRange.start > characterRange.start) {
- Bounds leadingSelectionBounds = getCharacterBounds(selectionRange.start - documentOffset);
- x0 = leadingSelectionBounds.x;
- } else {
- x0 = 0;
- }
-
- int x1;
- if (selectionRange.end < characterRange.end) {
- Bounds trailingSelectionBounds = getCharacterBounds(selectionRange.end - documentOffset);
- x1 = trailingSelectionBounds.x + trailingSelectionBounds.width;
- } else {
- x1 = width;
- }
-
- Rectangle selection = new Rectangle(x0, 0, x1 - x0, height);
-
- // Paint the unselected text
- Area unselectedArea = new Area();
- unselectedArea.add(new Area(new Rectangle(0, 0, width, height)));
- unselectedArea.subtract(new Area(selection));
-
- Graphics2D textGraphics = (Graphics2D)graphics.create();
- textGraphics.setColor(getEffectiveForegroundColor());
- textGraphics.clip(unselectedArea);
- textGraphics.drawGlyphVector(glyphVector, 0, ascent);
- textGraphics.dispose();
-
- // Paint the selection
- Color selectionColor;
- if (textArea.isFocused()) {
- selectionColor = TextAreaSkin.this.selectionColor;
- } else {
- selectionColor = inactiveSelectionColor;
- }
-
- Graphics2D selectedTextGraphics = (Graphics2D)graphics.create();
- selectedTextGraphics.setColor(textArea.isFocused() &&
- textArea.isEditable() ? selectionColor : inactiveSelectionColor);
- selectedTextGraphics.clip(selection.getBounds());
- selectedTextGraphics.drawGlyphVector(glyphVector, 0, ascent);
- selectedTextGraphics.dispose();
- } else {
- // Draw the text
- graphics.setColor(getEffectiveForegroundColor());
- graphics.drawGlyphVector(glyphVector, 0, ascent);
- }
- }
- }
-
- @Override
- public int getOffset() {
- return super.getOffset() + start;
- }
-
- @Override
- public int getCharacterCount() {
- return length;
- }
-
- @Override
- public NodeView getNext() {
- return next;
- }
-
- @Override
- public int getInsertionPoint(int x, int y) {
- FontRenderContext fontRenderContext = Platform.getFontRenderContext();
- LineMetrics lm = getEffectiveFont().getLineMetrics("", fontRenderContext);
- float ascent = lm.getAscent();
-
- int n = glyphVector.getNumGlyphs();
- int i = 0;
-
- while (i < n) {
- Shape glyphBounds = glyphVector.getGlyphLogicalBounds(i);
-
- if (glyphBounds.contains(x, y - ascent)) {
- Rectangle2D glyphBounds2D = glyphBounds.getBounds2D();
-
- if (x - glyphBounds2D.getX() > glyphBounds2D.getWidth() / 2
- && i < n - 1) {
- // The user clicked on the right half of the character; select
- // the next character
- i++;
- }
-
- break;
- }
-
- i++;
- }
-
- return i;
- }
-
- private Font getEffectiveFont() {
- Font font = null;
- // run up the tree until we find an element's style to apply
- Element element = getNode().getParent();
- while (element != null) {
- font = element.getFont();
- if (font != null) {
- break;
- }
-
- element = element.getParent();
- }
- // if we find nothing, use the default font
- if (element == null) {
- font = TextAreaSkin.this.font;
- }
- return font;
- }
-
- private Color getEffectiveForegroundColor() {
- Color foregroundColor = null;
- // run up the tree until we find an element's style to apply
- Element element = getNode().getParent();
- while (element != null) {
- foregroundColor = element.getForegroundColor();
- if (foregroundColor != null) {
- break;
- }
-
- element = element.getParent();
- }
- // if we find nothing, use the default color
- if (element == null) {
- foregroundColor = TextAreaSkin.this.color;
- }
- return foregroundColor;
- }
-
- @Override
- public int getNextInsertionPoint(int x, int from, FocusTraversalDirection direction) {
- int offset = -1;
-
- if (from == -1) {
- int n = glyphVector.getNumGlyphs();
- int i = 0;
-
- while (i < n) {
- Shape glyphBounds = glyphVector.getGlyphLogicalBounds(i);
- Rectangle2D glyphBounds2D = glyphBounds.getBounds2D();
-
- float glyphX = (float)glyphBounds2D.getX();
- float glyphWidth = (float)glyphBounds2D.getWidth();
-
- if (x >= glyphX && x < glyphX + glyphWidth) {
- if (x - glyphX > glyphWidth / 2
- && i < n - 1) {
- // The x position falls within the right half of the character;
- // select the next character
- i++;
- }
-
- offset = i;
- break;
- }
-
- i++;
- }
- }
-
- return offset;
- }
-
- @Override
- public int getRowIndex(int offset) {
- return -1;
- }
-
- @Override
- public int getRowCount() {
- return 0;
- }
-
- @Override
- public Bounds getCharacterBounds(int offset) {
- Shape glyphBounds = glyphVector.getGlyphLogicalBounds(offset);
- Rectangle2D glyphBounds2D = glyphBounds.getBounds2D();
-
- return new Bounds((int)Math.floor(glyphBounds2D.getX()), 0,
- (int)Math.ceil(glyphBounds2D.getWidth()), getHeight());
- }
-
- @Override
- public void charactersInserted(TextNode textNode, int index, int count) {
- invalidate();
- }
-
- @Override
- public void charactersRemoved(TextNode textNode, int index, String characters) {
- invalidate();
- }
-
- @Override
- public String toString() {
- TextNode textNode = (TextNode)getNode();
- String text = textNode.getText();
- return "[" + text.substring(start, start + length) + "]";
- }
- }
-
- public class ImageNodeView extends NodeView implements ImageNodeListener, ImageListener {
- public ImageNodeView(ImageNode imageNode) {
- super(imageNode);
- }
-
- @Override
- protected void attach() {
- super.attach();
-
- ImageNode imageNode = (ImageNode)getNode();
- imageNode.getImageNodeListeners().add(this);
-
- Image image = imageNode.getImage();
- if (image != null) {
- image.getImageListeners().add(this);
- }
- }
-
- @Override
- protected void detach() {
- super.detach();
-
- ImageNode imageNode = (ImageNode)getNode();
- imageNode.getImageNodeListeners().remove(this);
- }
-
- @Override
- public void validate() {
- if (!isValid()) {
- ImageNode imageNode = (ImageNode)getNode();
- Image image = imageNode.getImage();
-
- if (image == null) {
- setSize(0, 0);
- } else {
- setSize(image.getWidth(), image.getHeight());
- }
-
- super.validate();
- }
- }
-
- @Override
- protected void setSkinLocation(int skinX, int skinY) {
- }
-
- @Override
- public void paint(Graphics2D graphics) {
- ImageNode imageNode = (ImageNode)getNode();
- Image image = imageNode.getImage();
-
- if (image != null) {
- image.paint(graphics);
- }
- }
-
- @Override
- public NodeView getNext() {
- return null;
- }
-
- @Override
- public int getInsertionPoint(int x, int y) {
- return 0;
- }
-
- @Override
- public int getNextInsertionPoint(int x, int from, FocusTraversalDirection direction) {
- return (from == -1) ? 0 : -1;
- }
-
- @Override
- public int getRowIndex(int offset) {
- return -1;
- }
-
- @Override
- public int getRowCount() {
- return 0;
- }
-
- @Override
- public Bounds getCharacterBounds(int offset) {
- return new Bounds(0, 0, getWidth(), getHeight());
- }
-
- @Override
- public void imageChanged(ImageNode imageNode, Image previousImage) {
- invalidate();
-
- Image image = imageNode.getImage();
- if (image != null) {
- image.getImageListeners().add(this);
- }
-
- if (previousImage != null) {
- previousImage.getImageListeners().remove(this);
- }
- }
-
- @Override
- public void sizeChanged(Image image, int previousWidth, int previousHeight) {
- invalidate();
- }
-
- @Override
- public void baselineChanged(Image image, int previousBaseline) {
- // TODO Invalidate once baseline alignment of node view is supported
- }
-
- @Override
- public void regionUpdated(Image image, int x, int y, int width, int height) {
- // TODO Repaint the corresponding area of the component (add a repaint()
- // method to NodeView to facilitate this as well as paint-only updates
- // such as color changes)
- }
- }
-
- public class ComponentNodeView extends NodeView implements ComponentNodeListener {
-
- private final ComponentListener myComponentListener = new ComponentListener.Adapter() {
- @Override
- public void sizeChanged(Component component, int previousWidth, int previousHeight) {
- invalidate();
- }
- };
-
- public ComponentNodeView(ComponentNode componentNode) {
- super(componentNode);
- }
-
- @Override
- protected void attach() {
- super.attach();
-
- ComponentNode componentNode = (ComponentNode) getNode();
- componentNode.getComponentNodeListeners().add(this);
-
- Component component = componentNode.getComponent();
- if (component != null) {
- component.getComponentListeners().add(myComponentListener);
- }
- }
-
- @Override
- protected void detach() {
- super.detach();
-
- ComponentNode componentNode = (ComponentNode) getNode();
- componentNode.getComponentNodeListeners().remove(this);
- }
-
- @Override
- public void validate() {
- if (!isValid()) {
- ComponentNode componentNode = (ComponentNode) getNode();
- Component component = componentNode.getComponent();
-
- if (component == null) {
- setSize(0, 0);
- } else {
- component.validate();
- component.setSize(component.getPreferredWidth(), component.getPreferredHeight());
- setSize(component.getWidth(), component.getHeight());
- }
-
- super.validate();
- }
- }
-
- @Override
- protected void setSkinLocation(int skinX, int skinY) {
- ComponentNode componentNode = (ComponentNode) getNode();
- Component component = componentNode.getComponent();
-
- if (component != null) {
- // I have to un-translate the x and y coordinates because the
- // component is painted by the Container object, and it's co-ordinates
- // are relative to the Container object, not to the document node hierarchy.
- component.setLocation(skinX, skinY);
- }
- }
-
- @Override
- public void paint(Graphics2D graphics) {
- // do nothing
- }
-
- @Override
- public NodeView getNext() {
- return null;
- }
-
- @Override
- public int getInsertionPoint(int x, int y) {
- return 0;
- }
-
- @Override
- public int getNextInsertionPoint(int x, int from, FocusTraversalDirection direction) {
- return (from == -1) ? 0 : -1;
- }
-
- @Override
- public int getRowIndex(int offset) {
- return -1;
- }
-
- @Override
- public int getRowCount() {
- return 0;
- }
-
- @Override
- public Bounds getCharacterBounds(int offset) {
- return new Bounds(0, 0, getWidth(), getHeight());
- }
-
- @Override
- public void componentChanged(ComponentNode componentNode, Component previousComponent) {
- invalidate();
-
- Component component = componentNode.getComponent();
- if (component != null) {
- component.getComponentListeners().add(myComponentListener);
- }
-
- if (previousComponent != null) {
- previousComponent.getComponentListeners().remove(myComponentListener);
- }
- }
- }
-
private class BlinkCaretCallback implements Runnable {
@Override
public void run() {
@@ -1771,7 +113,7 @@ public class TextAreaSkin extends Contai
}
}
- private DocumentView documentView = null;
+ private TextAreaSkinDocumentView documentView = null;
private int caretX = 0;
private Rectangle caret = new Rectangle();
@@ -1801,7 +143,6 @@ public class TextAreaSkin extends Contai
private boolean wrapText = true;
- private static final int PARAGRAPH_TERMINATOR_WIDTH = 4;
private static final int SCROLL_RATE = 30;
public TextAreaSkin() {
@@ -1827,7 +168,7 @@ public class TextAreaSkin extends Contai
Document document = textArea.getDocument();
if (document != null) {
- documentView = (DocumentView)createNodeView(document);
+ documentView = (TextAreaSkinDocumentView)createNodeView(document);
documentView.attach();
updateSelection();
}
@@ -2683,7 +1024,7 @@ public class TextAreaSkin extends Contai
Document document = textArea.getDocument();
if (document != null) {
- documentView = (DocumentView)createNodeView(document);
+ documentView = (TextAreaSkinDocumentView)createNodeView(document);
documentView.attach();
}
@@ -2726,21 +1067,21 @@ public class TextAreaSkin extends Contai
}
}
- private NodeView createNodeView(Node node) {
- NodeView nodeView = null;
+ TextAreaSkinNodeView createNodeView(Node node) {
+ TextAreaSkinNodeView nodeView = null;
if (node instanceof Document) {
- nodeView = new DocumentView((Document)node);
+ nodeView = new TextAreaSkinDocumentView(this, (Document)node);
} else if (node instanceof Paragraph) {
- nodeView = new ParagraphView((Paragraph)node);
+ nodeView = new TextAreaSkinParagraphView(this, (Paragraph)node);
} else if (node instanceof TextNode) {
- nodeView = new TextNodeView((TextNode)node);
+ nodeView = new TextAreaSkinTextNodeView(this, (TextNode)node);
} else if (node instanceof ImageNode) {
- nodeView = new ImageNodeView((ImageNode)node);
+ nodeView = new TextAreaSkinImageNodeView((ImageNode)node);
} else if (node instanceof ComponentNode) {
- nodeView = new ComponentNodeView((ComponentNode)node);
+ nodeView = new TextAreaSkinComponentNodeView((ComponentNode)node);
} else if (node instanceof org.apache.pivot.wtk.text.Span) {
- nodeView = new SpanView((org.apache.pivot.wtk.text.Span)node);
+ nodeView = new TextAreaSkinSpanView(this, (org.apache.pivot.wtk.text.Span)node);
} else {
throw new IllegalArgumentException("Unsupported node type: "
+ node.getClass().getName());
Added: pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextAreaSkinComponentNodeView.java
URL: http://svn.apache.org/viewvc/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextAreaSkinComponentNodeView.java?rev=959922&view=auto
==============================================================================
--- pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextAreaSkinComponentNodeView.java (added)
+++ pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextAreaSkinComponentNodeView.java Fri Jul 2 09:08:54 2010
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pivot.wtk.skin;
+
+import java.awt.Graphics2D;
+
+import org.apache.pivot.wtk.Bounds;
+import org.apache.pivot.wtk.Component;
+import org.apache.pivot.wtk.ComponentListener;
+import org.apache.pivot.wtk.FocusTraversalDirection;
+import org.apache.pivot.wtk.text.ComponentNode;
+import org.apache.pivot.wtk.text.ComponentNodeListener;
+
+public class TextAreaSkinComponentNodeView extends TextAreaSkinNodeView implements ComponentNodeListener {
+
+ private final ComponentListener myComponentListener = new ComponentListener.Adapter() {
+ @Override
+ public void sizeChanged(Component component, int previousWidth, int previousHeight) {
+ invalidate();
+ }
+ };
+
+ public TextAreaSkinComponentNodeView(ComponentNode componentNode) {
+ super(componentNode);
+ }
+
+ @Override
+ protected void attach() {
+ super.attach();
+
+ ComponentNode componentNode = (ComponentNode) getNode();
+ componentNode.getComponentNodeListeners().add(this);
+
+ Component component = componentNode.getComponent();
+ if (component != null) {
+ component.getComponentListeners().add(myComponentListener);
+ }
+ }
+
+ @Override
+ protected void detach() {
+ super.detach();
+
+ ComponentNode componentNode = (ComponentNode) getNode();
+ componentNode.getComponentNodeListeners().remove(this);
+ }
+
+ @Override
+ public void validate() {
+ if (!isValid()) {
+ ComponentNode componentNode = (ComponentNode) getNode();
+ Component component = componentNode.getComponent();
+
+ if (component == null) {
+ setSize(0, 0);
+ } else {
+ component.validate();
+ component.setSize(component.getPreferredWidth(), component.getPreferredHeight());
+ setSize(component.getWidth(), component.getHeight());
+ }
+
+ super.validate();
+ }
+ }
+
+ @Override
+ protected void setSkinLocation(int skinX, int skinY) {
+ ComponentNode componentNode = (ComponentNode) getNode();
+ Component component = componentNode.getComponent();
+
+ if (component != null) {
+ // I have to un-translate the x and y coordinates because the
+ // component is painted by the Container object, and it's co-ordinates
+ // are relative to the Container object, not to the document node hierarchy.
+ component.setLocation(skinX, skinY);
+ }
+ }
+
+ @Override
+ public void paint(Graphics2D graphics) {
+ // do nothing
+ }
+
+ @Override
+ public TextAreaSkinNodeView getNext() {
+ return null;
+ }
+
+ @Override
+ public int getInsertionPoint(int x, int y) {
+ return 0;
+ }
+
+ @Override
+ public int getNextInsertionPoint(int x, int from, FocusTraversalDirection direction) {
+ return (from == -1) ? 0 : -1;
+ }
+
+ @Override
+ public int getRowIndex(int offset) {
+ return -1;
+ }
+
+ @Override
+ public int getRowCount() {
+ return 0;
+ }
+
+ @Override
+ public Bounds getCharacterBounds(int offset) {
+ return new Bounds(0, 0, getWidth(), getHeight());
+ }
+
+ @Override
+ public void componentChanged(ComponentNode componentNode, Component previousComponent) {
+ invalidate();
+
+ Component component = componentNode.getComponent();
+ if (component != null) {
+ component.getComponentListeners().add(myComponentListener);
+ }
+
+ if (previousComponent != null) {
+ previousComponent.getComponentListeners().remove(myComponentListener);
+ }
+ }
+}
\ No newline at end of file
Added: pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextAreaSkinDocumentView.java
URL: http://svn.apache.org/viewvc/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextAreaSkinDocumentView.java?rev=959922&view=auto
==============================================================================
--- pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextAreaSkinDocumentView.java (added)
+++ pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextAreaSkinDocumentView.java Fri Jul 2 09:08:54 2010
@@ -0,0 +1,236 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pivot.wtk.skin;
+
+import org.apache.pivot.collections.Sequence;
+import org.apache.pivot.wtk.Bounds;
+import org.apache.pivot.wtk.FocusTraversalDirection;
+import org.apache.pivot.wtk.text.Document;
+import org.apache.pivot.wtk.text.Element;
+import org.apache.pivot.wtk.text.Node;
+
+/**
+ * Document view.
+ */
+class TextAreaSkinDocumentView extends TextAreaSkinElementView {
+ private final TextAreaSkin textAreaSkin;
+
+ public TextAreaSkinDocumentView(TextAreaSkin textAreaSkin, Document document) {
+ super(document);
+ this.textAreaSkin = textAreaSkin;
+ }
+
+ @Override
+ public void attach() {
+ super.attach();
+
+ // Attach child node views
+ Document document = (Document)getNode();
+ for (Node node : document) {
+ add(textAreaSkin.createNodeView(node));
+ }
+ }
+
+ @Override
+ public void repaint(int x, int y, int width, int height) {
+ super.repaint(x, y, width, height);
+
+ textAreaSkin.repaintComponent(x, y, width, height);
+ }
+
+ @Override
+ public void invalidate() {
+ super.invalidate();
+ textAreaSkin.invalidateComponent();
+ }
+
+ @Override
+ public void validate() {
+ // TODO At some point, we may want to optimize this method by deferring layout of
+ // non-visible views. If so, we should not recycle views but rather recreate them
+ // (as is done in ParagraphView). This way, we avoid thread contention over the
+ // existing views (e.g. trying to paint one while modifying its size/location, etc.).
+ // Any invalid node views are simply replaced (in the queued callback, when the
+ // thread has finished processing the new ones). This allows the definition of
+ // validate() to remain as-is. Of course, if we redefine NodeView to implement
+ // ConstrainedVisual, this may no longer be an issue.
+ // Note that, if anything happens to invalidate the existence of the new views before
+ // they are added to the document view, we need to make sure they are disposed (i.e.
+ // detached).
+
+ if (!isValid()) {
+ int breakWidth = getBreakWidth();
+
+ int width = 0;
+ int height = 0;
+
+ int i = 0;
+ int n = getLength();
+
+ while (i < n) {
+ TextAreaSkinNodeView nodeView = get(i++);
+ nodeView.setBreakWidth(breakWidth);
+ nodeView.validate();
+
+ nodeView.setLocation(0, height);
+
+ width = Math.max(width, nodeView.getWidth());
+ height += nodeView.getHeight();
+ }
+
+ setSize(width, height);
+
+ super.validate();
+ }
+ }
+
+ @Override
+ protected void setSkinLocation(int skinX, int skinY) {
+ for (TextAreaSkinNodeView nodeView : this) {
+ nodeView.setSkinLocation(skinX, skinY + nodeView.getY());
+ }
+ }
+
+ @Override
+ public int getInsertionPoint(int x, int y) {
+ int offset = -1;
+
+ for (int i = 0, n = getLength(); i < n; i++) {
+ TextAreaSkinNodeView nodeView = get(i);
+ Bounds nodeViewBounds = nodeView.getBounds();
+
+ if (y >= nodeViewBounds.y
+ && y < nodeViewBounds.y + nodeViewBounds.height) {
+ offset = nodeView.getInsertionPoint(x - nodeView.getX(), y - nodeView.getY())
+ + nodeView.getOffset();
+ break;
+ }
+ }
+
+ return offset;
+ }
+
+ @Override
+ public int getNextInsertionPoint(int x, int from, FocusTraversalDirection direction) {
+ int offset = -1;
+
+ if (getLength() > 0) {
+ if (from == -1) {
+ int i = (direction == FocusTraversalDirection.FORWARD) ? 0 : getLength() - 1;
+ TextAreaSkinNodeView nodeView = get(i);
+ offset = nodeView.getNextInsertionPoint(x - nodeView.getX(), -1, direction);
+
+ if (offset != -1) {
+ offset += nodeView.getOffset();
+ }
+ } else {
+ // Find the node view that contains the offset
+ int n = getLength();
+ int i = 0;
+
+ while (i < n) {
+ TextAreaSkinNodeView nodeView = get(i);
+ int nodeViewOffset = nodeView.getOffset();
+ int characterCount = nodeView.getCharacterCount();
+
+ if (from >= nodeViewOffset
+ && from < nodeViewOffset + characterCount) {
+ break;
+ }
+
+ i++;
+ }
+
+ if (i < n) {
+ TextAreaSkinNodeView nodeView = get(i);
+ offset = nodeView.getNextInsertionPoint(x - nodeView.getX(),
+ from - nodeView.getOffset(), direction);
+
+ if (offset == -1) {
+ // Move to the next or previous node view
+ if (direction == FocusTraversalDirection.FORWARD) {
+ nodeView = (i < n - 1) ? get(i + 1) : null;
+ } else {
+ nodeView = (i > 0) ? get(i - 1) : null;
+ }
+
+ if (nodeView != null) {
+ offset = nodeView.getNextInsertionPoint(x - nodeView.getX(), -1, direction);
+ }
+ }
+
+ if (offset != -1) {
+ offset += nodeView.getOffset();
+ }
+ }
+ }
+ }
+
+ return offset;
+ }
+
+ @Override
+ public int getRowIndex(int offset) {
+ int rowIndex = 0;
+
+ for (TextAreaSkinNodeView nodeView : this) {
+ int nodeViewOffset = nodeView.getOffset();
+ int characterCount = nodeView.getCharacterCount();
+
+ if (offset >= nodeViewOffset
+ && offset < nodeViewOffset + characterCount) {
+ rowIndex += nodeView.getRowIndex(offset - nodeView.getOffset());
+ break;
+ }
+
+ rowIndex += nodeView.getRowCount();
+ }
+
+ return rowIndex;
+ }
+
+ @Override
+ public int getRowCount() {
+ int rowCount = 0;
+
+ for (TextAreaSkinNodeView nodeView : this) {
+ rowCount += nodeView.getRowCount();
+ }
+
+ return rowCount;
+ }
+
+ @Override
+ public TextAreaSkinNodeView getNext() {
+ return null;
+ }
+
+ @Override
+ public void nodeInserted(Element element, int index) {
+ super.nodeInserted(element, index);
+
+ Document document = (Document)getNode();
+ insert(textAreaSkin.createNodeView(document.get(index)), index);
+ }
+
+ @Override
+ public void nodesRemoved(Element element, int index, Sequence<Node> nodes) {
+ remove(index, nodes.getLength());
+
+ super.nodesRemoved(element, index, nodes);
+ }
+}
\ No newline at end of file
Added: pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextAreaSkinElementView.java
URL: http://svn.apache.org/viewvc/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextAreaSkinElementView.java?rev=959922&view=auto
==============================================================================
--- pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextAreaSkinElementView.java (added)
+++ pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextAreaSkinElementView.java Fri Jul 2 09:08:54 2010
@@ -0,0 +1,239 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pivot.wtk.skin;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+import java.util.Iterator;
+
+import org.apache.pivot.collections.ArrayList;
+import org.apache.pivot.collections.Sequence;
+import org.apache.pivot.util.ImmutableIterator;
+import org.apache.pivot.wtk.Bounds;
+import org.apache.pivot.wtk.text.Element;
+import org.apache.pivot.wtk.text.ElementListener;
+import org.apache.pivot.wtk.text.Node;
+
+/**
+ * Abstract base class for element views.
+ */
+abstract class TextAreaSkinElementView extends TextAreaSkinNodeView
+ implements Sequence<TextAreaSkinNodeView>, Iterable<TextAreaSkinNodeView>, ElementListener {
+ private ArrayList<TextAreaSkinNodeView> nodeViews = new ArrayList<TextAreaSkinNodeView>();
+
+ public TextAreaSkinElementView(Element element) {
+ super(element);
+ }
+
+ @Override
+ protected void attach() {
+ super.attach();
+
+ Element element = (Element)getNode();
+ element.getElementListeners().add(this);
+
+ // NOTE We don't attach child views here because this may not
+ // be efficient for all subclasses (e.g. paragraph views need to
+ // recreate child views when breaking across multiple lines)
+ }
+
+ @Override
+ protected void detach() {
+ Element element = (Element)getNode();
+ element.getElementListeners().remove(this);
+
+ // Detach child node views
+ for (TextAreaSkinNodeView nodeView : this) {
+ nodeView.detach();
+ }
+
+ super.detach();
+ }
+
+ @Override
+ public int add(TextAreaSkinNodeView nodeView) {
+ int index = getLength();
+ insert(nodeView, index);
+
+ return index;
+ }
+
+ @Override
+ public void insert(TextAreaSkinNodeView nodeView, int index) {
+ nodeView.setParent(this);
+ nodeView.attach();
+
+ nodeViews.insert(nodeView, index);
+ }
+
+ @Override
+ public TextAreaSkinNodeView update(int index, TextAreaSkinNodeView nodeView) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int remove(TextAreaSkinNodeView nodeView) {
+ int index = indexOf(nodeView);
+ if (index != -1) {
+ remove(index, 1);
+ }
+
+ return index;
+ }
+
+ @Override
+ public Sequence<TextAreaSkinNodeView> remove(int index, int count) {
+ Sequence<TextAreaSkinNodeView> removed = nodeViews.remove(index, count);
+
+ for (int i = 0, n = removed.getLength(); i < n; i++) {
+ TextAreaSkinNodeView nodeView = removed.get(i);
+ nodeView.setParent(null);
+ nodeView.detach();
+ }
+
+ return removed;
+ }
+
+ @Override
+ public TextAreaSkinNodeView get(int index) {
+ return nodeViews.get(index);
+ }
+
+ @Override
+ public int indexOf(TextAreaSkinNodeView nodeView) {
+ return nodeViews.indexOf(nodeView);
+ }
+
+ @Override
+ public int getLength() {
+ return nodeViews.getLength();
+ }
+
+ @Override
+ public void paint(Graphics2D graphics) {
+ // Determine the paint bounds
+ Bounds paintBounds = new Bounds(0, 0, getWidth(), getHeight());
+ Rectangle clipBounds = graphics.getClipBounds();
+ if (clipBounds != null) {
+ paintBounds = paintBounds.intersect(new Bounds(clipBounds));
+ }
+
+ for (TextAreaSkinNodeView nodeView : nodeViews) {
+ Bounds nodeViewBounds = nodeView.getBounds();
+
+ // Only paint node views that intersect the current clip rectangle
+ if (nodeViewBounds.intersects(paintBounds)) {
+ // Create a copy of the current graphics context and
+ // translate to the node view's coordinate system
+ Graphics2D nodeViewGraphics = (Graphics2D)graphics.create();
+ nodeViewGraphics.translate(nodeViewBounds.x, nodeViewBounds.y);
+
+ Color styledBackgroundColor = getStyledBackgroundColor();
+ if (styledBackgroundColor != null) {
+ nodeViewGraphics.setColor(styledBackgroundColor);
+ nodeViewGraphics.fillRect(nodeViewBounds.x, nodeViewBounds.y, nodeViewBounds.width, nodeViewBounds.height);
+ }
+
+ // NOTE We don't clip here because views should generally
+ // not overlap and clipping would impose an unnecessary
+ // performance penalty
+
+ // Paint the node view
+ nodeView.paint(nodeViewGraphics);
+
+ // Dispose of the node views's graphics
+ nodeViewGraphics.dispose();
+ }
+ }
+ }
+
+ private Color getStyledBackgroundColor() {
+ Color backgroundColor = null;
+ Node node = getNode();
+ // run up the tree until we find a Element's style to apply
+ while (node != null) {
+ if (node instanceof Element) {
+ backgroundColor = ((Element) node).getBackgroundColor();
+ if (backgroundColor != null) {
+ break;
+ }
+ }
+ node = node.getParent();
+ }
+ return backgroundColor;
+ }
+
+ @Override
+ public Bounds getCharacterBounds(int offset) {
+ Bounds characterBounds = null;
+
+ for (int i = 0, n = nodeViews.getLength(); i < n; i++) {
+ TextAreaSkinNodeView nodeView = nodeViews.get(i);
+ int nodeViewOffset = nodeView.getOffset();
+ int characterCount = nodeView.getCharacterCount();
+
+ if (offset >= nodeViewOffset
+ && offset < nodeViewOffset + characterCount) {
+ characterBounds = nodeView.getCharacterBounds(offset - nodeViewOffset);
+
+ if (characterBounds != null) {
+ characterBounds = characterBounds.translate(nodeView.getX(), nodeView.getY());
+ }
+
+ break;
+ }
+ }
+
+ if (characterBounds != null) {
+ characterBounds = characterBounds.intersect(0, 0, getWidth(), getHeight());
+ }
+
+ return characterBounds;
+ }
+
+ @Override
+ public void nodeInserted(Element element, int index) {
+ invalidate();
+ }
+
+ @Override
+ public void nodesRemoved(Element element, int index, Sequence<Node> nodes) {
+ invalidate();
+ }
+
+ @Override
+ public void fontChanged(Element element, Font previousFont) {
+ invalidate();
+ }
+
+ @Override
+ public void backgroundColorChanged(Element element, Color previousBackgroundColor) {
+ invalidate();
+ }
+
+ @Override
+ public void foregroundColorChanged(Element element, Color previousForegroundColor) {
+ invalidate();
+ }
+
+ @Override
+ public Iterator<TextAreaSkinNodeView> iterator() {
+ return new ImmutableIterator<TextAreaSkinNodeView>(nodeViews.iterator());
+ }
+}
\ No newline at end of file
Added: pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextAreaSkinImageNodeView.java
URL: http://svn.apache.org/viewvc/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextAreaSkinImageNodeView.java?rev=959922&view=auto
==============================================================================
--- pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextAreaSkinImageNodeView.java (added)
+++ pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextAreaSkinImageNodeView.java Fri Jul 2 09:08:54 2010
@@ -0,0 +1,144 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.pivot.wtk.skin;
+
+import java.awt.Graphics2D;
+
+import org.apache.pivot.wtk.Bounds;
+import org.apache.pivot.wtk.FocusTraversalDirection;
+import org.apache.pivot.wtk.media.Image;
+import org.apache.pivot.wtk.media.ImageListener;
+import org.apache.pivot.wtk.text.ImageNode;
+import org.apache.pivot.wtk.text.ImageNodeListener;
+
+class TextAreaSkinImageNodeView extends TextAreaSkinNodeView implements ImageNodeListener, ImageListener {
+ public TextAreaSkinImageNodeView(ImageNode imageNode) {
+ super(imageNode);
+ }
+
+ @Override
+ protected void attach() {
+ super.attach();
+
+ ImageNode imageNode = (ImageNode)getNode();
+ imageNode.getImageNodeListeners().add(this);
+
+ Image image = imageNode.getImage();
+ if (image != null) {
+ image.getImageListeners().add(this);
+ }
+ }
+
+ @Override
+ protected void detach() {
+ super.detach();
+
+ ImageNode imageNode = (ImageNode)getNode();
+ imageNode.getImageNodeListeners().remove(this);
+ }
+
+ @Override
+ public void validate() {
+ if (!isValid()) {
+ ImageNode imageNode = (ImageNode)getNode();
+ Image image = imageNode.getImage();
+
+ if (image == null) {
+ setSize(0, 0);
+ } else {
+ setSize(image.getWidth(), image.getHeight());
+ }
+
+ super.validate();
+ }
+ }
+
+ @Override
+ protected void setSkinLocation(int skinX, int skinY) {
+ }
+
+ @Override
+ public void paint(Graphics2D graphics) {
+ ImageNode imageNode = (ImageNode)getNode();
+ Image image = imageNode.getImage();
+
+ if (image != null) {
+ image.paint(graphics);
+ }
+ }
+
+ @Override
+ public TextAreaSkinNodeView getNext() {
+ return null;
+ }
+
+ @Override
+ public int getInsertionPoint(int x, int y) {
+ return 0;
+ }
+
+ @Override
+ public int getNextInsertionPoint(int x, int from, FocusTraversalDirection direction) {
+ return (from == -1) ? 0 : -1;
+ }
+
+ @Override
+ public int getRowIndex(int offset) {
+ return -1;
+ }
+
+ @Override
+ public int getRowCount() {
+ return 0;
+ }
+
+ @Override
+ public Bounds getCharacterBounds(int offset) {
+ return new Bounds(0, 0, getWidth(), getHeight());
+ }
+
+ @Override
+ public void imageChanged(ImageNode imageNode, Image previousImage) {
+ invalidate();
+
+ Image image = imageNode.getImage();
+ if (image != null) {
+ image.getImageListeners().add(this);
+ }
+
+ if (previousImage != null) {
+ previousImage.getImageListeners().remove(this);
+ }
+ }
+
+ @Override
+ public void sizeChanged(Image image, int previousWidth, int previousHeight) {
+ invalidate();
+ }
+
+ @Override
+ public void baselineChanged(Image image, int previousBaseline) {
+ // TODO Invalidate once baseline alignment of node view is supported
+ }
+
+ @Override
+ public void regionUpdated(Image image, int x, int y, int width, int height) {
+ // TODO Repaint the corresponding area of the component (add a repaint()
+ // method to NodeView to facilitate this as well as paint-only updates
+ // such as color changes)
+ }
+}
\ No newline at end of file