Author: rwhitcomb
Date: Sat Feb 10 00:46:28 2018
New Revision: 1823724

URL: http://svn.apache.org/viewvc?rev=1823724&view=rev
Log:
PIVOT-891:  More corrections to TerraTextInputSkin for the Shift-Home
and Shift-End cases when there was previous selection in process.

Move the "selectSpan" and word navigation code into a new common
class (CharUtils) and introduce a new CharSpan class to hold/return
a (start,length) pair. Then use this common code for double-click
word selection, as well as the left/right word navigation in both
TextInput and TextArea.

Add "setSelection(CharSpan)" and "CharSpan getCharSelection()" methods,
then make "getCharacters" methods to interface with this new common
code, which uses CharSequence.

Update the Javadoc in SelectDirection to be more specific.

Added:
    pivot/trunk/core/src/org/apache/pivot/text/CharSpan.java
    pivot/trunk/core/src/org/apache/pivot/util/CharUtils.java
Modified:
    
pivot/trunk/wtk-terra/src/org/apache/pivot/wtk/skin/terra/TerraTextInputSkin.java
    pivot/trunk/wtk/src/org/apache/pivot/wtk/SelectDirection.java
    pivot/trunk/wtk/src/org/apache/pivot/wtk/TextArea.java
    pivot/trunk/wtk/src/org/apache/pivot/wtk/TextInput.java
    pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextAreaSkin.java

