| /**************************************************************************** |
| * apps/graphics/nxwidgets/src/cmultilinetextbox.cxx |
| * |
| * 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. |
| * |
| **************************************************************************** |
| * |
| * Portions of this package derive from Woopsi (http://woopsi.org/) and |
| * portions are original efforts. It is difficult to determine at this |
| * point what parts are original efforts and which parts derive from Woopsi. |
| * However, in any event, the work of Antony Dzeryn will be acknowledged |
| * in most NxWidget files. Thanks Antony! |
| * |
| * Copyright (c) 2007-2011, Antony Dzeryn |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * * Neither the names "Woopsi", "Simian Zombie" nor the |
| * names of its contributors may be used to endorse or promote products |
| * derived from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY Antony Dzeryn ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL Antony Dzeryn BE LIABLE FOR ANY |
| * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #include <nuttx/config.h> |
| |
| #include <stdint.h> |
| #include <stdbool.h> |
| |
| #include "graphics/nxwidgets/cwidgetcontrol.hxx" |
| #include "graphics/nxwidgets/cnxfont.hxx" |
| #include "graphics/nxwidgets/ctext.hxx" |
| #include "graphics/nxwidgets/cgraphicsport.hxx" |
| #include "graphics/nxwidgets/singletons.hxx" |
| #include "graphics/nxwidgets/cstringiterator.hxx" |
| #include "graphics/nxwidgets/cnxtimer.hxx" |
| #include "graphics/nxwidgets/cmultilinetextbox.hxx" |
| |
| /**************************************************************************** |
| * Pre-Processor Definitions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Method Implementations |
| ****************************************************************************/ |
| |
| using namespace NXWidgets; |
| |
| /** |
| * Constructor. |
| * |
| * @param pWidgetControl The widget control for the display. |
| * @param x The x coordinate of the text box, relative to its parent. |
| * @param y The y coordinate of the text box, relative to its parent. |
| * @param width The width of the textbox. |
| * @param height The height of the textbox. |
| * @param text Pointer to a string to display in the textbox. |
| * @param flags Standard widget flag options. |
| * @param maxRows The maximum number of rows the textbox can track. Adding |
| * text beyond this number will cause rows at the start of the text to be |
| * forgotten; text is essentially stored as a queue, and adding to the back |
| * of a full queue causes the front items to be popped off. Setting this to |
| * 0 will make the textbox track only the visible rows. |
| * @param style The style that the widget should use. If this is not |
| * specified, the widget will use the values stored in the global |
| * g_defaultWidgetStyle object. The widget will copy the properties of |
| * the style into its own internal style object. |
| */ |
| |
| CMultiLineTextBox::CMultiLineTextBox(CWidgetControl *pWidgetControl, |
| nxgl_coord_t x, nxgl_coord_t y, |
| nxgl_coord_t width, nxgl_coord_t height, |
| const CNxString &text, uint32_t flags, |
| nxgl_coord_t maxRows, CWidgetStyle *style) |
| : CScrollingPanel(pWidgetControl, x, y, width, height, flags, style) |
| { |
| m_hAlignment = TEXT_ALIGNMENT_HORIZ_CENTER; |
| m_vAlignment = TEXT_ALIGNMENT_VERT_CENTER; |
| m_topRow = 0; |
| |
| // The border is one line thick |
| |
| m_borderSize.top = 1; |
| m_borderSize.right = 1; |
| m_borderSize.bottom = 1; |
| m_borderSize.left = 1; |
| |
| CRect rect; |
| getClientRect(rect); |
| m_text = new CText(getFont(), "", rect.getWidth()); |
| m_canvasWidth = rect.getWidth(); |
| |
| m_flags.draggable = true; |
| m_flags.doubleClickable = true; |
| m_maxRows = maxRows; |
| |
| calculateVisibleRows(); |
| |
| // Set maximum rows if value not set |
| |
| if (m_maxRows == 0) |
| { |
| m_maxRows = m_visibleRows + 1; |
| } |
| |
| m_cursorPos = 0; |
| m_showCursor = SHOW_CURSOR_NEVER; |
| m_wrapCursor = false; |
| |
| setText(text); |
| } |
| |
| /** |
| * Set the horizontal alignment of text within the textbox. |
| * |
| * @param alignment The horizontal position of the text. |
| */ |
| |
| void CMultiLineTextBox::setTextAlignmentHoriz(TextAlignmentHoriz alignment) |
| { |
| m_hAlignment = alignment; |
| redraw(); |
| } |
| |
| /** |
| * Set the vertical alignment of text within the textbox. |
| * |
| * @param alignment The vertical position of the text. |
| */ |
| |
| void CMultiLineTextBox::setTextAlignmentVert(TextAlignmentVert alignment) |
| { |
| m_vAlignment = alignment; |
| redraw(); |
| } |
| |
| /** |
| * Returns the number of "pages" that the text spans. A page |
| * is defined as the amount of text that can be displayed within |
| * the textbox at one time. |
| * |
| * @return The page count. |
| */ |
| |
| const int CMultiLineTextBox::getPageCount(void) const |
| { |
| if (m_visibleRows > 0) |
| { |
| return (m_text->getLineCount() / m_visibleRows) + 1; |
| } |
| else |
| { |
| return 1; |
| } |
| } |
| |
| /** |
| * Returns the current page. |
| * |
| * @return The current page. |
| * @see getPageCount(). |
| */ |
| |
| const int CMultiLineTextBox::getCurrentPage(void) const |
| { |
| // Calculate the top line of text |
| |
| int topRow = -m_canvasY / m_text->getLineHeight(); |
| |
| // Return the page on which the top row falls |
| |
| if (m_visibleRows > 0) |
| { |
| return topRow / m_visibleRows; |
| } |
| else |
| { |
| return 1; |
| } |
| } |
| |
| /** |
| * Set the text displayed in the textbox. |
| * |
| * @param text String to display. |
| */ |
| |
| void CMultiLineTextBox::setText(const CNxString &text) |
| { |
| bool drawingEnabled = m_flags.drawingEnabled; |
| disableDrawing(); |
| |
| m_text->setText(text); |
| |
| cullTopLines(); |
| limitCanvasHeight(); |
| jumpToTextBottom(); |
| |
| if (drawingEnabled) |
| { |
| enableDrawing(); |
| } |
| redraw(); |
| |
| m_widgetEventHandlers->raiseValueChangeEvent(); |
| } |
| |
| /** |
| * Append new text to the end of the current text |
| * displayed in the textbox. |
| * |
| * @param text String to append. |
| */ |
| |
| void CMultiLineTextBox::appendText(const CNxString &text) |
| { |
| bool drawingEnabled = m_flags.drawingEnabled; |
| disableDrawing(); |
| |
| m_text->append(text); |
| |
| cullTopLines(); |
| limitCanvasHeight(); |
| jumpToTextBottom(); |
| |
| if (drawingEnabled) |
| { |
| enableDrawing(); |
| } |
| redraw(); |
| |
| m_widgetEventHandlers->raiseValueChangeEvent(); |
| } |
| |
| /** |
| * Remove all characters from the string from the start index onwards. |
| * |
| * @param startIndex Index to remove from. |
| */ |
| |
| void CMultiLineTextBox::removeText(const unsigned int startIndex) |
| { |
| removeText(startIndex, m_text->getLength() - startIndex); |
| } |
| |
| /** |
| * Remove specified number of characters from the string from the |
| * start index onwards. |
| * |
| * @param startIndex Index to remove from. |
| * @param count Number of characters to remove. |
| */ |
| |
| void CMultiLineTextBox::removeText(const unsigned int startIndex, |
| const unsigned int count) |
| { |
| bool drawingEnabled = m_flags.drawingEnabled; |
| disableDrawing(); |
| |
| m_text->remove(startIndex, count); |
| |
| limitCanvasHeight(); |
| limitCanvasY(); |
| |
| moveCursorToPosition(startIndex); |
| |
| if (drawingEnabled) |
| { |
| enableDrawing(); |
| } |
| redraw(); |
| |
| m_widgetEventHandlers->raiseValueChangeEvent(); |
| } |
| |
| /** |
| * Set the font used in the textbox. |
| * |
| * @param font Pointer to the new font. |
| */ |
| |
| void CMultiLineTextBox::setFont(CNxFont *font) |
| { |
| bool drawingEnabled = m_flags.drawingEnabled; |
| disableDrawing(); |
| |
| m_style.font = font; |
| m_text->setFont(font); |
| |
| cullTopLines(); |
| limitCanvasHeight(); |
| limitCanvasY(); |
| |
| if (drawingEnabled) |
| { |
| enableDrawing(); |
| } |
| redraw(); |
| |
| m_widgetEventHandlers->raiseValueChangeEvent(); |
| } |
| |
| /** |
| * Get the length of the text string. |
| * |
| * @return The length of the text string. |
| */ |
| |
| const int CMultiLineTextBox::getTextLength(void) const |
| { |
| return m_text->getLength(); |
| } |
| |
| /** |
| * Sets the cursor display mode. |
| * |
| * @param cursorMode Determines cursor display mode |
| */ |
| |
| void CMultiLineTextBox::showCursor(EShowCursor cursorMode) |
| { |
| if (m_showCursor != cursorMode) |
| { |
| m_showCursor = (uint8_t)cursorMode; |
| redraw(); |
| } |
| } |
| |
| /** |
| * Move the cursor to the text position specified. 0 indicates the start |
| * of the string. If position is greater than the length of the string, |
| * the cursor is moved to the end of the string. |
| * |
| * @param position The new cursor position. |
| */ |
| |
| void CMultiLineTextBox::moveCursorToPosition(const int position) |
| { |
| CGraphicsPort *port = m_widgetControl->getGraphicsPort(); |
| |
| // Erase existing cursor |
| |
| drawCursor(port); |
| |
| // Force position to within confines of string |
| |
| if (position < 0) |
| { |
| m_cursorPos = 0; |
| } |
| else |
| { |
| int len = (int)m_text->getLength(); |
| m_cursorPos = len > position ? position : len; |
| } |
| |
| // Draw cursor in new position |
| |
| drawCursor(port); |
| } |
| |
| /** |
| * Insert text at the specified index. |
| * |
| * @param text The text to insert. |
| * @param index Index at which to insert the text. |
| */ |
| |
| void CMultiLineTextBox::insertText(const CNxString &text, |
| const unsigned int index) |
| { |
| bool drawingEnabled = m_flags.drawingEnabled; |
| disableDrawing(); |
| |
| m_text->insert(text, index); |
| |
| cullTopLines(); |
| limitCanvasHeight(); |
| |
| moveCursorToPosition(index + text.getLength()); |
| |
| if (drawingEnabled) |
| { |
| enableDrawing(); |
| } |
| redraw(); |
| |
| m_widgetEventHandlers->raiseValueChangeEvent(); |
| } |
| |
| /** |
| * Insert text at the current cursor position. |
| * |
| * @param text The text to insert. |
| */ |
| |
| void CMultiLineTextBox::insertTextAtCursor(const CNxString &text) |
| { |
| insertText(text, getCursorPosition()); |
| } |
| |
| /** |
| * Handle a keyboard press event. |
| * |
| * @param e The event data. |
| */ |
| |
| void CMultiLineTextBox::handleKeyPressEvent(const CWidgetEventArgs &e) |
| { |
| nxwidget_char_t key = e.getKey(); |
| |
| if (key == KEY_CODE_BACKSPACE) |
| { |
| // Delete character in front of cursor |
| |
| if (m_cursorPos > 0) |
| { |
| removeText(m_cursorPos - 1, 1); |
| } |
| } |
| else if (key != KEY_CODE_NONE) |
| { |
| // Not modifier; append value |
| |
| insertTextAtCursor(key); |
| } |
| } |
| |
| /** |
| * Get the coordinates of the cursor relative to the text. |
| * |
| * @param x Will be populated with the x coordinate of the cursor. |
| * @param y Will be populated with the y coordinate of the cursor. |
| */ |
| |
| void CMultiLineTextBox::getCursorCoordinates(nxgl_coord_t &x, nxgl_coord_t &y) const |
| { |
| unsigned int cursorRow = 0; |
| |
| x = 0; |
| y = 0; |
| |
| // Only calculate the cursor position if the cursor isn't at the start |
| // of the text |
| |
| if (m_cursorPos > 0) |
| { |
| // Calculate the row in which the cursor appears |
| |
| cursorRow = m_text->getLineContainingCharIndex(m_cursorPos); |
| |
| // Cursor line offset gives us the distance of the cursor from the |
| // start of the line |
| |
| uint8_t cursorLineOffset = m_cursorPos - m_text->getLineStartIndex(cursorRow); |
| |
| CStringIterator *iterator = m_text->newStringIterator(); |
| iterator->moveTo(m_text->getLineStartIndex(cursorRow)); |
| |
| // Sum the width of each char in the row to find the x coordinate |
| |
| for (int i = 0; i < cursorLineOffset; ++i) |
| { |
| x += getFont()->getCharWidth(iterator->getChar()); |
| iterator->moveToNext(); |
| } |
| |
| delete iterator; |
| } |
| |
| // Add offset of row to calculated value |
| |
| x += getRowX(cursorRow); |
| |
| // Calculate y coordinate of the cursor |
| |
| y = getRowY(cursorRow); |
| } |
| |
| /** |
| * Gets the index of the character at the specified x coordinate in the |
| * specified row. |
| * |
| * @param x X coordinate of the character. |
| * @param rowIndex Index of the row containing the character. |
| * @return The index of the character at the specified coordinate. |
| */ |
| |
| int CMultiLineTextBox::getCharIndexAtCoordinate(nxgl_coord_t x, int rowIndex) const |
| { |
| // Locate the character within the row |
| |
| int startIndex = m_text->getLineStartIndex(rowIndex); |
| int stopIndex = m_text->getLineLength(rowIndex); |
| int width = getRowX(rowIndex); |
| int index = -1; |
| |
| CStringIterator *iterator = m_text->newStringIterator(); |
| iterator->moveTo(startIndex); |
| |
| width += m_text->getFont()->getCharWidth(iterator->getChar()); |
| |
| for (int i = 0; i < stopIndex; ++i) |
| { |
| if (width > x) |
| { |
| if (i == 0) |
| { |
| // If the coordinate is on the left of the text, we add nothing |
| // to the index |
| |
| index = startIndex; |
| } |
| else |
| { |
| // Character within the row. |
| // This is the character that contains the coordinate. |
| |
| index = startIndex + i; |
| } |
| break; |
| } |
| |
| iterator->moveToNext(); |
| width += m_text->getFont()->getCharWidth(iterator->getChar()); |
| } |
| |
| delete iterator; |
| |
| // If the coordinate is past the last character, index will still be -1. |
| // We need to set it to the last character |
| |
| if (index == -1) |
| { |
| if (rowIndex == m_text->getLineCount() - 1) |
| { |
| // Index past the end point of the text, so return an index |
| // just past the text |
| |
| index = startIndex + stopIndex; |
| } |
| else |
| { |
| // Index at the end of a row, so return the last index of the |
| // row |
| |
| index = startIndex + stopIndex - 1; |
| } |
| } |
| |
| return index; |
| } |
| |
| /** |
| * Get the index of the character at the specified coordinates. |
| * |
| * @param x X coordinate of the character. |
| * @param y Y coordinate of the character. |
| * @return The index of the character at the specified coordinates. |
| */ |
| |
| unsigned int |
| CMultiLineTextBox::getCharIndexAtCoordinates(nxgl_coord_t x, nxgl_coord_t y) const |
| { |
| int rowIndex = getRowContainingCoordinate(y); |
| return getCharIndexAtCoordinate(x, rowIndex); |
| } |
| |
| /** |
| * Get the row containing the specified Y coordinate. |
| * |
| * @param y Y coordinate to locate. |
| * @return The index of the row containing the specified Y coordinate. |
| */ |
| |
| int CMultiLineTextBox::getRowContainingCoordinate(nxgl_coord_t y) const |
| { |
| int row = -1; |
| |
| // Locate the row containing the character |
| |
| for (int i = 0; i < m_text->getLineCount(); ++i) |
| { |
| // Abort search if we've found the row below the y coordinate |
| |
| if (getRowY(i) > y) |
| { |
| if (i == 0) |
| { |
| // If the coordinate is above the text, we return the top |
| // row |
| |
| row = 0; |
| } |
| else |
| { |
| // Row within the text, so return the previous row - this is |
| // the row that contains the coordinate. |
| |
| row = i - 1; |
| } |
| |
| break; |
| } |
| } |
| |
| // If the coordinate is below the text, row will still be -1. |
| // We need to set it to the last row |
| |
| if (row == -1) |
| { |
| row = m_text->getLineCount() - 1; |
| } |
| |
| return row; |
| } |
| |
| /** |
| * Draw the area of this widget that falls within the clipping region. |
| * Called by the redraw() function to draw all visible regions. |
| * |
| * @param port The CGraphicsPort to draw to. |
| * @see redraw() |
| */ |
| |
| void CMultiLineTextBox::drawContents(CGraphicsPort *port) |
| { |
| drawText(port); |
| |
| // Draw the cursor |
| |
| drawCursor(port); |
| } |
| |
| /** |
| * Draw the area of this widget that falls within the clipping region. |
| * Called by the redraw() function to draw all visible regions. |
| * |
| * @param port The CGraphicsPort to draw to. |
| * @see redraw() |
| */ |
| |
| void CMultiLineTextBox::drawBorder(CGraphicsPort *port) |
| { |
| port->drawFilledRect(getX(), getY(), getWidth(), getHeight(), |
| getBackgroundColor()); |
| |
| // Stop drawing if the widget indicates it should not have an outline |
| |
| if (!isBorderless()) |
| { |
| port->drawBevelledRect(getX(), getY(), getWidth(), getHeight(), |
| getShadowEdgeColor(), getShineEdgeColor()); |
| } |
| } |
| |
| /** |
| * Move cursor one character to the left. |
| */ |
| |
| void CMultiLineTextBox::moveCursorLeft(void) |
| { |
| if (m_cursorPos > 0) |
| { |
| moveCursorToPosition(m_cursorPos - 1); |
| } |
| |
| jumpToCursor(); |
| } |
| |
| /** |
| * Move cursor one character to the right. |
| */ |
| |
| void CMultiLineTextBox::moveCursorRight(void) |
| { |
| if (m_cursorPos < (int)m_text->getLength()) |
| { |
| moveCursorToPosition(m_cursorPos + 1); |
| } |
| |
| jumpToCursor(); |
| } |
| |
| /** |
| * Move cursor one row upwards. |
| */ |
| |
| void CMultiLineTextBox::moveCursorUp(void) |
| { |
| nxgl_coord_t cursorX = 0; |
| nxgl_coord_t cursorY = 0; |
| |
| getCursorCoordinates(cursorX, cursorY); |
| |
| // Get the midpoint of the cursor. We use the midpoint to ensure that |
| // the cursor does not drift off to the left as it moves up the text, which |
| // is a problem when we use the left edge as the reference point when the |
| // font is proportional |
| |
| cursorX += m_text->getFont()->getCharWidth(m_text->getCharAt(m_cursorPos)) >> 1; |
| |
| // Locate the character above the midpoint |
| |
| int index = getCharIndexAtCoordinates(cursorX, cursorY + m_text->getLineHeight()); |
| moveCursorToPosition(index); |
| jumpToCursor(); |
| } |
| |
| /** |
| * Move cursor one row downwards. |
| */ |
| |
| void CMultiLineTextBox::moveCursorDown(void) |
| { |
| nxgl_coord_t cursorX = 0; |
| nxgl_coord_t cursorY = 0; |
| |
| getCursorCoordinates(cursorX, cursorY); |
| |
| // Get the midpoint of the cursor. We use the midpoint to ensure that |
| // the cursor does not drift off to the left as it moves up the text, which |
| // is a problem when we use the left edge as the reference point when the |
| // font is proportional |
| |
| cursorX += m_text->getFont()->getCharWidth(m_text->getCharAt(m_cursorPos)) >> 1; |
| |
| // Locate the character above the midpoint |
| |
| int index = getCharIndexAtCoordinates(cursorX, cursorY - m_text->getLineHeight()); |
| moveCursorToPosition(index); |
| jumpToCursor(); |
| } |
| |
| /** |
| * Ensures that the textbox only contains the maximum allowed |
| * number of rows by culling any excess rows from the top of |
| * the text. |
| * |
| * @return True if lines were removed from the text; false if not. |
| */ |
| |
| bool CMultiLineTextBox::cullTopLines(void) |
| { |
| // Ensure that we have the correct number of rows |
| |
| if (m_text->getLineCount() > m_maxRows) |
| { |
| m_text->stripTopLines(m_text->getLineCount() - m_maxRows); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Ensures that the canvas height is the height of the widget, |
| * if the widget exceeds the size of the text, or the height of |
| * the text if the text exceeds the size of the widget. |
| */ |
| |
| void CMultiLineTextBox::limitCanvasHeight(void) |
| { |
| m_canvasHeight = m_text->getPixelHeight(); |
| |
| CRect rect; |
| getClientRect(rect); |
| if (m_canvasHeight < rect.getHeight()) |
| { |
| m_canvasHeight = rect.getHeight(); |
| } |
| } |
| |
| /** |
| * Ensures that the canvas cannot scroll beyond its height. |
| */ |
| |
| void CMultiLineTextBox::limitCanvasY(void) |
| { |
| CRect rect; |
| getClientRect(rect); |
| |
| // Ensure that the visible portion of the canvas is not less than the |
| // height of the viewer window |
| |
| if (m_canvasY + m_canvasHeight < rect.getHeight()) |
| { |
| jumpToTextBottom(); |
| } |
| } |
| |
| /** |
| * Jumps to the cursor coordinates of the text. |
| */ |
| |
| void CMultiLineTextBox::jumpToCursor(void) |
| { |
| // Get the co-odinates of the cursor |
| |
| nxgl_coord_t cursorX; |
| nxgl_coord_t cursorY; |
| |
| getCursorCoordinates(cursorX, cursorY); |
| |
| // Work out which row the cursor falls within |
| |
| int cursorRow = m_text->getLineContainingCharIndex(m_cursorPos); |
| nxgl_coord_t rowY = getRowY(cursorRow); |
| |
| // If the cursor is outside the visible portion of the canvas, jump to it |
| |
| CRect rect; |
| getClientRect(rect); |
| |
| if (rowY + m_text->getLineHeight() + m_canvasY > rect.getHeight()) |
| { |
| // Cursor is below the visible portion of the canvas, so |
| // jump down so that the cursor's row is the bottom row of |
| // text |
| |
| jump(0, -(rowY + m_text->getLineHeight() - rect.getHeight())); |
| } |
| else if (rowY + m_canvasY < 0) |
| { |
| // Cursor is above the visible portion of the canvas, so |
| // jump up so that the cursor's row is the top row of text |
| |
| jump(0, -cursorY); |
| } |
| } |
| |
| /** |
| * Jumps to the bottom of the text. |
| */ |
| |
| void CMultiLineTextBox::jumpToTextBottom(void) |
| { |
| CRect rect; |
| getClientRect(rect); |
| jump(0, -(m_canvasHeight - rect.getHeight())); |
| } |
| |
| /** |
| * Resize the textbox to the new dimensions. |
| * |
| * @param width The new width. |
| * @param height The new height. |
| */ |
| |
| void CMultiLineTextBox::onResize(nxgl_coord_t width, nxgl_coord_t height) |
| { |
| // Ensure the base class resize method is called |
| |
| CScrollingPanel::onResize(width, height); |
| |
| // Resize the canvas' width |
| |
| CRect rect; |
| getClientRect(rect); |
| |
| m_canvasWidth = rect.getWidth(); |
| m_canvasHeight = rect.getHeight(); |
| m_canvasX = 0; |
| m_canvasY = 0; |
| |
| calculateVisibleRows(); |
| |
| // Re-wrap the text |
| |
| m_text->setWidth(getWidth()); |
| m_text->wrap(); |
| |
| bool raiseEvent = cullTopLines(); |
| limitCanvasHeight(); |
| limitCanvasY(); |
| |
| if (raiseEvent) |
| { |
| m_widgetEventHandlers->raiseValueChangeEvent(); |
| } |
| } |
| |
| /** |
| * Starts the dragging system. |
| * |
| * @param x The x coordinate of the click. |
| * @param y The y coordinate of the click. |
| */ |
| |
| void CMultiLineTextBox::onClick(nxgl_coord_t x, nxgl_coord_t y) |
| { |
| startDragging(x, y); |
| |
| // Move cursor to clicked coordinates |
| |
| CRect rect; |
| getClientRect(rect); |
| |
| // Adjust x and y from screen coordinates to canvas coordinates |
| |
| nxgl_coord_t canvasRelativeX = x - getX() - rect.getX() - m_canvasX; |
| nxgl_coord_t canvasRelativeY = y - getY() - rect.getY() - m_canvasY; |
| |
| moveCursorToPosition(getCharIndexAtCoordinates(canvasRelativeX, canvasRelativeY)); |
| } |
| |
| /** |
| * Opens the keyboard on the bottom display. |
| * |
| * @param x The x coordinates of the click. |
| * @param y The y coordinates of the click. |
| */ |
| |
| void CMultiLineTextBox::onDoubleClick(nxgl_coord_t x, nxgl_coord_t y) |
| { |
| } |
| |
| /** |
| * Handles cursor control events. Moves the cursor |
| * in the direction selected. |
| * |
| * @param key The key that was pressed. |
| */ |
| |
| void CMultiLineTextBox::handleCursorControlEvent(const CWidgetEventArgs &e) |
| { |
| ECursorControl control = e.getCursorControl(); |
| |
| switch (control) |
| { |
| case CURSOR_LEFT: |
| moveCursorLeft(); |
| break; |
| |
| case CURSOR_RIGHT: |
| moveCursorRight(); |
| break; |
| |
| case CURSOR_UP: |
| moveCursorDown(); |
| break; |
| |
| case CURSOR_DOWN: |
| moveCursorUp(); |
| break; |
| |
| default: |
| // Not interested in other controls |
| |
| break; |
| } |
| } |
| |
| /** |
| * Handle a keyboard press event. |
| * |
| * @param e The event data. |
| */ |
| |
| void handleKeyPressEvent(const CWidgetEventArgs &e); |
| |
| /** |
| * Gets the x position of a row of text based on the width of the row and the |
| * type of horizontal alignment currently set. |
| * |
| * @param row The index of the row. |
| * @return The x coordinate of the row. |
| */ |
| |
| nxgl_coord_t CMultiLineTextBox::getRowX(int row) const |
| { |
| CRect rect; |
| getClientRect(rect); |
| |
| uint8_t rowLength = m_text->getLineTrimmedLength(row); |
| uint8_t rowPixelWidth = m_text->getFont()->getStringWidth(*m_text, m_text->getLineStartIndex(row), rowLength); |
| |
| // Calculate horizontal position |
| |
| switch (m_hAlignment) |
| { |
| case TEXT_ALIGNMENT_HORIZ_CENTER: |
| return (rect.getWidth() - rowPixelWidth) >> 1; |
| |
| case TEXT_ALIGNMENT_HORIZ_LEFT: |
| return 0; |
| |
| case TEXT_ALIGNMENT_HORIZ_RIGHT: |
| return rect.getWidth() - rowPixelWidth; |
| } |
| |
| // Will never be reached |
| |
| return 0; |
| } |
| |
| /** |
| * Gets the y position of the specified row of text based on the type of |
| * vertical alignment currently set. |
| * |
| * @param row The row number to find the y coordinate of. |
| * @return The y coordinate of the specified row of text. |
| */ |
| |
| nxgl_coord_t CMultiLineTextBox::getRowY(int row) const |
| { |
| // If the amount of text exceeds the size of the widget, force |
| // the text to be top-aligned |
| |
| if (m_visibleRows <= m_text->getLineCount()) |
| { |
| return row * m_text->getLineHeight(); |
| } |
| |
| // All text falls within the textbox, so obey the alignment |
| // options |
| |
| nxgl_coord_t textY = 0; |
| nxgl_coord_t startPos = 0; |
| |
| int canvasRows = 0; |
| int textRows = 0; |
| |
| CRect rect; |
| getClientRect(rect); |
| |
| // Calculate vertical position |
| |
| switch (m_vAlignment) |
| { |
| case TEXT_ALIGNMENT_VERT_CENTER: |
| |
| // Calculate the maximum number of rows |
| |
| canvasRows = m_canvasHeight / m_text->getLineHeight(); |
| textY = row * m_text->getLineHeight(); |
| |
| // Get the number of rows of text |
| |
| textRows = m_text->getLineCount(); |
| |
| // Ensure there's always one row |
| |
| if (textRows == 0) |
| { |
| textRows = 1; |
| } |
| |
| // Calculate the start position of the block of text |
| |
| startPos = ((canvasRows - textRows) * m_text->getLineHeight()) >> 1; |
| |
| // Calculate the row Y coordinate |
| |
| textY = startPos + textY; |
| break; |
| |
| case TEXT_ALIGNMENT_VERT_TOP: |
| textY = row * m_text->getLineHeight(); |
| break; |
| |
| case TEXT_ALIGNMENT_VERT_BOTTOM: |
| textY = rect.getHeight() - (((m_text->getLineCount() - row) * m_text->getLineHeight())); |
| break; |
| } |
| |
| return textY; |
| } |
| |
| /** |
| * Return true if the cursor is visible |
| */ |
| |
| bool CMultiLineTextBox::isCursorVisible(void) const |
| { |
| return (m_showCursor == SHOW_CURSOR_ALWAYS || |
| (m_showCursor == SHOW_CURSOR_ONFOCUS && hasFocus())); |
| } |
| |
| /** |
| * Gets the character under the cursor. |
| * |
| * @return The character under the cursor. |
| */ |
| |
| nxwidget_char_t CMultiLineTextBox::getCursorChar(void) const |
| { |
| if (m_cursorPos < (int)m_text->getLength()) |
| { |
| return m_text->getCharAt(m_cursorPos); |
| } |
| else |
| { |
| return ' '; |
| } |
| } |
| |
| /** |
| * Works out the number of visible rows within the textbox. |
| */ |
| |
| void CMultiLineTextBox::calculateVisibleRows(void) |
| { |
| CRect rect; |
| getClientRect(rect); |
| |
| m_visibleRows = rect.getHeight() / m_text->getLineHeight(); |
| } |
| |
| /** |
| * Draws text. |
| * |
| * @param port The CGraphicsPort to draw to. |
| */ |
| |
| void CMultiLineTextBox::drawText(CGraphicsPort *port) |
| { |
| // Early exit if there is no text to display |
| |
| if (m_text->getLineCount() == 0) |
| { |
| return; |
| } |
| |
| // Determine the top and bottom rows within the graphicsport's clip rect. |
| // We only draw these rows in order to increase the speed of the routine. |
| |
| int regionY = -m_canvasY; // Y coord of canvas visible region |
| int topRow = getRowContainingCoordinate(regionY); |
| int bottomRow = getRowContainingCoordinate(regionY + m_rect.getHeight()); |
| |
| // Early exit checks |
| |
| if ((topRow < 0) && (bottomRow < 0)) |
| { |
| return; |
| } |
| |
| if ((bottomRow >= m_text->getLineCount()) && (topRow >= m_text->getLineCount())) |
| { |
| return; |
| } |
| |
| // Prevent overflows |
| |
| if (topRow < 0) |
| { |
| topRow = 0; |
| } |
| |
| if (bottomRow >= m_text->getLineCount()) |
| { |
| bottomRow = m_text->getLineCount() - 1; |
| } |
| |
| // Draw lines of text |
| |
| int currentRow = topRow; |
| |
| // Draw all rows in this region |
| |
| while (currentRow <= bottomRow) |
| { |
| drawRow(port, currentRow); |
| currentRow++; |
| } |
| } |
| |
| /** |
| * Draws the cursor. |
| * |
| * @param port The CGraphicsPort to draw to. |
| */ |
| |
| void CMultiLineTextBox::drawCursor(CGraphicsPort *port) |
| { |
| // Get the cursor coordinates |
| |
| if (isCursorVisible()) |
| { |
| nxgl_coord_t cursorX = 0; |
| nxgl_coord_t cursorY = 0; |
| |
| getCursorCoordinates(cursorX, cursorY); |
| |
| // Adjust for canvas offsets |
| |
| cursorX += m_canvasX; |
| cursorY += m_canvasY; |
| |
| // Draw cursor |
| |
| port->invert(cursorX, cursorY, |
| m_text->getFont()->getCharWidth(getCursorChar()), |
| m_text->getFont()->getHeight()); |
| } |
| } |
| |
| /** |
| * Draws a single line of text. |
| * |
| * @param port The CGraphicsPort to draw to. |
| * @param row The index of the row to draw. |
| */ |
| |
| void CMultiLineTextBox::drawRow(CGraphicsPort *port, int row) |
| { |
| // Get the drawing region |
| |
| CRect rect; |
| getRect(rect); |
| |
| uint8_t rowLength = m_text->getLineTrimmedLength(row); |
| |
| struct nxgl_point_s pos; |
| pos.x = getRowX(row) + m_canvasX + rect.getX(); |
| pos.y = getRowY(row) + m_canvasY + rect.getY(); |
| |
| // Determine the background and text color |
| |
| nxgl_mxpixel_t textColor; |
| |
| if (!isEnabled()) |
| { |
| textColor = getDisabledTextColor(); |
| } |
| else |
| { |
| textColor = getEnabledTextColor(); |
| } |
| |
| // And draw the text using the selected color |
| |
| port->drawText(&pos, &rect, m_text->getFont(), *m_text, |
| m_text->getLineStartIndex(row), rowLength, textColor); |
| } |