/* * TextComponent.java * * Copyright � 1998-2011 Research In Motion Limited * * Licensed 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. * * Note: For the sake of simplicity, this sample application may not leverage * resource bundles and resource strings. However, it is STRONGLY recommended * that application developers make use of the localization features available * within the BlackBerry development platform to ensure a seamless application * experience across a variety of languages and geographies. For more information * on localizing your application, please refer to the BlackBerry Java Development * Environment Development Guide associated with this release. */ package com.rim.samples.device.accessibilitydemo.customcomponentsdemo; import java.util.Vector; import net.rim.device.api.system.Display; import net.rim.device.api.ui.AccessibleEventDispatcher; import net.rim.device.api.ui.Field; import net.rim.device.api.ui.Font; import net.rim.device.api.ui.Graphics; import net.rim.device.api.ui.Manager; import net.rim.device.api.ui.accessibility.AccessibleContext; import net.rim.device.api.ui.accessibility.AccessibleRole; import net.rim.device.api.ui.accessibility.AccessibleState; import net.rim.device.api.ui.accessibility.AccessibleTable; import net.rim.device.api.ui.accessibility.AccessibleText; import net.rim.device.api.ui.accessibility.AccessibleValue; import net.rim.device.api.ui.text.TextFilter; /** * Sample implementation for a textual accessible UI component. Provides the * screen reader application with the text data contained in the the custom * field and information about changes to the textual data. This field's text is * split up into lines and words */ public final class TextComponent extends Field implements AccessibleContext, AccessibleText { private final String _text; private int _state; // Lines and words are stored in Vectors so that a line or word at a given // index can be returned by the AccessibleText.getAtIndex() method. private Vector _lines; // Contains the text split into lines based on // component's width private Vector _words; // Contains the text split into words based on // whitespaces /** * Constructs a new TextComponent * * @param text * The text to be displayed by this component */ public TextComponent(final String text) { super(Field.FOCUSABLE); _text = text; } // *********************** Field implementation **************************** /** * @see Field#getAccessibleContext() */ public AccessibleContext getAccessibleContext() { return this; } /** * @see Field#getPreferredWidth() */ public int getPreferredWidth() { return Display.getWidth(); } /** * @see Field#getPreferredHeight() */ public int getPreferredHeight() { return Math.max(20, _lines.size() * 30); } /** * @see Field#layout(int, int) */ protected void layout(final int width, final int height) { // Split text into lines based on the given width final StringBuffer currentLine = new StringBuffer(); final Font font = getFont(); _lines = new Vector(); _words = new Vector(); // Add words one by one and form lines final TextComponentStringTokenizer tokenizer = new TextComponentStringTokenizer(_text); while (tokenizer.hasMoreTokens()) { final String word = tokenizer.nextToken(); _words.addElement(word); if (font.getAdvance(currentLine.toString() + word) < width) { // Current word still fits into the line, add it currentLine.append(word); } else { // The word doesn't fit, make a new line _lines.addElement(currentLine.toString()); currentLine.setLength(0); currentLine.append(word); } } if (currentLine.length() > 0) { _lines.addElement(currentLine.toString()); } setExtent(getPreferredWidth(), getPreferredHeight()); } /** * @see Field#onFocus(int) */ protected void onFocus(final int direction) { super.onFocus(direction); // Update accessible state and notify screen reader final int oldState = _state; _state = _state | AccessibleState.FOCUSED; AccessibleEventDispatcher.dispatchAccessibleEvent( AccessibleContext.ACCESSIBLE_STATE_CHANGED, new Integer( oldState), new Integer(_state), this); } /** * @see Field#onFocus(int) */ protected void onUnfocus() { super.onUnfocus(); // Update accessible state and notify screen reader final int oldState = _state; _state = _state & ~AccessibleState.FOCUSED; AccessibleEventDispatcher.dispatchAccessibleEvent( AccessibleContext.ACCESSIBLE_STATE_CHANGED, new Integer( oldState), new Integer(_state), this); } /** * @see Field#paint(Graphics) */ protected void paint(final Graphics graphics) { int y = 0; final int fontHeight = graphics.getFont().getHeight(); // Paint text line by line. final int linesCount = _lines.size(); for (int i = 0; i < linesCount; i++) { graphics.drawText((String) _lines.elementAt(i), 0, y); y += fontHeight; } } // ******************** AccessibleContext implementation ******************* /** * @see AccessibleContext#getAccessibleText() */ public AccessibleText getAccessibleText() { // The component implements AccessibleText return this; } /** * @see AccessibleContext#getAccessibleName() */ public String getAccessibleName() { // The name of the sample component, // will be read by the reader. return " My Text Field "; } /** * @see AccessibleContext#getAccessibleParent() */ public AccessibleContext getAccessibleParent() { // Return manager where text component is added final Manager manager = getManager(); return manager != null ? manager.getAccessibleContext() : null; } /** * @see AccessibleContext#getAccessibleRole() */ public int getAccessibleRole() { // Component serves as a text field. return AccessibleRole.TEXT_FIELD; } /** * @see AccessibleContext#getAccessibleStateSet() */ public int getAccessibleStateSet() { // Text component can be focused but not edited final boolean focused = isFocus(); if (focused) { return AccessibleState.FOCUSED | AccessibleState.FOCUSABLE; } else { return AccessibleState.FOCUSABLE; } } /** * @see AccessibleContext#isAccessibleStateSet(int) */ public boolean isAccessibleStateSet(final int state) { return (state & getAccessibleStateSet()) != 0; } /** * @see AccessibleContext#getAccessibleChildAt(int) */ public AccessibleContext getAccessibleChildAt(final int index) { // Text field doesn't have any children return null; } /** * @see AccessibleContext#getAccessibleChildCount() */ public int getAccessibleChildCount() { // Text field doesn't have any children return 0; } /** * @see AccessibleContext#getAccessibleSelectionAt(int) */ public AccessibleContext getAccessibleSelectionAt(final int index) { // Text field doesn't have any children return null; } /** * @see AccessibleContext#getAccessibleSelectionCount() */ public int getAccessibleSelectionCount() { // Text field doesn't have any children return 0; } /** * @see AccessibleContext#getAccessibleTable() */ public AccessibleTable getAccessibleTable() { // This is a text component, not a table return null; } /** * @see AccessibleContext#getAccessibleValue() */ public AccessibleValue getAccessibleValue() { // This is a text component, no numerical values return null; } /** * @see AccessibleContext#isAccessibleChildSelected(int) */ public boolean isAccessibleChildSelected(final int index) { // Text field doesn't have any children return false; } // ******************** AccessibleText implementation ******************* /** * @see AccessibleText#getAtIndex(int, int) */ public String getAtIndex(final int part, final int index) { // Return character, line or word at the given index switch (part) { case AccessibleText.CHAR: return String.valueOf(_text.charAt(index)); case AccessibleText.LINE: return (String) _lines.elementAt(index); case AccessibleText.WORD: return (String) _words.elementAt(index); } return null; } /** * @see AccessibleText#getCaretPosition() */ public int getCaretPosition() { // Our text component is not editable and doesn't support caret // navigation return 0; } /** * @see AccessibleText#getCharCount() */ public int getCharCount() { // Number of characters return _text.length(); } /** * @see AccessibleText#getInputFilterStyle() */ public int getInputFilterStyle() { return TextFilter.DEFAULT; } /** * @see AccessibleText#getLineCount() */ public int getLineCount() { // Number of lines in the component, based on current width return _lines.size(); } /** * @see AccessibleText#getSelectionEnd() */ public int getSelectionEnd() { // Text component doesn't have text selection feature return 0; } /** * @see AccessibleText#getSelectionStart() */ public int getSelectionStart() { // Text component doesn't have text selection feature return 0; } /** * @see AccessibleText#getSelectionText() */ public String getSelectionText() { // Text component doesn't have text selection feature return _text; } /** * @see AccessibleText#getWholeText() */ public String getWholeText() { // Return the whole text return _text; } /** * A string tokenizer class used by the TextComponent class */ private static final class TextComponentStringTokenizer { private int _currentPosition; private int _newPosition; private final int _maxPosition; private final String _str; private final String _delimiter; private boolean _delimsChanged; /** * Constructor * * @param str * The string to be parsed * @param delim * The delimiters to split the string on * @param returnDelims * Flag indicating whether to return the delimiters as tokens */ private TextComponentStringTokenizer(final String str) { // Initialize members _currentPosition = 0; _newPosition = -1; _str = str; _maxPosition = _str.length(); _delimiter = " "; } /** * Tests if there are more tokens available from this tokenizer's string * * @return True if there is at least one token in the string after the * current position, otherwise false */ boolean hasMoreTokens() { return _currentPosition < _maxPosition; } /** * Returns the next token from this string tokenizer * * @return The next token from this string tokenizer */ String nextToken() { _currentPosition = _newPosition >= 0 && !_delimsChanged ? _newPosition : _currentPosition; _delimsChanged = false; _newPosition = -1; if (_currentPosition >= _maxPosition) { return null; } final int start = _currentPosition; _currentPosition = scanToken(_currentPosition); return _str.substring(start, _currentPosition); } /** * Returns the end position of the next token * * @param startPos * Start position of the token * @return The end position of the next token */ private int scanToken(final int startPos) { int position = startPos; while (position < _maxPosition) { final char c = _str.charAt(position); if (_delimiter.indexOf(c) >= 0) { break; } ++position; } if (startPos == position) { final char c = _str.charAt(position); if (_delimiter.indexOf(c) >= 0) { ++position; } } return position; } } }