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 2009/10/29 18:57:39 UTC

svn commit: r831049 - in /incubator/pivot/trunk/wtk: src/org/apache/pivot/wtk/ src/org/apache/pivot/wtk/skin/ src/org/apache/pivot/wtk/skin/terra/ test/org/apache/pivot/wtk/test/

Author: gbrown
Date: Thu Oct 29 17:57:38 2009
New Revision: 831049

URL: http://svn.apache.org/viewvc?rev=831049&view=rev
Log:
Preliminary work towards using glyph vectors in TextInput; minor tweaks to TerraMeterSkin.

Added:
    incubator/pivot/trunk/wtk/test/org/apache/pivot/wtk/test/text_input_test.wtkx
Modified:
    incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/TextArea.java
    incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/TextInput.java
    incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextAreaSkin.java
    incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/terra/TerraMeterSkin.java
    incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/terra/TerraTextInputSkin.java

Modified: incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/TextArea.java
URL: http://svn.apache.org/viewvc/incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/TextArea.java?rev=831049&r1=831048&r2=831049&view=diff
==============================================================================
--- incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/TextArea.java (original)
+++ incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/TextArea.java Thu Oct 29 17:57:38 2009
@@ -149,13 +149,14 @@
         }
     }
 
-    private Document document = null;
-    private boolean editable = true;
-    private String textKey = null;
+    private Document document;
 
     private int selectionStart = 0;
     private int selectionLength = 0;
 
+    private boolean editable = true;
+    private String textKey = null;
+
     private NodeListener documentListener = new NodeListener() {
         @Override
         public void parentChanged(Node node, Element previousParent) {
@@ -276,155 +277,6 @@
         setDocument(document);
     }
 
-    /**
-     * 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;
-            textAreaListeners.textKeyChanged(this, previousTextKey);
-        }
-    }
-
-    @Override
-    public void load(Dictionary<String, ?> context) {
-        if (textKey != null
-            && JSONSerializer.containsKey(context, textKey)) {
-            Object value = JSONSerializer.get(context, textKey);
-            if (value != null) {
-                value = value.toString();
-            }
-
-            setText((String)value);
-        }
-    }
-
-    @Override
-    public void store(Dictionary<String, ?> context) {
-        if (isEnabled()
-            && textKey != null) {
-            JSONSerializer.put(context, textKey, getText());
-        }
-    }
-
-    @Override
-    public void clear() {
-        if (textKey != null) {
-            setText(null);
-        }
-    }
-
-    /**
-     * 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 input'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 (document == null
-            || document.getCharacterCount() == 0) {
-            throw new IllegalStateException();
-        }
-
-        if (selectionLength < 0) {
-            throw new IllegalArgumentException("selectionLength is negative.");
-        }
-
-        if (selectionStart < 0
-            || selectionStart + selectionLength > document.getCharacterCount()) {
-            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);
-        }
-    }
-
     public void insertText(char character) {
         // TODO Don't make every character undoable; break at word boundaries?
 
@@ -633,31 +485,21 @@
             throw new IllegalStateException();
         }
 
-        if (selectionLength > 0) {
-            // Copy selection to clipboard
-            Document selection = (Document)document.getRange(selectionStart, selectionLength);
+        String selectedText = getSelectedText();
 
-            String selectedText = null;
-            try {
-                PlainTextSerializer serializer = new PlainTextSerializer();
-                StringWriter writer = new StringWriter();
-                serializer.writeObject(selection, writer);
-                selectedText = writer.toString();
-            } catch(SerializationException exception) {
-                throw new RuntimeException(exception);
-            } catch(IOException exception) {
-                throw new RuntimeException(exception);
-            }
-
-            if (selectedText != null) {
-                LocalManifest clipboardContent = new LocalManifest();
-                clipboardContent.putText(selectedText);
-                Clipboard.setContent(clipboardContent);
-            }
+        if (selectedText != null) {
+            LocalManifest clipboardContent = new LocalManifest();
+            clipboardContent.putText(selectedText);
+            Clipboard.setContent(clipboardContent);
         }
     }
 
     public void paste() {
+        if (document == null
+            || document.getCharacterCount() == 0) {
+            throw new IllegalStateException();
+        }
+
         Manifest clipboardContent = Clipboard.getContent();
 
         if (clipboardContent != null
@@ -692,6 +534,201 @@
         // 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 input'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 (document == null
+            || document.getCharacterCount() == 0) {
+            throw new IllegalStateException();
+        }
+
+        if (selectionLength < 0) {
+            throw new IllegalArgumentException("selectionLength is negative.");
+        }
+
+        if (selectionStart < 0
+            || selectionStart + selectionLength > document.getCharacterCount()) {
+            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);
+        }
+    }
+
+    /**
+     * Selects all text.
+     */
+    public void selectAll() {
+        if (document == null) {
+            throw new IllegalStateException();
+        }
+
+        setSelection(0, document.getCharacterCount());
+    }
+
+    /**
+     * Clears the selection.
+     */
+    public void clearSelection() {
+        setSelection(0, 0);
+    }
+
+    /**
+     * Returns the currently selected text.
+     *
+     * @return
+     * A new string containing a copy of the text in the selected range, or
+     * <tt>null</tt> if nothing is selected.
+     */
+    public String getSelectedText() {
+        String selectedText = null;
+
+        if (selectionLength > 0) {
+            Document selection = (Document)document.getRange(selectionStart, selectionLength);
+
+            try {
+                PlainTextSerializer serializer = new PlainTextSerializer();
+                StringWriter writer = new StringWriter();
+                serializer.writeObject(selection, writer);
+                selectedText = writer.toString();
+            } catch(SerializationException exception) {
+                throw new RuntimeException(exception);
+            } catch(IOException exception) {
+                throw new RuntimeException(exception);
+            }
+        }
+
+        return selectedText;
+    }
+
+    /**
+     * 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;
+            textAreaListeners.textKeyChanged(this, previousTextKey);
+        }
+    }
+
+    @Override
+    public void load(Dictionary<String, ?> context) {
+        if (textKey != null
+            && JSONSerializer.containsKey(context, textKey)) {
+            Object value = JSONSerializer.get(context, textKey);
+            if (value != null) {
+                value = value.toString();
+            }
+
+            setText((String)value);
+        }
+    }
+
+    @Override
+    public void store(Dictionary<String, ?> context) {
+        if (isEnabled()
+            && textKey != null) {
+            JSONSerializer.put(context, textKey, getText());
+        }
+    }
+
+    @Override
+    public void clear() {
+        if (textKey != null) {
+            setText(null);
+        }
+    }
+
     public int getInsertionPoint(int x, int y) {
         TextArea.Skin textAreaSkin = (TextArea.Skin)getSkin();
         return textAreaSkin.getInsertionPoint(x, y);

Modified: incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/TextInput.java
URL: http://svn.apache.org/viewvc/incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/TextInput.java?rev=831049&r1=831048&r2=831049&view=diff
==============================================================================
--- incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/TextInput.java (original)
+++ incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/TextInput.java Thu Oct 29 17:57:38 2009
@@ -33,8 +33,33 @@
  */
 public class TextInput extends Component {
     /**
-     * Text input listener list.
+     * Text input skin interface. Text input skins are required to implement
+     * this.
      */
+    public interface Skin {
+        /**
+         * Returns the insertion point for a given location.
+         *
+         * @param x
+         * @param y
+         *
+         * @return
+         * The insertion point for the given location.
+         */
+        public int getInsertionPoint(int x, int y);
+
+        /**
+         * Returns the bounds of the character at a given offset within the
+         * document.
+         *
+         * @param offset
+         *
+         * @return
+         * The bounds of the character at the given offset.
+         */
+        public Bounds getCharacterBounds(int offset);
+    }
+
     private static class TextInputListenerList extends ListenerList<TextInputListener>
         implements TextInputListener {
         @Override
@@ -94,9 +119,6 @@
         }
     }
 
-    /**
-     * Text input text listener list.
-     */
     private static class TextInputTextListenerList extends ListenerList<TextInputTextListener>
         implements TextInputTextListener {
         @Override
@@ -107,9 +129,6 @@
         }
     }
 
