/*****************************************************************************
 *  Module for Microchip Graphics Library 
 *  GOL Layer 
 *  List Box
 *****************************************************************************
 * FileName:        ListBox.c
 * Dependencies:    None 
 * Processor:       PIC24
 * Compiler:       	MPLAB C30 V3.00
 * Linker:          MPLAB LINK30
 * Company:         Microchip Technology Incorporated
 *
 * Software License Agreement
 *
 * Copyright © 2007 Microchip Technology Inc.  All rights reserved.
 * Microchip licenses to you the right to use, modify, copy and distribute
 * Software only when embedded on a Microchip microcontroller or digital
 * signal controller, which is integrated into your product or third party
 * product (pursuant to the sublicense terms in the accompanying license
 * agreement).  
 *
 * You should refer to the license agreement accompanying this Software
 * for additional information regarding your rights and obligations.
 *
 * SOFTWARE AND DOCUMENTATION ARE PROVIDED “AS IS” WITHOUT WARRANTY OF ANY
 * KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION, ANY WARRANTY
 * OF MERCHANTABILITY, TITLE, NON-INFRINGEMENT AND FITNESS FOR A PARTICULAR
 * PURPOSE. IN NO EVENT SHALL MICROCHIP OR ITS LICENSORS BE LIABLE OR
 * OBLIGATED UNDER CONTRACT, NEGLIGENCE, STRICT LIABILITY, CONTRIBUTION,
 * BREACH OF WARRANTY, OR OTHER LEGAL EQUITABLE THEORY ANY DIRECT OR INDIRECT
 * DAMAGES OR EXPENSES INCLUDING BUT NOT LIMITED TO ANY INCIDENTAL, SPECIAL,
 * INDIRECT, PUNITIVE OR CONSEQUENTIAL DAMAGES, LOST PROFITS OR LOST DATA,
 * COST OF PROCUREMENT OF SUBSTITUTE GOODS, TECHNOLOGY, SERVICES, OR ANY
 * CLAIMS BY THIRD PARTIES (INCLUDING BUT NOT LIMITED TO ANY DEFENSE THEREOF),
 * OR OTHER SIMILAR COSTS.
 *
 * Author               Date        Comment
 *~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 * Anton Alkhimenok     11/12/07	Version 1.0 release
*****************************************************************************/
#include "Graphics.h"

#ifdef USE_LISTBOX

/*********************************************************************
* Function: LISTBOX  *LbCreate(WORD ID, SHORT left, SHORT top, SHORT right, SHORT bottom, 
*				               WORD state, XCHAR* pText, GOL_SCHEME *pScheme)
*
* Overview: creates the list box
*
********************************************************************/
LISTBOX *LbCreate(WORD ID, SHORT left, SHORT top, SHORT right, SHORT bottom, 
		   	      WORD state , XCHAR* pText, GOL_SCHEME *pScheme)

{
	LISTBOX *pLb = NULL;
    XCHAR* pointer;
    WORD  counter;    

  	pLb = malloc(sizeof(LISTBOX));

	if(pLb == NULL)
	    return pLb;

	pLb->ID      	= ID;
	pLb->pNxtObj 	= NULL;
	pLb->type    	= OBJ_LISTBOX;
	pLb->left    	= left;
	pLb->top     	= top;
	pLb->right   	= right;
	pLb->bottom  	= bottom;
	pLb->state   	= state; 
    pLb->pItemList  = NULL;	
    pLb->scrollY    = 0;
    pLb->itemsNumber = 0;

	// Set the style scheme to be used
	if (pScheme == NULL)
		pLb->pGolScheme = _pDefaultGolScheme; 
	else 	
		pLb->pGolScheme = (GOL_SCHEME *)pScheme; 	

    pLb->textHeight = GetTextHeight(pLb->pGolScheme->pFont);
	
    GOLAddObject((OBJ_HEADER*) pLb);

    // Add items if there's an initialization text (each line will become one item,
    // lines must be separated by '\n' character)
    if(pText != NULL){
        pointer = pText;
        counter = 0;
        while(*pointer){
            if(NULL == LbAddItem(pLb, NULL, pointer, 0, counter))
                break;
            while(*pointer++ > 31);
            if(*(pointer-1) == 0)
                break;
            counter++;
        }
    }
    
    pLb->pFocusItem = pLb->pItemList;

    // Set focus for the object if FOCUSED state is set
#ifdef  USE_FOCUS
    if(GetState(pLb,LB_FOCUSED))
        GOLSetFocus((OBJ_HEADER*)pLb);
#endif

	return pLb;
}

