Package com.rim.samples.device.unifiedsearchdemo

Source Code of com.rim.samples.device.unifiedsearchdemo.GraphicsHelper

/*
* GraphicsHelper.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.unifiedsearchdemo;

import net.rim.device.api.ui.DrawStyle;
import net.rim.device.api.ui.Font;
import net.rim.device.api.ui.Graphics;
import net.rim.device.api.ui.XYRect;
import net.rim.device.api.util.CharacterUtilities;
import net.rim.device.api.util.IntVector;
import net.rim.device.api.util.SimpleSortingIntVector;
import net.rim.device.api.util.StringUtilities;

/**
* Helper class to highlight keyword text
*/
public class GraphicsHelper {
    private int[] _highlightStartIndex = new int[0];
    private int[] _highlightLengths = new int[0];
    private int[] _highlightOffsets = new int[0];
    private String[] _highlightWords = new String[0];
    private String[] _queryWords = new String[0];
    private final SimpleSortingIntVector _highlightStartIndexIntVector =
            new SimpleSortingIntVector();
    private final IntVector _highlightLengthsIntVector = new IntVector(0);
    private String _currText;
    private String _currQuery;

    /**
     * Draws text with bolded highlight regions
     *
     * @param g
     *            Graphics context
     * @param text
     *            Text for which to highlight keywords
     * @param query
     *            Keyword to be highlighted
     * @param rect
     *            The extent in which to draw
     */
    public void drawTextWithHighlight(final Graphics g, final String text,
            final String query, final XYRect rect) {
        final boolean isNewQuery =
                _currQuery == null ? true : !_currQuery.equals(query);
        final boolean isNewText =
                _currText == null ? true : !_currText.equals(text);

        String[] queryWords = _queryWords;

        if (isNewQuery || isNewText) {
            _currQuery = query;
            queryWords =
                    query == null ? null : StringUtilities.stringToWords(query
                            .toLowerCase().trim());
        }

        drawTextWithHighlight(g, text, queryWords, rect);
    }

    /**
     * Draws text with bolded highlight regions
     *
     * @param g
     *            Graphics context
     * @param text
     *            Text for which to highlight keywords
     * @param queryWords
     *            Keywords to be highlighted
     * @param rect
     *            The extent in which to draw
     */
    public void drawTextWithHighlight(final Graphics g, final String text,
            final String[] queryWords, final XYRect rect) {
        boolean isNewQuery =
                _queryWords == null ? true
                        : _queryWords.length != queryWords.length;
        final boolean isNewText =
                _currText == null ? true : !_currText.equals(text);

        if (!isNewQuery) {
            // Before accepting that it is not a new query, verify that each
            // query word is identical.
            if (queryWords.length == _queryWords.length) {
                for (int i = 0; i < queryWords.length; i++) {
                    isNewQuery |= !queryWords[i].equals(_queryWords[i]);
                }
            }
        }

        if (isNewQuery || isNewText) {
            _queryWords = queryWords;
            _currText = text;

            // Since this is a new highlight request, create the highlight
            // regions, which are the highlight offsets and lengths.
            createHighlightRegions(_queryWords, text);
        }

        if (_highlightStartIndex.length > 0) {
            drawTextWithHighlight(g, text, rect, _highlightStartIndex,
                    _highlightLengths);
        } else {
            g.drawText(text, rect.x, rect.y);
        }
    }