Added: pivot/trunk/core/src/org/apache/pivot/text/CharSpan.java
URL: 
http://svn.apache.org/viewvc/pivot/trunk/core/src/org/apache/pivot/text/CharSpan.java?rev=1823724&view=auto
==============================================================================
--- pivot/trunk/core/src/org/apache/pivot/text/CharSpan.java (added)
+++ pivot/trunk/core/src/org/apache/pivot/text/CharSpan.java Sat Feb 10 
00:46:28 2018
@@ -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.text;
+
+import org.apache.pivot.collections.Dictionary;
+import org.apache.pivot.collections.List;
+import org.apache.pivot.collections.Sequence;
+import org.apache.pivot.json.JSONSerializer;
+import org.apache.pivot.serialization.SerializationException;
+import org.apache.pivot.util.Utils;
+
+/**
+ * Class representing a span of characters. The range includes all values
+ * in the interval <i><tt>[start, start+length-1]</tt></i> inclusive.  This is 
the paradigm
+ * used in a lot of places (notably the text controls) to indicate a selection.
+ * <p> A zero-length span indicates a single caret position at the given start.
+ * <p> Negative lengths are not supported and will throw exceptions, as will
+ * negative start positions.
+ */
+public final class CharSpan {
+    public final int start;
+    public final int length;
+
+    public static final String START_KEY = "start";
+    public static final String LENGTH_KEY = "length";
+
+    /**
+     * Construct a new char span of length zero at the given location.
+     *
+     * @param start The start of this char span.
+     */
+    public CharSpan(int start) {
+        Utils.checkNonNegative(start, "start");
+        this.start = start;
+        this.length = 0;
+    }
+
+    /**
+     * Construct a new char span with the given values.
+     * @param start The start of this char span.
+     * @param length The length of this char span.
+     */
+    public CharSpan(int start, int length) {
+        Utils.checkNonNegative(start, "start");
+        Utils.checkNonNegative(length, "length");
+        this.start = start;
+        this.length = length;
+    }
+
+    /**
+     * Construct a new char span from another one (a "copy
+     * constructor").
+     *
+     * @param charSpan An existing char span (which must not be {@code null}).
+     * @throws IllegalArgumentException if the given char span is {@code null}.
+     */
+    public CharSpan(CharSpan charSpan) {
+        Utils.checkNull(charSpan, "charSpan");
+
+        this.start = charSpan.start;
+        this.length = charSpan.length;
+    }
+
+    /**
+     * Construct a new char span from the given dictionary which must
+     * contain the {@link #START_KEY} and {@link #LENGTH_KEY} keys.
+     *
+     * @param charSpan A dictionary containing start and end values.
+     * @throws IllegalArgumentException if the given char span is {@code null}
+     * or if the dictionary does not contain the start and length keys.
+     */
+    public CharSpan(Dictionary<String, ?> charSpan) {
+        Utils.checkNull(charSpan, "charSpan");
+
+        if (!charSpan.containsKey(START_KEY)) {
+            throw new IllegalArgumentException(START_KEY + " is required.");
+        }
+
+        if (!charSpan.containsKey(LENGTH_KEY)) {
+            throw new IllegalArgumentException(LENGTH_KEY + " is required.");
+        }
+
+        int start = charSpan.getInt(START_KEY);
+        int length = charSpan.getInt(LENGTH_KEY);
+
+        Utils.checkNonNegative(start, "start");
+        Utils.checkNonNegative(length, "length");
+
+        this.start = start;
+        this.length = length;
+    }
+
+    /**
+     * Construct a new char span from the given sequence with two
+     * numeric values corresponding to the start and length values
+     * respectively.
+     *
+     * @param charSpan A sequence containing the start and length values.
+     * @throws IllegalArgumentException if the given char span is {@code null}.
+     */
+    public CharSpan(Sequence<?> charSpan) {
+        Utils.checkNull(charSpan, "charSpan");
+
+        int start = ((Number)charSpan.get(0)).intValue();
+        int length = ((Number)charSpan.get(1)).intValue();
+
+        Utils.checkNonNegative(start, "start");
+        Utils.checkNonNegative(length, "length");
+
+        this.start = start;
+        this.length = length;
+    }
+
+    /**
+     * Returns the inclusive end value of this char span, which is the
+     * <tt>start + length - 1</tt>.  So, if the length is zero,
+     * then the end will be less that the start.
+     *
+     * @return The computed inclusive end value of this char span.
+     */
+    public int getEnd() {
+        return start + length - 1;
+    }
+
+    /**
+     * Returns a new {@link CharSpan} with the start value offset by the given 
amount.
+     *
+     * @param offset The positive or negative amount by which to "move" this
+     * char span (the start value).
+     * @return A new {@link CharSpan} with the updated value.
+     * @throws IllegalArgumentException if the updated start value goes 
negative.
+     */
+    public CharSpan offset(int offset) {
+        return new CharSpan(this.start + offset, this.length);
+    }
+
+    /**
+     * Returns a new {@link CharSpan} with the length value offset by the 
given amount
+     * (either positive to lengthen the span or negative to shorten the span).
+     *
+     * @param offset The positive or negative amount by which to "lengthen" 
this
+     * char span (the length value).
+     * @return A new {@link CharSpan} with the updated value.
+     * @throws IllegalArgumentException if the updated length value goes 
negative.
+     */
+    public CharSpan lengthen(int offset) {
+        return new CharSpan(this.start, this.length + offset);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        boolean equal = false;
+
+        if (o instanceof CharSpan) {
+            CharSpan span = (CharSpan) o;
+            equal = (start == span.start && length == span.length);
+        }
+
+        return equal;
+    }
+
+    @Override
+    public int hashCode() {
+        return 31 * start + length;
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + " {start:" + start + ", length:" + 
length + "}";
+    }
+
+    /**
+     * Convert a string into a char span.
+     * <p> If the string value is a JSON map, then parse the map
+     * and construct using the {@link #CharSpan(Dictionary)} method.
+     * <p> If the string value is a JSON list, then parse the list
+     * and construct using the first two values as start and end
+     * respectively, using the {@link #CharSpan(int, int)} constructor.
+     * <p> Also accepted is a simple list of two integer values
+     * separated by comma or semicolon.
+     * <p> Otherwise the string should be a single integer value
+     * that will be used to construct the char span using the {@link 
#CharSpan(int)}
+     * constructor (just the start value, with a zero length).
+     *
+     * @param value The string value to decode into a new char span.
+     * @return The decoded char span.
+     * @throws IllegalArgumentException if the value is {@code null} or
+     * if the string starts with <code>"{"</code> but it cannot be parsed as
+     * a JSON map, or if it starts with <code>"["</code> but cannot be parsed
+     * as a JSON list.
+     */
+    public static CharSpan decode(String value) {
+        Utils.checkNullOrEmpty(value, "value");
+
+        CharSpan charSpan;
+        if (value.startsWith("{")) {
+            try {
+                charSpan = new CharSpan(JSONSerializer.parseMap(value));
+            } catch (SerializationException exception) {
+                throw new IllegalArgumentException(exception);
+            }
+        } else if (value.startsWith("[")) {
+            try {
+                charSpan = new CharSpan(JSONSerializer.parseList(value));
+            } catch (SerializationException exception) {
+                throw new IllegalArgumentException(exception);
+            }
+        } else {
+            String[] parts = value.split("\\s*[,;]\\s*");
+            try {
+                if (parts.length == 2) {
+                    charSpan = new CharSpan(Integer.parseInt(parts[0]), 
Integer.parseInt(parts[1]));
+                } else if (parts.length == 1) {
+                    charSpan = new CharSpan(Integer.parseInt(value));
+                } else {
+                    throw new IllegalArgumentException("Unknown format for 
CharSpan: " + value);
+                }
+            } catch (NumberFormatException ex) {
+                throw new IllegalArgumentException(ex);
+            }
+        }
+
+        return charSpan;
+    }
+}

Added: pivot/trunk/core/src/org/apache/pivot/util/CharUtils.java
URL: 
http://svn.apache.org/viewvc/pivot/trunk/core/src/org/apache/pivot/util/CharUtils.java?rev=1823724&view=auto
==============================================================================
--- pivot/trunk/core/src/org/apache/pivot/util/CharUtils.java (added)
+++ pivot/trunk/core/src/org/apache/pivot/util/CharUtils.java Sat Feb 10 
00:46:28 2018
@@ -0,0 +1,162 @@
+/*
+ * 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.util;
+
+import org.apache.pivot.text.CharSpan;
+
+/**
+ * A set of static methods that perform various character-based operations.
+ */
+public class CharUtils {
+
+    /**
+     * Return a {@link CharSpan} describing a "word" which contains the given
+     * starting location in the character sequence.
+     * <p> "Words" are defined as sequences of "Unicode Identifier Part" 
characters
+     * or single characters that are not part of this class (and are not 
whitespace).
+     *
+     * @param sequence The sequence of characters to examine.
+     * @param start The starting location from which to get a "word" selection.
+     * @return The {@link CharSpan} (start and length) that describes the 
selected
+     * word around the given starting point, or {@code null} if a word 
selection
+     * cannot be made.
+     */
+    public static CharSpan selectWord(CharSequence sequence, int start) {
+        int length = sequence.length();
+        int adjustedStart = start;
+        char ch;
+
+        // Adjust the start position to put it within the sequence length
+        // and skip any trailing line endings at that point
+        if (adjustedStart >= length) {
+            adjustedStart = length - 1;
+            if (adjustedStart < 0) {
+                return null;
+            }
+            while ((ch = sequence.charAt(adjustedStart)) == '\r' || ch == 
'\n') {
+                adjustedStart--;
+            }
+        }
+        if (adjustedStart < 0) {
+            return null;
+        }
+
+        int selectionStart = adjustedStart;
+        int selectionLength = 1;
+        ch = sequence.charAt(selectionStart);
+        if (Character.isWhitespace(ch)) {
+            // Move backward to beginning of whitespace block
+            // but not before the beginning of the text.
+            do {
+                selectionStart--;
+            } while (selectionStart >= 0
+                && Character.isWhitespace(sequence.charAt(selectionStart)));
+            selectionStart++;
+            selectionLength = start - selectionStart;
+
+            // Move forward to end of whitespace block
+            // but not past the end of the text.
+            do {
+                selectionLength++;
+            } while (selectionStart + selectionLength < length
+                && Character.isWhitespace(sequence.charAt(selectionStart + 
selectionLength)));
+        } else if (Character.isUnicodeIdentifierPart(ch)) {
+            // Move backward to beginning of identifier block
+            do {
+                selectionStart--;
+            } while (selectionStart >= 0
+                && 
Character.isUnicodeIdentifierPart(sequence.charAt(selectionStart)));
+            selectionStart++;
+            selectionLength = adjustedStart - selectionStart;
+
+            // Move forward to end of identifier block
+            // but not past end of text.
+            do {
+                selectionLength++;
+            } while (selectionStart + selectionLength < length
+                && 
Character.isUnicodeIdentifierPart(sequence.charAt(selectionStart
+                    + selectionLength)));
+        } else {
+            return null;
+        }
+
+        return new CharSpan(selectionStart, selectionLength);
+    }
+
+    /**
+     * Find the start of the "word" prior to the given starting point in the 
sequence.
+     *
+     * @param sequence The character sequence to search.
+     * @param start The starting point to find the start of the word prior to.
+     * @return The index of the prior word start.
+     */
+    public static int findPriorWord(CharSequence sequence, int start) {
+        int wordStart = start;
+
+        // Skip over any space immediately to the left
+        while (wordStart > 0 && 
Character.isWhitespace(sequence.charAt(wordStart - 1))) {
+            wordStart--;
+        }
+
+        // Skip over any word-letters to the left, or just skip one character 
for other stuff
+        if (wordStart > 0) {
+            if (Character.isUnicodeIdentifierPart(sequence.charAt(wordStart - 
1))) {
+                while (wordStart > 0
+                    && 
Character.isUnicodeIdentifierPart(sequence.charAt(wordStart - 1))) {
+                    wordStart--;
+                }
+            } else {
+                wordStart--;
+            }
+        }
+
+        return wordStart;
+    }
+
+    /**
+     * Find the start of the "word" after the given starting point in the 
sequence.
+     *
+     * @param sequence The character sequence to search.
+     * @param start The starting point to find the start of the word after.
+     * @return The index of the next word start.
+     */
+    public static int findNextWord(CharSequence sequence, int start) {
+        int wordStart = start;
+        int count = sequence.length();
+
+        // Skip over any word-letters to the right, or move one character for 
other stuff
+        if (wordStart < count) {
+            if (Character.isUnicodeIdentifierPart(sequence.charAt(wordStart))) 
{
+                while (wordStart < count
+                    && 
Character.isUnicodeIdentifierPart(sequence.charAt(wordStart))) {
+                    wordStart++;
+                }
+            } else {
+                wordStart++;
+            }
+
+            // Skip over any space immediately to the right to the beginning 
of the next word
+            while (wordStart < count
+                && Character.isWhitespace(sequence.charAt(wordStart))) {
+                wordStart++;
+            }
+        }
+
+        return wordStart;
+    }
+}
+

Modified: 
pivot/trunk/wtk-terra/src/org/apache/pivot/wtk/skin/terra/TerraTextInputSkin.java
URL: 
http://svn.apache.org/viewvc/pivot/trunk/wtk-terra/src/org/apache/pivot/wtk/skin/terra/TerraTextInputSkin.java?rev=1823724&r1=1823723&r2=1823724&view=diff
==============================================================================
--- 
pivot/trunk/wtk-terra/src/org/apache/pivot/wtk/skin/terra/TerraTextInputSkin.java
 (original)
+++ 
pivot/trunk/wtk-terra/src/org/apache/pivot/wtk/skin/terra/TerraTextInputSkin.java
 Sat Feb 10 00:46:28 2018
@@ -36,7 +36,9 @@ import java.text.AttributedCharacterIter
 import org.apache.pivot.collections.Dictionary;
 import org.apache.pivot.collections.Sequence;
 import org.apache.pivot.text.AttributedStringCharacterIterator;
+import org.apache.pivot.text.CharSpan;
 import org.apache.pivot.text.CompositeIterator;
+import org.apache.pivot.util.CharUtils;
 import org.apache.pivot.util.StringUtils;
 import org.apache.pivot.util.Utils;
 import org.apache.pivot.util.Vote;
@@ -1147,66 +1149,15 @@ public class TerraTextInputSkin extends
         return consumed;
     }
 
