You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pivot.apache.org by gb...@apache.org on 2010/09/11 14:46:53 UTC

svn commit: r996137 [1/2] - in /pivot/trunk: wtk-terra/src/org/apache/pivot/wtk/skin/terra/ wtk/src/org/apache/pivot/wtk/ wtk/src/org/apache/pivot/wtk/skin/

Author: gbrown
Date: Sat Sep 11 12:46:53 2010
New Revision: 996137

URL: http://svn.apache.org/viewvc?rev=996137&view=rev
Log:
Complete rename of TextArea2 to TextArea.

Added:
    pivot/trunk/wtk-terra/src/org/apache/pivot/wtk/skin/terra/TerraTextAreaSkin.java
    pivot/trunk/wtk/src/org/apache/pivot/wtk/TextArea.java
    pivot/trunk/wtk/src/org/apache/pivot/wtk/TextAreaBindingListener.java
    pivot/trunk/wtk/src/org/apache/pivot/wtk/TextAreaContentListener.java
    pivot/trunk/wtk/src/org/apache/pivot/wtk/TextAreaListener.java
    pivot/trunk/wtk/src/org/apache/pivot/wtk/TextAreaSelectionListener.java
    pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextAreaSkin.java
    pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextAreaSkinParagraphView.java

Added: pivot/trunk/wtk-terra/src/org/apache/pivot/wtk/skin/terra/TerraTextAreaSkin.java
URL: http://svn.apache.org/viewvc/pivot/trunk/wtk-terra/src/org/apache/pivot/wtk/skin/terra/TerraTextAreaSkin.java?rev=996137&view=auto
==============================================================================
--- pivot/trunk/wtk-terra/src/org/apache/pivot/wtk/skin/terra/TerraTextAreaSkin.java (added)
+++ pivot/trunk/wtk-terra/src/org/apache/pivot/wtk/skin/terra/TerraTextAreaSkin.java Sat Sep 11 12:46:53 2010
@@ -0,0 +1,69 @@
+/*
+ * 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.terra;
+
+import org.apache.pivot.wtk.Theme;
+import org.apache.pivot.wtk.skin.TextAreaSkin;
+
+/**
+ * Terra text area skin.
+ */
+public class TerraTextAreaSkin extends TextAreaSkin {
+    public TerraTextAreaSkin() {
+        setColor(1);
+        setInactiveColor(7);
+        setSelectionColor(4);
+        setSelectionBackgroundColor(14);
+        setInactiveSelectionColor(1);
+        setInactiveSelectionBackgroundColor(9);
+    }
+
+    public final void setColor(int color) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setColor(theme.getColor(color));
+    }
+
+    public final void setInactiveColor(int inactiveColor) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setInactiveColor(theme.getColor(inactiveColor));
+    }
+
+    public final void setBackgroundColor(int backgroundColor) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setBackgroundColor(theme.getColor(backgroundColor));
+    }
+
+    public final void setSelectionColor(int backgroundColor) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setSelectionColor(theme.getColor(backgroundColor));
+    }
+
+    public final void setSelectionBackgroundColor(int backgroundColor) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setSelectionBackgroundColor(theme.getColor(backgroundColor));
+    }
+
+    public final void setInactiveSelectionColor(int backgroundColor) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setInactiveSelectionColor(theme.getColor(backgroundColor));
+    }
+
+    public final void setInactiveSelectionBackgroundColor(int backgroundColor) {
+        TerraTheme theme = (TerraTheme)Theme.getTheme();
+        setInactiveSelectionBackgroundColor(theme.getColor(backgroundColor));
+    }
+}