    /**
     * Draws text with bolded highlight regions
     *
     * @param g
     *            Graphics context
     * @param text
     *            Text for which to highlight keywords
     * @param rect
     *            The extent in which to draw
     * @param highlightStartIndex
     *            Index at which to start highlighting
     * @param highlightLength
     *            Length of region to highlight
     */
    public void drawTextWithHighlight(final Graphics g, final String text,
            final XYRect rect, final int[] highlightStartIndex,
            final int[] highlightLength) {
        final int x = rect.x;
        final int y = rect.y;
        final int textWidth = rect.width;

        final Font oldFont = g.getFont();

        final Font font = g.getFont();
        final Font highlightFont =
                font.derive(font.isBold() ? Font.EXTRA_BOLD : Font.BOLD);

        try {
            // Just draw the text normally if there's no highlight region
            final int numHighlightStartIndexes =
                    highlightStartIndex == null ? 0
                            : highlightStartIndex.length;

            if (numHighlightStartIndexes == 0) {
                g.setFont(oldFont);
                g.drawText(text, 0, text.length(), x, y, DrawStyle.ELLIPSIS,
                        textWidth);
                return;
            }

            int textStartIndex = 0;
            int offsetX = 0;

            boolean hasEndText = true;

            /**
             * <pre>
             *   Text Highlight Algorithm
             *  
             *   Given the set of highlightStartIndex values and the
             *   highlightLength values, we already know exactly which
             *   portions of the text need to be highlighted and how many
             *   characters to highlight.
             *
             *   The algorithm below uses the aforementioned values to
             *   determine the highlighted and non-highlighted regions of
             *   text. Then in a single pass through the text, it draws the
             *   text regions as required.
             *
             *   The text highlighting algorithm works as follows by iterating
             *   through the set of highlight indexes and performing the required
             *   text highlighting operations. These operations are as follows:
             *
             *   1. Calculate the start index for the current portion of the text
             *      to draw.
             *
             *   2. Determine if the start index for the text to draw is
             *      equal to the current highlight index. If it's not equal,
             *      the characters from the start index to the current highlight
             *      index are not highlighted. If it is equal, then the amount of
             *      characters to highlight is dictated by the highlight length.
             *      
             *   3. Draw the text before the current highlight index in a
             *      non-highlighted font.
             *
             *   4. Draw the text from the highlight index to the
             *      highlight index + highlight length in a highlighted font.
             *      Repeat steps 1 to 4 until the last highlight region is drawn.
             *  
             *   5. There may be text after the last highlight index that hasn't
             *      been drawn. Therefore, there is a check to see if there is
             *      any such text and if so, it is drawn in the non-highlighted font.
             * </pre>
             */

            int i = 0;
            for (i = 0; i < numHighlightStartIndexes; i++) {
                if (i != 0) {
                    // Calculate the start index for current portion of the
                    // text to draw.
                    textStartIndex =
                            highlightStartIndex[i - 1] + highlightLength[i - 1];
                }

                // Draw the text before the highlight region
                if (highlightStartIndex[i] != textStartIndex) {
                    g.setFont(oldFont);
                    g.drawText(text, textStartIndex, highlightStartIndex[i]
                            - textStartIndex, x + offsetX, y, 0, textWidth
                            - offsetX);
                }

                // Draw the highlighted text
                offsetX +=
                        oldFont.getAdvance(text, textStartIndex,
                                highlightStartIndex[i] - textStartIndex);
                g.setFont(highlightFont);
                g.drawText(text, highlightStartIndex[i], highlightLength[i], x
                        + offsetX, y, 0, textWidth - offsetX);

                final int nextOffsetX =
                        highlightFont.getAdvance(text, highlightStartIndex[i],
                                highlightLength[i]);
                offsetX += nextOffsetX;
            }

            i--;

            // Draw the text after the highlight region
            hasEndText =
                    highlightStartIndex[i] + highlightLength[i] != text
                            .length();
            if (hasEndText) {
                g.setFont(oldFont);
                g.drawText(text, highlightStartIndex[i] + highlightLength[i],
                        text.length() - highlightStartIndex[i]
                                - highlightLength[i], x + offsetX, y, 0,
                        textWidth - offsetX);
            }

        } finally {
            g.setFont(oldFont);
        }
    }