/*********************************************************************
* Function: LISTITEM* LbAddItem(LISTBOX *pLb, LISTITEM *pPrevItem, XCHAR *pText, WORD status, WORD data)
*
* Input: pLb - the pointer to the list box,
*        pPrevItem - pointer to the item after which a new item must be inserted,
*                    if this pointer is NULL, the item will be appended at the end of the items list,
*        pText -  pointer to the text,
*        status - status after creation,
*        data -  some data associated with the item
*
* Output: pointer to the item created, NULL if the operation was not successful
*
* Overview: allocates memory for a new item and adds it to the list box
*
********************************************************************/
LISTITEM* LbAddItem(LISTBOX *pLb, LISTITEM *pPrevItem, XCHAR *pText, WORD status, WORD data){
LISTITEM* pItem;
LISTITEM* pCurItem; 

    pItem = (LISTITEM*) malloc(sizeof(LISTITEM));

    if(pItem == NULL){
        return NULL;
    }

    pLb->itemsNumber++;

    if(pLb->pItemList == NULL){

        pLb->pItemList = pItem;
        pItem->pNextItem = NULL;
        pItem->pPrevItem = NULL;

    }else{

        if(pPrevItem == NULL){
            // Find last item
            pCurItem = pLb->pItemList;
            while(pCurItem->pNextItem != NULL)
                pCurItem = pCurItem->pNextItem;
        }else{
            pCurItem = pPrevItem;
        }

        // Insert a new item
        pItem->pNextItem = pCurItem->pNextItem;
        pItem->pPrevItem = pCurItem;
        pCurItem->pNextItem = pItem;
    }
    
    pItem->pText = pText;
    pItem->status = status;
    pItem->data = data;

    return pItem;
}

/*********************************************************************
* Function: void LbDelItem(LISTBOX *pLb, LISTITEM *pItem)
*
* Input: pLb - the pointer to the object,
*        pItem - the pointer to the item must be deleted
*
* Output: none
*
* Overview: removes an item from the list box and frees memory
*
********************************************************************/
void LbDelItem(LISTBOX *pLb, LISTITEM *pItem){

    if(pItem->pNextItem != NULL){
        ((LISTITEM*)pItem->pNextItem)->pPrevItem = pItem->pPrevItem;
        if(pItem->pPrevItem == NULL)
            pLb->pItemList = pItem->pNextItem;
    }

    if(pItem->pPrevItem != NULL)
        ((LISTITEM*)pItem->pPrevItem)->pNextItem = pItem->pNextItem;

    free(pItem);

    pLb->itemsNumber--;
}

/*********************************************************************
* Function: void LbDelItemsList(LISTBOX *pLb)
*
* Input: pLb - the pointer to the object
*
* Output: none
*
* Overview: removes all items from list box and frees memory
*
********************************************************************/
void LbDelItemsList(LISTBOX *pLb){
LISTITEM* pCurItem;
LISTITEM* pItem;

    pCurItem = pLb->pItemList;

    while(pCurItem != NULL){
        pItem = pCurItem;
        pCurItem = pCurItem->pNextItem;
        free(pItem);
    }
}

/*********************************************************************
* Function: LISTITEM* LbGetSel(LISTBOX *pLb, LISTITEM *pFromItem)
*
* Input: pLb - the pointer to the object,
*        pFromItem - pointer to the item the seach must start from,
*                    if the pointer is NULL the search begins from the start of the items list
*
* Output: pointer to the selected item, NULL if there are no items selected
*
* Overview: seaches for selected items
*
********************************************************************/
LISTITEM* LbGetSel(LISTBOX *pLb, LISTITEM *pFromItem){
    if(pFromItem == NULL){
        pFromItem = pLb->pItemList;
    }
    while(pFromItem != NULL){
        if(pFromItem->status&LB_STS_SELECTED)
            break;
        pFromItem = pFromItem->pNextItem;
    }
    return pFromItem;
}

/*********************************************************************
* Function: void LbChangeSel(LISTBOX *pLb, LISTITEM *pItem)
*
* Input: pLb - the pointer to the object,
*        pItem - pointer to the item the selection status will be changed
*
* Output: none
*
* Overview: changes selection status of an item
*
********************************************************************/
void LbChangeSel(LISTBOX *pLb, LISTITEM *pItem){
LISTITEM* pCurItem;

    if( GetState(pLb, LB_SINGLE_SEL)){
        // Remove selection from all items
        pCurItem = pLb->pItemList;
        while(pCurItem != NULL){
            if(pCurItem->status&LB_STS_SELECTED){
                pCurItem->status &= ~LB_STS_SELECTED;
                pCurItem->status |= LB_STS_REDRAW;
            }
            pCurItem = pCurItem->pNextItem;
        }
    }
    pItem->status ^= LB_STS_SELECTED;
    pItem->status |= LB_STS_REDRAW;
}

