893387b2c5
Signed-off-by: Xiang Xiao <xiaoxiang@xiaomi.com>
1271 lines
30 KiB
C++
1271 lines
30 KiB
C++
/****************************************************************************
|
|
* 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);
|
|
}
|