Package pivot.wtk

Source Code of pivot.wtk.TextArea$TextAreaSelectionListenerList

/*
* 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 pivot.wtk;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;

import pivot.collections.Dictionary;
import pivot.collections.Sequence;
import pivot.serialization.SerializationException;
import pivot.util.ListenerList;
import pivot.wtk.media.Image;
import pivot.wtk.text.Document;
import pivot.wtk.text.Element;
import pivot.wtk.text.Node;
import pivot.wtk.text.NodeListener;
import pivot.wtk.text.Paragraph;
import pivot.wtk.text.PlainTextSerializer;
import pivot.wtk.text.TextNode;

/**
* Component that allows a user to enter and edit multiple lines of (optionally
* formatted) text.
*
* @author gbrown
*/
public class TextArea extends Component {
    /**
     * Text area skin interface. Text area skins are required to implement
     * this.
     *
     * @author gbrown
     */
    public interface Skin {
        /**
         * Returns the offset of the character at a given location.
         *
         * @param x
         * @param y
         *
         * @return
         * The character offset at the given location.
         */
        public int getCharacterAt(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 TextAreaListenerList extends ListenerList<TextAreaListener>
        implements TextAreaListener {
        public void documentChanged(TextArea textArea, Document previousText) {
            for (TextAreaListener listener : this) {
                listener.documentChanged(textArea, previousText);
            }
        }

        public void editableChanged(TextArea textArea) {
            for (TextAreaListener listener : this) {
                listener.editableChanged(textArea);
            }
        }

        public void textKeyChanged(TextArea textArea, String previousTextKey) {
            for (TextAreaListener listener : this) {
                listener.textKeyChanged(textArea, previousTextKey);
            }
        }
    }

    private static class TextAreaCharacterListenerList extends ListenerList<TextAreaCharacterListener>
        implements TextAreaCharacterListener {
        public void charactersInserted(TextArea textArea, int index, int count) {
            for (TextAreaCharacterListener listener : this) {
                listener.charactersInserted(textArea, index, count);
            }
        }

        public void charactersRemoved(TextArea textArea, int index, int count) {
            for (TextAreaCharacterListener listener : this) {
                listener.charactersRemoved(textArea, index, count);
            }
        }
    }

    private static class TextAreaSelectionListenerList extends ListenerList<TextAreaSelectionListener>
        implements TextAreaSelectionListener {
        public void selectionChanged(TextArea textArea,
            int previousSelectionStart, int previousSelectionLength) {
            for (TextAreaSelectionListener listener : this) {
                listener.selectionChanged(textArea, previousSelectionStart, previousSelectionLength);
            }
        }
    }

    private Document document = null;
    private boolean editable = true;
    private String textKey = null;

    private int selectionStart = 0;
    private int selectionLength = 0;

    private NodeListener documentListener = new NodeListener() {
        public void parentChanged(Node node, Element previousParent) {
        }

        public void offsetChanged(Node node, int previousOffset) {
        }

        public void rangeInserted(Node node, int offset, int characterCount) {
            if (selectionStart + selectionLength > offset) {
                if (selectionStart > offset) {
                    selectionStart += characterCount;
                } else {
                    selectionLength += characterCount;
                }
            }

            textAreaCharacterListeners.charactersInserted(TextArea.this, offset, characterCount);
        }

        public void rangeRemoved(Node node, int offset, int characterCount) {
            if (selectionStart + selectionLength > offset) {
                if (selectionStart > offset) {
                    selectionStart -= characterCount;
                } else {
                    selectionLength -= characterCount;
                }
            }

            textAreaCharacterListeners.charactersRemoved(TextArea.this, offset, characterCount);
        }
    };

    private TextAreaListenerList textAreaListeners = new TextAreaListenerList();
    private TextAreaCharacterListenerList textAreaCharacterListeners = new TextAreaCharacterListenerList();
    private TextAreaSelectionListenerList textAreaSelectionListeners = new TextAreaSelectionListenerList();

    public TextArea() {
        installSkin(TextArea.class);
        setText("");
    }

    @Override
    protected void setSkin(pivot.wtk.Skin skin) {
        if (!(skin instanceof TextArea.Skin)) {
            throw new IllegalArgumentException("Skin class must implement "
                + TextArea.Skin.class.getName());
        }

        super.setSkin(skin);
    }

    @Override
    protected void setParent(Container parent) {
        if (parent != null
            && !(parent instanceof Viewport)) {
            throw new IllegalArgumentException(getClass().getName()
                + " parent must be an instance of "
                + Viewport.class.getName());
        }

        super.setParent(parent);
    }

    public Document getDocument() {
        return document;
    }

    public void setDocument(Document document) {
        Document previousDocument = this.document;

        if (previousDocument != document) {
            if (previousDocument != null) {
                previousDocument.getNodeListeners().remove(documentListener);
            }

            if (document != null) {
                document.getNodeListeners().add(documentListener);
            }

            this.document = document;

            selectionStart = 0;
            selectionLength = 0;

            textAreaListeners.documentChanged(this, previousDocument);
        }
    }

    public String getText() {
        String text = null;
        Document document = getDocument();

        if (document != null) {
            try {
                PlainTextSerializer serializer = new PlainTextSerializer();
                StringWriter writer = new StringWriter();
                serializer.writeObject(document, writer);
                text = writer.toString();
            } catch(SerializationException exception) {
            } catch(IOException exception) {
            }
        }

        return text;
    }

    public void setText(String text) {
        Document document = null;

        if (text != null
            && text.length() > 0) {
            try {
                PlainTextSerializer serializer = new PlainTextSerializer();
                StringReader reader = new StringReader(text);
                document = serializer.readObject(reader);
            } catch(SerializationException exception) {
            } catch(IOException exception) {
            }
        } else {
            document = new Document();
            document.add(new Paragraph(""));
        }

        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 != null
            && textKey != null
            && !previousTextKey.equals(textKey))
            || previousTextKey != textKey) {
            this.textKey = textKey;
            textAreaListeners.textKeyChanged(this, previousTextKey);
        }
    }