/*********************************************************************
* Function: void LbSetFocusedItem(LISTBOX* pLb, SHORT index)
*
* Input: pLb - the pointer to the object
*        index - item number from the list beginning
*
* Output: none
*
* Overview: sets focus for the item with index defined
*
********************************************************************/
void LbSetFocusedItem(LISTBOX* pLb, SHORT index){
LISTITEM* pCurItem;

    // Look for item to be focused
    pCurItem = pLb->pItemList;
    while(pCurItem->pNextItem != NULL){
        if(index <= 0)
            break;
        index--;
        pCurItem = pCurItem->pNextItem;    
    }

    if(pCurItem != NULL){
        if(pLb->pFocusItem != NULL){
            pLb->pFocusItem->status |= LB_STS_REDRAW;
        }
        pLb->pFocusItem = pCurItem;
        pCurItem->status |= LB_STS_REDRAW;
    }
}

/*********************************************************************
* Function: SHORT LbGetFocusedItem(LISTBOX* pLb)
*
* Input: pLb - the pointer to the list box
*
* Output: index of the focused item, -1 if there's no item in focus
*
* Overview: returns focused item number from the list beginning
*
********************************************************************/
SHORT LbGetFocusedItem(LISTBOX* pLb){
LISTITEM* pCurItem;
SHORT     index;

    if(pLb->pFocusItem == NULL)
        return -1;

    // Look for the focused item index
    index = 0;
    pCurItem = pLb->pItemList;
    while(pCurItem->pNextItem != NULL){
        if(pCurItem == pLb->pFocusItem)
            break;
        index++;
        pCurItem = pCurItem->pNextItem;    
    }

    return index;
}

/*********************************************************************
* Function: WORD LbTranslateMsg(LISTBOX *pLb, GOL_MSG *pMsg)
*
* Overview: translates the GOL message for the list box
*
********************************************************************/
WORD LbTranslateMsg(LISTBOX *pLb, GOL_MSG *pMsg)
{
    // Evaluate if the message is for the list box
    // Check if disabled first
	if(GetState(pLb, LB_DISABLED))
      return OBJ_MSG_INVALID;

#ifdef  USE_TOUCHSCREEN
    if(pMsg->type == TYPE_TOUCHSCREEN) {
		// Check if it falls in list box borders
		if( (pLb->left     < pMsg->param1) &&
	  	    (pLb->right    > pMsg->param1) &&
	   	    (pLb->top      < pMsg->param2) &&
	   	    (pLb->bottom   > pMsg->param2) )
                 return LB_MSG_TOUCHSCREEN;
         
        return OBJ_MSG_INVALID;
	}
#endif	

#ifdef  USE_KEYBOARD
    if(pMsg->type == TYPE_KEYBOARD)
    if(pMsg->param1 == pLb->ID) {
        if(pMsg->uiEvent == EVENT_KEYSCAN){
            if( (pMsg->param2 == SCAN_UP_PRESSED)||
                (pMsg->param2 == SCAN_DOWN_PRESSED) )
                return LB_MSG_MOVE;

            if( (pMsg->param2 == SCAN_SPACE_PRESSED)||
                (pMsg->param2 == SCAN_CR_PRESSED) )
                return LB_MSG_SEL;

        }   
	    return OBJ_MSG_INVALID;
	}
#endif	


	return OBJ_MSG_INVALID;
}