Added: pivot/trunk/wtk/src/org/apache/pivot/wtk/TextArea.java
URL: http://svn.apache.org/viewvc/pivot/trunk/wtk/src/org/apache/pivot/wtk/TextArea.java?rev=996137&view=auto
==============================================================================
--- pivot/trunk/wtk/src/org/apache/pivot/wtk/TextArea.java (added)
+++ pivot/trunk/wtk/src/org/apache/pivot/wtk/TextArea.java Sat Sep 11 12:46:53 2010
@@ -0,0 +1,1201 @@
+/*
+ * 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;
+
+import java.awt.Toolkit;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.StringReader;
+import java.net.URL;
+import java.util.Iterator;
+import java.util.Locale;
+
+import org.apache.pivot.collections.ArrayList;
+import org.apache.pivot.collections.Sequence;
+import org.apache.pivot.json.JSON;
+import org.apache.pivot.util.ImmutableIterator;
+import org.apache.pivot.util.ListenerList;
+
+/**
+ * A component that allows a user to enter multiple lines of unformatted text.
+ */
+public class TextArea extends Component {
+    /**
+     * Class representing a paragraph of text.
+     */
+    public static final class Paragraph {
+        private static class ParagraphListenerList extends ListenerList<ParagraphListener>
+            implements ParagraphListener {
+            @Override
+            public void textInserted(Paragraph paragraph, int index, int count) {
+                for (ParagraphListener listener : this) {
+                    listener.textInserted(paragraph, index, count);
+                }
+            }
+
+            @Override
+            public void textRemoved(Paragraph paragraph, int index, int count) {
+                for (ParagraphListener listener : this) {
+                    listener.textRemoved(paragraph, index, count);
+                }
+            }
+        }
+
+        private StringBuilder characters = new StringBuilder(INITIAL_PARAGRAPH_CAPACITY);
+        private TextArea textArea = null;
+        private int offset = -1;
+
+        private ParagraphListenerList paragraphListeners = new ParagraphListenerList();
+
+        public CharSequence getCharacters() {
+            return characters;
+        }
+
+        public TextArea getTextArea() {
+            return textArea;
+        }
+
+        public void append(char character) {
+            if (textArea != null) {
+                throw new IllegalStateException();
+            }
+
+            characters.append(character);
+        }
+
+        public void clear() {
+            if (textArea != null) {
+                throw new IllegalStateException();
+            }
+
+            characters.delete(0, characters.length());
+        }
+
+        public void insertText(CharSequence text, int index) {
+            if (text == null) {
+                throw new IllegalArgumentException();
+            }
+
+            if (index < 0
+                || index > characters.length()) {
+                throw new IndexOutOfBoundsException();
+            }
+
+            int count = text.length();
+
+            if (textArea != null
+                && textArea.characterCount + count > textArea.maximumLength) {
+                throw new IllegalArgumentException("Insertion of text would exceed maximum length.");
+            }
+
+            characters.insert(index, text);
+
+            if (textArea != null) {
+                // Update offsets and character count
+                textArea.updateParagraphOffsets(textArea.paragraphs.indexOf(this) + 1, count);
+                textArea.characterCount += count;
+
+                // Update selection state
+                int previousSelectionStart = textArea.selectionStart;
+                int previousSelectionLength = textArea.selectionLength;
+                textArea.selectionStart = offset + index + count;
+                textArea.selectionLength = 0;
+
+                // Fire change events
+                paragraphListeners.textInserted(this, index, count);
+                textArea.textAreaContentListeners.textChanged(textArea);
+
+                if (textArea.selectionStart != previousSelectionStart
+                    || textArea.selectionLength != previousSelectionLength) {
+                    textArea.textAreaSelectionListeners.selectionChanged(textArea,
+                        textArea.selectionStart, textArea.selectionLength);
+                }
+            }
+        }
+
+        public void removeText(int index) {
+            removeText(index, characters.length() - index);
+        }
+
+        public void removeText(int index, int count) {
+            if (index < 0
+                || index + count > characters.length()) {
+                throw new IndexOutOfBoundsException();
+            }
+
+            characters.delete(index, index + count);
+
+            if (textArea != null) {
+                // Update offsets and character count
+                textArea.updateParagraphOffsets(textArea.paragraphs.indexOf(this) + 1, -count);
+                textArea.characterCount -= count;
+
+                // Update selection state
+                int previousSelectionStart = textArea.selectionStart;
+                int previousSelectionLength = textArea.selectionLength;
+                textArea.selectionStart = offset + index;
+                textArea.selectionLength = 0;
+
+                // Fire change events
+                paragraphListeners.textRemoved(this, index, count);
+                textArea.textAreaContentListeners.textChanged(textArea);
+
+                if (textArea.selectionStart != previousSelectionStart
+                    || textArea.selectionLength != previousSelectionLength) {
+                    textArea.textAreaSelectionListeners.selectionChanged(textArea,
+                        textArea.selectionStart, textArea.selectionLength);
+                }
+            }
+        }
+
+        public int getOffset() {
+            return offset;
+        }
+
+        public ListenerList<ParagraphListener> getParagraphListeners() {
+            return paragraphListeners;
+        }
+    }
+
+    /**
+     * Paragraph listener interface.
+     */
+    public interface ParagraphListener {
+        /**
+         * Paragraph listener interface adapter.
+         */
+        public static class Adapter implements ParagraphListener {
+            @Override
+            public void textInserted(Paragraph paragraph, int index, int count) {
+            }
+
+            @Override
+            public void textRemoved(Paragraph paragraph, int index, int count) {
+            }
+        }
+
+        /**
+         * Called when text has been inserted into a paragraph.
+         *
+         * @param paragraph
+         * The source of the event.
+         *
+         * @param index
+         * The index at which the text was inserted.
+         *
+         * @param count
+         * The number of characters that were inserted.
+         */
+        public void textInserted(Paragraph paragraph, int index, int count);
+
+        /**
+         * Called when characters have been removed from a paragraph.
+         *
+         * @param paragraph
+         * The source of the event.
+         *
+         * @param index
+         * The index from which the text was removed.
+         *
+         * @param count
+         * The number of characters that were removed.
+         */
+        public void textRemoved(Paragraph paragraph, int index, int count);
+    }
+
+    /**
+     * Enum representing a scroll direction.
+     */
+    public enum ScrollDirection {
+        UP,
+        DOWN
+    }
+
+    /**
+     * Text area skin interface. Text area skins are required to implement
+     * this.
+     */
+    public interface Skin {
+        /**
+         * Returns the insertion point for a given location.
+         *
+         * @param x
+         * @param y
+         */
+        public int getInsertionPoint(int x, int y);
+
+        /**
+         * Returns the next insertion point given an x coordinate and a
+         * character index.
+         *
+         * @param x
+         * @param from
+         * @param direction
+         */
+        public int getNextInsertionPoint(int x, int from, ScrollDirection direction);
+
+        /**
+         * Returns the row index of the character at a given index.
+         *
+         * @param index
+         */
+        public int getRowAt(int index);
+
+        /**
+         * Returns the index of the first character in the row containing
+         * a given character index.
+         *
+         * @param index
+         */
+        public int getRowOffset(int index);
+
+        /**
+         * Returns the number of characters in the row containing a given
+         * character index.
+         *
+         * @param index
+         */
+        public int getRowLength(int index);
+
+        /**
+         * Returns the total number of rows in the text area.
+         */
+        public int getRowCount();
+
+        /**
+         * Returns the bounds of the character at a given index.
+         *
+         * @param index
+         */
+        public Bounds getCharacterBounds(int index);
+    }
+
+    /**
+     * Translates between text and context data during data binding.
+     */
+    public interface TextBindMapping {
+        /**
+         * Converts a value from the bind context to a text representation during a
+         * {@link Component#load(Object)} operation.
+         *
+         * @param value
+         */
+        public String toString(Object value);
+
+        /**
+         * Converts a text string to a value to be stored in the bind context during a
+         * {@link Component#store(Object)} operation.
+         *
+         * @param text
+         */
+        public Object valueOf(String text);
+    }
+
+    public final class ParagraphSequence implements Sequence<Paragraph>, Iterable<Paragraph> {
+        public int add(Paragraph paragraph) {
+            int index = getLength();
+            insert(paragraph, index);
+
+            return index;
+        }
+
+        public void insert(Paragraph paragraph, int index) {
+            if (paragraph == null) {
+                throw new IllegalArgumentException("paragraph is null.");
+            }
+
+            if (paragraph.textArea != null) {
+                throw new IllegalArgumentException("paragraph is already in use by another text area.");
+            }
+
+            // Determine insertion count, including terminator character
+            int characterCount = paragraph.characters.length();
+
+            if (getLength() > 0) {
+                characterCount++;
+            }
+
+            if (TextArea.this.characterCount + characterCount > maximumLength) {
+                throw new IllegalArgumentException("Insertion of text would exceed maximum length.");
+            }
+
+            // Set the paragraph offset
+            if (index == paragraphs.getLength()) {
+                paragraph.offset = TextArea.this.characterCount;
+
+                // Include terminator character
+                if (index > 0) {
+                    paragraph.offset++;
+                }
+            } else {
+                paragraph.offset = paragraphs.get(index).offset;
+            }
+
+            // Insert the paragraph
+            paragraphs.insert(paragraph, index);
+            paragraph.textArea = TextArea.this;
+
+            // Update offsets and character count
+            updateParagraphOffsets(index + 1, characterCount);
+            TextArea.this.characterCount += characterCount;
+
+            // Update selection state
+            int previousSelectionStart = selectionStart;
+            int previousSelectionLength = selectionLength;
+            selectionStart = paragraph.offset + paragraph.characters.length();
+            selectionLength = 0;
+
+            // Fire change events
+            textAreaContentListeners.paragraphInserted(TextArea.this, index);
+            textAreaContentListeners.textChanged(TextArea.this);
+
+            if (selectionStart != previousSelectionStart
+                || selectionLength != previousSelectionLength) {
+                textAreaSelectionListeners.selectionChanged(TextArea.this,
+                    selectionStart, selectionLength);
+            }
+        }
+
+        public Paragraph update(int index, Paragraph paragraph) {
+            throw new UnsupportedOperationException();
+        }
+
+        public int remove(Paragraph paragraph){
+            int index = indexOf(paragraph);
+            if (index != -1) {
+                remove(index, 1);
+            }
+
+            return index;
+        }
+
+        public Sequence<Paragraph> remove(int index, int count) {
+            Sequence<Paragraph> removed = paragraphs.remove(index, count);
+
+            if (count > 0) {
+                int characterCount = 0;
+                for (int i = 0, n = removed.getLength(); i < n; i++) {
+                    Paragraph paragraph = removed.get(i);
+                    paragraph.textArea = null;
+                    paragraph.offset = -1;
+                    characterCount += paragraph.characters.length() + 1;
+                }
+
+                // Don't include the implicit final terminator in the character count
+                if (getLength() == 0) {
+                    characterCount--;
+                }
+
+                // Update offsets
+                updateParagraphOffsets(index, -characterCount);
+                TextArea.this.characterCount -= characterCount;
+
+                // Update selection state
+                int previousSelectionStart = selectionStart;
+                int previousSelectionLength = selectionLength;
+                selectionStart = (index == paragraphs.getLength()) ?
+                    TextArea.this.characterCount : paragraphs.get(index).offset;
+                selectionLength = 0;
+
+                // Fire change events
+                textAreaContentListeners.paragraphsRemoved(TextArea.this, index, removed);
+                textAreaContentListeners.textChanged(TextArea.this);
+
+                if (selectionStart != previousSelectionStart
+                    || selectionLength != previousSelectionLength) {
+                    textAreaSelectionListeners.selectionChanged(TextArea.this,
+                        selectionStart, selectionLength);
+                }
+            }
+
+            return removed;
+        }
+
+        public Paragraph get(int index) {
+            return paragraphs.get(index);
+        }
+
+        public int indexOf(Paragraph paragraph) {
+            return paragraphs.indexOf(paragraph);
+        }
+
+        public int getLength() {
+            return paragraphs.getLength();
+        }
+
+        public Iterator<Paragraph> iterator() {
+            return new ImmutableIterator<Paragraph>(paragraphs.iterator());
+        }
+    }
+
+    private static class TextAreaListenerList extends ListenerList<TextAreaListener>
+        implements TextAreaListener {
+        @Override
+        public void maximumLengthChanged(TextArea textArea, int previousMaximumLength) {
+            for (TextAreaListener listener : this) {
+                listener.maximumLengthChanged(textArea, previousMaximumLength);
+            }
+        }
+
+        @Override
+        public void editableChanged(TextArea textArea) {
+            for (TextAreaListener listener : this) {
+                listener.editableChanged(textArea);
+            }
+        }
+    }
+
+    private static class TextAreaContentListenerList extends ListenerList<TextAreaContentListener>
+        implements TextAreaContentListener {
+        @Override
+        public void paragraphInserted(TextArea textArea, int index) {
+            for (TextAreaContentListener listener : this) {
+                listener.paragraphInserted(textArea, index);
+            }
+        }
+
+        @Override
+        public void paragraphsRemoved(TextArea textArea, int index, Sequence<TextArea.Paragraph> removed) {
+            for (TextAreaContentListener listener : this) {
+                listener.paragraphsRemoved(textArea, index, removed);
+            }
+        }
+
+        @Override
+        public void textChanged(TextArea textArea) {
+            for (TextAreaContentListener listener : this) {
+                listener.textChanged(textArea);
+            }
+        }
+    }
+
+    private static class TextAreaSelectionListenerList extends ListenerList<TextAreaSelectionListener>
+        implements TextAreaSelectionListener {
+        @Override
+        public void selectionChanged(TextArea textArea, int previousSelectionStart,
+            int previousSelectionLength) {
+            for (TextAreaSelectionListener listener : this) {
+                listener.selectionChanged(textArea, previousSelectionStart,
+                    previousSelectionLength);
+            }
+        }
+    }
+
+    private static class TextAreaBindingListenerList extends ListenerList<TextAreaBindingListener>
+        implements TextAreaBindingListener {
+        @Override
+        public void textKeyChanged(TextArea textArea, String previousTextKey) {
+            for (TextAreaBindingListener listener : this) {
+                listener.textKeyChanged(textArea, previousTextKey);
+            }
+        }
+
+        @Override
+        public void textBindTypeChanged(TextArea textArea, BindType previousTextBindType) {
+            for (TextAreaBindingListener listener : this) {
+                listener.textBindTypeChanged(textArea, previousTextBindType);
+            }
+        }
+
+        @Override
+        public void textBindMappingChanged(TextArea textArea, TextBindMapping previousTextBindMapping) {
+            for (TextAreaBindingListener listener : this) {
+                listener.textBindMappingChanged(textArea, previousTextBindMapping);
+            }
+        }
+    }
+
+    private ArrayList<Paragraph> paragraphs = new ArrayList<Paragraph>();
+    private ParagraphSequence paragraphSequence = new ParagraphSequence();
+
+    private int characterCount = 0;
+
+    private int selectionStart = 0;
+    private int selectionLength = 0;
+
+    private int maximumLength = Integer.MAX_VALUE;
+    private boolean editable = true;
+
+    private String textKey = null;
+    private BindType textBindType = BindType.BOTH;
+    private TextBindMapping textBindMapping = null;
+
+    private TextAreaListenerList textAreaListeners = new TextAreaListenerList();
+    private TextAreaContentListenerList textAreaContentListeners = new TextAreaContentListenerList();
+    private TextAreaSelectionListenerList textAreaSelectionListeners = new TextAreaSelectionListenerList();
+    private TextAreaBindingListenerList textAreaBindingListeners = new TextAreaBindingListenerList();
+
+    private static final int INITIAL_PARAGRAPH_CAPACITY = 256;
+
+    public TextArea() {
+        installSkin(TextArea.class);
+    }
+
+    @Override
+    protected void setSkin(org.apache.pivot.wtk.Skin skin) {
+        if (!(skin instanceof TextArea.Skin)) {
+            throw new IllegalArgumentException("Skin class must implement "
+                + TextArea.Skin.class.getName());
+        }
+
+        super.setSkin(skin);
+    }
+
+    /**
+     * Returns the text content of the text area.
+     *
+     * @return
+     * A string containing a copy of the text area's text content.
+     */
+    public String getText() {
+        return getText(0, getCharacterCount());
+    }
+
+    /**
+     * Returns a portion of the text content of the text area.
+     *
+     * @param beginIndex
+     * @param endIndex
+     *
+     * @return
+     * A string containing a copy of the text area's text content.
+     */
+    public String getText(int beginIndex, int endIndex) {
+        if (beginIndex > endIndex) {
+            throw new IllegalArgumentException();
+        }
+
+        if (beginIndex < 0
+            || endIndex > characterCount) {
+            throw new IndexOutOfBoundsException();
+        }
+
+        int count = endIndex - beginIndex;
+        StringBuilder textBuilder = new StringBuilder(count);
+
+        // Get paragraph and character offset at beginIndex
+        int paragraphIndex = getParagraphAt(beginIndex);
+        Paragraph paragraph = paragraphs.get(paragraphIndex);
+
+        int characterOffset = beginIndex - paragraph.offset;
+
+        // Read characters until endIndex is reached, appending to text builder
+        // and moving to next paragraph as needed
+        int i = 0;
+        while (i < count) {
+            textBuilder.append(paragraph.characters.charAt(characterOffset++));
+            i++;
+
+            if (characterOffset == paragraph.characters.length()
+                && i < characterCount) {
+                textBuilder.append('\n');
+                i++;
+
+                paragraph = paragraphs.get(++paragraphIndex);
+                characterOffset = 0;
+            }
+        }
+
+        return textBuilder.toString();
+    }
+
+    /**
+     * Sets the text content of the text area.
+     *
+     * @param text
+     */
+    public void setText(String text) {
+        if (text == null) {
+            throw new IllegalArgumentException();
+        }
+
+        try {
+            setText(new StringReader(text));
+        } catch (IOException exception) {
+            throw new RuntimeException(exception);
+        }
+    }
+
+    public void setText(URL textURL) throws IOException {
+        if (textURL == null) {
+            throw new IllegalArgumentException();
+        }
+
+        InputStream inputStream = null;
+        try {
+            inputStream = textURL.openStream();
+            setText(new InputStreamReader(inputStream));
+        } finally {
+            if (inputStream != null) {
+                inputStream.close();
+            }
+        }
+    }
+
+    public void setText(Reader textReader) throws IOException {
+        if (textReader == null) {
+            throw new IllegalArgumentException();
+        }
+
+        // Construct the paragraph list
+        ArrayList<Paragraph> paragraphs = new ArrayList<Paragraph>();
+        int characterCount = 0;
+
+        Paragraph paragraph = new Paragraph();
+
+        int c = textReader.read();
+        while (c != -1) {
+            if (++characterCount > maximumLength) {
+                throw new IllegalArgumentException("Text length is greater than maximum length.");
+            }
+
+            if (c == '\n') {
+                paragraphs.add(paragraph);
+                paragraph = new Paragraph();
+            } else {
+                paragraph.append((char)c);
+            }
+
+            c = textReader.read();
+        }
+
+        paragraphs.add(paragraph);
+
+        // Update content
+        paragraphSequence.remove(0, paragraphSequence.getLength());
+
+        for (int i = 0, n = paragraphs.getLength(); i < n; i++) {
+            paragraphSequence.add(paragraphs.get(i));
+        }
+    }
+
+    public void insertText(CharSequence text, int index) {
+        if (text == null) {
+            throw new IllegalArgumentException();
+        }
+
+        if (index < 0
+            || index > characterCount) {
+            throw new IndexOutOfBoundsException();
+        }
+
+        if (text.length() > 0) {
+            // Insert the text
+            int paragraphIndex = getParagraphAt(index);
+            Paragraph paragraph = paragraphs.get(paragraphIndex);
+
+            int characterOffset = index - paragraph.offset;
+
+            StringBuilder textBuilder = new StringBuilder();
+
+            for (int i = 0, n = text.length(); i < n; i++) {
+                char c = text.charAt(i);
+
+                if (c == '\n') {
+                    // Split paragraph at current offset
+                    int count = paragraph.characters.length();
+
+                    CharSequence trailingCharacters = paragraph.characters.subSequence(characterOffset, count);
+                    paragraph.removeText(characterOffset, count - characterOffset);
+                    paragraph.insertText(textBuilder, characterOffset);
+
+                    paragraph = new Paragraph();
+                    paragraph.insertText(trailingCharacters, 0);
+                    paragraphSequence.insert(paragraph, ++paragraphIndex);
+                    characterOffset = 0;
+
+                    textBuilder = new StringBuilder();
+                } else {
+                    // Append character
+                    textBuilder.append(c);
+                }
+            }
+
+            paragraph.insertText(textBuilder, characterOffset);
+        }
+    }
+
+    public void removeText(int index, int count) {
+        if (index < 0
+            || index + count > characterCount) {
+            throw new IndexOutOfBoundsException();
+        }
+
+        if (count > 0) {
+            // Identify the leading and trailing paragraph indexes
+            int endParagraphIndex = getParagraphAt(index + count);
+            Paragraph endParagraph = paragraphs.get(endParagraphIndex);
+
+            int beginParagraphIndex = endParagraphIndex;
+            Paragraph beginParagraph = endParagraph;
+
+            while (beginParagraph.offset > index) {
+                beginParagraph = paragraphs.get(--beginParagraphIndex);
+            }
+
+            if (beginParagraphIndex == endParagraphIndex) {
+                // The removal affects only a single paragraph
+                beginParagraph.removeText(index - beginParagraph.offset, count);
+            } else {
+                // The removal spans paragraphs; remove any intervening paragraphs and
+                // merge the leading and trailing segments
+                String leadingText = beginParagraph.characters.substring(0, index - beginParagraph.offset);
+                endParagraph.removeText(0, (index + count) - endParagraph.offset);
+                paragraphSequence.remove(beginParagraphIndex, endParagraphIndex - beginParagraphIndex);
+                endParagraph.insertText(leadingText, 0);
+            }
+        }
+    }
+
+    private void updateParagraphOffsets(int from, int count) {
+        if (count != 0) {
+            for (int i = from, n = paragraphs.getLength(); i < n; i++) {
+                Paragraph paragraph = paragraphs.get(i);
+                paragraph.offset += count;
+            }
+        }
+    }
+
+    /**
+     * Returns the text area's paragraph sequence.
+     */
+    public ParagraphSequence getParagraphs() {
+        return paragraphSequence;
+    }
+
+    /**
+     * Returns the index of the paragraph containing a given character index.
+     *
+     * @param index
+     */
+    public int getParagraphAt(int index) {
+        if (index < 0
+            || index > characterCount) {
+            throw new IndexOutOfBoundsException();
+        }
+
+        int paragraphIndex = paragraphs.getLength() - 1;
+        Paragraph paragraph = paragraphs.get(paragraphIndex);
+
+        while (paragraph.offset > index) {
+            paragraph = paragraphs.get(--paragraphIndex);
+        }
+
+        return paragraphIndex;
+    }
+
+    /**
+     * Returns the character at a given index.
+     *
+     * @param index
+     */
+    public char getCharacterAt(int index) {
+        if (index < 0
+            || index >= characterCount) {
+            throw new IndexOutOfBoundsException();
+        }
+
+        int paragraphIndex = getParagraphAt(index);
+        Paragraph paragraph = paragraphs.get(paragraphIndex);
+
+        int characterOffset = index - paragraph.offset;
+
+        return (characterOffset == paragraph.characters.length()) ?
+            '\n' : paragraph.characters.charAt(characterOffset);
+    }
+
+    /**
+     * Returns the number of characters in the text area, including line break
+     * characters.
+     */
+    public int getCharacterCount() {
+        return characterCount;
+    }
+
+    /**
+     * Places any selected text on the clipboard and deletes it from
+     * the text input.
+     */
+    public void cut() {
+        copy();
+        removeText(selectionStart, selectionLength);
+    }
+
+    /**
+     * Places any selected text on the clipboard.
+     */
+    public void copy() {
+        // Copy selection to clipboard
+        String selectedText = getSelectedText();
+
+        if (selectedText.length() > 0) {
+            LocalManifest clipboardContent = new LocalManifest();
+            clipboardContent.putText(selectedText);
+            Clipboard.setContent(clipboardContent);
+        }
+    }
+
+    /**
+     * Inserts text from the clipboard into the text input.
+     */
+    public void paste() {
+        Manifest clipboardContent = Clipboard.getContent();
+
+        if (clipboardContent != null
+            && clipboardContent.containsText()) {
+            // Paste the string representation of the content
+            String text = null;
+            try {
+                text = clipboardContent.getText();
+            } catch(IOException exception) {
+                // No-op
+            }
+
+            if (text != null) {
+                if ((characterCount + text.length()) > maximumLength) {
+                    Toolkit.getDefaultToolkit().beep();
+                } else {
+                    insertText(text, selectionStart);
+                }
+            }
+        }
+    }
+
+    public void undo() {
+        // TODO
+    }
+
+    public void redo() {
+        // TODO
+    }
+
+    /**
+     * Returns the starting index of the selection.
+     *
+     * @return
+     * The starting index of the selection.
+     */
+    public int getSelectionStart() {
+        return selectionStart;
+    }
+
+    /**
+     * Returns the length of the selection.
+     *
+     * @return
+     * The length of the selection; may be <tt>0</tt>.
+     */
+    public int getSelectionLength() {
+        return selectionLength;
+    }
+
+    /**
+     * Returns a span representing the current selection.
+     *
+     * @return
+     * A span containing the current selection. Both start and end points are
+     * inclusive. Returns <tt>null</tt> if the selection is empty.
+     */
+    public Span getSelection() {
+        return (selectionLength == 0) ? null : new Span(selectionStart,
+            selectionStart + selectionLength - 1);
+    }
+
+    /**
+     * Sets the selection. The sum of the selection start and length must be
+     * less than the length of the text area's content.
+     *
+     * @param selectionStart
+     * The starting index of the selection.
+     *
+     * @param selectionLength
+     * The length of the selection.
+     */
+    public void setSelection(int selectionStart, int selectionLength) {
+        if (selectionLength < 0) {
+            throw new IllegalArgumentException("selectionLength is negative.");
+        }
+
+        if (selectionStart < 0
+            || selectionStart + selectionLength > characterCount) {
+            throw new IndexOutOfBoundsException();
+        }
+
+        int previousSelectionStart = this.selectionStart;
+        int previousSelectionLength = this.selectionLength;
+
+        if (previousSelectionStart != selectionStart
+            || previousSelectionLength != selectionLength) {
+            this.selectionStart = selectionStart;
+            this.selectionLength = selectionLength;
+
+            textAreaSelectionListeners.selectionChanged(this, previousSelectionStart,
+                previousSelectionLength);
+        }
+    }
+
+    /**
+     * Sets the selection.
+     *
+     * @param selection
+     *
+     * @see #setSelection(int, int)
+     */
+    public final void setSelection(Span selection) {
+        if (selection == null) {
+            throw new IllegalArgumentException("selection is null.");
+        }
+
+        setSelection(Math.min(selection.start, selection.end), (int)selection.getLength());
+    }
+
+    /**
+     * Selects all text.
+     */
+    public void selectAll() {
+        setSelection(0, characterCount);
+    }
+
+    /**
+     * Clears the selection.
+     */
+    public void clearSelection() {
+        setSelection(0, 0);
+    }
+
+    /**
+     * Returns the selected text.
+     *
+     * @return
+     * A string containing a copy of the selected text.
+     */
+    public String getSelectedText() {
+        return getText(selectionStart, selectionStart + selectionLength);
+    }
+
+    /**
+     * Returns the maximum length of the text area's text content.
+     *
+     * @return
+     * The maximum length of the text area's text content.
+     */
+    public int getMaximumLength() {
+        return maximumLength;
+    }
+
+    /**
+     * Sets the maximum length of the text area's text content.
+     *
+     * @param maximumLength
+     * The maximum length of the text area's text content.
+     */
+    public void setMaximumLength(int maximumLength) {
+        if (maximumLength < 0) {
+            throw new IllegalArgumentException("maximumLength is negative.");
+        }
+
+        int previousMaximumLength = this.maximumLength;
+
+        if (previousMaximumLength != maximumLength) {
+            this.maximumLength = maximumLength;
+
+            // Truncate the text, if necessary
+            if (characterCount > maximumLength) {
+                removeText(maximumLength, characterCount - maximumLength);
+            }
+
+            // Fire change events
+            textAreaListeners.maximumLengthChanged(this, previousMaximumLength);
+        }
+    }
+
+    /**
+     * Returns the text area's editable flag.
+     */
+    public boolean isEditable() {
+        return editable;
+    }
+
+    /**
+     * Sets the text area's editable flag.
+     *
+     * @param editable
+     */
+    public void setEditable(boolean editable) {
+        if (this.editable != editable) {
+            if (!editable) {
+                if (isFocused()) {
+                    clearFocus();
+                }
+            }
+
+            this.editable = editable;
+
+            textAreaListeners.editableChanged(this);
+        }
+    }
+
+    /**
+     * Returns the text area's text key.
+     *
+     * @return
+     * The text key, or <tt>null</tt> if no text key is set.
+     */
+    public String getTextKey() {
+        return textKey;
+    }
+
+    /**
+     * Sets the text area's text key.
+     *
+     * @param textKey
+     * The text key, or <tt>null</tt> to clear the binding.
+     */
+    public void setTextKey(String textKey) {
+        String previousTextKey = this.textKey;
+
+        if (previousTextKey != textKey) {
+            this.textKey = textKey;
+            textAreaBindingListeners.textKeyChanged(this, previousTextKey);
+        }
+    }
+
+    public BindType getTextBindType() {
+        return textBindType;
+    }
+
+    public void setTextBindType(BindType textBindType) {
+        if (textBindType == null) {
+            throw new IllegalArgumentException();
+        }
+
+        BindType previousTextBindType = this.textBindType;
+
+        if (previousTextBindType != textBindType) {
+            this.textBindType = textBindType;
+            textAreaBindingListeners.textBindTypeChanged(this, previousTextBindType);
+        }
+    }
+
+    public final void setTextBindType(String textBindType) {
+        if (textBindType == null) {
+            throw new IllegalArgumentException();
+        }
+
+        setTextBindType(BindType.valueOf(textBindType.toUpperCase(Locale.ENGLISH)));
+    }
+
+    public TextBindMapping getTextBindMapping() {
+        return textBindMapping;
+    }
+
+    public void setTextBindMapping(TextBindMapping textBindMapping) {
+        TextBindMapping previousTextBindMapping = this.textBindMapping;
+
+        if (previousTextBindMapping != textBindMapping) {
+            this.textBindMapping = textBindMapping;
+            textAreaBindingListeners.textBindMappingChanged(this, previousTextBindMapping);
+        }
+    }
+
+    @Override
+    public void load(Object context) {
+        if (textKey != null
+            && JSON.containsKey(context, textKey)
+            && textBindType != BindType.STORE) {
+            Object value = JSON.get(context, textKey);
+
+            if (textBindMapping == null) {
+                value = (value == null) ? "" : value.toString();
+            } else {
+                value = textBindMapping.toString(value);
+            }
+
+            setText((String)value);
+        }
+    }
+
+    @Override
+    public void store(Object context) {
+        if (textKey != null
+            && textBindType != BindType.LOAD) {
+            String text = getText();
+            JSON.put(context, textKey, (textBindMapping == null) ?
+                text : textBindMapping.valueOf(text));
+        }
+    }
+
+    @Override
+    public void clear() {
+        if (textKey != null) {
+            setText("");
+        }
+    }
+
+    public int getInsertionPoint(int x, int y) {
+        TextArea.Skin textAreaSkin = (TextArea.Skin)getSkin();
+        return textAreaSkin.getInsertionPoint(x, y);
+    }
+
+    public int getNextInsertionPoint(int x, int from, ScrollDirection direction) {
+        TextArea.Skin textAreaSkin = (TextArea.Skin)getSkin();
+        return textAreaSkin.getNextInsertionPoint(x, from, direction);
+    }
+
+    public int getRowAt(int index) {
+        TextArea.Skin textAreaSkin = (TextArea.Skin)getSkin();
+        return textAreaSkin.getRowAt(index);
+    }
+
+    public int getRowOffset(int index) {
+        TextArea.Skin textAreaSkin = (TextArea.Skin)getSkin();
+        return textAreaSkin.getRowOffset(index);
+    }
+
+    public int getRowLength(int index) {
+        TextArea.Skin textAreaSkin = (TextArea.Skin)getSkin();
+        return textAreaSkin.getRowLength(index);
+    }
+
+    public int getRowCount() {
+        TextArea.Skin textAreaSkin = (TextArea.Skin)getSkin();
+        return textAreaSkin.getRowCount();
+    }
+
+    public Bounds getCharacterBounds(int index) {
+        TextArea.Skin textAreaSkin = (TextArea.Skin)getSkin();
+        return textAreaSkin.getCharacterBounds(index);
+    }
+
+    public ListenerList<TextAreaListener> getTextAreaListeners() {
+        return textAreaListeners;
+    }
+
+    public ListenerList<TextAreaContentListener> getTextAreaContentListeners() {
+        return textAreaContentListeners;
+    }
+
+    public ListenerList<TextAreaSelectionListener> getTextAreaSelectionListeners() {
+        return textAreaSelectionListeners;
+    }
+
+    public ListenerList<TextAreaBindingListener> getTextAreaBindingListeners() {
+        return textAreaBindingListeners;
+    }
+}