    @Override
    public void load(Dictionary<String, ?> context) {
        if (textKey != null
            && context.containsKey(textKey)) {
            Object value = context.get(textKey);
            if (value != null) {
                value = value.toString();
            }

            setText((String)value);
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public void store(Dictionary<String, ?> context) {
        if (textKey != null) {
            ((Dictionary<String, String>)context).put(textKey, getText());
        }
    }

    /**
     * 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;
    }

    /**
     * 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) {
            throw new IllegalStateException("No document.");
        }

        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?

        insertText(Character.toString(character));
    }

    public void insertText(String text) {
        if (text == null) {
            throw new IllegalArgumentException("text is null.");
        }

        if (document == null) {
            throw new IllegalStateException("No document.");
        }

        if (selectionLength > 0) {
            // TODO Make this part of the undoable action (for all such
            // actions)
            document.removeRange(selectionStart, selectionLength);
        }

        Node descendant = document.getDescendantAt(selectionStart);
        int offset = selectionStart - descendant.getDocumentOffset();

        if (descendant instanceof TextNode) {
            // The caret is positioned within an existing text node
            TextNode textNode = (TextNode)descendant;
            textNode.insertText(text, offset);
        } else if (descendant instanceof Paragraph) {
            // The caret is positioned on the paragraph terminator
            Paragraph paragraph = (Paragraph)descendant;

            int n = paragraph.getLength();
            if (n > 0) {
                Node node = paragraph.get(n - 1);
                if (node instanceof TextNode) {
                    // Insert the text into the existing node
                    TextNode textNode = (TextNode)node;
                    textNode.insertText(text, offset);
                } else {
                    // Append a new text node
                    paragraph.add(new TextNode(text));
                }
            } else {
                // The paragraph is currently empty
                paragraph.add(new TextNode(text));
            }
        } else {
            // The caret is positioned on a non-text character node; insert
            // the text into the descendant's parent
            Element parent = descendant.getParent();
            int index = parent.indexOf(descendant);
            parent.insert(new TextNode(text), index);
        }

        // Set the selection start to the character following the insertion
        setSelection(selectionStart + text.length(), selectionLength);
    }

    public void insertImage(Image image) {
        if (image == null) {
            throw new IllegalArgumentException("image is null.");
        }

        if (document == null) {
            throw new IllegalStateException("No document.");
        }

        if (selectionLength > 0) {
            document.removeRange(selectionStart, selectionLength);
        }

        // TODO If the caret is placed in the middle of a text node, split it;
        // otherwise, insert an ImageNode immediately following the node
        // containing the caret

        // Set the selection start to the character following the insertion
        setSelection(selectionStart + 1, selectionLength);
    }

    public void insertParagraph() {
        if (document == null) {
            throw new IllegalStateException("No document.");
        }

        if (selectionLength > 0) {
            document.removeRange(selectionStart, selectionLength);
        }

        // Walk up the tree until we find a paragraph
        Node descendant = document.getDescendantAt(selectionStart);
        while (!(descendant instanceof Paragraph)) {
            descendant = descendant.getParent();
        }

        // Split the paragraph at the insertion point
        Paragraph leadingSegment = (Paragraph)descendant;
        int offset = selectionStart - leadingSegment.getDocumentOffset();
        int characterCount = leadingSegment.getCharacterCount() - offset;

        Paragraph trailingSegment = (Paragraph)leadingSegment.removeRange(offset, characterCount);

        Element parent = leadingSegment.getParent();
        int index = parent.indexOf(leadingSegment);
        parent.insert(trailingSegment, index + 1);

        // Set the selection start to the character following the insertion
        setSelection(selectionStart + 1, selectionLength);
    }

    public void delete(Direction direction) {
        if (direction == null) {
            throw new IllegalArgumentException("direction is null.");
        }

        if (document == null) {
            throw new IllegalStateException("No document.");
        }

        if (selectionLength > 0) {
            document.removeRange(selectionStart, selectionLength);
        } else {
            int offset = selectionStart;

            if (direction == Direction.BACKWARD) {
                offset--;
            }

            if (offset >= 0
                && offset < document.getCharacterCount()) {
                Node descendant = document.getDescendantAt(offset);

                if (descendant instanceof Paragraph) {
                    // We are deleting a paragraph terminator
                    Paragraph paragraph = (Paragraph)descendant;

                    Element parent = paragraph.getParent();
                    int index = parent.indexOf(paragraph);

                    // Attempt to merge any successive content into the paragraph
                    if (index < parent.getLength() - 1) {
                        // TODO This won't always be a paragraph - we'll need to
                        // find the next paragraph by walking the tree, then
                        // remove any empty nodes
                        Sequence<Node> removed = parent.remove(index + 1, 1);
                        Paragraph nextParagraph = (Paragraph)removed.get(0);
                        paragraph.insertRange(nextParagraph, paragraph.getCharacterCount() - 1);

                        // Move the caret to the merge point
                        setSelection(offset, 0);
                    }
                } else {
                    document.removeRange(offset, 1);
                }
            }
        }
    }

    public void cut() {
        // TODO
    }

    public void copy() {
        // TODO
    }

    public void paste() {
        // TODO
    }

    public void undo() {
        // TODO
    }

    public void redo() {
        // TODO
    }

    public int getCharacterAt(int x, int y) {
        TextArea.Skin textAreaSkin = (TextArea.Skin)getSkin();
        return textAreaSkin.getCharacterAt(x, y);
    }

    public Bounds getCharacterBounds(int offset) {
        TextArea.Skin textAreaSkin = (TextArea.Skin)getSkin();
        return textAreaSkin.getCharacterBounds(offset);
    }

    public ListenerList<TextAreaListener> getTextAreaListeners() {
        return textAreaListeners;
    }

    public ListenerList<TextAreaCharacterListener> getTextAreaCharacterListeners() {
        return textAreaCharacterListeners;
    }

    public ListenerList<TextAreaSelectionListener> getTextAreaSelectionListeners() {
        return textAreaSelectionListeners;
    }
}
TOP

Related Classes of pivot.wtk.TextArea$TextAreaSelectionListenerList

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.