From 788db64b8642bf31de6930d18a62177c64163ee0 Mon Sep 17 00:00:00 2001 From: Yuval Adam Date: Fri, 13 Mar 2015 12:24:02 +0200 Subject: Add grlib, fixes #5 --- grlib/listbox.c | 750 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 750 insertions(+) create mode 100644 grlib/listbox.c (limited to 'grlib/listbox.c') diff --git a/grlib/listbox.c b/grlib/listbox.c new file mode 100644 index 0000000..dc1f4f3 --- /dev/null +++ b/grlib/listbox.c @@ -0,0 +1,750 @@ +//***************************************************************************** +// +// listbox.c - A listbox widget. +// +// Copyright (c) 2008-2014 Texas Instruments Incorporated. All rights reserved. +// Software License Agreement +// +// Texas Instruments (TI) is supplying this software for use solely and +// exclusively on TI's microcontroller products. The software is owned by +// TI and/or its suppliers, and is protected under applicable copyright +// laws. You may not combine this software with "viral" open-source +// software in order to form a larger program. +// +// THIS SOFTWARE IS PROVIDED "AS IS" AND WITH ALL FAULTS. +// NO WARRANTIES, WHETHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING, BUT +// NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE APPLY TO THIS SOFTWARE. TI SHALL NOT, UNDER ANY +// CIRCUMSTANCES, BE LIABLE FOR SPECIAL, INCIDENTAL, OR CONSEQUENTIAL +// DAMAGES, FOR ANY REASON WHATSOEVER. +// +// This is part of revision 2.1.0.12573 of the Tiva Graphics Library. +// +//***************************************************************************** + +#include +#include +#include "driverlib/debug.h" +#include "grlib/grlib.h" +#include "grlib/widget.h" +#include "grlib/listbox.h" + +//***************************************************************************** +// +//! \addtogroup listbox_api +//! @{ +// +//***************************************************************************** + +//***************************************************************************** +// +// Make sure that the abs() macro is defined. +// +//***************************************************************************** +#ifndef abs +#define abs(a) (((a) >= 0) ? (a) : (-(a))) +#endif + +//***************************************************************************** +// +// Make sure min and max are defined. +// +//***************************************************************************** +#ifndef min +#define min(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +#ifndef max +#define max(a, b) (((a) < (b)) ? (b) : (a)) +#endif + +//***************************************************************************** +// +//! Draws the contents of a listbox. +//! +//! \param psWidget is a pointer to the listbox widget to be drawn. +//! +//! This function draws the contents of a listbox on the display. This is +//! called in response to a \b #WIDGET_MSG_PAINT message. +//! +//! \return None. +// +//***************************************************************************** +static void +ListBoxPaint(tWidget *psWidget) +{ + tListBoxWidget *pListBox; + tContext sCtx; + tRectangle sWidgetRect, sLineRect; + int16_t i16Height; + int32_t i32Width; + uint16_t ui16String; + + // + // Check the arguments. + // + ASSERT(psWidget); + + // + // Convert the generic widget pointer into a listbox widget pointer. + // + pListBox = (tListBoxWidget *)psWidget; + + // + // Initialize a drawing context. + // + GrContextInit(&sCtx, psWidget->psDisplay); + GrContextFontSet(&sCtx, pListBox->psFont); + + // + // Initialize the clipping region based on the extents of this listbox. + // + sWidgetRect = psWidget->sPosition; + GrContextClipRegionSet(&sCtx, &sWidgetRect); + + // + // See if the listbox outline style is selected. + // + if(pListBox->ui32Style & LISTBOX_STYLE_OUTLINE) + { + // + // Outline the listbox with the outline color. + // + GrContextForegroundSet(&sCtx, pListBox->ui32OutlineColor); + GrRectDraw(&sCtx, &(psWidget->sPosition)); + + // + // Shrink the widget region by one pixel on each side and draw another + // rectangle, this time in the background color. This ensures that the + // text will not interfere with the colored border. + // + sWidgetRect.i16XMin++; + sWidgetRect.i16YMin++; + sWidgetRect.i16XMax--; + sWidgetRect.i16YMax--; + GrContextForegroundSet(&sCtx, pListBox->ui32BackgroundColor); + GrRectDraw(&sCtx, &sWidgetRect); + + // + // Reduce the size of the rectangle by another pixel to get the final + // area into which we will put the text. + // + sWidgetRect.i16XMin++; + sWidgetRect.i16YMin++; + sWidgetRect.i16XMax--; + sWidgetRect.i16YMax--; + GrContextClipRegionSet(&sCtx, &sWidgetRect); + } + + // + // Start drawing at the top of the widget. + // + sLineRect = sWidgetRect; + ui16String = pListBox->ui16StartEntry; + i16Height = GrFontHeightGet(pListBox->psFont); + + // + // Keep drawing until we reach the bottom of the listbox or run out of + // strings to draw. + // + while((sLineRect.i16YMin < sWidgetRect.i16YMax) && + (ui16String < pListBox->ui16Populated)) + { + // + // Calculate the rectangle that will enclose this line of text. + // + sLineRect.i16YMax = sLineRect.i16YMin + i16Height - 1; + + // + // Set foreground and background colors appropriately. + // + GrContextBackgroundSet(&sCtx, ((ui16String == pListBox->i16Selected) ? + pListBox->ui32SelectedBackgroundColor : + pListBox->ui32BackgroundColor)); + GrContextForegroundSet(&sCtx, ((ui16String == pListBox->i16Selected) ? + pListBox->ui32SelectedTextColor : + pListBox->ui32TextColor)); + + // + // Draw the text. + // + GrStringDraw(&sCtx, pListBox->ppcText[ui16String], -1, + sLineRect.i16XMin, sLineRect.i16YMin, 1); + + // + // Determine the width of the string we just rendered. + // + i32Width = GrStringWidthGet(&sCtx, pListBox->ppcText[ui16String], -1); + + // + // Do we need to clear the area to the right of the string? + // + if(i32Width < (sLineRect.i16XMax - sLineRect.i16XMin + 1)) + { + // + // Yes - we need to fill the right side of this string with + // background color. + // + GrContextForegroundSet(&sCtx, + ((ui16String == pListBox->i16Selected) ? + pListBox->ui32SelectedBackgroundColor : + pListBox->ui32BackgroundColor)); + sLineRect.i16XMin += i32Width; + GrRectFill(&sCtx, &sLineRect); + sLineRect.i16XMin = sWidgetRect.i16XMin; + } + + // + // Move on to the next string, wrapping if necessary. + // + ui16String++; + if(ui16String == pListBox->ui16MaxEntries) + { + ui16String = 0; + } + sLineRect.i16YMin += i16Height; + + // + // If we are wrapping and got back at the oldest entry, we drop out. + // + if(ui16String == pListBox->ui16OldestEntry) + { + break; + } + } + + // + // Fill the remainder of the listbox area with the background color. + // + if(sLineRect.i16YMin < sWidgetRect.i16YMax) + { + // + // Determine the rectangle to be filled. + // + sLineRect.i16YMax = sWidgetRect.i16YMax; + + // + // Fill the rectangle with the background color. + // + GrContextForegroundSet(&sCtx, pListBox->ui32BackgroundColor); + GrRectFill(&sCtx, &sLineRect); + } +} + +//***************************************************************************** +// +// Handles pointer messages for a listbox widget. +// +// \param pListBox is a pointer to the listbox widget. +// \param ui32Msg is the message. +// \param i32X is the X coordinate of the pointer. +// \param i32Y is the Y coordinate of the pointer. +// +// This function receives pointer messages intended for this listbox widget +// and processes them accordingly. +// +// \return Returns a value appropriate to the supplied message. +// +//***************************************************************************** +static int32_t +ListBoxPointer(tListBoxWidget *pListBox, uint32_t ui32Msg, int32_t i32X, + int32_t i32Y) +{ + int32_t i32LineNum, i32Entry, i32Visible, i32MaxUp, i32MaxDown, i32Scroll; + + switch(ui32Msg) + { + // + // The touchscreen has been pressed. + // + case WIDGET_MSG_PTR_DOWN: + { + // + // Is the pointer press within the bounds of this widget? + // + if(!GrRectContainsPoint(&(pListBox->sBase.sPosition), i32X, i32Y)) + { + // + // This is not a message for us so return 0 to indicate that + // we did not process it. + // + return(0); + } + else + { + // + // The pointer was pressed within this control. Remember the Y + // coordinate and reset or scrolling flag. + // + pListBox->ui16Scrolled = 0; + pListBox->i32PointerY = i32Y; + + // + // Return 1 to indicate to the widget manager that we processed + // the message. This widget will now receive all pointer move + // messages until the pointer is released. + // + return(1); + } + } + + // + // The touchscreen has been released. + // + case WIDGET_MSG_PTR_UP: + { + // + // If the pointer is still within the bounds of the control and + // we have not scrolled the contents since the last time the + // pointer was pressed, we assume that this is a tap rather than + // a drag and select the element that falls beneath the current + // pointer position. If the pointer is outside our control, if + // we have scrolled already or if the control is locked, don't + // change the selection. + // + if((pListBox->ui16Scrolled == 0) && + !(pListBox->ui32Style & LISTBOX_STYLE_LOCKED) && + GrRectContainsPoint(&(pListBox->sBase.sPosition), i32X, i32Y)) + { + // + // It seems we need to change the selected element. What is + // the display line number that has been clicked on? + // + i32LineNum = (i32Y - + (int32_t)pListBox->sBase.sPosition.i16YMin) / + GrFontHeightGet(pListBox->psFont); + + // + // We now know the location of the click as a number of text + // lines from the top of the list box. Now determine what + // entry is shown there, remembering that the index may wrap. + // + i32Entry = ((int32_t)pListBox->ui16StartEntry + i32LineNum) % + pListBox->ui16MaxEntries; + + // + // If this is an unpopulated entry or the current selection, + // clear the selection. + // + if((i32Entry >= (int32_t)pListBox->ui16Populated) || + (i32Entry == (int32_t)pListBox->i16Selected)) + { + // + // Yes - update the selection and force a repaint. + // + pListBox->i16Selected = (int16_t)0xFFFF; + } + else + { + // + // The pointer was tapped on a valid entry other than the + // current selection so change the selection. + // + pListBox->i16Selected = (int16_t)i32Entry; + } + + // + // Force a repaint of the widget. + // + WidgetPaint((tWidget *)pListBox); + + // + // Tell the client that the selection changed. + // + if(pListBox->pfnOnChange) + { + (pListBox->pfnOnChange)((tWidget *)pListBox, + pListBox->i16Selected); + } + } + + // + // We process all pointer up messages so return 1 to tell the + // widget manager this. + // + return(1); + } + + // + // The pointer is moving while pressed. + // + case WIDGET_MSG_PTR_MOVE: + { + // + // How far has the pointer moved vertically from the point where it + // was pressed or where we last registered a scroll? i32LineNum + // will be negative for downward scrolling. + // + i32LineNum = pListBox->i32PointerY - i32Y; + + // + // If this distance is greater than or equal to the height of a + // line of text, we need to check to see if we need to scroll the + // list box contents. + // + if(abs(i32LineNum) >= GrFontHeightGet(pListBox->psFont)) + { + // + // We have to scroll if this is possible. How many lines can + // be visible on the display? + // + i32Visible = (pListBox->sBase.sPosition.i16YMax - + pListBox->sBase.sPosition.i16YMin) / + (int32_t)GrFontHeightGet(pListBox->psFont); + + // + // If we have fewer strings in the listbox than there are lines + // on the display, scrolling is not possible so give up now. + // + if(i32Visible > (int32_t)pListBox->ui16Populated) + { + return(1); + } + + // + // How many lines of scrolling does the latest pointer position + // indicate? A negative value implies downward scrolling (i.e. + // showing earlier strings). + // + i32Scroll = i32LineNum / + (int32_t)GrFontHeightGet(pListBox->psFont); + + // + // What is the farthest we could scroll downwards (i.e. moving + // the pointer towards the bottom of the screen)? Note - this + // will be negative or 0. + // + i32MaxDown = + (pListBox->ui16StartEntry >= pListBox->ui16OldestEntry) ? + (pListBox->ui16OldestEntry - pListBox->ui16StartEntry ) : + ((pListBox->ui16OldestEntry - pListBox->ui16StartEntry) - + pListBox->ui16MaxEntries); + + // + // What is the farthest we could scroll upwards? Note - this + // will be a positive number. + // + i32MaxUp = ((int32_t)pListBox->ui16Populated - i32Visible) + + i32MaxDown; + + // + // Determine the actual scroll distance given the maximum + // distances calculated. + // + i32Scroll = min(i32Scroll, i32MaxUp); + i32Scroll = max(i32Scroll, i32MaxDown); + + if(i32Scroll) + { + int32_t i32Temp; + + // + // Adjust the start entry appropriately, taking care to + // handle the wrap case. The use of a temporary variable + // here is required to work around a compiler bug which + // resulted in an invalid value of pListBox->ui16StartEntry + // following the calculation. + // + i32Temp = pListBox->ui16StartEntry; + i32Temp += i32Scroll; + i32Temp %= (int32_t)pListBox->ui16MaxEntries; + pListBox->ui16StartEntry = (uint16_t)i32Temp; + + // + // Remember that we scrolled. + // + pListBox->ui16Scrolled = 1; + + // + // Adjust the pointer position we record to take into account + // the amount we just scrolled. + // + pListBox->i32PointerY -= (i32Scroll * + GrFontHeightGet(pListBox->psFont)); + + // + // Repaint the contents of the widget. + // + WidgetPaint((tWidget *)pListBox); + } + } + + return(1); + } + } + + // + // We don't handle any other messages so return 0 if we get these. + // + return(0); +} + +//***************************************************************************** +// +//! Handles messages for a listbox widget. +//! +//! \param psWidget is a pointer to the listbox widget. +//! \param ui32Msg is the message. +//! \param ui32Param1 is the first parameter to the message. +//! \param ui32Param2 is the second parameter to the message. +//! +//! This function receives messages intended for this listbox widget and +//! processes them accordingly. The processing of the message varies based on +//! the message in question. +//! +//! Unrecognized messages are handled by calling WidgetDefaultMsgProc(). +//! +//! \return Returns a value appropriate to the supplied message. +// +//***************************************************************************** +int32_t +ListBoxMsgProc(tWidget *psWidget, uint32_t ui32Msg, uint32_t ui32Param1, + uint32_t ui32Param2) +{ + tListBoxWidget *pListBox; + + // + // Check the arguments. + // + ASSERT(psWidget); + + // + // Convert the generic pointer to a list box pointer. + // + pListBox = (tListBoxWidget *)psWidget; + + // + // Determine which message is being sent. + // + switch(ui32Msg) + { + // + // A pointer message has been received. + // + case WIDGET_MSG_PTR_DOWN: + case WIDGET_MSG_PTR_UP: + case WIDGET_MSG_PTR_MOVE: + return(ListBoxPointer(pListBox, ui32Msg, (int32_t)ui32Param1, + (int32_t)ui32Param2)); + + // + // The widget paint request has been sent. + // + case WIDGET_MSG_PAINT: + { + // + // Handle the widget paint request. + // + ListBoxPaint(psWidget); + + // + // Return one to indicate that the message was successfully + // processed. + // + return(1); + } + + // + // An unknown request has been sent. + // + default: + { + // + // Let the default message handler process this message. + // + return(WidgetDefaultMsgProc(psWidget, ui32Msg, ui32Param1, + ui32Param2)); + } + } +} + +//***************************************************************************** +// +//! Initializes a listbox widget. +//! +//! \param psWidget is a pointer to the listbox widget to initialize. +//! \param psDisplay is a pointer to the display on which to draw the listbox. +//! \param ppcText is a pointer to an array of character pointers which will +//! hold the strings that the listbox displays. +//! \param ui16MaxEntries provides the total number of entries in the +//! \e ppcText array. +//! \param ui16PopulatedEntries provides the number of entries in the +//! \e ppcText array which are populated. +//! \param i32X is the X coordinate of the upper left corner of the listbox. +//! \param i32Y is the Y coordinate of the upper left corner of the listbox. +//! \param i32Width is the width of the listbox. +//! \param i32Height is the height of the listbox. +//! +//! This function initializes the provided listbox widget. +//! +//! \return None. +// +//***************************************************************************** +void +ListBoxInit(tListBoxWidget *psWidget, const tDisplay *psDisplay, + const char **ppcText, uint16_t ui16MaxEntries, + uint16_t ui16PopulatedEntries, int32_t i32X, int32_t i32Y, + int32_t i32Width, int32_t i32Height) +{ + uint32_t ui32Idx; + + // + // Check the arguments. + // + ASSERT(psWidget); + ASSERT(psDisplay); + + // + // Clear out the widget structure. + // + for(ui32Idx = 0; ui32Idx < sizeof(tListBoxWidget); ui32Idx += 4) + { + ((uint32_t *)psWidget)[ui32Idx / 4] = 0; + } + + // + // Set the size of the listbox widget structure. + // + psWidget->sBase.i32Size = sizeof(tListBoxWidget); + + // + // Mark this widget as fully disconnected. + // + psWidget->sBase.psParent = 0; + psWidget->sBase.psNext = 0; + psWidget->sBase.psChild = 0; + + // + // Save the display pointer. + // + psWidget->sBase.psDisplay = psDisplay; + + // + // Set the extents of this listbox. + // + psWidget->sBase.sPosition.i16XMin = i32X; + psWidget->sBase.sPosition.i16YMin = i32Y; + psWidget->sBase.sPosition.i16XMax = i32X + i32Width - 1; + psWidget->sBase.sPosition.i16YMax = i32Y + i32Height - 1; + + // + // Use the listbox message handler to process messages to this listbox. + // + psWidget->sBase.pfnMsgProc = ListBoxMsgProc; + + // + // Initialize some of the widget fields that are not accessible via + // macros. + // + psWidget->ppcText = ppcText; + psWidget->ui16MaxEntries = ui16MaxEntries; + psWidget->ui16Populated = ui16PopulatedEntries; + psWidget->i16Selected = (int16_t)0xFFFF; +} + +//***************************************************************************** +// +//! Adds a line of text to a listbox. +//! +//! \param pListBox is a pointer to the listbox widget that is to receive the +//! new text string. +//! \param pcTxt is a pointer to the string that is to be added to the listbox. +//! +//! This function adds a new string to the listbox. If the listbox has +//! style \b #LISTBOX_STYLE_WRAP and the current string table is full, this +//! function will discard the oldest string and replace it with the one passed +//! here. If this style flag is absent, the function will return -1 if no +//! empty entries exist in the string table for the widget. +//! +//! The display is not automatically updated as a result of this function call. +//! An application must call WidgetPaint() to update the display after adding +//! a new string to the listbox. +//! +//! \note To replace the string associated with a particular, existing element +//! in the listbox, use ListBoxTextSet(). +//! +//! \return Returns the string table index into which the new string has been +//! placed if successful or -1 if the string table is full and +//! \b #LISTBOX_STYLE_WRAP is not set. +// +//***************************************************************************** +int32_t ListBoxTextAdd(tListBoxWidget *pListBox, const char *pcTxt) +{ + uint32_t ui32Index; + + // + // Is the list box full? + // + if(pListBox->ui16Populated == pListBox->ui16MaxEntries) + { + // + // The box is already full. If the wrap style is not set, fail + // the call. + // + if(!(pListBox->ui32Style & LISTBOX_STYLE_WRAP)) + { + // + // The listbox is full and it is not configured to wrap so we can't + // add another string to it. + // + return(-1); + } + else + { + // + // We are wrapping so replace the oldest entry in the box. + // + ui32Index = pListBox->ui16OldestEntry; + + // + // Check to see if we are displaying the oldest entry and, if so, + // move the start entry on by one to keep the display order + // correct. + // + if(pListBox->ui16OldestEntry == pListBox->ui16StartEntry) + { + pListBox->ui16StartEntry++; + if(pListBox->ui16StartEntry == pListBox->ui16MaxEntries) + { + pListBox->ui16StartEntry = 0; + } + } + + // + // The new oldest entry is the next one. Update the index and + // take care to wrap if we reach the end of the string table. + // + pListBox->ui16OldestEntry++; + if(pListBox->ui16OldestEntry == pListBox->ui16MaxEntries) + { + pListBox->ui16OldestEntry = 0; + } + } + } + else + { + // + // The listbox is not full so add the new string to the first free + // slot in the string table. + // + ui32Index = pListBox->ui16Populated; + pListBox->ui16Populated++; + } + + // + // Save the new string in the appropriate string table entry. + // + pListBox->ppcText[ui32Index] = pcTxt; + + // + // Tell the caller which string table entry was added. + // + return((int32_t)ui32Index); +} + +//***************************************************************************** +// +// Close the Doxygen group. +//! @} +// +//***************************************************************************** -- cgit v1.3.1