Added: pivot/trunk/wtk/src/org/apache/pivot/wtk/TextAreaBindingListener.java
URL: http://svn.apache.org/viewvc/pivot/trunk/wtk/src/org/apache/pivot/wtk/TextAreaBindingListener.java?rev=996137&view=auto
==============================================================================
--- pivot/trunk/wtk/src/org/apache/pivot/wtk/TextAreaBindingListener.java (added)
+++ pivot/trunk/wtk/src/org/apache/pivot/wtk/TextAreaBindingListener.java Sat Sep 11 12:46:53 2010
@@ -0,0 +1,63 @@
+/*
+ * 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;
+
+/**
+ * Text area binding listener interface.
+ */
+public interface TextAreaBindingListener {
+    /**
+     * Text area binding listener adapter.
+     */
+    public static class Adapter implements TextAreaBindingListener {
+        @Override
+        public void textKeyChanged(TextArea textArea, String previousTextKey) {
+        }
+
+        @Override
+        public void textBindTypeChanged(TextArea textArea, BindType previousTextBindType) {
+        }
+
+        @Override
+        public void textBindMappingChanged(TextArea textArea, TextArea.TextBindMapping previousTextBindMapping) {
+        }
+    }
+
+    /**
+     * Called when a text area's text key has changed.
+     *
+     * @param textArea
+     * @param previousTextKey
+     */
+    public void textKeyChanged(TextArea textArea, String previousTextKey);
+
+    /**
+     * Called when a text area's text bind type has changed.
+     *
+     * @param textArea
+     * @param previousTextBindType
+     */
+    public void textBindTypeChanged(TextArea textArea, BindType previousTextBindType);
+
+    /**
+     * Called when a text area's text bind mapping has changed.
+     *
+     * @param textArea
+     * @param previousTextBindMapping
+     */
+    public void textBindMappingChanged(TextArea textArea, TextArea.TextBindMapping previousTextBindMapping);
+}

