//***************************************************************************** // // listbox.c - A listbox widget. // // Copyright (c) 2008-2010 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 6288 of the Stellaris Graphics Library. // //***************************************************************************** #include "debug.h" #include "grlib.h" #include "widget.h" #include "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 pWidget 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 *pWidget) { tListBoxWidget *pListBox; tContext sCtx; tRectangle sWidgetRect, sLineRect; short sHeight; int lWidth; unsigned short usCount, usString; // // Check the arguments. // ASSERT(pWidget); // // Convert the generic widget pointer into a listbox widget pointer. // pListBox = (tListBoxWidget *)pWidget; // // Initialize a drawing context. // GrContextInit(&sCtx, pWidget->pDisplay); GrContextFontSet(&sCtx, pListBox->pFont); // // Initialize the clipping region based on the extents of this listbox. // sWidgetRect = pWidget->sPosition; GrContextClipRegionSet(&sCtx, &sWidgetRect); // // See if the listbox outline style is selected. // if(pListBox->ulStyle & LISTBOX_STYLE_OUTLINE) { // // Outline the listbox with the outline color. // GrContextForegroundSet(&sCtx, pListBox->ulOutlineColor); GrRectDraw(&sCtx, &(pWidget->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.sXMin++; sWidgetRect.sYMin++; sWidgetRect.sXMax--; sWidgetRect.sYMax--; GrContextForegroundSet(&sCtx, pListBox->ulBackgroundColor); 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.sXMin++; sWidgetRect.sYMin++; sWidgetRect.sXMax--; sWidgetRect.sYMax--; GrContextClipRegionSet(&sCtx, &sWidgetRect); } // // Start drawing at the top of the widget. // sLineRect = sWidgetRect; usCount = 0; usString = pListBox->usStartEntry; sHeight = GrFontHeightGet(pListBox->pFont); // // Keep drawing until we reach the bottom of the listbox or run out of // strings to draw. // while((sLineRect.sYMin < sWidgetRect.sYMax) && (usCount < pListBox->usPopulated)) { // // Calculate the rectangle that will enclose this line of text. // sLineRect.sYMax = sLineRect.sYMin + sHeight - 1; // // Set foreground and background colors appropriately. // GrContextBackgroundSet(&sCtx, ((usString == pListBox->sSelected) ? pListBox->ulSelectedBackgroundColor : pListBox->ulBackgroundColor)); GrContextForegroundSet(&sCtx, ((usString == pListBox->sSelected) ? pListBox->ulSelectedTextColor : pListBox->ulTextColor)); // // Draw the text. // GrStringDraw(&sCtx, pListBox->ppcText[usString], -1, sLineRect.sXMin, sLineRect.sYMin, 1); // // Determine the width of the string we just rendered. // lWidth = GrStringWidthGet(&sCtx, pListBox->ppcText[usString], -1); // // Do we need to clear the area to the right of the string? // if(lWidth < (sLineRect.sXMax - sLineRect.sXMin + 1)) { // // Yes - we need to fill the right side of this string with // background color. // GrContextForegroundSet(&sCtx, ((usString == pListBox->sSelected) ? pListBox->ulSelectedBackgroundColor : pListBox->ulBackgroundColor)); sLineRect.sXMin += lWidth; GrRectFill(&sCtx, &sLineRect); sLineRect.sXMin = sWidgetRect.sXMin; } // // Move on to the next string. // usCount++; usString++; if(usString == pListBox->usMaxEntries) { usString = 0; } sLineRect.sYMin += sHeight; } // // Fill the remainder of the listbox area with the background color. // if(sLineRect.sYMin < sWidgetRect.sYMax) { // // Determine the rectangle to be filled. // sLineRect.sYMax = sWidgetRect.sYMax; // // Fill the rectangle with the background color. // GrContextForegroundSet(&sCtx, pListBox->ulBackgroundColor); GrRectFill(&sCtx, &sLineRect); } } //***************************************************************************** // // Handles pointer messages for a listbox widget. // // \param pListBox is a pointer to the listbox widget. // \param ulMsg is the message. // \param lX is the X coordinate of the pointer. // \param lY 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 int ListBoxPointer(tListBoxWidget *pListBox, unsigned int ulMsg, int lX, int lY) { int lLineNum, lEntry, lVisible, lMaxUp, lMaxDown, lScroll; switch(ulMsg) { // // 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), lX, lY)) { // // 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->usScrolled = 0; pListBox->lPointerY = lY; // // 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->usScrolled == 0) && !(pListBox->ulStyle & LISTBOX_STYLE_LOCKED) && GrRectContainsPoint(&(pListBox->sBase.sPosition), lX, lY)) { // // It seems we need to change the selected element. What is // the display line number that has been clicked on? // lLineNum = (lY - (int)pListBox->sBase.sPosition.sYMin) / GrFontHeightGet(pListBox->pFont); // // 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. // lEntry = ((int)pListBox->usStartEntry + lLineNum) % pListBox->usMaxEntries; // // If this is an unpopulated entry or the current selection, // clear the selection. // if((lEntry >= (int)pListBox->usPopulated) || (lEntry == (int)pListBox->sSelected)) { // // Yes - update the selection and force a repaint. // pListBox->sSelected = (short)0xFFFF; } else { // // The pointer was tapped on a valid entry other than the // current selection so change the selection. // pListBox->sSelected = (short)lEntry; } // // Force a repaint of the widget. // WidgetPaint((tWidget *)pListBox); // // Tell the client that the selection changed. // if(pListBox->pfnOnChange) { (pListBox->pfnOnChange)((tWidget *)pListBox, pListBox->sSelected); } } // // 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? lLineNum will // be negative for downward scrolling. // lLineNum = pListBox->lPointerY - lY; // // 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(lLineNum) >= GrFontHeightGet(pListBox->pFont)) { // // We have to scroll if this is possible. How many lines can // be visible on the display? // lVisible = (pListBox->sBase.sPosition.sYMax - pListBox->sBase.sPosition.sYMin) / (int)GrFontHeightGet(pListBox->pFont); // // If we have fewer strings in the listbox than there are lines // on the display, scrolling is not possible so give up now. // if(lVisible > (int)pListBox->usPopulated) { return(1); } // // How many lines of scrolling does the latest pointer position // indicate? A negative value implies downward scrolling (i.e. // showing earlier strings). // lScroll = lLineNum / (int)GrFontHeightGet(pListBox->pFont); // // 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. // lMaxDown = (pListBox->usStartEntry >= pListBox->usOldestEntry) ? (pListBox->usOldestEntry - pListBox->usStartEntry ) : ((pListBox->usOldestEntry - pListBox->usStartEntry) - pListBox->usMaxEntries); // // What is the farthest we could scroll upwards? Note - this // will be a positive number. // lMaxUp = ((int)pListBox->usPopulated - lVisible) + lMaxDown; // // Determine the actual scroll distance given the maximum // distances calculated. // lScroll = min(lScroll, lMaxUp); lScroll = max(lScroll, lMaxDown); if(lScroll) { int lTemp; // // 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->usStartEntry following the // calculation. // lTemp = pListBox->usStartEntry; lTemp += lScroll; lTemp %= (int)pListBox->usMaxEntries; pListBox->usStartEntry = (unsigned short)lTemp; // // Remember that we scrolled. // pListBox->usScrolled = 1; // // Adjust the pointer position we record to take into account // the amount we just scrolled. // pListBox->lPointerY -= (lScroll * GrFontHeightGet(pListBox->pFont)); // // 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 pWidget is a pointer to the listbox widget. //! \param ulMsg is the message. //! \param ulParam1 is the first parameter to the message. //! \param ulParam2 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. // //***************************************************************************** int ListBoxMsgProc(tWidget *pWidget, unsigned int ulMsg, unsigned int ulParam1, unsigned int ulParam2) { tListBoxWidget *pListBox; // // Check the arguments. // ASSERT(pWidget); // // Convert the generic pointer to a list box pointer. // pListBox = (tListBoxWidget *)pWidget; // // Determine which message is being sent. // switch(ulMsg) { // // A pointer message has been received. // case WIDGET_MSG_PTR_DOWN: case WIDGET_MSG_PTR_UP: case WIDGET_MSG_PTR_MOVE: return(ListBoxPointer(pListBox, ulMsg, (int)ulParam1, (int)ulParam2)); // // The widget paint request has been sent. // case WIDGET_MSG_PAINT: { // // Handle the widget paint request. // ListBoxPaint(pWidget); // // 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(pWidget, ulMsg, ulParam1, ulParam2)); } } } //***************************************************************************** // //! Initializes a listbox widget. //! //! \param pWidget is a pointer to the listbox widget to initialize. //! \param pDisplay 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 usMaxEntries provides the total number of entries in the \e ppcText //! array. //! \param usPopulatedEntries provides the number of entries in the \e ppcText //! array which are populated. //! \param lX is the X coordinate of the upper left corner of the listbox. //! \param lY is the Y coordinate of the upper left corner of the listbox. //! \param lWidth is the width of the listbox. //! \param lHeight is the height of the listbox. //! //! This function initializes the provided listbox widget. //! //! \return None. // //***************************************************************************** void ListBoxInit(tListBoxWidget *pWidget, const tDisplay *pDisplay, const char **ppcText, unsigned short usMaxEntries, unsigned short usPopulatedEntries, int lX, int lY, int lWidth, int lHeight) { unsigned int ulIdx; // // Check the arguments. // ASSERT(pWidget); ASSERT(pDisplay); // // Clear out the widget structure. // for(ulIdx = 0; ulIdx < sizeof(tListBoxWidget); ulIdx += 4) { ((unsigned int *)pWidget)[ulIdx / 4] = 0; } // // Set the size of the listbox widget structure. // pWidget->sBase.lSize = sizeof(tListBoxWidget); // // Mark this widget as fully disconnected. // pWidget->sBase.pParent = 0; pWidget->sBase.pNext = 0; pWidget->sBase.pChild = 0; // // Save the display pointer. // pWidget->sBase.pDisplay = pDisplay; // // Set the extents of this listbox. // pWidget->sBase.sPosition.sXMin = lX; pWidget->sBase.sPosition.sYMin = lY; pWidget->sBase.sPosition.sXMax = lX + lWidth - 1; pWidget->sBase.sPosition.sYMax = lY + lHeight - 1; // // Use the listbox message handler to process messages to this listbox. // pWidget->sBase.pfnMsgProc = ListBoxMsgProc; // // Initialize some of the widget fields that are not accessible via // macros. // pWidget->ppcText = ppcText; pWidget->usMaxEntries = usMaxEntries; pWidget->usPopulated = usPopulatedEntries; pWidget->sSelected = (short)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. // //***************************************************************************** int ListBoxTextAdd(tListBoxWidget *pListBox, const char *pcTxt) { unsigned int ulIndex; // // Is the list box full? // if(pListBox->usPopulated == pListBox->usMaxEntries) { // // The box is already full. If the wrap style is not set, fail // the call. // if(!(pListBox->ulStyle & 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. // ulIndex = pListBox->usOldestEntry; // // 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->usOldestEntry == pListBox->usStartEntry) { pListBox->usStartEntry++; if(pListBox->usStartEntry == pListBox->usMaxEntries) { pListBox->usStartEntry = 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->usOldestEntry++; if(pListBox->usOldestEntry == pListBox->usMaxEntries) { pListBox->usOldestEntry = 0; } } } else { // // The listbox is not full so add the new string to the first free // slot in the string table. // ulIndex = pListBox->usPopulated; pListBox->usPopulated++; } // // Save the new string in the appropriate string table entry. // pListBox->ppcText[ulIndex] = pcTxt; // // Tell the caller which string table entry was added. // return((int)ulIndex); } //***************************************************************************** // // Close the Doxygen group. //! @} // //*****************************************************************************