-    /**
-     * Text input character listener list.
-     */
     private static class TextInputCharacterListenerList extends ListenerList<TextInputCharacterListener>
         implements TextInputCharacterListener {
         @Override
@@ -127,9 +146,6 @@
         }
     }
 
-    /**
-     * Text input selection listener list.
-     */
     private static class TextInputSelectionListenerList extends ListenerList<TextInputSelectionListener>
         implements TextInputSelectionListener {
         @Override
@@ -142,15 +158,19 @@
         }
     }
 
-    private TextNode textNode = null;
+    private TextNode textNode;
 
     private int selectionStart = 0;
     private int selectionLength = 0;
+
     private int textSize = DEFAULT_TEXT_SIZE;
     private int maximumLength = Integer.MAX_VALUE;
+
     private boolean password = false;
     private String prompt = null;
+
     private String textKey = null;
+
     private Validator validator = null;
     private boolean textValid = true;
 
@@ -175,7 +195,7 @@
 
             textInputCharacterListeners.charactersInserted(TextInput.this, offset, characterCount);
             textInputTextListeners.textChanged(TextInput.this);
-            updateTextValid();
+            validateText();
         }
 
         @Override
@@ -190,7 +210,7 @@
 
             textInputCharacterListeners.charactersRemoved(TextInput.this, offset, characterCount);
             textInputTextListeners.textChanged(TextInput.this);
-            updateTextValid();
+            validateText();
         }
     };
 
@@ -199,11 +219,21 @@
     private TextInputCharacterListenerList textInputCharacterListeners = new TextInputCharacterListenerList();
     private TextInputSelectionListenerList textInputSelectionListeners = new TextInputSelectionListenerList();
 