Added: pivot/trunk/wtk/src/org/apache/pivot/wtk/TextAreaContentListener.java
URL: http://svn.apache.org/viewvc/pivot/trunk/wtk/src/org/apache/pivot/wtk/TextAreaContentListener.java?rev=996137&view=auto
==============================================================================
--- pivot/trunk/wtk/src/org/apache/pivot/wtk/TextAreaContentListener.java (added)
+++ pivot/trunk/wtk/src/org/apache/pivot/wtk/TextAreaContentListener.java Sat Sep 11 12:46:53 2010
@@ -0,0 +1,76 @@
+/*
+ * 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;
+
+import org.apache.pivot.collections.Sequence;
+
+/**
+ * Text area text listener interface.
+ */
+public interface TextAreaContentListener {
+    /**
+     * Text input text listener adapter.
+     */
+    public static class Adapter implements TextAreaContentListener {
+        @Override
+        public void paragraphInserted(TextArea textArea, int index) {
+        }
+
+        @Override
+        public void paragraphsRemoved(TextArea textArea, int index, Sequence<TextArea.Paragraph> removed) {
+        }
+
+        @Override
+        public void textChanged(TextArea textArea) {
+        }
+    }
+
+    /**
+     * Called when a paragraph has been inserted into a text area's paragraph
+     * sequence.
+     *
+     * @param textArea
+     * The source of the event.
+     *
+     * @param index
+     * The index at which the paragraph was inserted.
+     */
+    public void paragraphInserted(TextArea textArea, int index);
+
+    /**
+     * Called when paragraphs have been removed from a text area's paragraph
+     * sequence.
+     *
+     * @param textArea
+     * The source of the event.
+     *
+     * @param index
+     * The starting index from which the paragraphs were removed.
+     *
+     * @param removed
+     * The paragraphs that were removed.
+     */
+    public void paragraphsRemoved(TextArea textArea, int index, Sequence<TextArea.Paragraph> removed);
+
+    /**
+     * Called when a text area's text has changed.
+     *
+     * @param textArea
+     * The source of the event.
+     */
+    public void textChanged(TextArea textArea);
+}