/*********************************************************************
* Function: void LbMsgDefault(WORD translatedMsg, LISTBOX *pLb, GOL_MSG *pMsg)
*
* Overview: changes the state of the list box by default
*
********************************************************************/
void LbMsgDefault(WORD translatedMsg, LISTBOX *pLb, GOL_MSG *pMsg){
SHORT     pos;
LISTITEM* pItem;

#ifdef  USE_TOUCHSCREEN
    if(pMsg->type == TYPE_TOUCHSCREEN) {

#ifdef  USE_FOCUS
        if(pMsg->type == TYPE_TOUCHSCREEN){
            if(!GetState(pLb,LB_FOCUSED)){
                GOLSetFocus((OBJ_HEADER*)pLb);
            }
        }
#endif

        if(pMsg->uiEvent == EVENT_PRESS){

            pos = (pMsg->param2-pLb->scrollY-pLb->top-LB_INDENT-GOL_EMBOSS_SIZE)/pLb->textHeight;
            pItem = pLb->pItemList;
            while(pos){
                if(pItem == NULL)
                    break;
                if(pItem->pNextItem == NULL)
                    break;
                pItem = pItem->pNextItem;
                pos--;
            }

            if(pLb->pFocusItem != pItem){
                pItem->status |= LB_STS_REDRAW;
                pLb->pFocusItem->status |= LB_STS_REDRAW;
                pLb->pFocusItem = pItem;
                SetState(pLb, LB_DRAW_ITEMS);
            }

        
            LbChangeSel(pLb, pItem);
            SetState(pLb, LB_DRAW_ITEMS);

        }

	}
#endif	

#ifdef  USE_KEYBOARD
    if(pMsg->type == TYPE_KEYBOARD){

        switch(translatedMsg){
            case LB_MSG_SEL:
                if(pLb->pFocusItem != NULL)
                    LbChangeSel(pLb, pLb->pFocusItem);
                    SetState(pLb, LB_DRAW_ITEMS);
                break;

            case LB_MSG_MOVE:
                switch(pMsg->param2){
                    case SCAN_UP_PRESSED:
                        LbSetFocusedItem(pLb,LbGetFocusedItem(pLb)-1);
                        SetState(pLb, LB_DRAW_ITEMS);
                        break;
                    case SCAN_DOWN_PRESSED:
                        LbSetFocusedItem(pLb,LbGetFocusedItem(pLb)+1);
                        SetState(pLb, LB_DRAW_ITEMS);
                        break;
                }
                break;
        }
    }// end of if
#endif	

}