    /**
     * Creates highlight regions for the given text based on the queryWords
     * parameter
     *
     * @param queryWords
     *            Keywords for which to create highlight regions
     * @param text
     *            The text for which to create highlight regions
     */
    private void createHighlightRegions(final String[] queryWords,
            final String text) {
        _highlightStartIndex = new int[0];
        _highlightLengths = new int[0];
        _highlightWords = new String[0];
        _highlightStartIndexIntVector.removeAllElements();
        _highlightLengthsIntVector.removeAllElements();

        if (queryWords != null) {
            final int len = queryWords.length;

            // Create the initial set of highlight offsets based on the
            // the index of each word in the text value. These are equal
            // to the index of each word in the text.
            StringUtilities.stringToWords(text, _highlightWords, 0);
            _highlightOffsets = new int[_highlightWords.length];
            StringUtilities.stringToWords(text, _highlightOffsets, 0);

            boolean noFurtherMatches;

            // Iterate through each of the query words and determine the
            // corresponding text highlight start position and highlight
            // length.
            for (int i = 0; i < len; i++) {
                noFurtherMatches = false;

                for (int j = 0; j < _highlightOffsets.length
                        && !noFurtherMatches; j++) {

                    final int highlightIndex =
                            getHighlightStartIndex(text, i,
                                    _highlightOffsets[j]);

                    if (highlightIndex == -1) {
                        // If the given query was not found at the current
                        // offset position
                        // then there are no further matches for this query word
                        // and the
                        // algorithm can exit early.
                        noFurtherMatches = true;
                    } else if (j == _highlightOffsets.length - 1
                            || highlightIndex < _highlightOffsets[j + 1]) {
                        int position =
                                _highlightStartIndexIntVector
                                        .binarySearch(
                                                highlightIndex,
                                                SimpleSortingIntVector.SORT_TYPE_NUMERIC);

                        if (position < 0) {
                            position = -position;
                            if (position > _highlightStartIndexIntVector.size()
                                    || highlightIndex < _highlightStartIndexIntVector
                                            .elementAt(position - 1)) {
                                --position;
                            }

                            // Since the position is less than 0, the highlight
                            // index is not yet present in the highlight start
                            // index array. Therefore, we add the the current
                            // highlight index value to the collection of
                            // highlight start indexes.
                            _highlightStartIndexIntVector.insertElementAt(
                                    highlightIndex, position);

                            // Next we add the current query index as a
                            // place holder for the for the query length.
                            // This will be converted to the actual length
                            // of the highlight region later.
                            _highlightLengthsIntVector.insertElementAt(i,
                                    position);
                        } else {
                            final int prevHighlightLength =
                                    _queryWords[_highlightLengthsIntVector
                                            .elementAt(position)].length();
                            final int currHighlightLength =
                                    _queryWords[i].length();

                            // If the query is found at an existing highlight
                            // position in the text, and the current query is
                            // larger than the query previously associated with
                            // the found highlight position, then the algorithm
                            // replaces the previous query index with the
                            // current
                            // query index.
                            if (currHighlightLength > prevHighlightLength) {
                                _highlightLengthsIntVector.setElementAt(i,
                                        position);
                            }
                        }
                    }
                }
            }

            if (_highlightStartIndexIntVector.size() > 0) {
                final int size = _highlightStartIndexIntVector.size();
                _highlightStartIndex = new int[size];
                _highlightStartIndexIntVector.copyInto(_highlightStartIndex);
                _highlightLengths = new int[size];
                _highlightLengthsIntVector.copyInto(_highlightLengths);
                for (int i = 0; i < _highlightStartIndex.length; i++) {
                    // For each highlight length, the algorithm now replaces the
                    // query index with the actual highlight length. This
                    // highlight
                    // length is equal to the query length.
                    final int highlightLength =
                            _queryWords[_highlightLengths[i]].length();
                    _highlightLengths[i] = highlightLength;
                }
            }
        }
    }

    /**
     * Retrieves the highlight start index of a given string
     *
     * @param text
     *            The text containing a region to highlight
     * @param queryIndex
     *            Index of the query word in the _queryWords array to highlight
     * @param fromIndex
     *            Index at which to start looking for for a highlight region in
     *            the given text
     * @return The highlight start index for the given text
     */
    private int getHighlightStartIndex(final String text, final int queryIndex,
            final int fromIndex) {
        final String lCaseElement = text.toLowerCase();

        int startIndex =
                lCaseElement.indexOf(_queryWords[queryIndex], fromIndex);

        if (startIndex > 0) {
            int realStartIndex = startIndex;
            char prevChar = lCaseElement.charAt(realStartIndex - 1);

            // Ensure that we don't incorrectly find a mid-phrase match
            while (realStartIndex != -1 && isAlphaNumericChar(prevChar)) {
                realStartIndex =
                        lCaseElement.indexOf(_queryWords[queryIndex],
                                realStartIndex + 1);

                if (realStartIndex != -1) {
                    prevChar = lCaseElement.charAt(realStartIndex - 1);
                }
            }

            startIndex = realStartIndex;
        }

        return startIndex;
    }

    /**
     * Checks whether a given char is alphanumeric
     *
     * @param ch
     *            The char to evaluate
     * @return True if the char is alphanumeric, otherwise false
     */
    private static boolean isAlphaNumericChar(final char ch) {
        return Character.isDigit(ch) || CharacterUtilities.isLetter(ch);
    }
}
TOP

Related Classes of com.rim.samples.device.unifiedsearchdemo.GraphicsHelper

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.