-    private static final int DEFAULT_TEXT_SIZE = 20;
+    public static final int DEFAULT_TEXT_SIZE = 20;
 
     public TextInput() {
-        setTextNode(new TextNode());
         installThemeSkin(TextInput.class);
+        setText("");
+    }
+
+    @Override
+    protected void setSkin(org.apache.pivot.wtk.Skin skin) {
+        if (!(skin instanceof TextInput.Skin)) {
+            throw new IllegalArgumentException("Skin class must implement "
+                + TextInput.Skin.class.getName());
+        }
+
+        super.setSkin(skin);
     }
 
     public TextNode getTextNode() {
@@ -211,11 +241,8 @@
     }
 
     public void setTextNode(TextNode textNode) {
-        if (textNode == null) {
-            throw new IllegalArgumentException("textNode is null.");
-        }
-
-        if (textNode.getCharacterCount() > maximumLength) {
+        if (textNode != null
+            && textNode.getCharacterCount() > maximumLength) {
             throw new IllegalArgumentException("Text length is greater than maximum length.");
         }
 
@@ -226,9 +253,10 @@
                 previousTextNode.getNodeListeners().remove(textNodeListener);
             }
 
-            textNode.getNodeListeners().add(textNodeListener);
+            if (textNode != null) {
+                textNode.getNodeListeners().add(textNodeListener);
+            }
 
-            // Clear the selection
             this.textNode = textNode;
 
             selectionStart = 0;
@@ -236,20 +264,17 @@
 
             textInputListeners.textNodeChanged(this, previousTextNode);
             textInputTextListeners.textChanged(this);
-            updateTextValid();
+
+            validateText();
         }
     }
 
     public String getText() {
-        return textNode.getText();
+        return (textNode == null) ? null : textNode.getText();
     }
 
     public void setText(String text) {
-        if (text == null) {
-            throw new IllegalArgumentException("text is null.");
-        }
-
-        setTextNode(new TextNode(text));
+        setTextNode((text == null) ? null : new TextNode(text));
     }
 
     /**
@@ -279,6 +304,14 @@
      * content.
      */
     public void insertText(String text, int index) {
+        if (textNode == null) {
+            throw new IllegalStateException();
+        }
+
+        if (text == null) {
+            throw new IllegalArgumentException("text is null.");
+        }
+
         if (index < 0
             || index > textNode.getCharacterCount()) {
             throw new IndexOutOfBoundsException();
@@ -305,10 +338,14 @@
     }
 
     public int getTextLength() {
-        return textNode.getCharacterCount();
+        return (textNode == null) ? -1 : textNode.getCharacterCount();
     }
 
     public void delete(Direction direction) {
+        if (textNode == null) {
+            throw new IllegalStateException();
+        }
+
         if (direction == null) {
             throw new IllegalArgumentException("direction is null.");
         }
@@ -332,6 +369,10 @@
     }
 
     public void cut() {
+        if (textNode == null) {
+            throw new IllegalStateException();
+        }
+
         // Delete any selected text and put it on the clipboard
         if (selectionLength > 0) {
             TextNode removedRange =
@@ -344,6 +385,10 @@
     }
 
     public void copy() {
+        if (textNode == null) {
+            throw new IllegalStateException();
+        }
+
         // Copy selection to clipboard
         String selectedText = getSelectedText();
 
@@ -355,6 +400,10 @@
     }
 
     public void paste() {
+        if (textNode == null) {
+            throw new IllegalStateException();
+        }
+
         Manifest clipboardContent = Clipboard.getContent();
 
         if (clipboardContent != null
@@ -545,9 +594,11 @@
 
         if (previousMaximumLength != maximumLength) {
             // Truncate the text, if necessary
-            int characterCount = textNode.getCharacterCount();
-            if (characterCount > maximumLength) {
-                textNode.removeText(maximumLength, characterCount - maximumLength);
+            if (textNode != null) {
+                int characterCount = textNode.getCharacterCount();
+                if (characterCount > maximumLength) {
+                    textNode.removeText(maximumLength, characterCount - maximumLength);
+                }
             }
 
             this.maximumLength = maximumLength;
@@ -656,6 +707,15 @@
         }
     }
 
+    public int getInsertionPoint(int x, int y) {
+        TextInput.Skin textInputSkin = (TextInput.Skin)getSkin();
+        return textInputSkin.getInsertionPoint(x, y);
+    }
+
+    public Bounds getCharacterBounds(int offset) {
+        TextInput.Skin textInputSkin = (TextInput.Skin)getSkin();
+        return textInputSkin.getCharacterBounds(offset);
+    }
 
     /**
      * Tells whether or not this text input's text is currently valid as
@@ -667,20 +727,6 @@
     }
 
     /**
-     * Updates the <tt>textValid</tt> flag and notifies listeners if the flag's
-     * value has changed. It is the responsibility of methods to call this
-     * method when the validity of the text may have changed.
-     */
-    private void updateTextValid() {
-        boolean textValid = (validator == null ? true : validator.isValid(getText()));
-
-        if (textValid != this.textValid) {
-            this.textValid = textValid;
-            textInputListeners.textValidChanged(this);
-        }
-    }
-
-    /**
      * Gets the validator associated with this text input.
      */
     public Validator getValidator() {
@@ -699,7 +745,21 @@
         if (validator != previousValidator) {
             this.validator = validator;
             textInputListeners.textValidatorChanged(this, previousValidator);
-            updateTextValid();
+            validateText();
+        }
+    }
+
+    /**
+     * Updates the valid state after the text or the validator has changed.
+     */
+    private void validateText() {
+        String text = getText();
+        boolean textValid = (validator == null
+            || text == null) ? true : validator.isValid(text);
+
+        if (textValid != this.textValid) {
+            this.textValid = textValid;
+            textInputListeners.textValidChanged(this);
         }
     }
 

Modified: incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextAreaSkin.java
URL: http://svn.apache.org/viewvc/incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextAreaSkin.java?rev=831049&r1=831048&r2=831049&view=diff
==============================================================================
--- incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextAreaSkin.java (original)
+++ incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/TextAreaSkin.java Thu Oct 29 17:57:38 2009
@@ -1023,6 +1023,7 @@
                 }
 
                 // Draw text
+                // TODO If all of the text is selected, don't bother doing this
                 graphics.setFont(font);
                 graphics.setPaint(color);
 

Modified: incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/terra/TerraMeterSkin.java
URL: http://svn.apache.org/viewvc/incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/terra/TerraMeterSkin.java?rev=831049&r1=831048&r2=831049&view=diff
==============================================================================
--- incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/terra/TerraMeterSkin.java (original)
+++ incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/terra/TerraMeterSkin.java Thu Oct 29 17:57:38 2009
@@ -59,9 +59,9 @@
         fillColor = theme.getColor(16);
         gridColor = theme.getColor(10);
         gridFrequency = 0.25f;
-        font = theme.getFont();
-        textColor = theme.getColor(16);
-        textFillColor = theme.getColor(1);
+        font = theme.getFont().deriveFont(Font.BOLD);
+        textColor = theme.getColor(1);
+        textFillColor = theme.getColor(4);
     }
 
     @Override
@@ -102,17 +102,18 @@
     public int getPreferredHeight(int width) {
         Meter meter = (Meter)getComponent();
         String text = meter.getText();
-        
+
         int preferredHeight = 0;
-        if (text!=null && text.length()>0) {
+        if (text != null
+            && text.length() > 0) {
             LineMetrics lm = font.getLineMetrics("", FONT_RENDER_CONTEXT);
             preferredHeight = (int)Math.ceil(lm.getHeight()) + 2;
         }
-        
+
         // If Meter has no content, its preferred height is hard coded in the
         // class and is not affected by the width constraint.
         preferredHeight = Math.max(preferredHeight, DEFAULT_HEIGHT);
-        
+
         return preferredHeight;
     }
 
@@ -120,35 +121,39 @@
     public Dimensions getPreferredSize() {
         Meter meter = (Meter)getComponent();
         String text = meter.getText();
-        
+
         int preferredWidth = 0;
         int preferredHeight = 0;
-        if (text!=null && text.length()>0) {
+        if (text != null
+            && text.length() > 0) {
             Rectangle2D stringBounds = font.getStringBounds(text, FONT_RENDER_CONTEXT);
             preferredWidth = (int)Math.ceil(stringBounds.getWidth()) + 2;
             LineMetrics lm = font.getLineMetrics("", FONT_RENDER_CONTEXT);
             preferredHeight = (int)Math.ceil(lm.getHeight()) + 2;
         }
-        
+
         // If Meter has no content, its preferred size is hard coded in the class.
         preferredWidth = Math.max(preferredWidth, DEFAULT_WIDTH);
         preferredHeight = Math.max(preferredHeight, DEFAULT_HEIGHT);
-        
+
         return new Dimensions(preferredWidth, preferredHeight);
     }
 
     @Override
     public int getBaseline(int width) {
+        int baseline = -1;
+
         Meter meter = (Meter)getComponent();
         String text = meter.getText();
-        if (text!=null && text.length()>0) {
+        if (text != null
+            && text.length() > 0) {
             LineMetrics lm = font.getLineMetrics("", FONT_RENDER_CONTEXT);
-            return (int)Math.ceil(lm.getAscent() - 2);
-        } else {
-            return -1;
+            baseline = (int)Math.ceil(lm.getAscent() - 2);
         }
+
+        return baseline;
     }
-    
+
     @Override
     public void layout() {
         // No-op
@@ -172,7 +177,7 @@
             graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
                 RenderingHints.VALUE_FRACTIONALMETRICS_ON);
         }
-        
+
         // Paint the interior fill
         graphics.setPaint(new GradientPaint(0, 0, TerraTheme.brighten(fillColor),
             0, height, TerraTheme.darken(fillColor)));
@@ -195,7 +200,7 @@
             Rectangle2D stringBounds = font.getStringBounds(text, FONT_RENDER_CONTEXT);
             int textWidth = (int)Math.ceil(stringBounds.getWidth());
             int textX = (width - textWidth - 2) / 2 + 1;
-            
+
             // Paint the text
             Shape previousClip = graphics.getClip();
             graphics.clipRect(0, 0, meterStop, height);
@@ -261,7 +266,7 @@
 
         setTextColor(GraphicsUtilities.decodeColor(color));
     }
-    
+
     public Color getTextFillColor() {
         return textFillColor;
     }
@@ -278,7 +283,7 @@
 
         setTextFillColor(GraphicsUtilities.decodeColor(color));
     }
-    
+
     public float getGridFrequency() {
         return gridFrequency;
     }
@@ -327,7 +332,7 @@
 
         setFont(Theme.deriveFont(font));
     }
-    
+
     /**
      * Listener for meter percentage changes.
      *

Modified: incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/terra/TerraTextInputSkin.java
URL: http://svn.apache.org/viewvc/incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/terra/TerraTextInputSkin.java?rev=831049&r1=831048&r2=831049&view=diff
==============================================================================
--- incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/terra/TerraTextInputSkin.java (original)
+++ incubator/pivot/trunk/wtk/src/org/apache/pivot/wtk/skin/terra/TerraTextInputSkin.java Thu Oct 29 17:57:38 2009
@@ -20,17 +20,20 @@
 import java.awt.Color;
 import java.awt.Font;
 import java.awt.Graphics2D;
+import java.awt.Rectangle;
 import java.awt.RenderingHints;
 import java.awt.Shape;
 import java.awt.Toolkit;
 import java.awt.font.FontRenderContext;
+import java.awt.font.GlyphVector;
 import java.awt.font.LineMetrics;
-import java.awt.font.TextHitInfo;
-import java.awt.font.TextLayout;
 import java.awt.geom.Rectangle2D;
+import java.text.CharacterIterator;
+import java.text.StringCharacterIterator;
 
 import org.apache.pivot.collections.Dictionary;
 import org.apache.pivot.wtk.ApplicationContext;
+import org.apache.pivot.wtk.Bounds;
 import org.apache.pivot.wtk.Component;
 import org.apache.pivot.wtk.Cursor;
 import org.apache.pivot.wtk.Dimensions;
@@ -54,27 +57,17 @@
 /**
  * Text input skin.
  */
-public class TerraTextInputSkin extends ComponentSkin
-    implements TextInputListener, TextInputCharacterListener, TextInputSelectionListener {
+public class TerraTextInputSkin extends ComponentSkin implements TextInput.Skin,
+    TextInputListener, TextInputCharacterListener, TextInputSelectionListener {
     private class BlinkCaretCallback implements Runnable {
         @Override
         public void run() {
             caretOn = !caretOn;
 
-            java.awt.Rectangle caretBounds = caretShapes[0].getBounds();
-            LineMetrics lm = font.getLineMetrics("", FONT_RENDER_CONTEXT);
-
-            int ascent = Math.round(lm.getAscent());
-            caretBounds.x += (padding.left - scrollLeft + 1);
-            caretBounds.y += (padding.top + ascent + 1);
-
-            if (caretBounds.width == 0) {
-                caretBounds.width++;
+            if (caret != null) {
+                TextInput textInput = (TextInput)getComponent();
+                textInput.repaint(caret.x, caret.y, caret.width, caret.height, true);
             }
-
-            TextInput textInput = (TextInput)getComponent();
-            textInput.repaint(caretBounds.x, caretBounds.y,
-                caretBounds.width, caretBounds.height, true);
         }
     }
 
@@ -104,11 +97,16 @@
         }
     }
 
+    private GlyphVector glyphVector = null;
+    private boolean showPrompt = false;
+
     private boolean caretOn = true;
-    private Shape[] caretShapes = null;
-    private Shape logicalHighlightShape = null;
+    private Rectangle caret = new Rectangle();
+    private Rectangle selection = null;
 
     private int scrollLeft = 0;
+
+    // TODO Use an anchor and a scroll direction like TextArea
     private int scrollX = 0;
 
     private BlinkCaretCallback blinkCaretCallback = new BlinkCaretCallback();
@@ -139,12 +137,16 @@
     private Color disabledBevelColor;
     private Color invalidBevelColor;
 
+    private Dimensions averageCharacterSize;
+
     private static final FontRenderContext FONT_RENDER_CONTEXT = new FontRenderContext(null, true, false);
     private static final int SCROLL_RATE = 50;
+    private static final char BULLET = 0x2022;
 
     public TerraTextInputSkin() {
         TerraTheme theme = (TerraTheme)Theme.getTheme();
-        font = theme.getFont();
+        setFont(theme.getFont());
+
         color = theme.getColor(1);
         promptColor = theme.getColor(7);
         disabledColor = theme.getColor(7);
@@ -162,7 +164,6 @@
         inactiveSelectionColor = theme.getColor(1);
         inactiveSelectionBackgroundColor = theme.getColor(9);
 
-        // Set the derived colors
         bevelColor = TerraTheme.darken(backgroundColor);
         disabledBevelColor = disabledBackgroundColor;
         invalidBevelColor = TerraTheme.darken(invalidBackgroundColor);
@@ -179,7 +180,10 @@
 
         textInput.setCursor(Cursor.TEXT);
 
-        selectionChanged(textInput, 0, 0);
+        TextNode textNode = textInput.getTextNode();
+        if (textNode != null) {
+            updateSelection();
+        }
     }
 
     @Override
@@ -187,22 +191,13 @@
         TextInput textInput = (TextInput)getComponent();
         int textSize = textInput.getTextSize();
 
-        // TODO Use the missing character glyph bounds (see Font#getMissingGlyphCode())
-        // rather than calculating an average width
-        String testString = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz";
-
-        Rectangle2D testStringBounds = font.getStringBounds(testString, FONT_RENDER_CONTEXT);
-        int averageCharWidth = (int)Math.round((testStringBounds.getWidth() / testString.length()));
-
-        return textSize * averageCharWidth + (padding.left + padding.right) + 2;
+        return (int)Math.ceil(averageCharacterSize.width * textSize)
+            + (padding.left + padding.right) + 2;
     }
 
     @Override
     public int getPreferredHeight(int width) {
-        Rectangle2D maxCharBounds = font.getMaxCharBounds(FONT_RENDER_CONTEXT);
-        int maxCharHeight = (int)Math.ceil(maxCharBounds.getHeight());
-
-        return maxCharHeight + (padding.top + padding.bottom) + 2;
+        return averageCharacterSize.height + (padding.top + padding.bottom) + 2;
     }
 
     @Override
@@ -218,7 +213,44 @@
 
     @Override
     public void layout() {
-        // No-op
+        TextInput textInput = (TextInput)getComponent();
+        TextNode textNode = textInput.getTextNode();
+
+        glyphVector = null;
+        showPrompt = false;
+
+        if (textNode != null) {
+            // Construct the glyph vector (using password characters or prompt text as
+            // appropriate)
+            int n = textNode.getCharacterCount();
+
+            CharacterIterator ci = null;
+            if (textInput.isPassword()) {
+                StringBuilder buf = new StringBuilder(n);
+                for (int i = 0; i < n; i++) {
+                    buf.append(BULLET);
+                }
+
+                ci = new StringCharacterIterator(buf.toString());
+            } else {
+                if (n > 0) {
+                    ci= textNode.getCharacterIterator();
+                } else {
+                    String prompt = textInput.getPrompt();
+
+                    if (prompt != null) {
+                        ci = new StringCharacterIterator(prompt);
+                        showPrompt = true;
+                    }
+                }
+            }
+
+            if (ci != null) {
+                glyphVector = font.createGlyphVector(FONT_RENDER_CONTEXT, ci);
+            }
+        }
+
+        updateSelection();
     }
 
     @Override
@@ -267,29 +299,9 @@
         GraphicsUtilities.drawRect(graphics, 0, 0, width, height);
 
         // Paint the content
-        String text = getText();
-
-        boolean prompt = false;
-        if (text.length() == 0
-            && !textInput.isFocused()) {
-            text = textInput.getPrompt();
-
-            if (text == null) {
-                text = "";
-            } else {
-                prompt = true;
-            }
-        }
-
         boolean textValid = textInput.isTextValid();
 
-        LineMetrics lm = font.getLineMetrics("", FONT_RENDER_CONTEXT);
-        int ascent = Math.round(lm.getAscent());
-
-        graphics.translate(padding.left - scrollLeft + 1, padding.top + ascent + 1);
-
-        if (text.length() > 0) {
-            // Paint the text
+        if (glyphVector != null) {
             if (FONT_RENDER_CONTEXT.isAntiAliased()) {
                 graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
                     Platform.getTextAntialiasingHint());
@@ -300,9 +312,12 @@
                     RenderingHints.VALUE_FRACTIONALMETRICS_ON);
             }
 
+            LineMetrics lm = font.getLineMetrics("", FONT_RENDER_CONTEXT);
+            int ascent = Math.round(lm.getAscent());
+
             Color color;
             if (textInput.isEnabled()) {
-                if (prompt) {
+                if (showPrompt) {
                     color = promptColor;
                 } else if (!textValid) {
                     color = invalidColor;
@@ -313,14 +328,15 @@
                color = disabledColor;
             }
 
+            // TODO If all of the text is selected, don't bother doing this
             graphics.setFont(font);
             graphics.setPaint(color);
-            graphics.drawString(text, 0, 0);
+            graphics.drawGlyphVector(glyphVector, padding.left - scrollLeft + 1, padding.top + ascent + 1);
 
             if (textInput.getSelectionLength() > 0) {
                 // Paint the selection
                 Graphics2D selectionGraphics = (Graphics2D)graphics.create();
-                selectionGraphics.clip(logicalHighlightShape.getBounds());
+                selectionGraphics.clip(selection.getBounds());
 
                 Color selectionColor;
                 Color selectionBackgroundColor;
@@ -334,10 +350,10 @@
                 }
 
                 selectionGraphics.setPaint(selectionBackgroundColor);
-                selectionGraphics.fill(logicalHighlightShape);
+                selectionGraphics.fill(selection);
 
                 selectionGraphics.setPaint(selectionColor);
-                selectionGraphics.drawString(text, 0, 0);
+                selectionGraphics.drawGlyphVector(glyphVector, padding.left - scrollLeft + 1, padding.top + ascent + 1);
 
                 selectionGraphics.dispose();
             }
@@ -350,60 +366,60 @@
             if (!textValid) {
                 color = invalidColor;
             } else {
-                color = this.color;
+                color = Color.BLACK;
             }
 
             graphics.setPaint(color);
-            graphics.draw(caretShapes[0]);
+            graphics.fill(caret);
         }
     }
 