/*********************************************************************
* Function: WORD LbDraw(LISTBOX *pLb)
*
* Output: returns the status of the drawing
*		  0 - not completed
*         1 - done
*
* Overview: draws edit box
*
********************************************************************/
WORD LbDraw(LISTBOX *pLb)
{
typedef enum {
	LB_STATE_START,
	LB_STATE_PANEL,
	LB_STATE_ERASEITEM,
    LB_STATE_ITEMFOCUS,
	LB_STATE_TEXT
} LB_DRAW_STATES;

static LB_DRAW_STATES state = LB_STATE_START;
static LISTITEM* pCurItem;
static SHORT  temp;
        
    switch(state){

/////////////////////////////////////////////////////////////////////
// REMOVE FROM SCREEN
/////////////////////////////////////////////////////////////////////
        case LB_STATE_START:

          	if (GetState(pLb, LB_HIDE)) {
                if(IsDeviceBusy())
                    return 0;
   	   	        SetColor(pLb->pGolScheme->CommonBkColor);
    	        Bar(pLb->left,pLb->top,pLb->right,pLb->bottom);
    	        return 1;
    	    }    


            if( GetState(pLb,LB_DRAW_FOCUS) ){
                if(pLb->pFocusItem != NULL)
                    ((LISTITEM*)pLb->pFocusItem)->status |= LB_STS_REDRAW;
                SetState(pLb,LB_DRAW_ITEMS);
            }
/////////////////////////////////////////////////////////////////////
// DRAW PANEL
/////////////////////////////////////////////////////////////////////
if(GetState(pLb, LB_DRAW)){
 
            if(GetState(pLb,WND_DISABLED)){
                temp = pLb->pGolScheme->ColorDisabled;
	        }else{
                temp = pLb->pGolScheme->Color0;
            }

            GOLPanelDraw(pLb->left,pLb->top,pLb->right,pLb->bottom,0,
                         temp,
                         pLb->pGolScheme->EmbossDkColor,
                         pLb->pGolScheme->EmbossLtColor,
                         NULL,
                         GOL_EMBOSS_SIZE);

            state = LB_STATE_PANEL;

        case LB_STATE_PANEL:

            if(!GOLPanelDrawTsk())
                return 0;

}
/////////////////////////////////////////////////////////////////////
// DRAW ITEMS
/////////////////////////////////////////////////////////////////////
L_LB_DRAW:
            SetClip(CLIP_ENABLE);

            SetClipRgn(pLb->left+GOL_EMBOSS_SIZE+LB_INDENT,
                       pLb->top+GOL_EMBOSS_SIZE+LB_INDENT,
                       pLb->right-GOL_EMBOSS_SIZE-LB_INDENT,
                       pLb->bottom-GOL_EMBOSS_SIZE-LB_INDENT);

	        SetFont(pLb->pGolScheme->pFont);

            // Set graphics cursor
            MoveTo(pLb->left+GOL_EMBOSS_SIZE+LB_INDENT,
                   pLb->top+GOL_EMBOSS_SIZE+LB_INDENT+pLb->scrollY);

            pCurItem = pLb->pItemList;

/////////////////////////////////////////////////////////////////////
// DRAW CURRENT ITEM
/////////////////////////////////////////////////////////////////////
L_LB_DRAWITEM:
            if( pCurItem == NULL ) {
                state = LB_STATE_START;
                SetClip(CLIP_DISABLE);
                return 1;
            }

/////////////////////////////////////////////////////////////////////
if( GetY() < pLb->bottom-GOL_EMBOSS_SIZE-LB_INDENT )
if((GetY()+ pLb->textHeight) >= (pLb->top+GOL_EMBOSS_SIZE+LB_INDENT)){

            if(! GetState(pLb, LB_DRAW))
            if(!(pCurItem->status&LB_STS_REDRAW))
                goto L_LB_NEXTITEM;

            pCurItem->status &= ~LB_STS_REDRAW;

            state = LB_STATE_ERASEITEM;

        case LB_STATE_ERASEITEM:

            if(IsDeviceBusy())
                return 0;

            if( GetState(pLb, LB_DISABLED)){
                SetColor(pLb->pGolScheme->ColorDisabled);
            }else{
                SetColor(pLb->pGolScheme->Color0);
                if(pCurItem != NULL)
                if(pCurItem->status&LB_STS_SELECTED){
                    SetColor(pLb->pGolScheme->Color1);
                }
            }

            Bar(pLb->left+GOL_EMBOSS_SIZE+LB_INDENT,
                GetY(),
                pLb->right-GOL_EMBOSS_SIZE-LB_INDENT,
                GetY()+ pLb->textHeight);


            if (!GetState(pLb, EB_CENTER_ALIGN|EB_RIGHT_ALIGN)) {
                MoveTo(pLb->left+GOL_EMBOSS_SIZE+EB_INDENT, GetY());
            }else{
                temp = GetTextWidth(pCurItem->pText, pLb->pGolScheme->pFont);
                if (GetState(pLb, EB_RIGHT_ALIGN)) {
			        MoveTo(pLb->right-temp-EB_INDENT-GOL_EMBOSS_SIZE, GetY());
                }else{
                    MoveTo((pLb->left+pLb->right-temp)>>1, GetY());
                }
            }

            if( GetState(pLb, LB_DISABLED)){
                SetColor(pLb->pGolScheme->TextColorDisabled);
            }else{
                if(pCurItem->status&LB_STS_SELECTED){
                    SetColor(pLb->pGolScheme->TextColor1);
                }else{
                    SetColor(pLb->pGolScheme->TextColor0);
                }
            }

            state = LB_STATE_TEXT;

        case LB_STATE_TEXT:
            if(!OutText(pCurItem->pText))
                return 0;
}
/////////////////////////////////////////////////////////////////////

L_LB_NEXTITEM:
            state = LB_STATE_ITEMFOCUS;

        case LB_STATE_ITEMFOCUS:

            if(pLb->pFocusItem == pCurItem){
                if(IsDeviceBusy())
                    return 0;

                if( GetState(pLb,LB_FOCUSED) ){
                    SetColor(pLb->pGolScheme->TextColor1);
                }else{
                    SetColor(pLb->pGolScheme->TextColor0);
                }
          
                SetLineType(FOCUS_LINE);
                temp = GetY();
                Rectangle(pLb->left+GOL_EMBOSS_SIZE+LB_INDENT,
                          GetY(),
                          pLb->right-GOL_EMBOSS_SIZE-LB_INDENT,
                          GetY()+pLb->textHeight-1);
                MoveTo(0,temp);
                SetLineType(SOLID_LINE);

                // Scroll if needed
                if(GetY() < (pLb->top+GOL_EMBOSS_SIZE+LB_INDENT)){
                    pLb->scrollY += (pLb->top+GOL_EMBOSS_SIZE+LB_INDENT)-GetY();
                    SetState(pLb, LB_DRAW);
                    goto L_LB_DRAW;
                }
                if((GetY()+pLb->textHeight) > (pLb->bottom-GOL_EMBOSS_SIZE-LB_INDENT)){
                    pLb->scrollY += pLb->bottom-GetY()-pLb->textHeight-GOL_EMBOSS_SIZE-LB_INDENT;                 
                    SetState(pLb, LB_DRAW);
                    goto L_LB_DRAW;
                }
            }

            pCurItem = pCurItem->pNextItem;
            MoveRel(0,pLb->textHeight);

            goto L_LB_DRAWITEM;

    } // end of switch
    return 1;

}

#endif // USE_LISTBOX