-    private void selectSpan(TextInput textInput, int start) {
-        int length = textInput.getCharacterCount();
-        if (start >= length) {
-            start = length - 1;
-            if (start < 0) {
-                return;
-            }
-            char ch = textInput.getCharacterAt(start);
-            if (ch == '\r' || ch == '\n') {
-                start--;
-            }
-        }
-        if (start < 0) {
-            return;
-        }
-        char ch = textInput.getCharacterAt(start);
-        int selectionStart = start;
-        int selectionLength = 1;
-        if (Character.isWhitespace(ch)) {
-            // Move backward to beginning of whitespace block
-            // but not before the beginning of the text.
-            do {
-                selectionStart--;
-            } while (selectionStart >= 0
-                && 
Character.isWhitespace(textInput.getCharacterAt(selectionStart)));
-            selectionStart++;
-            selectionLength = start - selectionStart;
-            // Move forward to end of whitespace block
-            // but not past the end of the text.
-            do {
-                selectionLength++;
-            } while (selectionStart + selectionLength < length
-                && 
Character.isWhitespace(textInput.getCharacterAt(selectionStart + 
selectionLength)));
-        } else if (Character.isUnicodeIdentifierPart(ch)) {
-            // Move backward to beginning of identifier block
-            do {
-                selectionStart--;
-            } while (selectionStart >= 0
-                && 
Character.isUnicodeIdentifierPart(textInput.getCharacterAt(selectionStart)));
-            selectionStart++;
-            selectionLength = start - selectionStart;
-            // Move forward to end of identifier block
-            // but not past end of text.
-            do {
-                selectionLength++;
-            } while (selectionStart + selectionLength < length
-                && 
Character.isUnicodeIdentifierPart(textInput.getCharacterAt(selectionStart
-                    + selectionLength)));
-        } else {
-            return;
-        }
-        textInput.setSelection(selectionStart, selectionLength);
-    }
-
     @Override
     public boolean mouseClick(Component component, Mouse.Button button, int x, 
int y, int count) {
         if (button == Mouse.Button.LEFT && count > 1) {
             TextInput textInput = (TextInput) getComponent();
             if (count == 2) {
-                selectSpan(textInput, getInsertionPoint(x));
+                CharSpan charSpan = 
CharUtils.selectWord(textInput.getCharacters(), getInsertionPoint(x));
+                if (charSpan != null) {
+                    textInput.setSelection(charSpan);
+                }
             } else if (count == 3) {
                 textInput.selectAll();
             }
@@ -1332,11 +1283,17 @@ public class TerraTextInputSkin extends
         } else if (keyCode == Keyboard.KeyCode.HOME
             || (keyCode == Keyboard.KeyCode.LEFT && isMetaPressed)) {
             if (isShiftPressed) {
-                // Select from the beginning of the text to the current 
position
-                textInput.setSelection(0, start);
+                // Select from the beginning of the text to the current pivot 
position
+                if (selectDirection == SelectDirection.LEFT) {
+                    textInput.setSelection(0, start + length);
+                } else {
+                    textInput.setSelection(0, start);
+                }
+                selectDirection = SelectDirection.LEFT;
             } else {
                 // Move the caret to the beginning of the text
                 textInput.setSelection(0, 0);
+                selectDirection = null;
             }
 
             scrollCharacterToVisible(0);
@@ -1347,11 +1304,16 @@ public class TerraTextInputSkin extends
             int n = textInput.getCharacterCount();
 
             if (isShiftPressed) {
-                // Select from current position to the end of the text
+                // Select from current pivot position to the end of the text
+                if (selectDirection == SelectDirection.LEFT) {
+                    start += length;
+                }
                 textInput.setSelection(start, n - start);
+                selectDirection = SelectDirection.RIGHT;
             } else {
                 // Move the caret to the end of the text
                 textInput.setSelection(n, 0);
+                selectDirection = null;
             }
 
             scrollCharacterToVisible(n);
@@ -1362,22 +1324,7 @@ public class TerraTextInputSkin extends
                 int wordStart = (selectDirection == SelectDirection.RIGHT) ? 
start + length : start;
                 // Find the start of the next word to the left
                 if (wordStart > 0) {
-                    // Skip over any space immediately to the left
-                    while (wordStart > 0 && 
Character.isWhitespace(textInput.getCharacterAt(wordStart - 1))) {
-                        wordStart--;
-                    }
-
-                    // Skip over any word-letters to the left, or just skip 
one character for other stuff
-                    if (wordStart > 0) {
-                        if 
(Character.isUnicodeIdentifierPart(textInput.getCharacterAt(wordStart - 1))) {
-                            while (wordStart > 0
-                                && 
Character.isUnicodeIdentifierPart(textInput.getCharacterAt(wordStart - 1))) {
-                                wordStart--;
-                            }
-                        } else {
-                            wordStart--;
-                        }
-                    }
+                    wordStart = 
CharUtils.findPriorWord(textInput.getCharacters(), wordStart);
 
                     if (isShiftPressed) {
                         if (wordStart >= start) {
@@ -1460,23 +1407,7 @@ public class TerraTextInputSkin extends
                 int wordStart = (selectDirection == SelectDirection.LEFT) ? 
start : start + length;
                 // Find the start of the next word to the right
                 if (wordStart < textInput.getCharacterCount()) {
-                    // Skip over any space immediately to the right
-                    while (wordStart < textInput.getCharacterCount()
-                        && 
Character.isWhitespace(textInput.getCharacterAt(wordStart))) {
-                        wordStart++;
-                    }
-
-                    // Skip over any word-letters to the right, or move one 
character for other stuff
-                    if (wordStart < textInput.getCharacterCount()) {
-                        if 
(Character.isUnicodeIdentifierPart(textInput.getCharacterAt(wordStart))) {
-                            while (wordStart < textInput.getCharacterCount()
-                                && 
Character.isUnicodeIdentifierPart(textInput.getCharacterAt(wordStart))) {
-                                wordStart++;
-                            }
-                        } else {
-                            wordStart++;
-                        }
-                    }
+                    wordStart = 
CharUtils.findNextWord(textInput.getCharacters(), wordStart);
 
                     if (isShiftPressed) {
                         if (wordStart <= start + length) {

Modified: pivot/trunk/wtk/src/org/apache/pivot/wtk/SelectDirection.java
URL: 
http://svn.apache.org/viewvc/pivot/trunk/wtk/src/org/apache/pivot/wtk/SelectDirection.java?rev=1823724&r1=1823723&r2=1823724&view=diff
==============================================================================
--- pivot/trunk/wtk/src/org/apache/pivot/wtk/SelectDirection.java (original)
+++ pivot/trunk/wtk/src/org/apache/pivot/wtk/SelectDirection.java Sat Feb 10 
00:46:28 2018
@@ -19,9 +19,9 @@ package org.apache.pivot.wtk;
 /**
  * Enumeration of the possible directions we can select in.  In other words,
  * which arrow key was first used to initiate selecting?
- * <p> Used by {@link TextArea} and {@link TextPane} for two-dimensional
- * selection logic.  {@link TextInput} uses just the {@link #LEFT} and
- * {@link #RIGHT} values.
+ * <p> All four values used by {@link TextArea} and {@link TextPane} for
+ * two-dimensional selection logic.  {@link TextInput} uses just the
+ * {@link #LEFT} and {@link #RIGHT} values.
  */
 public enum SelectDirection {
     LEFT,

Modified: 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=1823724&r1=1823723&r2=1823724&view=diff
==============================================================================
--- pivot/trunk/wtk/src/org/apache/pivot/wtk/TextArea.java (original)
+++ pivot/trunk/wtk/src/org/apache/pivot/wtk/TextArea.java Sat Feb 10 00:46:28 
2018
@@ -29,6 +29,7 @@ import org.apache.pivot.collections.Arra
 import org.apache.pivot.collections.LinkedStack;
 import org.apache.pivot.collections.Sequence;
 import org.apache.pivot.json.JSON;
+import org.apache.pivot.text.CharSpan;
 import org.apache.pivot.util.ImmutableIterator;
 import org.apache.pivot.util.ListenerList;
 import org.apache.pivot.util.Utils;
@@ -540,9 +541,26 @@ public class TextArea extends Component
      * @return A string containing a copy of the text area's text content.
      */
     public String getText(int beginIndex, int endIndex) {
-        Utils.checkTwoIndexBounds(beginIndex, endIndex, 0, characterCount);
+        return getCharacters(beginIndex, endIndex).toString();
+    }
+
+    /**
+     * @return A character sequence representing the text input's content.
+     */
+    public CharSequence getCharacters() {
+        return getCharacters(0, getCharacterCount());
+    }
+
+    /**
+     * @return A (sub) character sequence representing the contents between
+     * the given indices.
+     * @param start The start of the sequence (inclusive).
+     * @param end The end of the sequence (exclusive).
+     */
+    public CharSequence getCharacters(int start, int end) {
+        Utils.checkTwoIndexBounds(start, end, 0, characterCount);
 
-        int count = endIndex - beginIndex;
+        int count = end - start;
         if (count == 0) {
             return "";
         }
@@ -550,12 +568,12 @@ public class TextArea extends Component
         StringBuilder textBuilder = new StringBuilder(count);
 
         // Get paragraph and character offset at beginIndex
-        int paragraphIndex = getParagraphAt(beginIndex);
+        int paragraphIndex = getParagraphAt(start);
         Paragraph paragraph = paragraphs.get(paragraphIndex);
 
-        int characterOffset = beginIndex - paragraph.offset;
+        int characterOffset = start - paragraph.offset;
 
-        // Read characters until endIndex is reached, appending to text builder
+        // Read characters until end is reached, appending to text builder
         // and moving to next paragraph as needed
         int i = 0;
         while (i < count) {
@@ -570,7 +588,7 @@ public class TextArea extends Component
             i++;
         }
 
-        return textBuilder.toString();
+        return textBuilder;
     }
 
     /**
@@ -899,6 +917,15 @@ public class TextArea extends Component
     }
 
     /**
+     * Returns a character span (start, length) representing the current 
selection.
+     *
+     * @return A char span with the start and length values.
+     */
+    public CharSpan getCharSelection() {
+        return new CharSpan(selectionStart, selectionLength);
+    }
+
+    /**
      * Sets the selection. The sum of the selection start and length must be
      * less than the length of the text area's content.
      *
@@ -933,6 +960,19 @@ public class TextArea extends Component
     }
 
     /**
+     * Sets the selection.
+     *
+     * @param selection The character span (start and length) for the 
selection.
+     * @see #setSelection(int, int)
+     * @throws IllegalArgumentException if the character span is {@code null}.
+     */
+    public final void setSelection(CharSpan selection) {
+        Utils.checkNull(selection, "selection");
+
+        setSelection(selection.start, selection.length);
+    }
+
+    /**
      * Selects all text.
      */
     public void selectAll() {
@@ -1138,6 +1178,13 @@ public class TextArea extends Component
         return textAreaSkin.getRowLength(index);
     }
 
+    public CharSequence getRowCharacters(int index) {
+        TextArea.Skin textAreaSkin = (TextArea.Skin) getSkin();
+        int offset = textAreaSkin.getRowOffset(index);
+        int length = textAreaSkin.getRowLength(index);
+        return getCharacters(offset, offset + length);
+    }
+
     public int getRowCount() {
         TextArea.Skin textAreaSkin = (TextArea.Skin) getSkin();
         return textAreaSkin.getRowCount();

Modified: pivot/trunk/wtk/src/org/apache/pivot/wtk/TextInput.java
URL: 
http://svn.apache.org/viewvc/pivot/trunk/wtk/src/org/apache/pivot/wtk/TextInput.java?rev=1823724&r1=1823723&r2=1823724&view=diff
==============================================================================
--- pivot/trunk/wtk/src/org/apache/pivot/wtk/TextInput.java (original)
+++ pivot/trunk/wtk/src/org/apache/pivot/wtk/TextInput.java Sat Feb 10 00:46:28 
2018
@@ -22,6 +22,7 @@ import java.io.IOException;
 import org.apache.pivot.collections.LinkedStack;
 import org.apache.pivot.json.JSON;
 import org.apache.pivot.text.AttributedStringCharacterIterator;
+import org.apache.pivot.text.CharSpan;
 import org.apache.pivot.util.ListenerList;
 import org.apache.pivot.util.Utils;
 import org.apache.pivot.util.Vote;
@@ -443,6 +444,15 @@ public class TextInput extends Component
     }
 
     /**
+     * Returns a character span (start, length) representing the current 
selection.
+     *
+     * @return A char span with the start and length values.
+     */
+    public CharSpan getCharSelection() {
+        return new CharSpan(selectionStart, selectionLength);
+    }
+
+    /**
      * Sets the selection. The sum of the selection start and length must be
      * less than the length of the text input's content.
      *
@@ -483,6 +493,19 @@ public class TextInput extends Component
     }
 
     /**
+     * Sets the selection.
+     *
+     * @param selection The character span (start and length) for the 
selection.
+     * @see #setSelection(int, int)
+     * @throws IllegalArgumentException if the character span is {@code null}.
+     */
+    public final void setSelection(CharSpan selection) {
+        Utils.checkNull(selection, "selection");
+
+        setSelection(selection.start, selection.length);
+    }
+
+    /**
      * Selects all text.
      */
     public void selectAll() {

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=1823724&r1=1823723&r2=1823724&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 Sat Feb 10 
00:46:28 2018
@@ -31,6 +31,8 @@ import java.awt.geom.Rectangle2D;
 import org.apache.pivot.collections.ArrayList;
 import org.apache.pivot.collections.Dictionary;
 import org.apache.pivot.collections.Sequence;
+import org.apache.pivot.text.CharSpan;
+import org.apache.pivot.util.CharUtils;
 import org.apache.pivot.util.Utils;
 import org.apache.pivot.wtk.ApplicationContext;
 import org.apache.pivot.wtk.Bounds;
@@ -915,61 +917,6 @@ public class TextAreaSkin extends Compon
         return consumed;
     }
 
-    private void selectSpan(TextArea textArea, int start) {
-        int rowStart = textArea.getRowOffset(start);
-        int rowLength = textArea.getRowLength(start);
-        if (start - rowStart >= rowLength) {
-            start = rowStart + rowLength - 1;
-            if (start < 0) {
-                return;
-            }
-            char ch = textArea.getCharacterAt(start);
-            if (ch == '\r' || ch == '\n') {
-                start--;
-            }
-        }
-        if (start < 0) {
-            return;
-        }
-        char ch = textArea.getCharacterAt(start);
-        int selectionStart = start;
-        int selectionLength = 1;
-        if (Character.isWhitespace(ch)) {
-            // Move backward to beginning of whitespace block
-            // but not before the beginning of the line.
-            do {
-                selectionStart--;
-            } while (selectionStart >= rowStart
-                && 
Character.isWhitespace(textArea.getCharacterAt(selectionStart)));
-            selectionStart++;
-            selectionLength = start - selectionStart;
-            // Move forward to end of whitespace block
-            // but not past the end of the text or the end of line
-            do {
-                selectionLength++;
-            } while (selectionStart + selectionLength - rowStart < rowLength
-                && 
Character.isWhitespace(textArea.getCharacterAt(selectionStart + 
selectionLength)));
-        } else if (Character.isJavaIdentifierPart(ch)) {
-            // Move backward to beginning of identifier block
-            do {
-                selectionStart--;
-            } while (selectionStart >= rowStart
-                && 
Character.isJavaIdentifierPart(textArea.getCharacterAt(selectionStart)));
-            selectionStart++;
-            selectionLength = start - selectionStart;
-            // Move forward to end of identifier block
-            // but not past end of text
-            do {
-                selectionLength++;
-            } while (selectionStart + selectionLength - rowStart < rowLength
-                && 
Character.isJavaIdentifierPart(textArea.getCharacterAt(selectionStart
-                    + selectionLength)));
-        } else {
-            return;
-        }
-        textArea.setSelection(selectionStart, selectionLength);
-    }
-
     @Override
     public boolean mouseClick(Component component, Mouse.Button button, int x, 
int y, int count) {
         boolean consumed = super.mouseClick(component, button, x, y, count);
@@ -980,7 +927,10 @@ public class TextAreaSkin extends Compon
             int index = getInsertionPoint(x, y);
             if (index != -1) {
                 if (count == 2) {
-                    selectSpan(textArea, index);
+                    CharSpan charSpan = 
CharUtils.selectWord(textArea.getRowCharacters(index), index);
+                    if (charSpan != null) {
+                        textArea.setSelection(charSpan);
+                    }
                 } else if (count == 3) {
                     textArea.setSelection(textArea.getRowOffset(index),
                         textArea.getRowLength(index));


Reply via email to