Added: pivot/trunk/wtk/src/org/apache/pivot/wtk/TextAreaListener.java
URL: http://svn.apache.org/viewvc/pivot/trunk/wtk/src/org/apache/pivot/wtk/TextAreaListener.java?rev=996137&view=auto
==============================================================================
--- pivot/trunk/wtk/src/org/apache/pivot/wtk/TextAreaListener.java (added)
+++ pivot/trunk/wtk/src/org/apache/pivot/wtk/TextAreaListener.java Sat Sep 11 12:46:53 2010
@@ -0,0 +1,50 @@
+/*
+ * 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;
+
+/**
+ * Text area listener interface.
+ */
+public interface TextAreaListener {
+    /**
+     * Text area listener adapter.
+     */
+    public static class Adapter implements TextAreaListener {
+        @Override
+        public void maximumLengthChanged(TextArea textArea, int previousMaximumLength) {
+        }
+
+        @Override
+        public void editableChanged(TextArea textArea) {
+        }
+    }
+
+    /**
+     * Called when a text area's maximum length has changed.
+     *
+     * @param textArea
+     * @param previousMaximumLength
+     */
+    public void maximumLengthChanged(TextArea textArea, int previousMaximumLength);
+
+    /**
+     * Called when a text area's editable state has changed.
+     *
+     * @param textArea
+     */
+    public void editableChanged(TextArea textArea);
+}

Added: pivot/trunk/wtk/src/org/apache/pivot/wtk/TextAreaSelectionListener.java
URL: http://svn.apache.org/viewvc/pivot/trunk/wtk/src/org/apache/pivot/wtk/TextAreaSelectionListener.java?rev=996137&view=auto
==============================================================================
--- pivot/trunk/wtk/src/org/apache/pivot/wtk/TextAreaSelectionListener.java (added)
+++ pivot/trunk/wtk/src/org/apache/pivot/wtk/TextAreaSelectionListener.java Sat Sep 11 12:46:53 2010
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+/**
+ * Text area selection listener interface.
+ */
+public interface TextAreaSelectionListener {
+    /**
+     * Called when a text area's selection state has changed.
+     *
+     * @param textArea
+     * @param previousSelectionStart
+     * @param previousSelectionLength
+     */
+    public void selectionChanged(TextArea textArea, int previousSelectionStart,
+        int previousSelectionLength);
+}