-    protected String getText() {
-        TextInput textInput = (TextInput)getComponent();
+    public int getInsertionPoint(int x, int y) {
+        LineMetrics lm = font.getLineMetrics("", FONT_RENDER_CONTEXT);
+        float ascent = lm.getAscent();
 
-        // TODO Use the internal character iterator instead of getting a copy
-        // of the string
-        String text = textInput.getText();
+        // Translate to glyph coordinates
+        x -= (padding.left - scrollLeft + 1);
+        y -= (padding.top + 1);
 
-        if (textInput.isPassword()) {
-            int n = text.length();
-            StringBuilder passwordTextBuilder = new StringBuilder(n);
-            for (int i = 0; i < n; i++) {
-                passwordTextBuilder.append("*");
+        int n = glyphVector.getNumGlyphs();
+        int i = 0;
+
+        while (i < n) {
+            Shape glyphLogicalBounds = glyphVector.getGlyphLogicalBounds(i);
+
+            if (glyphLogicalBounds.contains(x, y - ascent)) {
+                Rectangle2D glyphBounds2D = glyphLogicalBounds.getBounds2D();
+
+                if (x - glyphBounds2D.getX() > glyphBounds2D.getWidth() / 2) {
+                    // The user clicked on the right half of the character; select
+                    // the next character
+                    i++;
+                }
+
+                break;
             }
 
-            text = passwordTextBuilder.toString();
+            i++;
         }
 
-        return text;
-    }
-
-    protected int getInsertionPoint(String text, int x) {
-        // TODO Rename to getInsertionPoint() and implement for consistency with
-        // TextArea (including skin pass-through method)
-        TextLayout textLayout = new TextLayout(text, font, FONT_RENDER_CONTEXT);
-        TextHitInfo textHitInfo = textLayout.hitTestChar(x + scrollLeft - padding.left - 1, 0);
-        int index = textHitInfo.getInsertionIndex();
+        if (i == n) {
+            i = -1;
+        }
 
-        return index;
+        return i;
     }
 
-    public void showCaret(boolean show) {
-        if (scheduledBlinkCaretCallback != null) {
-            scheduledBlinkCaretCallback.cancel();
-        }
+    public Bounds getCharacterBounds(int offset) {
+        Shape glyphLogicalBounds = glyphVector.getGlyphLogicalBounds(offset);
+        Rectangle2D bounds2D = glyphLogicalBounds.getBounds2D();
 
-        if (show) {
-            caretOn = true;
-            scheduledBlinkCaretCallback =
-                ApplicationContext.scheduleRecurringCallback(blinkCaretCallback,
-                    Platform.getCursorBlinkRate());
+        int x = (int)Math.floor(bounds2D.getX()) + padding.left - scrollLeft + 1;
+        int y = padding.top + 1;
+        int width = (int)Math.ceil(bounds2D.getWidth());
+        int height = getHeight() - (padding.top + padding.bottom + 2);
 
-            // Run the callback once now to show the cursor immediately
-            blinkCaretCallback.run();
-        } else {
-            scheduledBlinkCaretCallback = null;
-        }
+        return new Bounds(x, y, width, height);
     }
 
     public Font getFont() {
@@ -416,6 +432,16 @@
         }
 
         this.font = font;
+
+        int missingGlyphCode = font.getMissingGlyphCode();
+        GlyphVector missingGlyphVector = font.createGlyphVector(FONT_RENDER_CONTEXT,
+            new int[] {missingGlyphCode});
+        Rectangle2D logicalBounds = missingGlyphVector.getLogicalBounds();
+
+        Rectangle2D maxCharBounds = font.getMaxCharBounds(FONT_RENDER_CONTEXT);
+        averageCharacterSize = new Dimensions((int)Math.ceil(logicalBounds.getWidth()),
+            (int)Math.ceil(maxCharBounds.getHeight()));
+
         invalidateComponent();
     }
 
@@ -830,11 +856,11 @@
         boolean consumed = super.mouseMove(component, x, y);
 
         if (Mouse.getCapturer() == component) {
-            String text = getText();
-
-            if (text.length() > 0) {
-                TextInput textInput = (TextInput)getComponent();
+            TextInput textInput = (TextInput)getComponent();
+            TextNode textNode = textInput.getTextNode();
 
+            if (textNode != null
+                && textNode.getCharacterCount() > 0) {
                 if (x >= 0
                     && x < textInput.getWidth()) {
                     // Stop the scroll selection timer
@@ -848,7 +874,7 @@
                     int selectionLength = textInput.getSelectionLength();
 
                     // Get the insertion index
-                    int index = getInsertionPoint(text, x);
+                    int index = getInsertionPoint(x, y);
 
                     if (index < selectionStart) {
                         selectionLength += (selectionStart - index);
@@ -889,11 +915,11 @@
         if (button == Mouse.Button.LEFT) {
             // Move the caret to the insertion point
             TextInput textInput = (TextInput)getComponent();
-            String text = getText();
 
-            if (text.length() > 0) {
-                int index = getInsertionPoint(text, x);
-                textInput.setSelection(index, 0);
+            // TODO Use anchor here
+            int offset = getInsertionPoint(x, y);
+            if (offset != -1) {
+                textInput.setSelection(offset, 0);
             }
 
             // Set focus to the text input
@@ -926,7 +952,10 @@
             && count > 1) {
             TextInput textInput = (TextInput)getComponent();
             TextNode textNode = textInput.getTextNode();
-            textInput.setSelection(0, textNode.getCharacterCount());
+
+            if (textNode != null) {
+                textInput.setSelection(0, textNode.getCharacterCount());
+            }
         }
 
         return super.mouseClick(component, button, x, y, count);
@@ -943,25 +972,27 @@
             TextInput textInput = (TextInput)getComponent();
             TextNode textNode = textInput.getTextNode();
 
-            if (textNode.getCharacterCount() < textInput.getMaximumLength()) {
-                int index = textInput.getSelectionStart();
-                Validator validator = textInput.getValidator();
-
-                if (validator != null
-                    && strictValidation) {
-                    StringBuilder buf = new StringBuilder(textNode.getText());
-                    buf.insert(index, character);
-
-                    if (validator.isValid(buf.toString())) {
-                        textInput.insertText(character, index);
+            if (textNode != null) {
+                if (textNode.getCharacterCount() < textInput.getMaximumLength()) {
+                    int index = textInput.getSelectionStart();
+                    Validator validator = textInput.getValidator();
+
+                    if (validator != null
+                        && strictValidation) {
+                        StringBuilder buf = new StringBuilder(textNode.getText());
+                        buf.insert(index, character);
+
+                        if (validator.isValid(buf.toString())) {
+                            textInput.insertText(character, index);
+                        } else {
+                            Toolkit.getDefaultToolkit().beep();
+                        }
                     } else {
-                        Toolkit.getDefaultToolkit().beep();
+                        textInput.insertText(character, index);
                     }
                 } else {
-                    textInput.insertText(character, index);
+                    Toolkit.getDefaultToolkit().beep();
                 }
-            } else {
-                Toolkit.getDefaultToolkit().beep();
             }
         }
 
@@ -975,168 +1006,170 @@
         TextInput textInput = (TextInput)getComponent();
         TextNode textNode = textInput.getTextNode();
 
-        Keyboard.Modifier commandModifier = Platform.getCommandModifier();
-        if (keyCode == Keyboard.KeyCode.DELETE
-            || keyCode == Keyboard.KeyCode.BACKSPACE) {
-            consumed = true;
-
-            Direction direction = (keyCode == Keyboard.KeyCode.DELETE ?
-                Direction.FORWARD : Direction.BACKWARD);
-
-            Validator validator = textInput.getValidator();
-
-            if (validator != null
-                && strictValidation) {
-                StringBuilder buf = new StringBuilder(textNode.getText());
-                int index = textInput.getSelectionStart();
-                int count = textInput.getSelectionLength();
+        if (textNode != null) {
+            Keyboard.Modifier commandModifier = Platform.getCommandModifier();
+            if (keyCode == Keyboard.KeyCode.DELETE
+                || keyCode == Keyboard.KeyCode.BACKSPACE) {
+                consumed = true;
 
-                if (count > 0) {
-                    buf.delete(index, index + count);
-                } else {
-                    if (direction == Direction.BACKWARD) {
-                        index--;
-                    }
+                Direction direction = (keyCode == Keyboard.KeyCode.DELETE ?
+                    Direction.FORWARD : Direction.BACKWARD);
+
+                Validator validator = textInput.getValidator();
+
+                if (validator != null
+                    && strictValidation) {
+                    StringBuilder buf = new StringBuilder(textNode.getText());
+                    int index = textInput.getSelectionStart();
+                    int count = textInput.getSelectionLength();
 
-                    if (index >= 0
-                        && index < textNode.getCharacterCount()) {
-                        buf.deleteCharAt(index);
+                    if (count > 0) {
+                        buf.delete(index, index + count);
+                    } else {
+                        if (direction == Direction.BACKWARD) {
+                            index--;
+                        }
+
+                        if (index >= 0
+                            && index < textNode.getCharacterCount()) {
+                            buf.deleteCharAt(index);
+                        }
                     }
-                }
 
-                if (validator.isValid(buf.toString())) {
-                    textInput.delete(direction);
+                    if (validator.isValid(buf.toString())) {
+                        textInput.delete(direction);
+                    } else {
+                        Toolkit.getDefaultToolkit().beep();
+                    }
                 } else {
-                    Toolkit.getDefaultToolkit().beep();
+                    textInput.delete(direction);
                 }
-            } else {
-                textInput.delete(direction);
-            }
-        } else if (keyCode == Keyboard.KeyCode.LEFT) {
-            consumed = true;
+            } else if (keyCode == Keyboard.KeyCode.LEFT) {
+                consumed = true;
 
-            int selectionStart = textInput.getSelectionStart();
-            int selectionLength = textInput.getSelectionLength();
+                int selectionStart = textInput.getSelectionStart();
+                int selectionLength = textInput.getSelectionLength();
 
-            if (Keyboard.isPressed(Keyboard.Modifier.SHIFT)
-                && Keyboard.isPressed(Keyboard.Modifier.CTRL)) {
-                // Add all preceding text to the selection
-                selectionLength = selectionStart + selectionLength;
-                selectionStart = 0;
-            } else if (Keyboard.isPressed(Keyboard.Modifier.SHIFT)) {
-                // Add the previous character to the selection
-                if (selectionStart > 0) {
-                    selectionStart--;
-                    selectionLength++;
-                }
-            } else if (Keyboard.isPressed(Keyboard.Modifier.CTRL)) {
-                // Clear the selection and move the caret to the beginning of
-                // the text
-                selectionStart = 0;
-                selectionLength = 0;
-            } else {
-                // Clear the selection and move the caret back by one
-                // character
-                if (selectionLength == 0) {
+                if (Keyboard.isPressed(Keyboard.Modifier.SHIFT)
+                    && Keyboard.isPressed(Keyboard.Modifier.CTRL)) {
+                    // Add all preceding text to the selection
+                    selectionLength = selectionStart + selectionLength;
+                    selectionStart = 0;
+                } else if (Keyboard.isPressed(Keyboard.Modifier.SHIFT)) {
+                    // Add the previous character to the selection
                     if (selectionStart > 0) {
                         selectionStart--;
-                    } else {
-                        consumed = false;
+                        selectionLength++;
+                    }
+                } else if (Keyboard.isPressed(Keyboard.Modifier.CTRL)) {
+                    // Clear the selection and move the caret to the beginning of
+                    // the text
+                    selectionStart = 0;
+                    selectionLength = 0;
+                } else {
+                    // Clear the selection and move the caret back by one
+                    // character
+                    if (selectionLength == 0) {
+                        if (selectionStart > 0) {
+                            selectionStart--;
+                        } else {
+                            consumed = false;
+                        }
                     }
-                }
 
-                selectionLength = 0;
-            }
+                    selectionLength = 0;
+                }
 
-            textInput.setSelection(selectionStart, selectionLength);
-        } else if (keyCode == Keyboard.KeyCode.RIGHT) {
-            consumed = true;
+                textInput.setSelection(selectionStart, selectionLength);
+            } else if (keyCode == Keyboard.KeyCode.RIGHT) {
+                consumed = true;
 
-            int selectionStart = textInput.getSelectionStart();
-            int selectionLength = textInput.getSelectionLength();
+                int selectionStart = textInput.getSelectionStart();
+                int selectionLength = textInput.getSelectionLength();
 
-            if (Keyboard.isPressed(Keyboard.Modifier.SHIFT)
-                && Keyboard.isPressed(Keyboard.Modifier.CTRL)) {
-                // Add all subsequent text to the selection
-                selectionLength = textNode.getCharacterCount() - selectionStart;
-            } else if (Keyboard.isPressed(Keyboard.Modifier.SHIFT)) {
-                // Add the next character to the selection
-                if (selectionStart + selectionLength < textNode.getCharacterCount()) {
-                    selectionLength++;
-                }
-            } else if (Keyboard.isPressed(Keyboard.Modifier.CTRL)) {
-                // Clear the selection and move the caret to the end of
-                // the text
-                selectionStart = textNode.getCharacterCount();
-                selectionLength = 0;
-            } else {
-                // Clear the selection and move the caret forward by one
-                // character
-                selectionStart += selectionLength;
-
-                if (selectionLength == 0) {
-                    if (selectionStart < textNode.getCharacterCount()) {
-                        selectionStart++;
-                    } else {
-                        consumed = false;
+                if (Keyboard.isPressed(Keyboard.Modifier.SHIFT)
+                    && Keyboard.isPressed(Keyboard.Modifier.CTRL)) {
+                    // Add all subsequent text to the selection
+                    selectionLength = textNode.getCharacterCount() - selectionStart;
+                } else if (Keyboard.isPressed(Keyboard.Modifier.SHIFT)) {
+                    // Add the next character to the selection
+                    if (selectionStart + selectionLength < textNode.getCharacterCount()) {
+                        selectionLength++;
                     }
+                } else if (Keyboard.isPressed(Keyboard.Modifier.CTRL)) {
+                    // Clear the selection and move the caret to the end of
+                    // the text
+                    selectionStart = textNode.getCharacterCount();
+                    selectionLength = 0;
+                } else {
+                    // Clear the selection and move the caret forward by one
+                    // character
+                    selectionStart += selectionLength;
+
+                    if (selectionLength == 0) {
+                        if (selectionStart < textNode.getCharacterCount()) {
+                            selectionStart++;
+                        } else {
+                            consumed = false;
+                        }
+                    }
+
+                    selectionLength = 0;
                 }
 
-                selectionLength = 0;
-            }
+                textInput.setSelection(selectionStart, selectionLength);
+            } else if (keyCode == Keyboard.KeyCode.HOME) {
+                // Move the caret to the beginning of the text
+                if (Keyboard.isPressed(Keyboard.Modifier.SHIFT)) {
+                    textInput.setSelection(0, textInput.getSelectionStart());
+                } else {
+                    textInput.setSelection(0, 0);
+                }
 
-            textInput.setSelection(selectionStart, selectionLength);
-        } else if (keyCode == Keyboard.KeyCode.HOME) {
-            // Move the caret to the beginning of the text
-            if (Keyboard.isPressed(Keyboard.Modifier.SHIFT)) {
-                textInput.setSelection(0, textInput.getSelectionStart());
-            } else {
-                textInput.setSelection(0, 0);
-            }
+                consumed = true;
+            } else if (keyCode == Keyboard.KeyCode.END) {
+                // Move the caret to the end of the text
+                if (Keyboard.isPressed(Keyboard.Modifier.SHIFT)) {
+                    int selectionStart = textInput.getSelectionStart();
+                    textInput.setSelection(selectionStart, textNode.getCharacterCount()
+                        - selectionStart);
+                } else {
+                    textInput.setSelection(textNode.getCharacterCount(), 0);
+                }
 
-            consumed = true;
-        } else if (keyCode == Keyboard.KeyCode.END) {
-            // Move the caret to the end of the text
-            if (Keyboard.isPressed(Keyboard.Modifier.SHIFT)) {
-                int selectionStart = textInput.getSelectionStart();
-                textInput.setSelection(selectionStart, textNode.getCharacterCount()
-                    - selectionStart);
-            } else {
-                textInput.setSelection(textNode.getCharacterCount(), 0);
-            }
+                consumed = true;
+            } else if (keyCode == Keyboard.KeyCode.A
+                && Keyboard.isPressed(commandModifier)) {
+                consumed = true;
+
+                // Select all
+                textInput.setSelection(0, textNode.getCharacterCount());
+            } else if (keyCode == Keyboard.KeyCode.X
+                && Keyboard.isPressed(commandModifier)) {
+                consumed = true;
 
-            consumed = true;
-        } else if (keyCode == Keyboard.KeyCode.A
-            && Keyboard.isPressed(commandModifier)) {
-            consumed = true;
-
-            // Select all
-            textInput.setSelection(0, textNode.getCharacterCount());
-        } else if (keyCode == Keyboard.KeyCode.X
-            && Keyboard.isPressed(commandModifier)) {
-            consumed = true;
+                if (textInput.isPassword()) {
+                    Toolkit.getDefaultToolkit().beep();
+                } else {
+                    textInput.cut();
+                }
+            } else if (keyCode == Keyboard.KeyCode.C
+                && Keyboard.isPressed(commandModifier)) {
+                consumed = true;
 
-            if (textInput.isPassword()) {
-                Toolkit.getDefaultToolkit().beep();
-            } else {
-                textInput.cut();
-            }
-        } else if (keyCode == Keyboard.KeyCode.C
-            && Keyboard.isPressed(commandModifier)) {
-            consumed = true;
+                if (textInput.isPassword()) {
+                    Toolkit.getDefaultToolkit().beep();
+                } else {
+                    textInput.copy();
+                }
+            } else if (keyCode == Keyboard.KeyCode.V
+                && Keyboard.isPressed(commandModifier)) {
+                consumed = true;
 
-            if (textInput.isPassword()) {
-                Toolkit.getDefaultToolkit().beep();
+                textInput.paste();
             } else {
-                textInput.copy();
+                consumed = super.keyPressed(component, keyCode, keyLocation);
             }
-        } else if (keyCode == Keyboard.KeyCode.V
-            && Keyboard.isPressed(commandModifier)) {
-            consumed = true;
-
-            textInput.paste();
-        } else {
-            consumed = super.keyPressed(component, keyCode, keyLocation);
         }
 
         return consumed;
@@ -1185,7 +1218,7 @@
     // Text input events
     @Override
     public void textNodeChanged(TextInput textInput, TextNode previousTextNode) {
-        updateSelection(0);
+        invalidateComponent();
     }
 
     @Override
@@ -1200,12 +1233,12 @@
 
     @Override
     public void passwordChanged(TextInput textInput) {
-        repaintComponent();
+        invalidateComponent();
     }
 
     @Override
     public void promptChanged(TextInput textInput, String previousPrompt) {
-      repaintComponent();
+        invalidateComponent();
     }
 
     @Override
@@ -1220,120 +1253,97 @@
 
     @Override
     public void textValidatorChanged(TextInput textInput, Validator previousValidator) {
-        // No-op
+        repaintComponent();
     }
 
     // Text input character events
     @Override
     public void charactersInserted(TextInput textInput, int index, int count) {
-        updateSelection(0);
+        invalidateComponent();
     }
 
     @Override
     public void charactersRemoved(TextInput textInput, int index, int count) {
-        String text = getText();
-        Rectangle2D textBounds = font.getStringBounds(text, FONT_RENDER_CONTEXT);
-
-        int textWidth = (int)textBounds.getWidth();
-        int width = getWidth();
-
-        // If the right edge of the text is less than the right inset, align
-        // the text's right edge with the inset
-        if (textWidth - scrollLeft + padding.left + 1 < width - padding.right - 1) {
-            scrollLeft = Math.max(textWidth + (padding.left + padding.right + 2) - width, 0);
-        }
-
-        updateSelection(0);
+        invalidateComponent();
     }
 
     // Text input selection events
     @Override
     public void selectionChanged(TextInput textInput, int previousSelectionStart,
         int previousSelectionLength) {
-        int selectionStart = textInput.getSelectionStart();
-        int selectionLength = textInput.getSelectionLength();
-
-        int bias;
-        if (selectionStart < previousSelectionStart) {
-            bias = -1;
-        } else if (selectionLength > previousSelectionLength) {
-            bias = 1;
-        } else {
-            bias = 0;
-        }
+        // If the text input is valid, repaint the selection state; otherwise,
+        // the selection will be updated in layout()
+        if (textInput.isValid()) {
+            // Repaint any previous caret bounds
+            if (caret != null) {
+                textInput.repaint(caret.x, caret.y, caret.width, caret.height);
+            }
 
-        updateSelection(bias);
-    }
+            // Repaint any previous selection bounds
+            if (selection != null) {
+                Rectangle bounds = selection.getBounds();
+                textInput.repaint(bounds.x, bounds.y, bounds.width, bounds.height);
+            }
 
-    private void updateSelection(int bias) {
-        // Update the selection bounding box
-        String text = getText();
+            if (textInput.getSelectionLength() == 0) {
+                updateSelection();
+                showCaret(textInput.isFocused());
+            } else {
+                updateSelection();
+                showCaret(false);
 
-        // NOTE For some reason, TextLayout does not accept zero-length
-        // strings. This may be a bug in AWT, since an empty string should be
-        // valid, and is necessary to determine the caret shape for an empty
-        // text input.
-        // TODO Report this issue to Sun?
-        if (text.length() == 0) {
-            text = " ";
+                Rectangle bounds = selection.getBounds();
+                textInput.repaint(bounds.x, bounds.y, bounds.width, bounds.height);
+            }
         }
+    }
 
+    private void updateSelection() {
         TextInput textInput = (TextInput)getComponent();
+        TextNode textNode = textInput.getTextNode();
 
-        int selectionStart = textInput.getSelectionStart();
-        int selectionLength = textInput.getSelectionLength();
-
-        TextLayout textLayout = new TextLayout(text, font, FONT_RENDER_CONTEXT);
-
-        caretShapes = textLayout.getCaretShapes(selectionStart);
-        logicalHighlightShape = textLayout.getLogicalHighlightShape(selectionStart,
-            selectionStart + selectionLength);
-
-        int width = getWidth();
+        if (textNode.getCharacterCount() > 0) {
+            int selectionStart = textInput.getSelectionStart();
+            int selectionLength = textInput.getSelectionLength();
 
-        if (width <= padding.left + padding.right + 2) {
-            scrollLeft = 0;
-        } else {
-            if (textInput.getSelectionLength() == 0) {
-                Rectangle2D caretBounds = caretShapes[0].getBounds();
-                int caretLeft = (int)caretBounds.getX();
+            Bounds leadingSelectionBounds = getCharacterBounds(selectionStart);
 
-                if (caretLeft - scrollLeft < 0
-                    && bias <= 0) {
-                    // Ensure that the left edge of caret is visible
-                    scrollLeft = caretLeft;
-                } else {
-                    // Ensure that the right edge of the caret is visible
-                    int caretRight = (int)caretBounds.getMaxX();
+            if (selectionLength == 0) {
+                caret = leadingSelectionBounds.toRectangle();
+                caret.width = 1;
 
-                    if (caretRight - scrollLeft + padding.left + 1 > width - padding.right - 1) {
-                        scrollLeft = Math.max(caretRight
-                            - (width - (padding.left + padding.right + 2)), 0);
-                    }
-                }
+                selection = null;
             } else {
-                Rectangle2D logicalHighlightBounds = logicalHighlightShape.getBounds();
-                int logicalHighlightLeft = (int)logicalHighlightBounds.getX();
-
-                if (logicalHighlightLeft - scrollLeft < 0
-                    && bias <= 0) {
-                    // Ensure that the left edge of the highlight is visible
-                    scrollLeft = logicalHighlightLeft;
-                } else {
-                    // Ensure that the right edge of the highlight is visible
-                    int logicalHighlightRight = (int)logicalHighlightBounds.getMaxX();
+                caret = null;
 
-                    if (logicalHighlightRight - scrollLeft + padding.left + 1 > width - padding.right - 1) {
-                        scrollLeft = Math.max(logicalHighlightRight
-                            - (width - (padding.left + padding.right + 2)), 0);
-                    }
-                }
+                Bounds trailingSelectionBounds = getCharacterBounds(selectionStart
+                    + selectionLength - 1);
+                selection = new Rectangle(leadingSelectionBounds.x, leadingSelectionBounds.y,
+                    trailingSelectionBounds.x + trailingSelectionBounds.width - leadingSelectionBounds.x,
+                    trailingSelectionBounds.y + trailingSelectionBounds.height - leadingSelectionBounds.y);
             }
+        } else {
+            // Clear both the caret and the selection
+            caret = null;
+            selection = null;
+        }
+    }
+
+    public void showCaret(boolean show) {
+        if (scheduledBlinkCaretCallback != null) {
+            scheduledBlinkCaretCallback.cancel();
         }
 
-        showCaret(textInput.isFocused()
-            && textInput.getSelectionLength() == 0);
+        if (show) {
+            caretOn = true;
+            scheduledBlinkCaretCallback =
+                ApplicationContext.scheduleRecurringCallback(blinkCaretCallback,
+                    Platform.getCursorBlinkRate());
 
-        repaintComponent();
+            // Run the callback once now to show the cursor immediately
+            blinkCaretCallback.run();
+        } else {
+            scheduledBlinkCaretCallback = null;
+        }
     }
 }

Added: incubator/pivot/trunk/wtk/test/org/apache/pivot/wtk/test/text_input_test.wtkx
URL: http://svn.apache.org/viewvc/incubator/pivot/trunk/wtk/test/org/apache/pivot/wtk/test/text_input_test.wtkx?rev=831049&view=auto
==============================================================================
--- incubator/pivot/trunk/wtk/test/org/apache/pivot/wtk/test/text_input_test.wtkx (added)
+++ incubator/pivot/trunk/wtk/test/org/apache/pivot/wtk/test/text_input_test.wtkx Thu Oct 29 17:57:38 2009
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+
+<Window title="Text Input Test" maximized="true"
+    xmlns:wtkx="http://pivot.apache.org/wtkx"
+    xmlns="org.apache.pivot.wtk">
+    <content>
+        <BoxPane orientation="vertical" styles="{padding:4}">
+            <TextInput text="ABCD 1234"/>
+            <TextInput text="abcd 5678"/>
+        </BoxPane>
+    </content>
+</Window>



Re: svn commit: r831049

Posted by Sandro Martini <sa...@gmail.com>.
> This will not be a trivial change. Can you explain in more detail why the current implementation is not sufficient?
Yes.

> I understand that the bar code scanner appends a CR/LF to the text, but this keystroke will simply be ignored by TextInput. Even if TextInput accepted the control characters, it wouldn't display them - so I am wondering why you even need this functionality.
Mhhh so probably current features could be enough for the display part
... I don't have to display control chars (never), and by default they
have to be filtered out, probably as currently, Ok.

But in a my application (done in dot Net, sigh) I had to test in the
input string for CR LF chars to identify if the barcode read was
complete (because barcode readers only writes the code in the input
box, char by char).
In my first version the application had a thread that any second tried
to read the input text and verify if changed with the previous
(without using control chars for marking the end of code) but this I
had some problems with timings (the app was running on a Windows CE
device, with the compact version of the latest dot Net) etc ...
So in the second version I set the dot Net input text to not filter
control chars, and simply use an event listener of onChange of
contents with inside a simple test of CR LF to ensure the barcode was
read successfully.

All of this because my customers used many different barcodes so I
couldn't rely also on a (one or more) common pattern (like a regexp)
to identify codes.


Tell me if it's not enough clear.

I agree that this could be a corner case for us ... but if dot Net
have this feature probably could be useful in many other situations
:-) .


Have you got some suggestion for the description the feature the JIRA ticket ?
Then probably this feature could be applied also to TextArea ... but
maybe later.

Thanks for the help,
Sandro

Re: svn commit: r831049

Posted by Greg Brown <gk...@mac.com>.
This will not be a trivial change. Can you explain in more detail why the current implementation is not sufficient? I understand that the bar code scanner appends a CR/LF to the text, but this keystroke will simply be ignored by TextInput. Even if TextInput accepted the control characters, it wouldn't display them - so I am wondering why you even need this functionality.

 
On Friday, October 30, 2009, at 09:34AM, "Sandro Martini" <sa...@gmail.com> wrote:
>> It is possible. I would consider it a very low priority item, but feel free to file a JIRA ticket if you like. I would suggest assigning it to version 2.0 or later.
>Ok.
>But for a my application I'll need it (so this is one of my
>priorities), so I can start to look at it (when you'll have finished
>the work on TextInput), Ok ?
>
>So I'll add in JIRA, but for 1.5 (and if possible I'll release before).
>
>Thanks,
>Sandro
>
>

Re: svn commit: r831049

Posted by Sandro Martini <sa...@gmail.com>.
> It is possible. I would consider it a very low priority item, but feel free to file a JIRA ticket if you like. I would suggest assigning it to version 2.0 or later.
Ok.
But for a my application I'll need it (so this is one of my
priorities), so I can start to look at it (when you'll have finished
the work on TextInput), Ok ?

So I'll add in JIRA, but for 1.5 (and if possible I'll release before).

Thanks,
Sandro

Re: svn commit: r831049

Posted by Greg Brown <gk...@mac.com>.
It is possible. I would consider it a very low priority item, but feel free to file a JIRA ticket if you like. I would suggest assigning it to version 2.0 or later.
  
On Friday, October 30, 2009, at 06:15AM, "Sandro Martini" <sa...@gmail.com> wrote:
>Hi Greg,
>with the TextInput component, is it possible to handle also control
>chars (like CR + LF) and maybe a style flag to keep them in the text
>(but without displaying them) or by default filtering them out ?
>
>In dot Net the related component has this feature, and in some cases
>could be very useful.
>For example I used this feature in an application that reads data from
>BarCode Readers (where the end of the barcode was a CR LF).
>
>What do you think ?
>
>Bye
>
>

Re: svn commit: r831049

Posted by Sandro Martini <sa...@gmail.com>.
Hi Greg,
with the TextInput component, is it possible to handle also control
chars (like CR + LF) and maybe a style flag to keep them in the text
(but without displaying them) or by default filtering them out ?

In dot Net the related component has this feature, and in some cases
could be very useful.
For example I used this feature in an application that reads data from
BarCode Readers (where the end of the barcode was a CR LF).

What do you think ?

Bye

Re: svn commit: r831049

Posted by Greg Brown <gk...@mac.com>.
OK, TextInput is still not completely functional but it is now usable.  
Features that are still missing include auto-scrolling and maintaining  
caret visibility when the text is too long to fit into the available  
space. These features will be restored as soon as possible.


On Oct 29, 2009, at 2:10 PM, Greg Brown wrote:

> Please note that, as a result of this change, TextInput is  
> temporarily not completely functional. If you are developing an  
> application that relies on a working TextInput component, you may  
> want to avoid syncing with SVN until the remaining issues are worked  
> out (hopefully fairly soon).
> G
>


Re: svn commit: r831049

Posted by Greg Brown <gk...@mac.com>.
OK, TextInput is still not completely functional but it is now usable.  
Features that are still missing include auto-scrolling and maintaining  
caret visibility when the text is too long to fit into the available  
space. These features will be restored as soon as possible.


On Oct 29, 2009, at 2:10 PM, Greg Brown wrote:

> Please note that, as a result of this change, TextInput is  
> temporarily not completely functional. If you are developing an  
> application that relies on a working TextInput component, you may  
> want to avoid syncing with SVN until the remaining issues are worked  
> out (hopefully fairly soon).
> G
>


Re: svn commit: r831049

Posted by Greg Brown <gk...@mac.com>.
Please note that, as a result of this change, TextInput is temporarily  
not completely functional. If you are developing an application that  
relies on a working TextInput component, you may want to avoid syncing  
with SVN until the remaining issues are worked out (hopefully fairly  
soon).
G


Re: svn commit: r831049

Posted by Greg Brown <gk...@mac.com>.
Please note that, as a result of this change, TextInput is temporarily  
not completely functional. If you are developing an application that  
relies on a working TextInput component, you may want to avoid syncing  
with SVN until the remaining issues are worked out (hopefully fairly  
soon).
G