/*======================================================

                   ** IMPORTANT ** 

This source is provide strictly for reference and debug
purposes to licensed parties.  Modification of this
code will likely cause instability and/or incompatibility with
future software versions and failure of applications
that leverage these interfaces.  

FILE:  AEEMenu.c

SERVICES:  AEE Menu Interfaces

GENERAL DESCRIPTION:  Provides support for IMenuCtl services
in AEE.  These include:

- Menus
- Lists
- Icon Views
- Calendar List View
- Softkey Menus

PUBLIC CLASSES AND STATIC FUNCTIONS: IMenuCtl

Copyright ?1999-2002 QUALCOMM Incorporated.
         All Rights Reserved.
       QUALCOMM Proprietary/GTDR

=====================================================*/

/*=========================================================================

INCLUDE FILES FOR MODULE

===========================================================================*/

#include "AEE_OEM.h"
#include "AEEStdLib.h"
#include "AEEMenu_priv.h"
#include "AEEStatic_Priv.h"
#include "AEEModTable.h"
#include "AEEBridle.h"

/*===========================================================================

DEFINITIONS

===========================================================================*/
#define __MAX(x,y)            ((x) > (y) ? (x) : (y))       // Max of X and Y
#define __MIN(x,y)            ((x) < (y) ? (x) : (y))       // Min of X and Y

#define IMAGE_WIDTH           (4)                           // Frame Width for AEE_IDB_SMALLFONTS

#define SOFT_PAGE_ITEMS       (3)                           // Initializes # of soft key items visible at once
#define MAX_SOFT_PAGE_ITEMS   (12)                          // Max SK items/screen
#define CXARROW               (3)                           // Arrow Width
#define CYARROW               (5)                           // Arrow Height
#define ARROWMARGIN           (5)                           // Spacing for arrow (margins and pad)
#define CELL_LMARGIN          (1 + CXARROW + 1)             // Left Margin - space, arrow, space, border
#define CELL_RMARGIN          CELL_LMARGIN                  // Right Margin - same as left margin
#define CELL_SIDE_WIDTH       (CELL_LMARGIN + CELL_RMARGIN) // space taken up by both cell margins
#define MIN_CELL_WIDTH        (10)

#define MENU_IMAGE_PAD        (5)                           // Space between image and menu text
#define LIST_TITLE_PAD        (5)                           // Space between list title and text
#define AUTO_SCROLL_TIME      (150)                         // Auto Scroll - Update Delay when Device Info shows zero
#define AUTO_SCROLL_PIXELS    (6)                           // number of pixels to scroll during autoscroll
#define IMAGE_DELAY_TIME      (200)                         // Image Draw Delay when Device Info shows zero delay
#define AUTO_SCROLL_START     (1000)                        // Auto Scroll - (Re)Start Delay
#define ARROW_LINES           (5)
#define INVALID_MARK          (-100)

#define PT_IN_RECT(a,b,rct)      (boolean)( ((a) >= (rct).x && (a) <= ((rct).x + (rct).dx)) && ((b) >= (rct).y && (b) <= ((rct).y + (rct).dy)) )

/*===========================================================================

Class Definitions

===========================================================================*/
// Pen Tracking Hit flags 8-bit (1 byte)
#define  PTRCK_HIT_ABOVE   (0x01)      // Above the thumb, y position < thumb.y OR x position < thumb.x
#define  PTRCK_HIT_THUMB   (0x02)      // On the thumb
#define  PTRCK_HIT_ITEM    (0x02)      // Matches above, overloaded for non SB entities
#define  PTRCK_HIT_BELOW   (0x04)      // Below the thumb, y position > thumb.y + thumb.dy OR x position < thumb.x + thumb.dx
#define  PTRCK_HIT_SCRL    (0x08)      // 1: Hit Scrollbar; 0: Didn't hit Scrollbar
#define  PTRCK_NOTTHUMB    (PTRCK_HIT_BELOW | PTRCK_HIT_ABOVE)

// Pen Tracking General flags
#define  PTRCK_GEN_TMRSET  (0x01)   // Timer is set

typedef struct
{
   uint8    cbHit;
   uint8    cbFlags;
   uint16   wData;
   AEEPoint ptPosition;
} PenTracker;

// PenTracker::cbHit is:
// SK:
// PTRCK_HIT_ABOVE - Left arrow
// PTRCK_HIT_ITEM - An Item was hit
// PTRCK_HIT_BELOW - Right arrow
// PenTracker::wData is:
// Index of Selection
//
// List:
// PTRCK_HIT_ITEM - An Item was hit
// PenTracker::wData is:
// Unused
// 
// Menu and IconView:
// if( !(PenTracker::cbFlags & PTRCK_HIT_SCRL) )
// PTRCK_HIT_ITEM - An Item was hit
// PenTracker::wData is:
// Index of Selection
//
// if( PenTracker::cbFlags & PTRCK_HIT_SCRL )
// PTRCK_HIT_ABOVE - Above thumb
// PTRCK_HIT_THUMB - On Thumb
// PTRCK_HIT_BELOW - Below thumb
// PenTracker::wData is:
// Offset hit inside Thumb


OBJECT(CMenuItem)
{
   CMenuItem *    pNext;         // Pointer to next item in the list
   uint32         lData;         // 32-bit user data value
   uint16         nItemID;       // Item ID
   AEEFont        nFont;         // Font for text
   uint16         wCalStart;     // Calendar item start time in minutes
   uint16         wCalEnd;       // Calendar item end time in minutes
   uint16         y;             // Y position on screen
   uint16         cxText;        // Text length (pixels)
   uint16         cyImage;       // Height of image
   uint16         cxImage;       // Width of image
   const char *   pszImage;      // Resource file for cached image
   uint16         wImageID;      // Image ID in resource file
   IImage *       pImage;        // Image object
   boolean        bPressed;      // Down/Up state
   boolean        bIsSel;        // Is item sel, for multi sel
   boolean        bOD;           // Item is owner drawn
   uint16         cyText;        // Text Height for owner drawn icon view
   AECHAR         text[1];       // text starts here
};

//
// Structure for softkey menu item layout
//

OBJECT(SKDrawItem) {
   int         x;                // item cell x position
   int         dx;               // item cell width
   int         xTruncatedExtent; // Width of truncated item text
   int         cbFits;           // Number of item text characters which fit in item width
   boolean     bTrunc;           // Flag to indicate if item text is truncated
   boolean     bSelected;        // Item is selected
   CMenuItem * pmi;              // Menu Item
};

//
// Menu manager object (IMenuCtl)
//
OBJECT(CMenuCtl)
{
   DECLARE_VTBL(IMenuCtl)

   // private object data;

   long           m_nRefs;                   // reference count

   flg            m_bActive:1;               // control is active flag
   flg            m_bSendCmd:1;              // enable sending of EVT_COMMAND event
   flg            m_bIconVScroll:1;          // Multi-line icon view - Vertical Scroll
   flg            m_bIconHScroll:1;          // Single-line icon view - Horizontal Scroll
   flg            m_bHasText:1;              // Menu items have text
   flg            m_bAutoScroll:1;           // Auto Scroll
   flg            m_bColor:1;                // If color screens - color depth > 1
   flg            m_bSKPageFullNotify:1;     // Set if the SK menu has displayed a full page
   flg            m_bResumeCmd:1;            // Set if using async EVT_COMMAND posting - Post-1.0

   IImage *       m_pFont;                   // Holds AEE_IDB_SMALLFONTS for Calendar Menu
   int            m_yLastTimeMark;           // Previous position of mark that indicates current time

   int16          m_nSBWidth;                // Scroll Bar Width (cxScrollBar of AEEDeviceInfo)

   uint32         m_dwProps;                 // menu control properties
   AEECLSID       m_cls;                     // menu control class
   CMenuItem *    m_pItemList;               // list of menu items
   IShell *       m_pIShell;                 // IShell interface ptr
   IDisplay *     m_pIDisplay;               // IDisplay interface ptr
   uint16         m_nItems;                  // number of items in list
   uint16         m_nSelect;                 // index of current menu selection
   uint16         m_nScrollItem;             // index of current scroll position (row index for grid icon view, item index otherwise)
   uint16         m_nPageItems;              // number of items that can fit in a page
   int            m_nAscent;                 // ascent in m_cyFont font
   int            m_cyFont;                  // height of char cell of menu font 
   AECHAR *       m_pTitle;                  // menu title
   int            m_nVisibleItems;           // number of items displayed at current scroll position

   SKDrawItem     m_draw[MAX_SOFT_PAGE_ITEMS];

   // Styles
   AEEItemStyle   m_style[2];                // Style for normal and selection
   uint16         m_cxItemExtra;             // Extra pixels added to width for style
   uint16         m_cyItemExtra;             // Extra pixels added to height for style

   AECHAR         m_szEllipses[2];           // Holds AEE_SYM_ELLIPSES for Normal Font
   int            m_cxDotWidth;              // Holds Ellipses symbol width for Bold Font

   // Icon View Stuff
   int            m_nTotalRows;              // Total number of icon rows
   int            m_nCols;                   // Number of displayed icon cols
   int            m_nRows;                   // Number of displayed icon rows
   int            m_xImagePad;               // Horizontal padding around item icon
   int            m_yImagePad;               // Vertical padding around item icon

   int            m_cxMaxItem;               // Max width of a menu item
   int            m_cyMaxItem;               // Max height of a menu item

   AEERect        m_rc;                      // control rectangle

   // Auto-Scroll stuff
   int            m_nAutoScrollIdx;          // Auto Scroll - Scroll Character Index
   AEERect        m_rcAutoScroll;            // Auto Scroll Rect
   uint32         m_wMenuTextScroll;         // Auto Scroll - Update Timer
   uint32         m_wMenuImageDelay;         // Delay for SINGLE_FRAME Icon View with MP_ICON_DELAY_IMAGE property
   
   AEEMenuColors  m_c;                       // Control Colors

   // Posted-COMMANDs   
   AEECallback    m_cbCommand;               // Callback for posting EVT_COMMAND

   CMenuItem *    m_pEnum;                   // Used for Menu Item enumeration
   CMenuItem *    m_pLastItem;               // Last Item in List, quicker AddItem when Dupe opt on!

   // Owner Draw Stuff
   PFNOWNERDRAWCB m_pfnCB;                   // Callback for Owner Draw
   void *         m_pvContext;               // Context for Owner Draw Callback
   uint16         m_nODFlags;                // Owner Draw Flags
   AEERect        m_rcTitle;                 // Title Rect

   // Pen handling memebers
   PenTracker     m_ptTracker;
   AEEAutoRepeat  m_arPenRepeat;
};

typedef struct 
{
   AEEMenuSortType      t;
   PFNMENUSORTCOMPARE   pfn;
   void *               pUser;
} MSortArgs;

typedef struct
{
   CMenuItem *          pmi;
   MSortArgs *          pa;
} MSortItem;

#define IS_PROP_SET(dw,dwProp)   ((dw) & (dwProp))

#define WRAP_SCROLL(p)        IS_PROP_SET(p->m_dwProps,MP_WRAPSCROLL)
#define MAX_SK_ITEMS(p)       IS_PROP_SET(p->m_dwProps,MP_MAXSOFTKEYITEMS)
#define NO_REDRAW(p)          IS_PROP_SET(p->m_dwProps,MP_NO_REDRAW)
#define IS_CALENDAR_VIEW(p)   IS_PROP_SET(p->m_dwProps,MP_CALENDAR)
#define AUTO_SCROLL(p)        IS_PROP_SET(p->m_dwProps,MP_AUTOSCROLLTIME)
#define ALIGN_TEXT_TOP(p)     IS_PROP_SET(p->m_dwProps,MP_ICON_TEXT_TOP)
#define NO_ARROWS(p)          IS_PROP_SET(p->m_dwProps,MP_NO_ARROWS)
#define SINGLE_FRAME(p)       IS_PROP_SET(p->m_dwProps,MP_ICON_SINGLE_FRAME)
#define UNDERLINE(p)          IS_PROP_SET(p->m_dwProps,MP_UNDERLINE_TITLE)
#define IS_ANIMATED(p)        IS_PROP_SET(p->m_dwProps,MP_ICON_ANIMATED)
#define IS_MULTI_IMAGE(p)     IS_PROP_SET(p->m_dwProps,(MP_TRI_STATE_IMAGE|MP_BI_STATE_IMAGE))
#define IS_TRI_STATE(p)       IS_PROP_SET(p->m_dwProps,MP_TRI_STATE_IMAGE)
#define DELAY_IMAGE(p)        IS_PROP_SET(p->m_dwProps,MP_ICON_DELAY_IMAGE)
#define IS_MULTI_SEL(p)       IS_PROP_SET(p->m_dwProps,MP_MULTI_SEL)
#define IS_ALLOWING_DUPE(p)   IS_PROP_SET(p->m_dwProps,MP_ALLOW_DUPE_IDS)

#define IS_OD_CB(p)           ((p)->m_pfnCB)
#define IS_TITLE_OD(p)        ((p)->m_nODFlags & ODF_TITLE_OD)
#define IS_SB_OD(p)           ((p)->m_nODFlags & ODF_SB_OD)
#define OD_ITEMS_ALLOWED(p)   ((p)->m_nODFlags & ODF_ITEM_OD)
#define IS_MEASURE_ONCE(p)    ((p)->m_nODFlags & ODF_MEASURE_ONCE)            
#define IS_ITEM_OD(pi)        ((pi)->bOD)

#define ITEM_IS_VISIBLE(p,n)  (n >= (p)->m_nScrollItem && n < (p)->m_nScrollItem + (p)->m_nPageItems)
#define ICON_IS_VISIBLE(p,n)  (n >= ((p)->m_nScrollItem * (p)->m_nCols) && n < ((p)->m_nScrollItem * (p)->m_nCols) + ((p)->m_nRows * (p)->m_nCols))

#define ICON_IS_SINGLEROW_VISIBLE(p,n)  (n >= (p)->m_nScrollItem && n < (p)->m_nScrollItem + (p)->m_nCols)

#define IS_SOFTKEY_MENU(p)    ((p)->m_cls == AEECLSID_SOFTKEYCTL)
#define IS_LIST_MENU(p)       ((p)->m_cls == AEECLSID_LISTCTL)
#define IS_MENU(p)            ((p)->m_cls == AEECLSID_MENUCTL)
#define IS_ICON_VIEW(p)       ((p)->m_cls == AEECLSID_ICONVIEWCTL)

//
// Local function prototypes
//

static int           Menu_New(IShell * pIShell, AEECLSID cls, void ** ppobj);
static uint32        IMenuCtl_AddRef(IMenuCtl * po);
static uint32        IMenuCtl_Release(IMenuCtl * po);
static boolean       IMenuCtl_HandleEvent(IMenuCtl * po, AEEEvent eCode, uint16 wParam, uint32 dwParam);
static boolean       IMenuCtl_Redraw(IMenuCtl * po);
static void          IMenuCtl_SetActive(IMenuCtl * po, boolean bActive);
static boolean       IMenuCtl_IsActive(IMenuCtl * po);
static void          IMenuCtl_SetRect(IMenuCtl * po, const AEERect * prc);
static void          IMenuCtl_GetRect(IMenuCtl * po, AEERect * prc);
static void          IMenuCtl_SetProperties(IMenuCtl * po, uint32 nProperties);
static uint32        IMenuCtl_GetProperties(IMenuCtl * po);
static void          IMenuCtl_Reset(IMenuCtl * po);
static boolean       IMenuCtl_SetTitle(IMenuCtl * po, const char * pszResFile, uint16 wResID, AECHAR * pText);
static boolean       IMenuCtl_AddItem(IMenuCtl * po, const char * pszResFile, uint16 wResID, uint16 nItemID, AECHAR * pText, uint32 lData);
static boolean       IMenuCtl_AddItemEx(IMenuCtl * po, CtlAddItem *pai);
static boolean       IMenuCtl_GetItemData(IMenuCtl * po, uint16 nItemID, uint32 * plData);
static boolean       IMenuCtl_DeleteItem(IMenuCtl * po, uint16 nItemID);
static boolean       IMenuCtl_DeleteAll(IMenuCtl * po);
static void          IMenuCtl_SetSel(IMenuCtl * po, uint16 nSelect);
static uint16        IMenuCtl_GetSel(IMenuCtl * po);
static void          IMenuCtl_EnableCommand(IMenuCtl * po, boolean bEnable);
static void          IMenuCtl_SetItemText(IMenuCtl * po, uint16 nItemID, const char * pszResFile, uint16 wResID, AECHAR * pText);
static void          IMenuCtl_SetItemTime(IMenuCtl * po, uint16 nItemID, uint16 wTime, uint16 wDuration);
static int           IMenuCtl_GetItemTime(IMenuCtl * po, uint16 nItemID, uint16 * pwDuration);
static void          IMenuCtl_SetStyle(IMenuCtl * po, AEEItemStyle * pNormal, AEEItemStyle * pSel);
static void          IMenuCtl_SetColors(IMenuCtl * po, AEEMenuColors * pc);
static int16         IMenuCtl_MoveItem(IMenuCtl * po, uint16 wID1, int nDir);

static boolean       IMenuCtl_SetItem(IMenuCtl * po, uint16 wID,uint16 wFlags,CtlAddItem * pai);
static int           IMenuCtl_GetItemCount(IMenuCtl * po);
static uint16        IMenuCtl_GetItemID(IMenuCtl * po, int nIdx);
static void          IMenuCtl_Sort(IMenuCtl * po, AEEMenuSortType t, PFNMENUSORTCOMPARE pfn, void * pUser);
static boolean       IMenuCtl_GetItem(IMenuCtl * po, uint16 wID, CtlAddItem * pai);

static void          IMenuCtl_SetSelEx(IMenuCtl * po, uint16 nItemID, boolean bSelected);
static boolean       IMenuCtl_EnumSelInit(IMenuCtl * po);
static uint16        IMenuCtl_EnumNextSel(IMenuCtl * po);
   
static void          IMenuCtl_GetStyle(IMenuCtl * pIMenuCtl,AEEItemStyle * pNormal, AEEItemStyle * pSel);
static void          IMenuCtl_GetColors(IMenuCtl * pIMenuCtl, AEEMenuColors  * pc);
static void          IMenuCtl_GetItemRect(IMenuCtl * pIMenuCtl, uint16 nIdx, AEERect * prc);
static int           IMenuCtl_SetOwnerDrawCB(IMenuCtl * po, PFNOWNERDRAWCB pfnCB, void * pvContext, uint16 nODFlags);

static boolean       MenuCtl_AddODItem(CMenuCtl * pme, uint16 nItemID, uint32 lData);
static void          Menu_FreeItem(CMenuCtl * pme, CMenuItem * pmi);
static void          Menu_SetItemColor(CMenuCtl * pme, IDisplay * pd, boolean bSel,RGBVAL * pcBack);
static void          Menu_NotifySelChange(CMenuCtl * pme);
static void          Menu_DrawScrollBar(CMenuCtl * pme);
static int           Menu_GetScrollBarRects(CMenuCtl * pme, AEERect * prcFrame, AEERect * prcThumb, uint16 * pwPos, uint16 * pwLen);
static void          Menu_ScrollTimerCB(CMenuCtl *pme);
static boolean       Menu_ScrollByPos(CMenuCtl * pme, int nDir, int16 wXPos, int16 wYPos);
static boolean       Menu_Draw(CMenuCtl * pme);
static void          Menu_MoveSelect(CMenuCtl * pme, int nDir, boolean bRedraw);
static void          Menu_Recalc(CMenuCtl * pme);
static void          Menu_GetStyleOffsets(CMenuCtl * pme,int * pcx, int * pcy);
static void          Menu_AutoScroll(CMenuCtl * pme);
static void          Menu_ResetAutoScroll(CMenuCtl * pme);
static IImage *      Menu_LoadImage(CMenuCtl * pme, const char * pszRes, uint16 wID, boolean * pbCached);
static boolean       Menu_ItemScrolls(CMenuCtl * pme, CMenuItem *pi, int nIdx);
static void          Menu_DrawItem(CMenuCtl * pme, CMenuItem * p, AEERect * prc, boolean bSel, int xTrunc);
static void          Menu_DrawODItem(CMenuCtl * pme, CMenuItem * p, AEERect * prc, boolean bSel);
static void          Menu_InitColors(CMenuCtl * pme, RGBVAL * pBack, RGBVAL * pText, RGBVAL * pFrame);
static void          Menu_DrawArrows(CMenuCtl * pme, int x, int y, int xEnd);
static void          Menu_SetupImage(CMenuCtl * pme, CMenuItem * pmi, IImage * pImage);
static boolean       Menu_ResolveImage(CMenuCtl * pme, CtlAddItem * pai,const char ** ppszResImage, IImage ** ppImage);
static CMenuItem *   Menu_AllocItem(CMenuCtl * pme, CMenuItem *piOrig, AECHAR * pText, const char * pszResFile,CMenuItem * piPrev);
static boolean       Menu_HandleSelection(CMenuCtl * pme, int nSelect);
static uint16        Menu_HandleTap(CMenuCtl * pme, int x, int y);
static void          Menu_SendCommand(CMenuCtl * pme);
static int           Menu_SortCB(const void * p1, const void * p2);
static void          Menu_DrawNewSelectItem(CMenuCtl * pme, int nIdx,int nIdxSel);
static void          Menu_HandleChar(CMenuCtl * pme, AECHAR ch);
static boolean       Menu_HandlePens(CMenuCtl * pme, AEEEvent evtCode, uint16 wParam, int16 wXPos, int16 wYPos);

static boolean       List_Draw(CMenuCtl * pme);
static void          List_MoveSelect(CMenuCtl * pme, int nDir);

static boolean       SK_Draw(CMenuCtl * pme);
static boolean       SK_CalcLayout(CMenuCtl * pme);
static boolean       SK_MoveSelect(CMenuCtl * pme, int nDir);
static int           SK_SetDrawItems(CMenuCtl * pme, int nDir, int * pnVisibleItems, int * pnLastItem,boolean * pbNoFit);
static uint8         SK_HandlePen(CMenuCtl * pme, int16 wXPos, int16 wYPos, uint16 * pwExtraData, uint16 * pwIndex);

static boolean       Icon_HandleKey(CMenuCtl * pme, uint16 wKey);
static boolean       Icon_Draw(CMenuCtl * pme);
static void          Icon_DrawAllItems(CMenuCtl * pme);
static void          Icon_CalcGridRect(CMenuCtl * pme, AEERect * prc, int nSel);
static void          Icon_DrawItem(CMenuCtl * pme, CMenuItem * pmi,AEERect * prc, boolean bSel);
static void          Icon_DrawItemText(CMenuCtl * pme, CMenuItem * pmi, uint16 cyText, boolean bSel);
static void          Icon_DrawODItem(CMenuCtl * pme, CMenuItem * pmi, AEERect * prc, boolean bSel);
static void          Icon_DrawODItemText(CMenuCtl * pme, CMenuItem * pmi, uint16 cyText, boolean bSel);
static int           Icon_CalcCols(CMenuCtl * pme, int cx);
static void          Icon_CalcRows(CMenuCtl * pme);
static void          Icon_StopAnimation(CMenuCtl * pme);
static uint16        Icon_GetTextHeight(CMenuCtl * pme);
static uint16        Icon_GetMaxTextHeight(CMenuCtl * pme);
static __inline boolean Icon_MapPointToItem(CMenuCtl * pme, int16 wXPos, int16 wYPos, int * pnItem, int * pnIdx);
#define              Icon_GetImageOffset(p)   (Icon_GetMaxTextHeight(p) + AEE_FRAME_SIZE)


static void          ResetColors(IDisplay * pd, RGBVAL clrBack, RGBVAL clrText,RGBVAL cFrame);
static CMenuItem *   GetItemByID(CMenuItem * pl, int nID, CMenuItem ** pLast);
static CMenuItem *   GetItemByIDEx(CMenuItem * pl, int nID, CMenuItem ** pLast, int* pIndex);
static CMenuItem *   GetItemByIdx(CMenuItem * pl, int nIdx);
static void          AdjustRect(AEERect * prc, AEEItemStyle * ps);
static int           GetItemWidth(CMenuItem * pi);
static boolean       Intersects(AEERect * prc, int x, int y);
static uint16        GetItemHeight(CMenuItem * p, uint16 * pwLeft, uint16 wLine);

static void          Cal_DrawTime(CMenuCtl * pme, IDisplay * pd, uint16 wTimeMin, int x, int yOrig, int y);
static void          Cal_Reset(CMenuCtl * pme);
static void          Cal_CalcPageItems(CMenuCtl * pme);
static void          Cal_AutoScroll(CMenuCtl * pme, boolean bForceSel);
static void          Cal_AutoScrollCB(CMenuCtl * pme);
static uint16        Cal_ShowTimeMark(CMenuCtl * pme, CMenuItem * pi, uint16 wIdx, uint16 wTimeNow);
static CMenuItem *   Cal_FindNowItem(CMenuCtl * pme, uint16 * pwNow, uint16 * pwIdx);
static void          Cal_DrawItem(CMenuCtl * pme, CMenuItem * pi, int nIdx, boolean bClear);
static void          Cal_DrawArrow(CMenuCtl * pme, int y);

static void          SK_CalcItemRect(CMenuCtl * pme, uint16 nIdx, AEERect* prc);
static void          Menu_CalcItemRect(CMenuCtl * pme, int nIdx, AEERect* prc);
static void          List_CalcItemRect(CMenuCtl * pme, AEERect * prc);
static void          Icon_CalcItemRect(CMenuCtl * pme, uint16 nIdx, AEERect* prc);

//
// Menu vtable
//

const AEEStaticClass gMenuClassList[] = {
   {AEECLSID_ICONVIEWCTL,     ASCF_UPGRADE,0,NULL,Menu_New},        
   {AEECLSID_ICONVIEWCTL_10,  ASCF_UPGRADE,0,NULL,Menu_New},   // Pre 1.10
   {AEECLSID_MENUCTL,         ASCF_UPGRADE,0,NULL,Menu_New},
   {AEECLSID_MENUCTL_10,      ASCF_UPGRADE,0,NULL,Menu_New},   // Pre 1.10
   {AEECLSID_SOFTKEYCTL,      ASCF_UPGRADE,0,NULL,Menu_New},
   {AEECLSID_SOFTKEYCTL_10,   ASCF_UPGRADE,0,NULL,Menu_New},   // Pre 1.10
   {AEECLSID_LISTCTL,         ASCF_UPGRADE,0,NULL,Menu_New},
   {AEECLSID_LISTCTL_10,      ASCF_UPGRADE,0,NULL,Menu_New},   // Pre 1.10
   {0,0,0,NULL,NULL},
};

static const VTBL(IMenuCtl) gCMenuCtlFuncs = {IMenuCtl_AddRef,
                                             IMenuCtl_Release,
                                             IMenuCtl_HandleEvent,
                                             IMenuCtl_Redraw,
                                             IMenuCtl_SetActive,
                                             IMenuCtl_IsActive,
                                             IMenuCtl_SetRect,
                                             IMenuCtl_GetRect,
                                             IMenuCtl_SetProperties,
                                             IMenuCtl_GetProperties,
                                             IMenuCtl_Reset,
                                             IMenuCtl_SetTitle,
                                             IMenuCtl_AddItem,
                                             IMenuCtl_AddItemEx,
                                             IMenuCtl_GetItemData,
                                             IMenuCtl_DeleteItem,
                                             IMenuCtl_DeleteAll,
                                             IMenuCtl_SetSel,
                                             IMenuCtl_GetSel,
                                             IMenuCtl_EnableCommand,
                                             IMenuCtl_SetItemText,
                                             IMenuCtl_SetItemTime,
                                             IMenuCtl_GetItemTime,
                                             IMenuCtl_SetStyle,
                                             IMenuCtl_SetColors,
                                             IMenuCtl_MoveItem,
                                             IMenuCtl_GetItemCount,
                                             IMenuCtl_GetItemID,
                                             IMenuCtl_GetItem,
                                             IMenuCtl_SetItem,
                                             IMenuCtl_Sort,
                                             IMenuCtl_SetSelEx,
                                             IMenuCtl_EnumSelInit,
                                             IMenuCtl_EnumNextSel,
                                             IMenuCtl_GetStyle,
                                             IMenuCtl_GetColors,
                                             IMenuCtl_GetItemRect,
                                             IMenuCtl_SetOwnerDrawCB
};

/*===========================================================================

EXTERNAL - Called from OEM static table.  Supports creation of menu classes.

===========================================================================*/
static int Menu_New(IShell * pIShell, AEECLSID cls, void ** ppobj)
{
   CMenuCtl *     pme = NULL;
   int            descent,cx,cy;
   AEEItemType    t;
   AEEDeviceInfo  dm;
   boolean        bResumeCmd;
   IBitmap *      pbmDst;
   AEEBitmapInfo  bi;
   int            nErr = EFAILED,
                  nSize;

  // allocate memory for object

   if (!pIShell || !ppobj)
      return(EBADPARM);

   *ppobj = NULL;

   if((pme = (CMenuCtl *)AEE_NewClass((IBaseVtbl *)&gCMenuCtlFuncs, sizeof(CMenuCtl))) == NULL)
      return(ENOMEMORY);

  // create IDisplay interface

   ISHELL_CreateInstance(pIShell, AEECLSID_DISPLAY, (void **)&pme->m_pIDisplay);
   if (!pme->m_pIDisplay) goto Error;

   // Get destination bitmap info
   pbmDst = IDISPLAY_GetDestination(pme->m_pIDisplay);
   if (!pbmDst)
      goto Error;
   nErr = IBITMAP_GetInfo(pbmDst, &bi, sizeof(bi));
   IBITMAP_Release(pbmDst);
   if (SUCCESS != nErr)
      goto Error;

   // Increment the reference count on the shell...

   ISHELL_AddRef(pIShell);

   t = AEE_IT_MENU;
   
   // Post-1.0 uses an async EVT_COMMAND posting mechanism...

   bResumeCmd = (boolean)(cls < AEECLSID_MENUCTL ? FALSE : TRUE);

  // Get the OEM defaults.  Note that IShell catches the case where the OEM has failed to set them...

   switch(cls){

   // Fixup ID for new stuff...

      case AEECLSID_ICONVIEWCTL_10:
         cls = AEECLSID_ICONVIEWCTL;
      case AEECLSID_ICONVIEWCTL:
         t = AEE_IT_ICONVIEW;
         break;

   // Fixup ID for new stuff...

      case AEECLSID_SOFTKEYCTL_10:
         cls = AEECLSID_SOFTKEYCTL;
      case AEECLSID_SOFTKEYCTL:
         t = AEE_IT_SOFTKEY;
         break;

   // Fixup ID for new stuff...

      case AEECLSID_MENUCTL_10:
         cls = AEECLSID_MENUCTL;
         break;

   // Fixup ID for new stuff...

      case AEECLSID_LISTCTL_10:
         cls = AEECLSID_LISTCTL;
         break;

   }

   // Get and Cache Style for Normal and Selected Items
   ISHELL_GetItemStyle(pIShell, t, &pme->m_style[0], &pme->m_style[1]);

   pme->m_pIShell          = pIShell;           // IShell Pointer
   pme->m_cls              = cls;               // Control Class
   pme->m_nPageItems       = SOFT_PAGE_ITEMS;   // initialized to # of soft key items visible at once
   pme->m_bSendCmd         = 1;                 // defaults to on
   pme->m_bResumeCmd       = bResumeCmd;        // Set TRUE for Post 1.0

   // Default Properties
   IMenuCtl_SetProperties((IMenuCtl *)pme,0);   

   // Default Color Values
   IMenuCtl_SetColors((IMenuCtl *)pme, NULL);

   pme->m_yLastTimeMark = INVALID_MARK;

   // obtain font metrics for later use
   pme->m_cyFont = IDISPLAY_GetFontMetrics(pme->m_pIDisplay, AEE_FONT_BOLD, &pme->m_nAscent, &descent);

   // get display metrics
   ISHELL_GetDeviceInfo(pme->m_pIShell, &dm);
   pme->m_nSBWidth = dm.cxScrollBar;               // Scroll Bar Width
   pme->m_bColor = (boolean)(bi.nDepth > 1);  // Color Depth

   pme->m_nSBWidth += 2*AEE_FRAME_SIZE;            // Adjust for frame

   // Image Draw Delay for SINGLE_FRAME Icon View with MP_ICON_DELAY_IMAGE property
   if(dm.wMenuImageDelay)
      pme->m_wMenuImageDelay = (uint32)dm.wMenuImageDelay;
   else
      pme->m_wMenuImageDelay = IMAGE_DELAY_TIME;

   // Auto Scroll - Update Delay
   if(dm.wMenuTextScroll)
      pme->m_wMenuTextScroll = (uint32)dm.wMenuTextScroll;
   else
      pme->m_wMenuTextScroll = AUTO_SCROLL_TIME;

   // Default Control Rect
   if (cls == AEECLSID_SOFTKEYCTL) {
      pme->m_rc.x    = 0;
      pme->m_rc.dx   = (int16)bi.cx;
      Menu_GetStyleOffsets(pme,&cx,&cy);
      pme->m_rc.dy   = pme->m_cyFont + cy;
      pme->m_rc.y    = (int16)(bi.cy - pme->m_rc.dy); // Bottom of Screen
   }
   else 
      SETAEERECT(&pme->m_rc, 0, 0, bi.cx, bi.cy);  // Entire Screen

   // Default Title Rect
   pme->m_rcTitle.dx = (int16)bi.cy;
   pme->m_rcTitle.dy = pme->m_cyFont;

   // Load Ellipses Symbol & Cache its width
   pme->m_szEllipses[0] = (AECHAR)IDISPLAY_GetSymbol(pme->m_pIDisplay,AEE_SYM_ELLIPSES,AEE_FONT_NORMAL);
   pme->m_cxDotWidth = IDISPLAY_MeasureText(pme->m_pIDisplay, AEE_FONT_BOLD, (const AECHAR *)pme->m_szEllipses);
   
   pme->m_pEnum   = NULL;
   pme->m_arPenRepeat.wItem = AEEREPEAT_SCROLLBAR;
   nSize = sizeof(AEEAutoRepeat);
   ISHELL_GetDeviceInfoEx(pIShell, AEE_DEVICEITEM_REPEAT_TIME, (void *)&pme->m_arPenRepeat, &nSize);
   MEMSET((void *)&pme->m_ptTracker, 0, sizeof(pme->m_ptTracker));

   *ppobj = pme;

   return(0);

Error:
   IMenuCtl_Release((IMenuCtl *)pme);
   return nErr;
}

/*=====================================================================

Public Method - Increments the reference count on the menu class object.

======================================================================*/
static uint32 IMenuCtl_AddRef(IMenuCtl * po)
{
   return(++((CMenuCtl*)po)->m_nRefs);
}

/*=====================================================================

Public Method - Decrements the reference count on the menu class object.
If the value reaches 0, it frees the object and all associated data.

======================================================================*/
static uint32 IMenuCtl_Release(IMenuCtl * po)
{
   CMenuCtl * pme = (CMenuCtl*)po;

   if(pme->m_nRefs){
      if (--pme->m_nRefs)
         return(pme->m_nRefs);

      // Cancel All Timers
      ISHELL_CancelTimer(pme->m_pIShell, NULL, pme);

      // Delete All Items
      IMenuCtl_DeleteAll((IMenuCtl*)pme);

      // Free Title
      aee_freeobj((void **)&pme->m_pTitle);
   
      // Reset Calendar
      Cal_Reset(pme);
      
      CALLBACK_Cancel(&pme->m_cbCommand);

      // Release IShell & IDisplay
      aee_releaseobj((void **)&pme->m_pIShell);
      aee_releaseobj((void **)&pme->m_pIDisplay);

      // Free Menu Object
      FREE(pme);
   }      
   return(0);
}

/*=====================================================================

Public Method - Handles events send by the app to the menu.  

======================================================================*/
static boolean IMenuCtl_HandleEvent(IMenuCtl * po, AEEEvent eCode, uint16 wParam, uint32 dwParam)
{
   CMenuCtl * pme = (CMenuCtl*)po;

   if (!pme->m_bActive && eCode != EVT_CTL_SET_TITLE && eCode != EVT_CTL_ADD_ITEM)
      return(FALSE);  // Event not handled.

   // First handle the pens, most time sensitive events we get
   if( eCode >= EVT_PEN_DOWN && eCode <= EVT_PEN_STALE_MOVE ){
      // Stale move is not handled and likely the largest stream of events
      // For speed, abort quick.
      if( eCode == EVT_PEN_STALE_MOVE ){
         return FALSE;
      }
      return Menu_HandlePens(pme, eCode, wParam, AEE_GET_X(dwParam), AEE_GET_Y(dwParam));
   }
   switch (eCode) {

      // Menu Title Changed - Event from Dialog      
      case EVT_CTL_SET_TITLE:
      {
         AECHAR *pText;
         const char *pRes;

         if(wParam){
            pRes = (const char *)dwParam;
            pText = NULL;
         }
         else{
            pRes = NULL;
            pText = (AECHAR *)dwParam;
         }   
         return(IMenuCtl_SetTitle(po, pRes, wParam, pText));
      }
      
      // Add Item - Event from Dialog      
      case EVT_CTL_ADD_ITEM:
         return(IMenuCtl_AddItemEx(po, (CtlAddItem *)dwParam));

      case EVT_KEY:
        
         if(pme->m_cls == AEECLSID_ICONVIEWCTL && Icon_HandleKey(pme, wParam))
            return(TRUE);
         
         //Check if its a TAP event (i.e. EVT_KEY event with wParam set to AVK_TAP)

         if(wParam == AVK_TAP){
            wParam = Menu_HandleTap(pme, AEE_GET_X(dwParam), AEE_GET_Y(dwParam));
            if( wParam == AVK_UNDEFINED ) 
               return(FALSE);
         }
         
         switch (wParam) {
     
            case AVK_SELECT:
               return Menu_HandleSelection(pme, pme->m_nSelect);

         // Note: We have already handled keys for AEECLSID_ICONVIEWCTL so here we only care
         // about remaining control types

            // AVK_LEFT and AVK_RIGHT needs to be handled only by softkey control. For others,
            // we generate EVT_CTL_TAB

            case AVK_LEFT:
               if (IS_SOFTKEY_MENU(pme))
                  return SK_MoveSelect(pme, -1);

               if( ISHELL_HandleEvent(pme->m_pIShell, EVT_CTL_TAB, (uint16)0, (uint32)po) )
                  return TRUE;
               break;

            case AVK_RIGHT:
               if (IS_SOFTKEY_MENU(pme))
                  return SK_MoveSelect(pme, 1);

               if( ISHELL_HandleEvent(pme->m_pIShell, EVT_CTL_TAB, (uint16)1, (uint32)po) )
                  return TRUE;
               break;

            // AVK_UP and AVK_DOWN need not be handled by softkey control. For softkey control,
            // we generate EVT_CTL_TAB

            case AVK_UP:
               if (!IS_SOFTKEY_MENU(pme)) {
                  if (IS_LIST_MENU(pme))
                     List_MoveSelect(pme, -1);
                  else
                     Menu_MoveSelect(pme, -1, TRUE);
                  return(TRUE);
               }

               if( ISHELL_HandleEvent(pme->m_pIShell, EVT_CTL_TAB, (uint16)0, (uint32)po) )
                  return TRUE;
               break;

            case AVK_DOWN:
               if (!IS_SOFTKEY_MENU(pme)) {
                  if (IS_LIST_MENU(pme))
                     List_MoveSelect(pme, 1);
                  else
                     Menu_MoveSelect(pme, 1, TRUE);
                  return(TRUE);
               }

               if( ISHELL_HandleEvent(pme->m_pIShell, EVT_CTL_TAB, (uint16)1, (uint32)po) )
                  return TRUE;
               break;

         // For calendar, we treat numeric key press as input hour. Zero is treated as 10. We
         // iterate items to check if an entry starts at input hour or (input + 12) hour and 
         // if so, that entry is set as selection
            default:
            {
               CMenuItem * p;
               int         i;
               uint16      nHour;

               if(wParam >= AVK_0 && wParam <= AVK_9 && IS_CALENDAR_VIEW(pme)){
                  nHour = (uint16)(wParam - AVK_0);
                  if(wParam == AVK_0)
                     nHour = (uint16)10;
                  for(i = 0; i < 2; i++, nHour += 12){
                     for(p = pme->m_pItemList; p ; p = p->pNext){
                        if((uint16)(p->wCalStart / 60) == nHour){
                           IMenuCtl_SetSel(po, (uint16)p->nItemID);
                           return(TRUE);
                        }
                     }
                  }
               }
               break;
            }
         }
         break;

   // We compare entered character with first character of a menu item text and upon match
   // set the item as current selection
      case EVT_CHAR:
         Menu_HandleChar(pme, (AECHAR)wParam);
         return TRUE;

      default:
         break;
   }
   return(FALSE);   // did not handle the event
}

/*=====================================================================

Public Method - Instructs the menu to redraw the contents.

======================================================================*/
static boolean IMenuCtl_Redraw(IMenuCtl * po)
{
   CMenuCtl * pme = (CMenuCtl*)po;
   
   switch(pme->m_cls){

      case AEECLSID_SOFTKEYCTL:
         SK_CalcLayout(pme);
         return(SK_Draw(pme));

      case AEECLSID_LISTCTL:
         return(List_Draw(pme));

      case AEECLSID_MENUCTL:
         Menu_Draw(pme);
         Menu_ResetAutoScroll(pme);
         return(TRUE);

      case AEECLSID_ICONVIEWCTL:
         return(Icon_Draw(pme));
   }

   return(FALSE);
}

/*=====================================================================

Public Method - Sets the menu in an "active" state whereby keys are
processed.

======================================================================*/
static void IMenuCtl_SetActive(IMenuCtl * po, boolean bActive)
{
   CMenuCtl * pme = (CMenuCtl *)po;

   if(pme->m_bActive != bActive) {

      pme->m_bActive = bActive;

      if(AUTO_SCROLL(pme) && bActive)
         Cal_AutoScroll(pme, TRUE);       // Active Calendar Menu with Auto Scroll
      else{
         pme->m_yLastTimeMark = INVALID_MARK;            // Invalidate time mark
         ISHELL_CancelTimer(pme->m_pIShell, NULL, pme);  // Cancel calendar menu updates

         if(IS_MENU(pme) && bActive)
            Menu_ResetAutoScroll(pme);    // Active Menu - Reset item text auto scroll

   // Icon views redraw async - If this is a disable, do not allow it!

         if(!NO_REDRAW(pme)){
            if(IS_ICON_VIEW(pme) && !bActive)
               Icon_StopAnimation(pme);         // Don't animate if its not selection
            else
              IMenuCtl_Redraw(po);              // Redraw
         }
      }
   }
}

/*=====================================================================

Public Method - Returns the active state of the menu.

======================================================================*/
static boolean IMenuCtl_IsActive(IMenuCtl * po)
{
   return((boolean)((CMenuCtl*)po)->m_bActive);
}

/*=====================================================================

Public Method - Sets the rectangle for the menu.

======================================================================*/
static void IMenuCtl_SetRect(IMenuCtl * po, const AEERect * prc)
{
   CMenuCtl * pme = (CMenuCtl*)po;

   if (prc) {
      pme->m_rc = *prc;
      Menu_Recalc(pme);
   }
}

/*=====================================================================

Public Method - Returns the rectangle for the menu.

======================================================================*/
static void IMenuCtl_GetRect(IMenuCtl * po, AEERect * prc)
{
   if(prc)
      *prc = ((CMenuCtl*)po)->m_rc;
}

/*=====================================================================

Public Method - Sets the properties (flags) for the menu.

======================================================================*/
static void IMenuCtl_SetProperties(IMenuCtl * po, uint32 nProperties)
{
   CMenuCtl *     pme = (CMenuCtl*)po;

   // Force this property regardless of what they set!

   if (IS_SOFTKEY_MENU(pme) || IS_LIST_MENU(pme))
      nProperties |= MP_WRAPSCROLL;
   else
      nProperties &= ~MP_WRAPSCROLL;

   // Calendar isn't owner drawn - ensure that for owner draw menu,
   // calendar specific properties aren't set

   if (IS_OD_CB(pme))
      nProperties &= ~(MP_CALENDAR|MP_AUTOSCROLLTIME);

   pme->m_dwProps = nProperties;

   if (MAX_SK_ITEMS(pme))
      pme->m_nPageItems = __MIN(pme->m_nItems, MAX_SOFT_PAGE_ITEMS);
}
   
/*=====================================================================

Public Method - Returns the active properties for the menu.

======================================================================*/
static uint32 IMenuCtl_GetProperties(IMenuCtl * po)
{
   return(((CMenuCtl *)po)->m_dwProps);
}

/*=====================================================================

Public Method - Clears the menu contents and cancels all pending 
activities.

======================================================================*/
static void IMenuCtl_Reset(IMenuCtl * po)
{
   CMenuCtl * pme = (CMenuCtl*)po;

   CALLBACK_Cancel(&pme->m_cbCommand);

   IMenuCtl_DeleteAll(po);

   aee_freeobj((void **)&pme->m_pTitle);

   pme->m_bActive = 0;
   pme->m_pEnum   = NULL;
   MEMSET((void *)&pme->m_ptTracker, 0, sizeof(pme->m_ptTracker));

   Cal_Reset(pme);
}

/*=====================================================================

Public Method - Sets the menu title per AEEMenu.h

======================================================================*/
static boolean IMenuCtl_SetTitle(IMenuCtl * po, const char * pszResFile, uint16 wResID, AECHAR * pText)
{
   CMenuCtl * pme = (CMenuCtl*)po;
   AECHAR  szBuff[32];
   boolean bRet = FALSE;

   // Return FALSE if title is owner drawn
   if(IS_TITLE_OD(pme))
      return bRet;

   MEMSET(szBuff, 0, sizeof(szBuff));

   // Load Title if necessary
   if (!pText && pszResFile
      && ISHELL_LoadResString(pme->m_pIShell, pszResFile, wResID, szBuff, sizeof(szBuff))) {
      pText = szBuff;
   }

   // Allocate Memory 
   if (pText){
      // Free Old Title
      aee_freeobj((void **)&pme->m_pTitle);
      pme->m_pTitle = WSTRDUP(pText);
      bRet = TRUE;
   }

   return(bRet && pme->m_pTitle != NULL);
}

/*=====================================================================

Public Method - Adds a menu item per AEEMenu.h

======================================================================*/
static boolean IMenuCtl_AddItem(IMenuCtl * po, const char * pszResFile, uint16 wResID, uint16 nItemID, AECHAR * pText, uint32 lData)
{
   CtlAddItem  ai;
   CMenuCtl* pme = (CMenuCtl*)po;

   // Check if they have given us something to draw
   // If not, add item if flag required to allow owner draw items is set
   // Otherwise fail
   if (!pszResFile && !pText){
      if (OD_ITEMS_ALLOWED(pme))
         return MenuCtl_AddODItem(pme, nItemID, lData);
      else
         return FALSE;
   }
   
   // Non owner drawn menu item
   MEMSET(&ai, 0, sizeof(CtlAddItem));
   ai.pszResText  = pszResFile;
   ai.pText       = pText;
   ai.wText       = wResID;
   ai.wItemID     = nItemID;
   ai.dwData      = lData;
   return(IMenuCtl_AddItemEx(po, &ai));
}

/*=====================================================================

Public Method - Enhanced mechanism to add menu items.  See AEEMenu.h

======================================================================*/
static boolean IMenuCtl_AddItemEx(IMenuCtl * po, CtlAddItem * pai)
{
   CMenuCtl *     pme = (CMenuCtl*)po;
   CMenuItem *    pmiLast = NULL;
   CMenuItem *    pmi = NULL;
   AECHAR *       pText = NULL;
   IImage *       pImage = NULL;
   const char *   pszResImage = NULL;
   boolean        bFreeText = FALSE;
   boolean        bSingleFrame = (boolean)(SINGLE_FRAME(pme) ? TRUE : FALSE);

   // Can't do any thing without pai
   if(!pai)
      return(FALSE);

   // If it is not allowing Duplicate ID's, do check
   if( !(IS_ALLOWING_DUPE(pme)) ){
   for(pmiLast = NULL, pmi = pme->m_pItemList; pmi != NULL; pmi = pmi->pNext) {
      pmiLast = pmi;
         if(pmi->nItemID == pai->wItemID)
            return(FALSE);
      }
   }else{
      pmiLast = pme->m_pLastItem;
   }

   // Check Sanity of Image & Text Pointers
   if(pai->pImage && 
      !AEE_CHECK_PTR("IMENU_AddItemEx", pai->pImage, 1, FALSE) || 
      pai->pText && 
      !AEE_CHECK_PTR("IMENU_AddItemEx", pai->pText, 1, FALSE)) {
      return(FALSE);
   }

   // See if they passed us an image.  If so, just AddRef the object.
   // If not, see they specified valid ID and resource file. If yes,
   // then try to load it. Otherwise fail if this is Icon View.
   // We need to get out on failure.
   if(!Menu_ResolveImage(pme, pai, &pszResImage, &pImage))
      return(FALSE);
   
   // Try to load the text if specified
   pText = (AECHAR *)pai->pText;

   // We cannot show anything!
   if(!pImage && !bSingleFrame && !pText && (!pai->pszResText || !pai->wText))
      return(FALSE);

   // If text not specified, load it from resource file
   // Note: Icon views do NOT require a text string
   // Note: pai->wText is 0xffff by default for item IDs created in resource editor
   if(!pText && pai->wText && pai->wText != 0xffff){

      // Allocate memory for item text
      if ((pText = (AECHAR *)MALLOC(MAX_MENU_TEXT * sizeof(AECHAR))) != NULL)
         bFreeText = TRUE;    // Note that we now need to free the text
      else {
         aee_releaseobj((void **)&pImage);
         return FALSE;
      }

      // Load item text
      if(!ISHELL_LoadResString(pme->m_pIShell, pai->pszResText, pai->wText, pText, sizeof(AECHAR) * MAX_MENU_TEXT)) {
         aee_releaseobj((void **)&pImage);
         if(bFreeText)
            FREE(pText);
         return FALSE;
      }
   }

   // Allocate memory for the item. If allocation is successful, item is added to the
   // end of the list
   if ((pmi = Menu_AllocItem(pme, NULL, pText, pszResImage, pmiLast)) == NULL) {
      aee_releaseobj((void **)&pImage);
      if(bFreeText)
         FREE(pText);
      return(FALSE);
   }

   // If we are caching image resource file name, we need to cache resource ID also.
   if(pmi->pszImage)
      pmi->wImageID = pai->wImage;

   // Pre-calc the image stuff in case we unload it
   if(pImage){
      if(IS_MULTI_IMAGE(pme))
         IIMAGE_SetParm(pImage,IPARM_NFRAMES,(int)(IS_TRI_STATE(pme) ? 3 : 2),0);
      Menu_SetupImage(pme,pmi,pImage);
   }

   // copy other stuff
   pmi->lData     = pai->dwData;
   pmi->nItemID   = pai->wItemID;
   pmi->nFont     = (AEEFont)pai->wFont;   

   // AEE_FONT_BOLD is default for soft key and icon view
   if(!pmi->nFont)
      pmi->nFont = (AEEFont)(IS_SOFTKEY_MENU(pme) || IS_ICON_VIEW(pme) ? AEE_FONT_BOLD : AEE_FONT_NORMAL);

   // Free Text memory if we allocated
   if(bFreeText)
      FREE(pText);
   
   // Recalc the entire layout of the menu based upon the size of the items and the style(s)
   Menu_Recalc(pme);

   // For softkey, send EVT_CTL_SKMENU_PAGE_FULL if page full upon addition of this item
   if(IS_SOFTKEY_MENU(pme) && !pme->m_bSKPageFullNotify && pme->m_nItems >= pme->m_nPageItems){
      pme->m_bSKPageFullNotify = TRUE;
      ISHELL_HandleEvent(pme->m_pIShell, EVT_CTL_SKMENU_PAGE_FULL, 0, (uint32)pme);
   }

   // For icon view, we keep height of item text
   if (IS_ICON_VIEW(pme) && *pmi->text)
      pmi->cyText = Icon_GetTextHeight(pme);

   return(TRUE);
}

/*=====================================================================

Public Method - Moves an item up/down in the list.

======================================================================*/
static int16 IMenuCtl_MoveItem(IMenuCtl * po, uint16 wID, int nDir)
{
   CMenuCtl *  pme = (CMenuCtl *)po;
   CMenuItem * pi1, * pi1Prev;
   CMenuItem * pi2, * pi2Prev;
   CMenuItem * pTemp;
   CMenuItem * piSel;
   int16       nRet;

   // Get specified item and its previous item
   pi1 = GetItemByID(pme->m_pItemList, wID, &pi1Prev);

   if(!pi1 || !nDir)
      return(-1);

   pi2Prev = NULL;

   if(nDir > 0){
      // Find nth item after specified item and also that items previous item
      for(pi2 = pi1; pi2 != NULL && nDir; pi2 = pi2->pNext, nDir--)
         pi2Prev = pi2;

      if( pi2 && pi2 == pme->m_pLastItem ){
         pme->m_pLastItem = pi1;
      }
   }
   else{
      // Find nth item before specified item and also that items previous item
      pi2 = pi1Prev;
      while(pi2){
         GetItemByID(pme->m_pItemList, pi2->nItemID, &pi2Prev);
         if(!++nDir)
            break;
         pi2 = pi2Prev;
      }
      if( pi2 && pi1 == pme->m_pLastItem ){
         pme->m_pLastItem = pi1Prev;
      }
   }

   if(!pi2)
      return(-1);

   nRet = pi2->nItemID;

   // Save item corresponding to the selection
   piSel = GetItemByIdx(pme->m_pItemList, pme->m_nSelect);

   // Special Case: pi1->pi2 or pi2->pi1

   if(pi2Prev == pi1 || pi1Prev == pi2){  // CASE : Move Down/Up one position
      if(pi1Prev == pi2){  // CASE : Move Up one position
         // Exchange pi1 and pi2
         pTemp = pi1;
         pi1 = pi2;
         pi2 = pTemp;
         // Exchange pi1Prev and pi2Prev
         pTemp = pi1Prev;
         pi1Prev = pi2Prev;
         pi2Prev = pTemp;
      }

   // Start:   pi0 -> pi1 -> pi2 - pi3
   // 1) pi1 -> pi3
   // 2) pi2 -> pi1
   // 1) pi0 -> pi2
   // Result:  pi0 -> pi2 -> pi1 -> pi3

      pi1->pNext = pi2->pNext;
      pi2->pNext = pi1;
      if(pi1Prev)
         pi1Prev->pNext = pi2;
      else
         pme->m_pItemList = pi2;
   }
   else{

   // pi0 -> pi1 -> piA -> pi2
   // 1)  pi0 -> pi1 -> NULL, pi2 -> piA -> pi2 (loop)
   // 2)  pi0 -> pi2 -> piA -> pi2 (loop)
   // 3)  piA -> pi1 -> NULL
   // Result:  pi0 -> pi2 -> piA -> pi1 - NULL

      pTemp = pi1->pNext;
      pi1->pNext = pi2->pNext;
      pi2->pNext = pTemp;

      if(pi1Prev)
         pi1Prev->pNext = pi2;
      else
         pme->m_pItemList = pi2;
      if(pi2Prev)
         pi2Prev->pNext = pi1;
      else
         pme->m_pItemList = pi1;
   }

   // Restore Selection
   if(piSel)
      IMenuCtl_SetSel(po, piSel->nItemID);

   return(nRet);
}

/*=====================================================================

Public Method - Updates information regarding a menu item. See AEEMenu.h

======================================================================*/
static boolean IMenuCtl_SetItem(IMenuCtl * po, uint16 wID, uint16 wFlags, CtlAddItem * pai)
{
   CMenuCtl *  pme = (CMenuCtl *)po;
   CMenuItem * pmi,*pmiPrev;
   IImage *    pImage;
   const char *pszResImage;

   if(!pai)
      return(FALSE);

   pmi = GetItemByID(pme->m_pItemList,(int)wID, &pmiPrev);

   if(!pmi)
      return(FALSE);

   // Set the data 
   
   if(wFlags & MSIF_DATA)
      pmi->lData = pai->dwData;

   // If item is owner drawn, Ignore others

   if (IS_ITEM_OD(pmi))
      return(TRUE);

   // Set the font

   if(wFlags & MSIF_FONT)
      pmi->nFont = (AEEFont)pai->wFont;


   // Set the text

   if(wFlags & MSIF_TEXT){
      IMenuCtl_SetItemText(po,wID,pai->pszResText,pai->wText,(AECHAR *)pai->pText);

   // Must refetch the item - may have been reallocated...

      pmi = GetItemByID(pme->m_pItemList,(int)wID, NULL);

      if(!pmi)
         return(FALSE);
   }

   // Set the image
   
   if(wFlags & MSIF_IMAGE){
      aee_releaseobj((void **)&pmi->pImage);
      if(!Menu_ResolveImage(pme, pai, &pszResImage, &pImage))
         return(FALSE);

      pmi = Menu_AllocItem(pme, pmi, pmi->text, pszResImage, pmiPrev);

      if(!pmi){
         aee_releaseobj((void **)&pImage);
         return(FALSE);
      }
      if(pmi->pszImage) {
         pmi->wImageID = pai->wImage;
			STRCPY((char*)pmi->pszImage, pai->pszResImage); 	
		}

   // Pre-calc the image stuff in case we unload it...

      if(pImage){
         if(IS_MULTI_IMAGE(pme))
            IIMAGE_SetParm(pImage,IPARM_NFRAMES,(int)(IS_TRI_STATE(pme) ? 3 : 2),0);
         Menu_SetupImage(pme,pmi,pImage);
      }
   }

   // Recalculate Menu Layout if a new font or image is specified
   if ((wFlags & MSIF_FONT) || (wFlags & MSIF_IMAGE))
      Menu_Recalc(pme);

   return(TRUE);
}

/*=====================================================================

Public Method - Returns the item count in a menu.

======================================================================*/
static int IMenuCtl_GetItemCount(IMenuCtl * po)
{
   CMenuCtl * pme = (CMenuCtl *)po;

   return(pme->m_nItems);
}

/*=====================================================================

Public Method - Returns the item ID given the index value.

======================================================================*/
static uint16 IMenuCtl_GetItemID(IMenuCtl * po, int nIdx)
{
   CMenuCtl * pme = (CMenuCtl *)po;
   CMenuItem * pmi;
 
   if((pmi = GetItemByIdx(pme->m_pItemList, nIdx)) != NULL)
      return(pmi->nItemID);
   return(0);
}

/*=====================================================================

Public Method - Sorts the menu.  See AEEMenu.h

======================================================================*/
static void  IMenuCtl_Sort(IMenuCtl * po, AEEMenuSortType t, PFNMENUSORTCOMPARE pfn,void * pUser)
{
   CMenuCtl *     pme = (CMenuCtl *)po;
   MSortArgs      ms;
   MSortItem *    psi;
   CMenuItem *    pmi,*pmiNext;
   int            i;
   
   if(!pme->m_nItems)
      return;

   // If owner draw items are allowed, only custom and data based sorting are allowed
   if (OD_ITEMS_ALLOWED(pme) && ((t == MCS_NAME_DESCENDING) || (t == MCS_NAME_ASCENDING)))
      return;

   MEMSET(&ms, 0, sizeof(ms));
   ms.pfn   = pfn;
   ms.pUser = pUser;
   ms.t     = t;

   psi = (MSortItem *)MALLOC(sizeof(MSortItem) * pme->m_nItems);
   if(psi){
      for(pmi = pme->m_pItemList,i = 0; pmi ; pmi = pmi->pNext,i++){
         psi[i].pa = &ms;
         psi[i].pmi = pmi;
      }

   // Save the selected item.  We need to reset the index!

      pme->m_nSelect = 0;

   // Sort - Menu_SortCB does the hard work...

      QSORT(psi, pme->m_nItems, sizeof(MSortItem), Menu_SortCB);

   // List has been sorted, reorder the pointers

      pmi = pme->m_pItemList = psi->pmi;

      for(i = 1; i < pme->m_nItems;i++){
         pmiNext     = psi[i].pmi;
         pmi->pNext  = pmiNext;
         pmi         = pmiNext;
      }
      pme->m_pLastItem = pmi;
      pmi->pNext = NULL;

      FREE(psi);

   // Only redraw if it's allowed...

      if(!NO_REDRAW(pme))
         IMenuCtl_Redraw(po);
   }
}

/*=====================================================================

Public Method - Returns information about the item.  See AEEMenu.h

======================================================================*/
static boolean IMenuCtl_GetItem(IMenuCtl * po, uint16 wID, CtlAddItem *pai)
{
   CMenuCtl *  pme = (CMenuCtl *)po;
   CMenuItem * pmi;

   if(!pai)
      return(FALSE);

   pmi = GetItemByID(pme->m_pItemList, (int)wID, NULL);
   if(!pmi)
      return(FALSE);

   MEMSET(pai,0,sizeof(CtlAddItem));
   pai->wFont        = (uint16)pmi->nFont;
   pai->dwData       = pmi->lData;
   pai->pImage       = pmi->pImage;
   pai->pszResImage  = pmi->pszImage;
   pai->wImage       = pmi->wImageID;
   pai->wItemID      = wID;
   pai->pText        = pmi->text;
   return(TRUE);
}

/*=====================================================================

Public Method - Returns information about the item.  See AEEMenu.h

======================================================================*/
static boolean IMenuCtl_GetItemData(IMenuCtl * po, uint16 nItemID, uint32 * plData)
{
   CMenuCtl *  pme = (CMenuCtl*)po;
   CMenuItem * pItem;

   if (plData) {
      pItem = GetItemByID(pme->m_pItemList, nItemID, NULL);
      if (pItem){
         *plData = pItem->lData;
         return(TRUE);
      }
   }
   return(FALSE);
}

/*=====================================================================

Public Method - Deletes an item from the menu.  See AEEMenu.h

======================================================================*/
static boolean IMenuCtl_DeleteItem(IMenuCtl * po, uint16 nItemID)
{
   CMenuCtl *  pme = (CMenuCtl*)po;
   CMenuItem * pmi, *pmiLast;
   int         nItemIdx;

   if((pmi = GetItemByIDEx(pme->m_pItemList, nItemID, &pmiLast, &nItemIdx)) != NULL){
      if(pmiLast){
         pmiLast->pNext = pmi->pNext;
         if( pmi == pme->m_pLastItem ){
            // Deleting the last item, so engage to prev [maybe NULL in empty list]
            pme->m_pLastItem = pmiLast;
         }
      }else{
         pme->m_pItemList = pmi->pNext;
         if(!pme->m_pItemList)
            pme->m_pLastItem = NULL;
      }
      Menu_FreeItem(pme, pmi);
      pme->m_nItems--;

      // Recalculate select if its more than or equal to the index of deleted item - CR05183
      if (pme->m_nSelect >= nItemIdx) {
         if (pme->m_nSelect)    
            pme->m_nSelect--; 
      }

      // Recalculate Menu Layout
      Menu_Recalc(pme);

      // Redraw Menu
      IMenuCtl_Redraw(po);

      return(TRUE);
   }
   return(FALSE);
}

/*=====================================================================

Public Method - Deletes all items from the menu.  See AEEMenu.h

======================================================================*/
static boolean IMenuCtl_DeleteAll(IMenuCtl * po)
{
   CMenuCtl *  pme = (CMenuCtl*)po;
   CMenuItem * pmi = NULL;

   while ((pmi = pme->m_pItemList) != NULL){
      pme->m_pItemList = pmi->pNext;
      Menu_FreeItem(pme, pmi);
   }

   pme->m_bSKPageFullNotify = FALSE;
   pme->m_pItemList     = NULL;
   pme->m_pLastItem     = NULL;
   pme->m_nItems        = 0;
   pme->m_nSelect       = 0;
   pme->m_nScrollItem   = 0;
   return(TRUE);
}

/*=====================================================================

Public Method - Sets selection in single-selection menu. See AEEMenu.h

======================================================================*/
static void IMenuCtl_SetSel(IMenuCtl * po, uint16 nItemID)
{
   CMenuCtl *  pme = (CMenuCtl*)po;
   int         i,nDir,nCurr,nCurrScroll;
   CMenuItem * pmi;
   uint32      dwProps;

   // Find the item ID in the list.  Scroll the list if necessary to show it...

   pmi = pme->m_pItemList;


   // Find the matching item.  Need to do this rather than doing FindByID
   // so that we have the index value.

   for(i = 0; pmi ; pmi = pmi->pNext, i++){
      if(pmi->nItemID == nItemID)
         break;
   }
   // If it's -1, and did not match an item reset the menu...
   if(nItemID == (uint16)-1 && !pmi){
      pmi = pme->m_pItemList;
      if(!pmi)
         return;
      nItemID = pmi->nItemID;
      i = 0;
   }

   if(!pmi)
      return;


   // If it's already selected, return...

   nCurr       = pme->m_nSelect;
   nCurrScroll = pme->m_nScrollItem;

   nDir = i - pme->m_nSelect;
   if(!nDir)
      return;

   dwProps = pme->m_dwProps;
   pme->m_dwProps |= MP_NO_REDRAW;

   switch(pme->m_cls){

   // If it is a menu, then do the following:
   //
   // - If visible, just show the item;
   // - Above visible viewport - make it top visible item
   // - Below visible viewport - make it last visible item
        
      case AEECLSID_MENUCTL:
         if(!ITEM_IS_VISIBLE(pme, i)){
            if(i < pme->m_nScrollItem)
               nDir = i;
            else{
               nDir = (i + 1) - pme->m_nPageItems;
               if(nDir < 0)
                  nDir = 0;
            }
            pme->m_nScrollItem = (uint16)nDir;
         }
         break;

   // If it's a softkey, cycle the selection until it's found...

      case AEECLSID_SOFTKEYCTL:
         while(pme->m_nSelect != i)
            SK_MoveSelect(pme, nDir);
         break;

   // If it is a icon menu, then do the following:
   //
   // - If visible, just show the item;
        
      case AEECLSID_ICONVIEWCTL:
         if (pme->m_bIconHScroll)
         {
            // - Left of visible viewport - make it leftmost visible item
            // - Right of visible viewport - make it rightmost visible item
            if(!ICON_IS_SINGLEROW_VISIBLE(pme, i)){
               if(i < pme->m_nScrollItem)
                  nDir = i;
               else{
                  nDir = (i + 1) - pme->m_nCols;
                  if(nDir < 0)
                     nDir = 0;
               }
               pme->m_nScrollItem = (uint16)nDir;
            }
         }
         else
         {
            // - Above visible viewport - make item row first visible row
            // - Below visible viewport - make item row last visible row
            if(!ICON_IS_VISIBLE(pme, i)){
               if(i < pme->m_nScrollItem * pme->m_nCols)
                  nDir = i/pme->m_nCols; // Index of item row
               else{
                  nDir = (i/pme->m_nCols) + 1;
                  nDir -= pme->m_nRows;
                  if(nDir < 0)
                     nDir = 0;
               }
               pme->m_nScrollItem = (uint16)nDir;
            }
         }
         break;
   }

   pme->m_nSelect = i;
   pme->m_dwProps = dwProps;

   if(!NO_REDRAW(pme)){
      if(pme->m_cls == AEECLSID_MENUCTL && 
         pme->m_nScrollItem == nCurrScroll && 
         ITEM_IS_VISIBLE(pme,nCurr) && 
         ITEM_IS_VISIBLE(pme,i))
         Menu_DrawNewSelectItem(pme,nCurr,i);
      else
         IMenuCtl_Redraw(po);
   }

   // Notify them that the selection changed.
   if( !IS_MULTI_SEL(pme) ){
      if(nCurr != i)
         Menu_NotifySelChange(pme);
   }
}

static void IMenuCtl_SetSelEx(IMenuCtl * po, uint16 nItemID, boolean bSelected)
{
   CMenuCtl *     pme = (CMenuCtl*)po;
   CMenuItem *    pmi;

   // Make sure it is multi-sel
   if( !IS_MULTI_SEL(pme) )
      return;

   // Find the item
   for( pmi = pme->m_pItemList; pmi; pmi = pmi->pNext ){
      if( pmi->nItemID == nItemID )
         break;
   }
   // If the item id is -1 and noone else has this value...
   // Then set all menu items as asked and reset the menu
   if( !pmi && nItemID == (uint16)-1 ){
      for( pmi = pme->m_pItemList; pmi; pmi = pmi->pNext )
         pmi->bIsSel = bSelected;
      IMENUCTL_SetFocus(po, nItemID);
      Menu_NotifySelChange(pme);
      return;
   }
   // Invalid ID and not -1 special case
   if( !pmi )
      return;
   // Only if they differ update and notify
   if( pmi->bIsSel != bSelected ){
      pmi->bIsSel = bSelected;
      Menu_NotifySelChange(pme);
   }
   // Always Change Focus
   IMENUCTL_SetFocus(po, pmi->nItemID);
}

/*=====================================================================

Public Method - Returns selection on single-selection menu. See AEEMenu.h

======================================================================*/
static uint16 IMenuCtl_GetSel(IMenuCtl * po)
{
   CMenuCtl * pme = (CMenuCtl *)po;
   CMenuItem * pmi;
 
   if((pmi = GetItemByIdx(pme->m_pItemList, pme->m_nSelect)) != NULL)
      return(pmi->nItemID);
   return(0);
}

/*=====================================================================

Public Method - Enables the sending of the EVT_COMMAND event. See AEEMenu.h

======================================================================*/
static void IMenuCtl_EnableCommand(IMenuCtl * po, boolean bEnable)
{
   ((CMenuCtl*)po)->m_bSendCmd = (bEnable != 0);
}

/*=====================================================================

Public Method - Sets the text for a menu item. See AEEMenu.h

======================================================================*/
static void IMenuCtl_SetItemText(IMenuCtl * po, uint16 nItemID, const char * pszResFile, uint16 wResID, AECHAR * pText)
{
   CMenuCtl *  pme = (CMenuCtl*)po;
   CMenuItem * pmi, * pmiLast;
   boolean     bFreeText = FALSE;

   // must supply one of these

   pmi = GetItemByID(pme->m_pItemList, nItemID, &pmiLast);

   if (!pmi || (!pText && !pszResFile) || IS_ITEM_OD(pmi))
      return;

  // allocate and initialize a new menu item

   if(!pText && pszResFile){
      // Need to load text from resource file

      pText = (AECHAR *)MALLOC(MAX_MENU_TEXT * sizeof(AECHAR));

      if(!pText)
         return;

      bFreeText = TRUE;

      ISHELL_LoadResString(pme->m_pIShell, pszResFile, wResID, pText, MAX_MENU_TEXT * sizeof(AECHAR));
   }

   // Allocate the new item.  Note that the structure ALREADY contains the 
   // room for the 0 terminator on the string.  Thus, the size we need to allocate
   // is actually less the 0 terminator...

   Menu_AllocItem(pme, pmi, pText, NULL, pmiLast);

   Menu_Recalc(pme);

   if(bFreeText)
      FREE(pText);
}

/*=====================================================================

Public Method - Sets the time for a calendar menu item. See AEEMenu.h

======================================================================*/
static void IMenuCtl_SetItemTime(IMenuCtl * po, uint16 nItemID, uint16 wMinStart, uint16 wDuration)
{
   CMenuCtl *  pme = (CMenuCtl*)po;
   CMenuItem * pmi;

   // must supply one of these

   if(pme && !OD_ITEMS_ALLOWED(pme)){
      pme->m_dwProps |= MP_CALENDAR;
      pmi = GetItemByID(pme->m_pItemList, nItemID, NULL);
      if(pmi){
         pmi->wCalStart = wMinStart;
         pmi->wCalEnd = wMinStart + wDuration;
      }
   }
}

/*=====================================================================

Public Method - Returns the time for a calendar menu item. See AEEMenu.h

======================================================================*/
static int IMenuCtl_GetItemTime(IMenuCtl * po, uint16 nItemID, uint16 * pwDuration)
{
   CMenuCtl *  pme = (CMenuCtl*)po;
   CMenuItem * pmi;

   if (OD_ITEMS_ALLOWED(pme))
      return(-1);
    
   // must supply one of these

   pmi = GetItemByID(pme->m_pItemList, nItemID, NULL);

   if (pmi){
      if(pwDuration)
         *pwDuration = pmi->wCalEnd - pmi->wCalStart;
      return((int)pmi->wCalStart);
   }
   return(-1);
}

/*=====================================================================

Public Method - Sets the menu item style. See AEEMenu.h

======================================================================*/
static void IMenuCtl_SetStyle(IMenuCtl * po, AEEItemStyle * pNormal, AEEItemStyle * pSel)
{
   CMenuCtl * pme = (CMenuCtl *)po;

   if(pNormal)
      MEMCPY(&pme->m_style[0], pNormal, sizeof(AEEItemStyle));

   if(pSel)
      MEMCPY(&pme->m_style[1], pSel, sizeof(AEEItemStyle));

   Menu_Recalc(pme);
}

/*=====================================================================

Public Method - Gets the menu item style. See AEEMenu.h

======================================================================*/
static void IMenuCtl_GetStyle(IMenuCtl * po, AEEItemStyle * pNormal, AEEItemStyle * pSel)
{
   CMenuCtl * pme = (CMenuCtl *)po;

   if(pNormal)
      MEMCPY(pNormal, &pme->m_style[0], sizeof(AEEItemStyle));

   if(pSel)
      MEMCPY(pSel, &pme->m_style[1], sizeof(AEEItemStyle));
}

/*=====================================================================

Public Method - Sets the menu colors. See AEEMenu.h

======================================================================*/
static void IMenuCtl_SetColors(IMenuCtl * po, AEEMenuColors * pc)
{
   CMenuCtl * pme = (CMenuCtl *)po;

   if(pc){
      if(pc->wMask & MC_BACK)
         pme->m_c.cBack = pc->cBack;
      if(pc->wMask & MC_TEXT)
         pme->m_c.cText = pc->cText;
      if(pc->wMask & MC_SEL_BACK)
         pme->m_c.cSelBack = pc->cSelBack;
      if(pc->wMask & MC_SEL_TEXT)
         pme->m_c.cSelText = pc->cSelText;
      if(pc->wMask & MC_FRAME)
         pme->m_c.cFrame = pc->cFrame;
      if(pc->wMask & MC_SCROLLBAR)
         pme->m_c.cScrollbar = pc->cScrollbar;
      if(pc->wMask & MC_SCROLLBAR_FILL)
         pme->m_c.cScrollbarFill = pc->cScrollbarFill;
      if(pc->wMask & MC_TITLE)
         pme->m_c.cTitle = pc->cTitle;
      if(pc->wMask & MC_TITLE_TEXT)
         pme->m_c.cTitleText = pc->cTitleText;
   }
   else{
      pme->m_c.cBack          = CLR_SYS_ITEM;
      pme->m_c.cText          = CLR_SYS_ITEM_TEXT;
      pme->m_c.cSelBack       = CLR_SYS_ITEM_SEL;
      pme->m_c.cSelText       = CLR_SYS_ITEM_SEL_TEXT;
      pme->m_c.cFrame         = CLR_SYS_FRAME_LO;
      pme->m_c.cScrollbar     = CLR_SYS_SCROLLBAR;
      pme->m_c.cScrollbarFill = CLR_SYS_SCROLLBAR_FILL;
      pme->m_c.cTitle         = CLR_SYS_TITLE;
      pme->m_c.cTitleText     = CLR_SYS_TITLE_TEXT;
   }
}

/*=====================================================================

Public Method - Gets the menu colors. See AEEMenu.h

======================================================================*/
static void IMenuCtl_GetColors(IMenuCtl * po, AEEMenuColors * pc)
{
   CMenuCtl * pme = (CMenuCtl *)po;

   if(pc)
      MEMCPY(pc, &pme->m_c, sizeof(AEEMenuColors));
}

/*=====================================================================

Public Method - Gets the menu item rect. See AEEMenu.h

======================================================================*/
static void IMenuCtl_GetItemRect(IMenuCtl * po, uint16 nIdx, AEERect * prc)
{
   CMenuCtl * pme = (CMenuCtl *)po;

   // Check NULL prc
   if (!prc)
      return;

   // Clear prc contents
   MEMSET(prc, 0, sizeof(AEERect));

   // Return if index isn't valid
   if (nIdx > (pme->m_nItems - 1))
      return;

   // Calculate Item Rect
   switch(pme->m_cls){

      case AEECLSID_SOFTKEYCTL:
         SK_CalcItemRect(pme, nIdx, prc);
         break;

      case AEECLSID_LISTCTL:
         if (IMenuCtl_GetSel(po) == nIdx)
            List_CalcItemRect(pme, prc);
         break;

      case AEECLSID_MENUCTL:
         if (ITEM_IS_VISIBLE(pme, nIdx))
            Menu_CalcItemRect(pme, nIdx, prc);
         break;

      case AEECLSID_ICONVIEWCTL:
         Icon_CalcItemRect(pme, nIdx, prc);
         break;
   }

}

/*=====================================================================

Public Method - Initializes a multi selection menu for selection enumeration

======================================================================*/
static boolean IMenuCtl_EnumSelInit(IMenuCtl * po)
{
   CMenuCtl * pme = (CMenuCtl *)po;

   if( IS_MULTI_SEL(pme) ){
      pme->m_pEnum = pme->m_pItemList;
      if( !pme->m_pEnum )
         return FALSE;

      return TRUE;
   }
   return FALSE;
}

/*=====================================================================

Public Method - Returns next selection in enumeration of a multi selection 
menu

======================================================================*/
static uint16 IMenuCtl_EnumNextSel(IMenuCtl * po) 
{
   CMenuCtl *  pme = (CMenuCtl *)po;

   if( IS_MULTI_SEL(pme) ){
      CMenuItem * pItem;

      for( pItem = pme->m_pEnum; pItem; pItem = pItem->pNext ){
         if( pItem->bIsSel ){
            uint16 nRet    = pItem->nItemID;
            pme->m_pEnum   = pItem->pNext;
            return nRet;
         }
      }
      pme->m_pEnum = NULL;
   }
   return (uint16)-1;
}

/*=====================================================================

Public Method - Sets callbacks to be used for drawing/measuring owner draw
item. See AEEMenu.h

======================================================================*/
static int IMenuCtl_SetOwnerDrawCB(IMenuCtl * po, PFNOWNERDRAWCB pfnCB, void * pvContext, uint16 nODFlags)
{
   CMenuCtl *  pme = (CMenuCtl *)po;
   AEEODCBData cbData;

   // If callback and flag are NULL, reset menu and nullify owner drawn stuff
   if (!pfnCB && !nODFlags)
   {
      IMenuCtl_Reset(po);
      pme->m_pfnCB = NULL;
      pme->m_pvContext = NULL;
      pme->m_nODFlags = 0;
      return AEE_SUCCESS;
   }

   // Make sure that callback and flags are valid
   if (!pfnCB || !nODFlags)
      return EFAILED;

   pme->m_pfnCB = pfnCB;
   pme->m_pvContext = pvContext;
   pme->m_nODFlags = nODFlags;

   // Check if title is owner drawn. If so, ensure that there is no title text and measure
   // title rect
   if(IS_TITLE_OD(pme))
   {
      // Free Old Title
      aee_freeobj((void **)&pme->m_pTitle);

      // Initialize Callback Data
      MEMSET(&cbData, 0, sizeof(AEEODCBData));

      cbData.nSize = sizeof(AEEODCBData);
      cbData.nAction = ODA_MEASURE_TITLE;

      // Measure Title Rect
      BRIDLE_ASSERT_SAFE_PFN(pme->m_pfnCB);
      pme->m_pfnCB(pme->m_pvContext, &cbData);

      // Cache Title Rect
      pme->m_rcTitle = cbData.rc;
   }

   // Check if scroll bar is owner drawn. If so, measure scroll bar width
   if(IS_SB_OD(pme))
   {
      MEMSET(&cbData, 0, sizeof(AEEODCBData));

      cbData.nSize = sizeof(AEEODCBData);
      cbData.nAction = ODA_MEASURE_SB;

      // Measure ScrollBar Width
      BRIDLE_ASSERT_SAFE_PFN(pme->m_pfnCB);
      pme->m_pfnCB(pme->m_pvContext, &cbData);

      // Cache ScrollBar Width
      pme->m_nSBWidth = cbData.rc.dx;
   }

   return AEE_SUCCESS;
}

/*=====================================================================



   Local/Private Menu Methods



======================================================================*/
/*=====================================================================

Local Method - Returns first owner draw item from a menu

======================================================================*/
CMenuItem* MenuCtl_GetFirstODItem(CMenuCtl* pme)
{
   CMenuItem * pmi = pme->m_pItemList;

   while(pmi)
   {
      if (IS_ITEM_OD(pmi))
         return pmi;
      else
         pmi = pmi->pNext;
   }
   
   return NULL;
}

/*=====================================================================

Local Method - Adds an owner drawn menu item to the menu

======================================================================*/
static boolean MenuCtl_AddODItem(CMenuCtl * pme, uint16 nItemID, uint32 lData)
{
   CMenuItem *    pmiLast = NULL;
   CMenuItem *    pmi = NULL;
   CMenuItem *    pmiOD = NULL;
   AEEODCBData    cbData;

   // Check if we already added this item
   for(pmiLast = NULL, pmi = pme->m_pItemList; pmi != NULL; pmi = pmi->pNext) {
      pmiLast = pmi;
      if(pmi->nItemID == nItemID)
         return(FALSE);
   }

   // Allocate memory for the item. If allocation is successful, item is added to the
   // end of the list
   if ((pmi = Menu_AllocItem(pme, NULL, NULL, NULL, pmiLast)) == NULL)
      return(FALSE);

   // copy other stuff and set OD flag
   pmi->lData     = lData;
   pmi->nItemID   = nItemID;
   pmi->bOD       = TRUE;

   if (IS_MEASURE_ONCE(pme) && ((pmiOD = MenuCtl_GetFirstODItem(pme)) != NULL))
   {
      pmi->cxImage = pmiOD->cxImage;
      pmi->cyImage = pmiOD->cyImage;
      
      if (IS_ICON_VIEW(pme))
         pmi->cyText = pmiOD->cyText;
   }
   else
   {
      // Measure Height and Width of this item
      MEMSET(&cbData, 0, sizeof(AEEODCBData));

      cbData.nSize = sizeof(AEEODCBData);
      cbData.nAction = ODA_MEASURE_ITEM;
      cbData.nActionInfo = nItemID;
      cbData.nActionInfo <<= 16;
      BRIDLE_ASSERT_SAFE_PFN(pme->m_pfnCB);
      pme->m_pfnCB(pme->m_pvContext, &cbData);

      // We cache height and width as image height and width
      pmi->cxImage = cbData.rc.dx;
      pmi->cyImage = cbData.rc.dy;

      // Check if they plan to draw text also
      if (IS_ICON_VIEW(pme))
      {  
         // Measure Text Height of this item
         MEMSET(&cbData, 0, sizeof(AEEODCBData));

         cbData.nSize = sizeof(AEEODCBData);
         cbData.nAction = ODA_MEASURE_ITEM_TEXT;
         cbData.nActionInfo = nItemID;
         cbData.nActionInfo <<= 16;
         BRIDLE_ASSERT_SAFE_PFN(pme->m_pfnCB);
         pme->m_pfnCB(pme->m_pvContext, &cbData);

         // Cache text height
         pmi->cyText = cbData.rc.dy;
      }
   }

   // Recalc the entire layout of the menu based upon the size of the items and the style
   Menu_Recalc(pme);

   // For softkey, send EVT_CTL_SKMENU_PAGE_FULL if page full upon addition of this item
   if(IS_SOFTKEY_MENU(pme) && !pme->m_bSKPageFullNotify && pme->m_nItems >= pme->m_nPageItems){
      pme->m_bSKPageFullNotify = TRUE;
      ISHELL_HandleEvent(pme->m_pIShell, EVT_CTL_SKMENU_PAGE_FULL, 0, (uint32)pme);
   }
   
   return(TRUE);
}

/*=====================================================================

Local Method - Loads an image for a menu item from either a resource 
file or cached version in another item.  If cached, it saves the file
name and increments the reference count on the IImage.

======================================================================*/
static IImage * Menu_LoadImage(CMenuCtl * pme, const char * pszRes, uint16 wID, boolean * pbCached)
{
   CMenuItem * pi;

   // Need valid resource file
   if(!pszRes || !*pszRes)
      return(NULL);

   // Search all items for the same image.  If found, add to it's reference count and return it   
   for(pi = pme->m_pItemList; pi ; pi = pi->pNext){
      if(pi->pImage && pi->pszImage && pi->wImageID == wID && !STRCMP(pi->pszImage, pszRes)){
         *pbCached = TRUE;
         IIMAGE_AddRef(pi->pImage);
         return(pi->pImage);
      }
   }

   *pbCached = FALSE;

   return(ISHELL_LoadResImage(pme->m_pIShell, pszRes, wID));
}

/*=====================================================================

Local Method - Function resumed by the dispatcher to send commands
from a menu based upon the SELECT key being pressed.

======================================================================*/
static void Menu_SendCommand(CMenuCtl * pme)
{
   CMenuItem   *  pItem;

   if((pItem = GetItemByIdx(pme->m_pItemList, pme->m_nSelect)) != NULL)
      ISHELL_HandleEvent(pme->m_pIShell, EVT_COMMAND, (uint16)pItem->nItemID, (uint32)pItem->lData);
}

/*=====================================================================

Local Method - Compares two menu items.

======================================================================*/
static int Menu_SortCB(const void * p1, const void * p2)
{
   MSortItem *    psi1 = (MSortItem *)p1;
   MSortItem *    psi2 = (MSortItem *)p2;
   CMenuItem *    pi1,*pi2;

   pi1 = psi1->pmi;
   pi2 = psi2->pmi;

   switch(psi1->pa->t){
      case MCS_NAME_ASCENDING:
         return(WSTRICMP(pi1->text,pi2->text));
      case MCS_NAME_DESCENDING:
         return(WSTRICMP(pi2->text,pi1->text));
      case MCS_PARAM_ASCENDING:
         return((int)pi1->lData - (int)pi2->lData);
      case MCS_PARAM_DESCENDING:
         return((int)pi2->lData - (int)pi1->lData);
      default:
         if(psi1->pa->pfn) {
            BRIDLE_ASSERT_SAFE_PFN(psi1->pa->pfn);
            return(psi1->pa->pfn(psi1->pa->pUser, pi1->lData, pi2->lData));
         }
   }
   return(0);
}

/*=====================================================================

Local Method - Determines whether the image specified is in a resource
or associated with a previously cached version.

======================================================================*/
static boolean Menu_ResolveImage(CMenuCtl * pme, CtlAddItem * pai,const char ** ppszResImage, IImage ** ppImage)
{
   IImage *       pImage = NULL;
   const char *   pszResImage = NULL;
   boolean        bCached = FALSE;
   boolean        bSingleFrame = (boolean)(SINGLE_FRAME(pme) ? TRUE : FALSE);

   pImage = pai->pImage;

   if(!pImage){
      if(pai->pszResImage || (pai->wImage && pai->wImage != 0xffff)){

         // Use image resource file if available. Otherwise use text resource file.
         pszResImage = (const char *)(pai->pszResImage ? pai->pszResImage : pai->pszResText);

         if(!bSingleFrame){
            // Load Image
            if ((pImage = Menu_LoadImage(pme,pszResImage,pai->wImage,&bCached)) == NULL)
               return(FALSE);
         }
         else{
            // Do not load the image if we are in single frame mode. Just ensure that this is a valid resource.             
            if(!ISHELL_IsValidResource(pme->m_pIShell, pszResImage, pai->wImage,RESTYPE_IMAGE,HTYPE_VIEWER))
               return(FALSE);
         }
      }
      else{
         if(IS_ICON_VIEW(pme))
            return(FALSE);                // Need image for icon view
      }
   }
   else
      IIMAGE_AddRef(pImage); // AddRef Image

   *ppszResImage = pszResImage;
   *ppImage = pImage;
   return(TRUE);
}

/*=====================================================================

Local Method - Determines the size of an image and deals with animation.

======================================================================*/
static void Menu_SetupImage(CMenuCtl * pme, CMenuItem * pmi, IImage * pImage)
{
   AEEImageInfo   ii;

   IIMAGE_GetInfo(pImage, &ii);

   pmi->cxImage = ii.cxFrame;       // cache image width
   pmi->cyImage = ii.cy;            // cache image height

   if(IS_ICON_VIEW(pme) && IS_ANIMATED(pme)){      
      if(!ii.bAnimated){
         // This might change width, if animated
         IIMAGE_SetFrameCount(pImage, -1);
         IIMAGE_GetInfo(pImage, &ii);
         pmi->cxImage = ii.cxFrame;
      }
   }
   pmi->pImage = pImage;            // cache image pointer
}
/*=====================================================================

Local Method - Handles the Char message for typing first char to select.

======================================================================*/
static void Menu_HandleChar(CMenuCtl * pme, AECHAR ch)
{
   CMenuItem * pWalker, *pStart;
   AECHAR      szwTyped[2];
   AECHAR      szwBuf[2];

   MEMSET(szwTyped, 0, sizeof(szwTyped));
   MEMSET(szwBuf, 0, sizeof(szwBuf));
   *szwTyped = ch;
   WSTRUPPER(szwTyped);
   if((pStart = pWalker = GetItemByIdx(pme->m_pItemList, pme->m_nSelect)) != NULL){
      for( pWalker = (pWalker->pNext != NULL) ? pWalker->pNext : pme->m_pItemList; 
            pWalker != pStart; 
            pWalker = (pWalker->pNext != NULL) ? pWalker->pNext : pme->m_pItemList){
         if(pWalker->cxText){
            *szwBuf = pWalker->text[0];
            WSTRUPPER(szwBuf);
            if(*szwBuf == *szwTyped){
               Menu_ResetAutoScroll(pme);
               IMENUCTL_SetSel((IMenuCtl *)pme, pWalker->nItemID);
               Menu_AutoScroll(pme);
               return;
            }
         }
      }
   }
}

/*=====================================================================

Local Method - Handles all the pen events, moved here for handleevent clarity

======================================================================*/
static boolean Menu_HandlePens(CMenuCtl * pme, AEEEvent evtCode, uint16 wParam, int16 wXPos, int16 wYPos)
{
   switch( evtCode ){
      case EVT_PEN_MOVE:
      {
         if( pme->m_ptTracker.cbHit & PTRCK_HIT_SCRL ){
            // Need to do things in relation to where the pen is and whether on thumb or off
            if( pme->m_ptTracker.cbHit & PTRCK_HIT_THUMB ){
               // This logic is married to Menu_GetScrollBarRects()
               // It is inverted as in Solve for pme->m_nIdx
               if( wXPos >= ((pme->m_rc.x + pme->m_rc.dx) - pme->m_nSBWidth)
                  && wXPos < (pme->m_rc.x + pme->m_rc.dx) ){
                  int         cx;
                  int         nScrollItem, x, y, cy, nItems, nPageItems;
                  uint16      nRange, nLen;
                  int         nPos;

                  if(IS_ICON_VIEW(pme)){

                     nItems = pme->m_nTotalRows;
                     nPageItems = pme->m_nRows;
                  }
                  else{
                     nItems = pme->m_nItems;
                     nPageItems = pme->m_nPageItems;
                  }
                  nScrollItem = pme->m_nScrollItem;

                  if (nItems > nPageItems) {
                     // Adjust for frame size
                     cx = IS_SB_OD(pme) ? pme->m_nSBWidth : (pme->m_nSBWidth - 2*AEE_FRAME_SIZE);
                     x = (pme->m_rc.x + pme->m_rc.dx - cx);
                     y = pme->m_rc.y;

                     if(IS_MENU(pme))
                        cy = nPageItems * pme->m_cyMaxItem;
                     else
                        cy = pme->m_rc.dy;

                     // In an icon view, the scrollbar sits beside the entire icon window.  That
                     // means everything EXCEPT the text area...
                     if(IS_ICON_VIEW(pme)){
                        if(pme->m_bHasText){
                           uint16 cyText = Icon_GetMaxTextHeight(pme);
                           cy -= cyText;
                           if(ALIGN_TEXT_TOP(pme))
                              y += cyText;
                        }
                     }
                     else{       
                        if(pme->m_pTitle || IS_TITLE_OD(pme)){
                           if(!IS_MENU(pme))
                              cy -= pme->m_rcTitle.dy;
                           y += pme->m_rcTitle.dy;
                        } 
                     }    

                     if (IS_SB_OD(pme))
                        nRange = cy;
                     else
                     {
                        nRange = cy - 2*AEE_FRAME_SIZE;
                        if (WRAP_SCROLL(pme))
                           nRange -= 2*CXARROW;
                     }
 
                     nRange = cy * 10;    // multiply by 10 for rounding

                     nLen = (nRange * nPageItems)/nItems;      // Scroll Bar Length    
                     nPos = (nRange * nScrollItem)/nItems;     // Scroll Bar Position
                     nPos = (((((wYPos+5)-y)-pme->m_ptTracker.wData)*10)*nItems)/nRange; // Rounding
                     if(nPos < 0 ){
                        nPos = 0;
                     }else if(nPos > (nItems - nPageItems) ){
                        nPos = nItems - nPageItems;
                     }
                     pme->m_nScrollItem = (uint16)nPos;
                     IMenuCtl_Redraw((IMenuCtl *)pme);
                     if(IS_ICON_VIEW(pme)){
                        // Icons need to redraw text, as focus is not moved!
                        CMenuItem * pmi = GetItemByIdx(pme->m_pItemList, pme->m_nSelect);
                        if(pmi){
                           if (IS_ITEM_OD(pmi))
                              Icon_DrawODItemText(pme, pmi, 0, TRUE);
                           else
                              Icon_DrawItemText(pme, pmi, 0, TRUE);
                        }
                     }
                  }
               }
            }else{
               // Need to check the timer, since this only cares
               // IF the pen is moved when the thumb is stalled
               // AND the move was in the direction of the flow
               if( !(pme->m_ptTracker.cbFlags & PTRCK_GEN_TMRSET) ){
                  Menu_ScrollByPos(pme, ((pme->m_ptTracker.cbHit & PTRCK_HIT_BELOW) ? 1 : -1), wXPos, wYPos);
               }
            }
            return TRUE;
         }
      }
      return FALSE;
      case EVT_PEN_DOWN:
      {
         if( PT_IN_RECT(wXPos, wYPos, pme->m_rc) ){
            MEMSET((void *)&pme->m_ptTracker, 0, sizeof(pme->m_ptTracker));
            switch( pme->m_cls ){
            case AEECLSID_SOFTKEYCTL:
               {
                  uint16 wIdx;
                  pme->m_ptTracker.cbHit = SK_HandlePen(pme, wXPos, wYPos, &pme->m_ptTracker.wData, &wIdx);
                  if( pme->m_ptTracker.cbHit == PTRCK_HIT_ITEM ){
                     // If an item is hit, change focus. Do not select it at this time.
                     IMENUCTL_SetFocus((IMenuCtl *)pme, pme->m_ptTracker.wData);
                  }
               }
               break;
            case AEECLSID_MENUCTL:
               {
                  AEERect  rct;
                  int      nIdx  = pme->m_nScrollItem;
                  int      nMax  = pme->m_nPageItems + nIdx;

                  if(nMax > pme->m_nItems)
                     nMax = pme->m_nItems;

                  if( !IS_CALENDAR_VIEW(pme) ){
                     // Get a Displayed Item's rect
                     Menu_CalcItemRect(pme, nIdx, &rct);
                     if( wXPos < (rct.x + rct.dx) ){
                        // In an Item
                        for(; nIdx < nMax; nIdx++){
                           if( PT_IN_RECT(wXPos, wYPos, rct) ){
                              CMenuItem * pItem;
                              pme->m_ptTracker.cbHit = PTRCK_HIT_ITEM;
                              if( (pItem = GetItemByIdx(pme->m_pItemList, nIdx)) != NULL ){
                                 pme->m_ptTracker.wData = (uint16)nIdx;
                                 IMENUCTL_SetFocus((IMenuCtl *)pme, pItem->nItemID);
                              }
                              break;
                           }
                           rct.y += rct.dy;
                        }
                     }else{
                        // In the scrollbar
                        AEERect rctFrame, rctThumb;
                        if( !Menu_GetScrollBarRects(pme, &rctFrame, &rctThumb, NULL, NULL) ){
                           // Check the overall frame first to see if it is within the vertical scrollbar element
                           if( PT_IN_RECT(wXPos, wYPos, rctFrame) ){
                              // Check the thumb first!
                              // Don't want to restrict the thumb from the scroll edges, 
                              // so entire width is valid given it is in vertical thumb range
                              if( wYPos >= rctThumb.y && wYPos <= (rctThumb.y + rctThumb.dy) ){
                                 pme->m_ptTracker.cbHit = PTRCK_HIT_THUMB;
                              }else if( wYPos < rctThumb.y ){
                                 pme->m_ptTracker.cbHit = PTRCK_HIT_ABOVE;
                              }else{
                                 pme->m_ptTracker.cbHit = PTRCK_HIT_BELOW;
                              }
                              pme->m_ptTracker.cbHit |= PTRCK_HIT_SCRL;
                              pme->m_ptTracker.wData = wYPos - rctThumb.y;
                              pme->m_ptTracker.ptPosition.x = wXPos;
                              pme->m_ptTracker.ptPosition.y = wYPos;
                              if( pme->m_ptTracker.cbHit & PTRCK_NOTTHUMB ){
                                 Menu_ScrollTimerCB(pme);
                                 if( !ISHELL_SetTimer(pme->m_pIShell, pme->m_arPenRepeat.dwStart, (PFNNOTIFY)Menu_ScrollTimerCB, (void *)pme) ){
                                    pme->m_ptTracker.cbFlags |= PTRCK_GEN_TMRSET;
                                 }
                              }
                           }
                        }
                     }
                  }else{
                     int         i;
                     AEERect     rct;
                     CMenuItem * p = GetItemByIdx(pme->m_pItemList, pme->m_nScrollItem);
                     uint16      cyLeft,
                                 cyFont = pme->m_cyFont;
                     
                     Cal_CalcPageItems(pme);
                     rct = pme->m_rc;
                     cyLeft = rct.dy;

                     if(pme->m_pTitle || IS_TITLE_OD(pme)){
                        cyLeft -= pme->m_rcTitle.dy;
                        rct.y += pme->m_rcTitle.dy;
                        rct.dy -= pme->m_rcTitle.dy; 
                     }

                     for (i = 0; i < pme->m_nPageItems && p; p = p->pNext, i++){
                        rct.dy = GetItemHeight(p, &cyLeft, cyFont);
                        if( PT_IN_RECT(wXPos, wYPos, rct) ){
                           pme->m_ptTracker.cbHit = PTRCK_HIT_ITEM;
                           pme->m_ptTracker.wData = (uint16) i + pme->m_nScrollItem;
                           IMENUCTL_SetFocus((IMenuCtl *)pme, p->nItemID);
                           break;
                        }
                        rct.y += rct.dy;
                     }
                  }
               }
               break;
            case AEECLSID_ICONVIEWCTL:
               {
                  AEERect rctFrame, rctThumb;
                  if( !Menu_GetScrollBarRects(pme, &rctFrame, &rctThumb, NULL, NULL) ){

                     if( PT_IN_RECT(wXPos, wYPos, rctFrame) ){
                        // Scrollbar!

                        // Check the thumb first!
                        // Don't want to restrict the thumb from the scroll edges, 
                        // so entire width is valid given it is in vertical thumb range
                        if( wYPos >= rctThumb.y && wYPos <= (rctThumb.y + rctThumb.dy) ){
                           pme->m_ptTracker.cbHit = PTRCK_HIT_THUMB;
                        }else if( wYPos < rctThumb.y ){
                           pme->m_ptTracker.cbHit = PTRCK_HIT_ABOVE;
                        }else{
                           pme->m_ptTracker.cbHit = PTRCK_HIT_BELOW;
                        }
                        pme->m_ptTracker.cbHit |= PTRCK_HIT_SCRL;
                        pme->m_ptTracker.wData = wYPos - rctThumb.y;
                        pme->m_ptTracker.ptPosition.x = wXPos;
                        pme->m_ptTracker.ptPosition.y = wYPos;
                        if( pme->m_ptTracker.cbHit & PTRCK_NOTTHUMB ){
                           Menu_ScrollTimerCB(pme);
                           if( !ISHELL_SetTimer(pme->m_pIShell, pme->m_arPenRepeat.dwStart, (PFNNOTIFY)Menu_ScrollTimerCB, (void *)pme) ){
                              pme->m_ptTracker.cbFlags |= PTRCK_GEN_TMRSET;
                           }
                        }
                     }else{
                        int nItem, nID;
                        if( Icon_MapPointToItem(pme, wXPos, wYPos, &nItem, &nID) ){
                           pme->m_ptTracker.cbHit = PTRCK_HIT_ITEM;
                           pme->m_ptTracker.wData = (uint16)nID;
                           IMENUCTL_SetFocus((IMenuCtl *)pme, (uint16)nItem);
                        }
                     }
                  }else{
                     if( pme->m_bIconHScroll ){
                        AEERect  rctText;
                        uint16   cyText = Icon_GetMaxTextHeight(pme);

                        SETAEERECT(&rctText, pme->m_rc.x, pme->m_rc.y, pme->m_rc.dx, cyText);
                        if(!ALIGN_TEXT_TOP(pme))
			                  rctText.y += (pme->m_rc.dy - cyText);
                        rctText.dy++;

                        if( PT_IN_RECT(wXPos, wYPos, rctText) ){
                           if( !NO_ARROWS(pme) ){
                              if( wXPos <= (rctText.x + (CXARROW + 1)) ){
                                 pme->m_ptTracker.cbHit = PTRCK_HIT_ABOVE;
                              }else if( wXPos >= ((rctText.x + rctText.dx) - (CXARROW + 1)) ){
                                 pme->m_ptTracker.cbHit = PTRCK_HIT_BELOW;
                              }
                           }
                        }else{
                           pme->m_ptTracker.cbHit = PTRCK_HIT_ITEM;
                           pme->m_ptTracker.wData = pme->m_nSelect;
                        }
                     }
                  }
               }
               break;
            case AEECLSID_LISTCTL:
               {
                  pme->m_ptTracker.cbHit = PTRCK_HIT_ITEM;
               }
               break;
            }
            return TRUE;
         }
      }
      return FALSE;

      case EVT_PEN_UP:
      {
         boolean bRet = FALSE;
         if( PT_IN_RECT(wXPos, wYPos, pme->m_rc) ){
            switch( pme->m_cls ){
            case AEECLSID_SOFTKEYCTL:
               {
                  uint16   wData, wIdx;
                  uint8    cbHit;
                  if( ((cbHit = SK_HandlePen(pme, wXPos, wYPos, &wData, &wIdx)) == pme->m_ptTracker.cbHit) ){
                     switch( cbHit ){
                     case PTRCK_HIT_ITEM:
                        if( wData == pme->m_ptTracker.wData ){
                           (void)Menu_HandleSelection(pme, wIdx);
                           bRet = TRUE;
                        }
                        break;
                     case PTRCK_HIT_BELOW:
                        SK_MoveSelect(pme, 1);
                        bRet = TRUE;
                        break;
                     case PTRCK_HIT_ABOVE:
                        SK_MoveSelect(pme, -1);
                        bRet = TRUE;
                        break;
                     }
                  }
               }
               break;
            case AEECLSID_MENUCTL:
               {
                  if( pme->m_ptTracker.cbHit == PTRCK_HIT_ITEM ){
                     if( !IS_CALENDAR_VIEW(pme) ){
                        AEERect  rct;
                        int      nIdx  = pme->m_nScrollItem;
                        int      nMax  = pme->m_nPageItems + nIdx;

                        if(nMax > pme->m_nItems)
                           nMax = pme->m_nItems;

                        // Get a Displayed Item's rect
                        Menu_CalcItemRect(pme, nIdx, &rct);
                        if( wXPos < (rct.x + rct.dx) ){
                           // Still, in an Item
                           for(; nIdx < nMax; nIdx++){
                              if( PT_IN_RECT(wXPos, wYPos, rct) ){
                                 if( pme->m_ptTracker.wData == (uint16)nIdx ){
                                    (void)Menu_HandleSelection(pme, nIdx);
                                    bRet = TRUE;
                                 }
                                 break;
                              }
                              rct.y += rct.dy;
                           }
                        }
                     }else{
                        int         i;
                        AEERect     rct;
                        CMenuItem * p = GetItemByIdx(pme->m_pItemList, pme->m_nScrollItem);
                        uint16      cyLeft,
                                    cyFont = pme->m_cyFont;
                     
                        Cal_CalcPageItems(pme);
                        rct = pme->m_rc;
                        cyLeft = rct.dy;

                        if(pme->m_pTitle || IS_TITLE_OD(pme)){
                           cyLeft -= pme->m_rcTitle.dy;
                           rct.y += pme->m_rcTitle.dy;
                           rct.dy -= pme->m_rcTitle.dy; 
                        }

                        for (i = 0; i < pme->m_nPageItems && p; p = p->pNext, i++){
                           rct.dy = GetItemHeight(p, &cyLeft, cyFont);
                           if( PT_IN_RECT(wXPos, wYPos, rct) ){
                              if( pme->m_ptTracker.wData == (uint16)(i + pme->m_nScrollItem) ){
                                 (void)Menu_HandleSelection(pme, pme->m_ptTracker.wData);
                                 bRet = TRUE;
                              }
                              break;
                           }
                           rct.y += rct.dy;
                        }
                     }
                  }else{
                     // Scrollbar needs no extra action, Timer is killed below
                  }
               }
               break;
            case AEECLSID_ICONVIEWCTL:
               {
                  if( pme->m_ptTracker.cbHit == PTRCK_HIT_ITEM ){
                     int nItem, nID;

                     if( pme->m_bIconVScroll && Icon_MapPointToItem(pme, wXPos, wYPos, &nItem, &nID) ){
                        if( pme->m_ptTracker.wData == (uint16)nID ){
                           (void)Menu_HandleSelection(pme, nID);
                           bRet = TRUE;
                        }
                     }else if( pme->m_bIconHScroll ){
                        AEERect  rctText;
                        uint16   cyText = Icon_GetMaxTextHeight(pme);

                        SETAEERECT(&rctText, pme->m_rc.x, pme->m_rc.y, pme->m_rc.dx, cyText);
                        if(!ALIGN_TEXT_TOP(pme))
			                  rctText.y += (pme->m_rc.dy - cyText);
                        rctText.dy++;

                        if( !PT_IN_RECT(wXPos, wYPos, rctText) && pme->m_ptTracker.wData == pme->m_nSelect ){
                           (void)Menu_HandleSelection(pme, pme->m_nSelect);
                           bRet= TRUE;
                        }
                     }
                  }else{
                     if( pme->m_bIconHScroll && !NO_ARROWS(pme) ){
                        AEERect  rctText;
                        uint16   cyText = Icon_GetMaxTextHeight(pme);

                        SETAEERECT(&rctText, pme->m_rc.x, pme->m_rc.y, pme->m_rc.dx, cyText);
                        if(!ALIGN_TEXT_TOP(pme))
			                  rctText.y += (pme->m_rc.dy - cyText);
                        rctText.dy++;


                        if( PT_IN_RECT(wXPos, wYPos, rctText) ){
                           // Handle as if a appropriate key was pressed for code savings...
                           if( wXPos <= (rctText.x + (CXARROW + 1)) ){
                              (void)Icon_HandleKey(pme, AVK_LEFT);
                              bRet = TRUE;
                           }else if( wXPos >= ((rctText.x + rctText.dx) - (CXARROW + 1)) ){
                              (void)Icon_HandleKey(pme, AVK_RIGHT);
                              bRet = TRUE;
                           }
                        }
                     }
                     // VScrollbar needs no extra action, Timer is killed below
                  }
               }
               break;
            case AEECLSID_LISTCTL:
               {
                  if( pme->m_ptTracker.cbHit == PTRCK_HIT_ITEM ){
                     (void)Menu_HandleSelection(pme, pme->m_nSelect);
                     bRet = TRUE;
                  }
               }
               break;
            }
         }
         if( pme->m_cls == AEECLSID_MENUCTL && (pme->m_ptTracker.cbHit & PTRCK_HIT_SCRL) ){
            bRet = TRUE;
         }
         if( pme->m_ptTracker.cbFlags & PTRCK_GEN_TMRSET ){
            ISHELL_CancelTimer(pme->m_pIShell, (PFNNOTIFY)Menu_ScrollTimerCB, (void *)pme);
         }
         MEMSET((void *)&pme->m_ptTracker, 0, sizeof(pme->m_ptTracker));
         return bRet;
      }
   }

   return FALSE;
}
/*=====================================================================

Local Method - Recalculates the size information in the menu.

======================================================================*/
static void Menu_Recalc(CMenuCtl * pme)
{
   CMenuItem *    pi;
   int            cy,cx,cyMax,cxMax,cys,cxs,nFrame,cxMaxItem,cyMaxItem;

   cxMax = cyMax = 0;

   pme->m_bHasText = FALSE;

   // Get extra pixels added to height and width because of style
   Menu_GetStyleOffsets(pme, &cxs, &cys);

   nFrame = (uint16)(AEE_FRAME_SIZE * 2);
	cxMaxItem = pme->m_rc.dx - cxs - nFrame;     // Max item width
	cyMaxItem = pme->m_rc.dy - cys - nFrame;     // Max item height

   if(SINGLE_FRAME(pme)){
      cxMax = cxMaxItem;                        // Width of widest item - Max item width
      cyMax = cyMaxItem;                        // Height of tallest item - Max item height
   }

   // Calculate width of widest item and height of tallest item
   for(pi = pme->m_pItemList; pi; pi = pi->pNext){
      // Measure Item Text Width
      if (!pi->cxText && *pi->text)
         pi->cxText = IDISPLAY_MeasureText(pme->m_pIDisplay, pi->nFont, pi->text);

      // Icon Views only measure the image. The text is shown as a title or below the menu.
      if(IS_ICON_VIEW(pme)){
         cx = pi->cxImage;
         cy = pi->cyImage;

         if (IS_ITEM_OD(pi))
         {
            // Adjust for offset so that comparison is fair
            cx -= cxs;
            cy -= cys;
         }

         // Make sure that we avoid going off-screen
         if(cy > cyMaxItem)
            cy = cyMaxItem;

         if(cx > cxMaxItem)
            cx = cxMaxItem;
      }
      else{
         // Item Width (Includes Text and Padding)
         cx = (IS_ITEM_OD(pi) ? pi->cxImage : GetItemWidth(pi));

         // Bigger of font/image height
         cy = (int)(pi->text[0] ? pme->m_cyFont : 0);
         if(pi->cyImage > cy)
            cy = pi->cyImage;

         if (IS_ITEM_OD(pi))
         {
            // Adjust for offset so that comparison is fair
            cx -= cxs;
            cy -= cys;
         }
      }

      // Need tallest/widest
      if(cx > cxMax)
         cxMax = cx;
      if(cy > cyMax)
         cyMax = cy;

      if(pi->cxText || pi->cyText)
         pme->m_bHasText = TRUE;
   }

   cxMax += cxs;              // Add Width Offset
   cyMax += cys;              // Add Height Offset

   pme->m_cxMaxItem = cxMax;  // Width of widest item
   pme->m_cyMaxItem = cyMax;  // Height of tallest item

   switch(pme->m_cls){
      case AEECLSID_ICONVIEWCTL:
         // Refresh screen layout for icon view
         Icon_CalcRows(pme);
         break;

      case AEECLSID_SOFTKEYCTL:
         {
            int   nAdd;
  
            // If SK control height isn't same as the height of tallest item, adjust it
            if(pme->m_rc.dy != cyMax){
               nAdd = pme->m_rc.dy - cyMax;
               pme->m_rc.dy = cyMax;
               pme->m_rc.y += nAdd;
            }

            // If max SK items are to be displayed, calculate page items using width of
            // the widest item
            if(MAX_SK_ITEMS(pme)){
               if(cxMax)
                  pme->m_nPageItems = pme->m_rc.dx / cxMax;
               else
                  pme->m_nPageItems = __MIN(pme->m_nItems, MAX_SOFT_PAGE_ITEMS);
            }   
         }
         break;

      default:
         // Calculate items on a page full of the control
         if(cyMax){
            cy = pme->m_rc.dy;
            if(pme->m_pTitle || IS_TITLE_OD(pme))
               cy -= pme->m_rcTitle.dy;
            pme->m_nPageItems = (cy / cyMax);
         }
         else
            pme->m_nPageItems = 0;

         // Regular Menu - Reset Auto Scroll
         Menu_ResetAutoScroll(pme);
   }
}
/*=====================================================================

Local Method - Draws only new selected item in a menu.

======================================================================*/
static void Menu_CalcItemRect(CMenuCtl * pme, int nIdx, AEERect* prc)
{
   int         x = pme->m_rc.x;
   int         y = pme->m_rc.y;
   uint16      cyLeft;
   uint16      cy,cx;

   cyLeft = pme->m_rc.dy;

   if(pme->m_pTitle || IS_TITLE_OD(pme)){
      cyLeft -= pme->m_rcTitle.dy;
      y += pme->m_rcTitle.dy;
   }
   
   cx = pme->m_rc.dx;
   cy = pme->m_cyMaxItem;

   if (pme->m_nItems > pme->m_nPageItems) 
      cx -= pme->m_nSBWidth;

   SETAEERECT(prc, x, y + ((nIdx - pme->m_nScrollItem) * cy), cx, cy);
}

/*=====================================================================

Local Method - Draws only new selected item in a menu.

======================================================================*/
static void Menu_DrawNewSelectItem(CMenuCtl * pme, int nIdx,int nIdxSel)
{
   IDisplay *  pd = pme->m_pIDisplay;
   AEERect     qrc;
   RGBVAL      clrBack, clrText, clrFrame;
   CMenuItem * pItem = NULL;

   // Nothing to do...
   if(!pme->m_bActive)
      return;

   // Initialize CLR_USER_BACKGROUND, CLR_USER_TEXT and CLR_USER_FRAME and
   // save current background, text and frame colors for restoration
   Menu_InitColors(pme, &clrBack, &clrText, &clrFrame);

   pItem = GetItemByIdx(pme->m_pItemList, nIdx);

   if(IS_CALENDAR_VIEW(pme))
      Cal_DrawItem(pme, pItem, nIdx, TRUE);
   else
   {
      Menu_CalcItemRect(pme, nIdx, &qrc);

      if (IS_ITEM_OD(pItem))
         Menu_DrawODItem(pme, pItem, &qrc, FALSE);
      else
         Menu_DrawItem(pme, pItem, &qrc, FALSE, 0);
   }

   // Draw the selected item...
   
   pItem = GetItemByIdx(pme->m_pItemList, nIdxSel);

   pme->m_bAutoScroll = Menu_ItemScrolls(pme, pItem, 0);

   if(IS_CALENDAR_VIEW(pme))
      Cal_DrawItem(pme, pItem, nIdxSel, TRUE);
   else
   {
      Menu_CalcItemRect(pme, nIdxSel, &qrc);

      if (IS_ITEM_OD(pItem))
         Menu_DrawODItem(pme, pItem, &qrc, TRUE);
      else
         Menu_DrawItem(pme, pItem, &qrc, TRUE, 0);
   }

   // Reset the colors.  If this is not done here, it will screw-up the drawing of the scrollbar...
   ResetColors(pd, clrBack, clrText,clrFrame);

   // Draw Scroll Bar
   Menu_DrawScrollBar(pme);

   IDISPLAY_Update(pd);
}


/*=====================================================================

Local Method - Sets the text and background color for drawing a selected/normal 
menu item. Returns previous background color in pcBack.

======================================================================*/
static void Menu_SetItemColor(CMenuCtl * pme, IDisplay * pd, boolean bSel,RGBVAL * pcBack)
{
   RGBVAL  nText, nBack;

   if(bSel){
      nText = pme->m_c.cSelText;
      nBack = pme->m_c.cSelBack;
   }
   else{
      nText = pme->m_c.cText;
      nBack = pme->m_c.cBack;
   }

   nBack = IDISPLAY_SetColor(pd, CLR_USER_BACKGROUND, nBack);
   IDISPLAY_SetColor(pd, CLR_USER_TEXT, nText);

   if(pcBack)
      *pcBack = nBack;
}


/*=====================================================================

Local Method - Frees the memory for a menu item

======================================================================*/
static void Menu_FreeItem(CMenuCtl * pme, CMenuItem * pmi)
{
   aee_releaseobj((void **)&pmi->pImage);
   FREE(pmi);
}

/*=====================================================================

Local Method - Draws a standard menu

======================================================================*/
static boolean Menu_Draw(CMenuCtl * pme)
{
   int         i;
   AEERect     qrc;
   uint16      cyLeft;
   uint16      cy,cx;
   RGBVAL      clrBack, clrText,clrFrame;
   boolean     bSel;
   int         cyFont = pme->m_cyFont;
   int         x = pme->m_rc.x;
   int         y = pme->m_rc.y;
   CMenuItem * p = NULL;
   IDisplay *  pd = pme->m_pIDisplay;

   Menu_InitColors(pme, &clrBack, &clrText, &clrFrame);

   // Erase Previous Menu - Use default system color

   IDISPLAY_FillRect(pd, &pme->m_rc, CLR_USER_BACKGROUND);

   // draw the title text

   cyLeft = pme->m_rc.dy;

   if(pme->m_pTitle || IS_TITLE_OD(pme)){

      SETAEERECT(&qrc, x, y, pme->m_rc.dx, pme->m_rcTitle.dy);

      if (pme->m_pTitle)
         IDISPLAY_DrawText(pd, AEE_FONT_BOLD, pme->m_pTitle, -1, x, y, &qrc, IDF_RECT_FILL | ParagraphAlignment(pme->m_pTitle, WSTRLEN(pme->m_pTitle)));
      else
      {
         // Title is owner draw

         AEEODCBData cbData;
         MEMSET(&cbData, 0, sizeof(AEEODCBData));

         cbData.nSize = sizeof(AEEODCBData);
         cbData.nAction = ODA_DRAW_TITLE;
         cbData.pIDisplay = pd;
         cbData.rc = qrc;

         // Draw Title
         BRIDLE_ASSERT_SAFE_PFN(pme->m_pfnCB);
         pme->m_pfnCB(pme->m_pvContext, &cbData);
      }
      cyLeft -= pme->m_rcTitle.dy;
      y += pme->m_rcTitle.dy;

      if(UNDERLINE(pme)){
         qrc.y = y - 1;
         qrc.dy = 1;
         IDISPLAY_FillRect(pme->m_pIDisplay, &qrc, CLR_USER_FRAME);
      }
   }
   
   pme->m_yLastTimeMark = INVALID_MARK;
   Cal_CalcPageItems(pme);

   pme->m_nVisibleItems = 0;
   cx = pme->m_rc.dx;
   cy = pme->m_cyMaxItem;

   if (pme->m_nItems > pme->m_nPageItems) 
      cx -= pme->m_nSBWidth;

   p = GetItemByIdx(pme->m_pItemList, pme->m_nScrollItem);

   for (i = pme->m_nScrollItem; p && i < pme->m_nScrollItem + pme->m_nPageItems; i++, p = p->pNext) {

      SETAEERECT(&qrc, x, y, cx, cy);

      bSel = (boolean)(pme->m_bActive && i == pme->m_nSelect);
      if(IS_CALENDAR_VIEW(pme)){
         p->cyImage  = cy = GetItemHeight(p, &cyLeft, (uint16)cyFont);
         p->y        = y;
         Menu_SetItemColor(pme,pd,bSel,NULL);
         Cal_DrawItem(pme, p, i, FALSE);
      }
      else
      {
         if (IS_ITEM_OD(p))
            Menu_DrawODItem(pme, p, &qrc, bSel);
         else
            Menu_DrawItem(pme, p, &qrc, bSel, 0);
      }
      y += cy;
      pme->m_nVisibleItems++;
   }

   // Reset the colors.  If this is not done here, it will screw-up the drawing of the scrollbar...
   ResetColors(pd, clrBack, clrText,clrFrame);

   // Draw Scrollbar
   Menu_DrawScrollBar(pme);

   if(IS_CALENDAR_VIEW(pme) && AUTO_SCROLL(pme))
      Cal_ShowTimeMark(pme, NULL,0, 0);

   IDISPLAY_Update(pd);
   return(TRUE);
}

/*=====================================================================

Local Method - Draws a standard menu item

======================================================================*/
static void Menu_DrawItem(CMenuCtl * pme, CMenuItem * p, AEERect * prc, boolean bSel, int nTruncExtent)
{
   int            x,y,cx,cxText,yImage;
   RGBVAL         cBack;
   boolean        b3State;
   boolean        bMulti;
   IDisplay    *  pd = pme->m_pIDisplay;
   AECHAR *       pText = p->text;
   AEEItemStyle * ps = &pme->m_style[(bSel ? 1 : 0)];
   boolean        bTitleRightAligned = FALSE;
   AEERect        rcText;
   uint32         dwItemTextAlignment;

   // icons are drawn according to the alignment of the menu title ...
   if (pme->m_pTitle) {
      bTitleRightAligned = (ParagraphAlignment(pme->m_pTitle, WSTRLEN(pme->m_pTitle))) == IDF_ALIGN_RIGHT;
   }

   b3State = (boolean)(IS_MULTI_IMAGE(pme) ? TRUE : FALSE);
   bMulti  = (boolean)(IS_MULTI_SEL(pme) ? TRUE : FALSE);

   // If item is selected and m_bAutoScroll is set, update position to start drawing
   // text from
   if(bSel && pme->m_bAutoScroll){
      MEMCPY(&pme->m_rcAutoScroll, prc, sizeof(AEERect));
   }

   // Draw the frame and fill with the sel/unsel color.  Note that if this
   // is a tabbed-dialog, the "masked" pixels will draw with the CLR_USER_BACKGROUND
   // color.  This means we must preserve it below...

   if(!b3State){
      AEEFrameType   ft;
      // If it is not multi, go with regular behavior
      // Otherwise, if it has focus but is not sel draw a box
      // OR draw the frame type given as select/not select
      if( !bMulti )
         ft = ps->ft;
      else{
         if( bSel && !p->bIsSel )
            ft = AEE_FT_BOX;
         else
            ft = (AEEFrameType)(p->bIsSel ? pme->m_style[1].ft : pme->m_style[0].ft);
      }
      IDISPLAY_DrawFrame(pd, prc, ft, (bSel ? pme->m_c.cSelBack : pme->m_c.cBack));
   }

   // Now setup the background colors for the item.  This will insure the image
   // is drawn correctly during animation, etc.

   Menu_SetItemColor(pme, pd, bSel, &cBack);

   AdjustRect(prc,ps);
           
   x = prc->x;
   y = prc->y;
   cx = prc->dx;

   cxText = p->cxText;
    
   if(p->pImage){
      IIMAGE_SetParm(p->pImage, IPARM_ROP, (b3State ? AEE_RO_MASK : ps->roImage), 0);

      // If no text and its softkey control, center the image...

      if(!cxText && IS_SOFTKEY_MENU(pme))
         x += ((prc->dx - p->cxImage) / 2);

      // If text and a right aligned title, draw the image on the right
      if (cxText && bTitleRightAligned) {
         x += cx - p->cxImage;
      }

      yImage = y + ((prc->dy - p->cyImage) / 2);

      if(b3State)
         IIMAGE_DrawFrame(p->pImage, (p->bPressed ? 2 : (bSel ? 1 : 0)), x, yImage);
      else
         IIMAGE_Draw(p->pImage, x , yImage);

      if(cxText){
         x += MENU_IMAGE_PAD + p->cxImage;
         cx -= p->cxImage + MENU_IMAGE_PAD;
      }
   }

	if(cxText){
      // For softkey, we draw text in center of remaining space
		if(pme->m_cls == AEECLSID_SOFTKEYCTL && cxText < cx)
			x += (cx - cxText) / 2;

      // Bidirectional Text Support      -jdboyd 05/09/03
      //
      // In addition to the changes above to draw the icon on the right side of menus that
      // are right aligned, the code below draws the correct portion of the text in the
      // menu item's rectangle.
      //
      // In all cases, the clipping rectangle is computed from the item's rectangle while
      // taking the size and position of the icon into account if one exists.
      //
      // Left to Right Text
      //
      // The menu item text to be drawn is drawn Left Aligned if it will fit.
      // If it does not fit, then as much text that will fit is drawn from the starting 
      // coordinate, clipped by the text rectangle.  The autoscroll value, which  contains
      // a pixel offset rather than a character count, is used to determine where to
      // start drawing the text.  As only text within the rectangle actually appears on
      // screen, this has the effect of scrolling the item to the left.
      //
      // Right to Left Text
      //
      // The menu item text to be drawn is drawn Right ALigned if it will fit.
      // If it does not fit, then only that portion of the text starting at the rightmost
      // end of the entire text string is drawn.  A block is drawn starting at the
      // rightmost end of the text, incrementing to the left each time the AutoScroll
      // routine fires.  This makes right aligned menu items scroll to the right.
      //
      // Note, when testing this without a DrawText implementation that does not render text
      // right to left, expect to see the following:  The last characters in the menu
      // item string are drawn first, then scrolls to the right until the first character
      // fits on screen.  In other words, you'll see a reverse scroll of the case above.

      // Adjust start and width of text if we drew a right aligned icon
      if (IS_MENU(pme) && p->pImage) {
         if (bTitleRightAligned ) {
            x = prc->x;
         } 
      }

		y += ((prc->dy - pme->m_cyFont) / 2);

      // adjust the clipping rectangle for the text
      rcText = *prc;
      if (IS_MENU(pme) && p->pImage) {
         if (!bTitleRightAligned)
            rcText.x += p->cxImage + MENU_IMAGE_PAD;
         rcText.dx -= p->cxImage + MENU_IMAGE_PAD;  
      }

      // determine the direction of this menu item's text
      dwItemTextAlignment = ParagraphAlignment(pText, WSTRLEN(pText));

      // determine start position of text based on auto scroll, clipping rectangle and alignment
      if (Menu_ItemScrolls(pme, p, 0)) {
         if (dwItemTextAlignment == IDF_ALIGN_LEFT) {
            // adjust the start position of the text only if it's selected
            if (bSel)
            x -= pme->m_nAutoScrollIdx;
         } else {
            // right aligned text, always adjust the start position.  But don't
            // factor in the auto scroll index if we're not selected.
            x -= IDISPLAY_MeasureText(pme->m_pIDisplay, p->nFont, pText) - rcText.dx - (bSel ? pme->m_nAutoScrollIdx : 0);
         }
      }

      // draw the entire text.  Align it unless it's scrolling or a softkey.
      if (Menu_ItemScrolls(pme, p, 0) || (pme->m_cls == AEECLSID_SOFTKEYCTL))
         IDISPLAY_DrawText(pd, p->nFont, pText, -1, x, y, &rcText, 0);
      else 
         IDISPLAY_DrawText(pd, p->nFont, pText, -1, x, y, &rcText, dwItemTextAlignment);
	}

   // Reset the background color - it may have changed above...

   IDISPLAY_SetColor(pd,CLR_USER_BACKGROUND,cBack);
}

/*=====================================================================

Local Method - Draws a standard menu owner drawn item

======================================================================*/
static void Menu_DrawODItem(CMenuCtl * pme, CMenuItem * p, AEERect * prc, boolean bSel)
{
   RGBVAL         cBack;
   AEEODCBData    cbData;
   IDisplay    *  pd = pme->m_pIDisplay;

   // Prepare display context with the right background color for the item
   Menu_SetItemColor(pme, pd, bSel, &cBack);

   // Initialize callback for owner draw item        
   MEMSET(&cbData, 0, sizeof(AEEODCBData));

   cbData.nSize = sizeof(AEEODCBData);
   cbData.nAction = ODA_DRAW_ITEM;
   cbData.pIDisplay = pd;
   cbData.rc = *prc;
   cbData.nActionInfo = p->nItemID;
   cbData.nActionInfo <<= 16;
   if (bSel)
      cbData.nActionInfo |= 0x00000001;

   // Draw Item 
   BRIDLE_ASSERT_SAFE_PFN(pme->m_pfnCB);
   pme->m_pfnCB(pme->m_pvContext, &cbData);

   // Reset the background color
   IDISPLAY_SetColor(pd,CLR_USER_BACKGROUND,cBack);
}

/*=====================================================================

Local Method - Auto-scroll logic for standard menu items.

Right to Left text support - jdboyd 05/05/03

This algorithm has been changed from returning an index into the
charcters to be drawn to an actual pixel offset.

Before, this routine would store the first character at which drawing was
to start in the m_nAutoScrollIdx member.

Now, this routine stores the pixel offset within a block of text at
which to start drawing while taking the clipping rect into effect.  Note,
when calculating this value, we don't care how the text is rendered, 
rather only how long it is.

======================================================================*/
static void Menu_AutoScroll(CMenuCtl * pme)
{
   CMenuItem * pi;
   int         n,nIdxNew,nIdx;
   AEERect     rc;
   RGBVAL      clrBack,clrText,clrFrame;

   if(!pme->m_bAutoScroll)
      return;
   
   // Find the selected item
   if ((pi = GetItemByIdx(pme->m_pItemList, pme->m_nSelect)) == NULL)
      return;

   // See if the index is within range for the string...

   nIdx  = nIdxNew = pme->m_nAutoScrollIdx;
   n     = IDISPLAY_MeasureText(pme->m_pIDisplay, pi->nFont, pi->text);

   if(nIdx < n){

      // Measure the text starting from the auto-scroll offset
      if(Menu_ItemScrolls(pme, pi, nIdx))
         nIdxNew += AUTO_SCROLL_PIXELS;
      else
         nIdxNew = 0;

      rc = pme->m_rcAutoScroll;  // Scroll Rect

      if(rc.dx){
         // Initialize CLR_USER_BACKGROUND, CLR_USER_TEXT and CLR_USER_FRAME and
         // save current background, text and frame colors for restoration
         Menu_InitColors(pme, &clrBack, &clrText, &clrFrame);

         // Draw Menu Item
         Menu_DrawItem(pme, pi, &rc, TRUE, 0);

         // Restore Colors
         ResetColors(pme->m_pIDisplay, clrBack, clrText,clrFrame);
         IDISPLAY_Update(pme->m_pIDisplay);
      }
   }        
   else
      nIdxNew = 0;   // Reset Scroll Character Index

   pme->m_nAutoScrollIdx = nIdxNew;

   // Delay for first/last character
   ISHELL_SetTimer(pme->m_pIShell,(uint32)(nIdx && nIdxNew ? pme->m_wMenuTextScroll : AUTO_SCROLL_START), (PFNNOTIFY)(Menu_AutoScroll), pme);
}

/*=====================================================================

Local Method - Resets the auto-scroll timer for a menu item.

======================================================================*/
static void Menu_ResetAutoScroll(CMenuCtl * pme)
{
   CMenuItem   * pi;

   // Get current selection
   pi = GetItemByIdx(pme->m_pItemList, pme->m_nSelect);
   pme->m_nAutoScrollIdx = 0;

   if(pi && pme->m_bActive && Menu_ItemScrolls(pme, pi, 0)){
      pme->m_bAutoScroll = TRUE;

      // We set the timer to go off pretty quickly.  That's because the AutoScroll function
      // will automatically set a longer timer for the first and last characters...

      ISHELL_SetTimer(pme->m_pIShell,pme->m_wMenuTextScroll, (PFNNOTIFY)(Menu_AutoScroll), pme);
   }
   else{
      // Caccel Auto Scroll Timer
      ISHELL_CancelTimer(pme->m_pIShell, (PFNNOTIFY)(Menu_AutoScroll), pme);

      pme->m_bAutoScroll = FALSE;

      // Reset Auto Scroll Rect
      MEMSET(&pme->m_rcAutoScroll, 0, sizeof(AEERect));
   }
}

/*=====================================================================

Local Method - Determines whether a menu item scrolls

======================================================================*/
static boolean Menu_ItemScrolls(CMenuCtl * pme, CMenuItem *pi, int nIdx)
{
   int   cx,cxMax;

   // Measure item text width starting from index
   cx = IDISPLAY_MeasureText(pme->m_pIDisplay, pi->nFont, pi->text) - nIdx + pme->m_cxItemExtra;

   // Adjust with image width & padding
   if(pi->cxImage){
      if(cx)
         cx += MENU_IMAGE_PAD;
      cx += pi->cxImage;
   }

   cxMax = pme->m_rc.dx;   // Max Width

   // Adjust Max Width for scroll bar
   if (pme->m_nItems > pme->m_nPageItems) 
      cxMax -= pme->m_nSBWidth;

   return(cx > cxMax);
}

/*=====================================================================

Local Method - Moves the selection on a standard menu.

======================================================================*/
static void Menu_MoveSelect(CMenuCtl * pme, int nDir, boolean bRedraw)
{
   uint16 nSaveSelect;
   uint16 nSaveScroll;
   int    nDiff;

   Cal_CalcPageItems(pme);

   nSaveSelect = pme->m_nSelect;
   nSaveScroll = pme->m_nScrollItem;

   if (nDir < 0) {   
      if (WRAP_SCROLL(pme) && (pme->m_nSelect == 0)) {
         pme->m_nSelect = pme->m_nItems-1;
         pme->m_nScrollItem = (uint16)__MAX(0, (int)pme->m_nItems - (int)pme->m_nPageItems);
      }
      else {
         if (pme->m_nSelect > 0) {
            pme->m_nSelect--;
            if (pme->m_nSelect < pme->m_nScrollItem) 
               pme->m_nScrollItem = pme->m_nSelect;
         }
      }
   }
   else {
      if (WRAP_SCROLL(pme) && (pme->m_nSelect == pme->m_nItems-1)) {
         pme->m_nSelect = 0;
         pme->m_nScrollItem = 0;
      }
      else {
         if (pme->m_nSelect < pme->m_nItems-1) {
            pme->m_nSelect++;

      // If the list item sizes are dynamic, we need to keep recalculating until it fits...

            while(1){
               nDiff = (int)(pme->m_nSelect - (pme->m_nScrollItem + pme->m_nPageItems)) + 1;
               if(nDiff <= 0)
                  break;
               pme->m_nScrollItem += nDiff;
               Cal_CalcPageItems(pme);
            }
         } 
      }
   }

   if (bRedraw && (nSaveSelect != pme->m_nSelect || nSaveScroll != pme->m_nScrollItem)){
      if(nSaveSelect != pme->m_nSelect)
         Menu_ResetAutoScroll(pme);

      if(nSaveScroll == pme->m_nScrollItem && ITEM_IS_VISIBLE(pme, nSaveSelect) && ITEM_IS_VISIBLE(pme, pme->m_nSelect))
         Menu_DrawNewSelectItem(pme,nSaveSelect,pme->m_nSelect);
      else
         Menu_Draw(pme);
   }

   // Notify them that the selection changed.
   if(nSaveSelect != pme->m_nSelect)
      Menu_NotifySelChange(pme);
}

/*=====================================================================

Local Method - Notifies the owner when selection changes.

======================================================================*/
static void Menu_NotifySelChange(CMenuCtl * pme)
{
   CMenuItem * pmi = GetItemByIdx(pme->m_pItemList,pme->m_nSelect);

   if(pmi)
      ISHELL_HandleEvent(pme->m_pIShell, EVT_CTL_SEL_CHANGED, (uint16)pmi->nItemID, (uint32)pme);
}

/*=====================================================================

Local Method - Draws a standard menu scroll bar

======================================================================*/
static void Menu_DrawScrollBar(CMenuCtl * pme)
{
   IDisplay *  pd = pme->m_pIDisplay;
   AEERect rctFrame, rctThumb;
   uint16   wPos, wLen;

   if( !Menu_GetScrollBarRects(pme, &rctFrame, &rctThumb, &wPos, &wLen) ){
      if (IS_SB_OD(pme))
      {
         // Scroll Bar is owner draw

         AEEODCBData cbData;
         MEMSET(&cbData, 0, sizeof(AEEODCBData));

         cbData.nSize = sizeof(AEEODCBData);
         cbData.nAction = ODA_DRAW_SB;
         cbData.pIDisplay = pd;
         SETAEERECT(&cbData.rc, rctFrame.x, rctFrame.y, rctFrame.dx, rctFrame.dy);
         cbData.nActionInfo = (uint32)(rctFrame.y + wPos);
         cbData.nActionInfo <<= 16;
         cbData.nActionInfo |= wLen;

         // Draw Scroll Bar
         BRIDLE_ASSERT_SAFE_PFN(pme->m_pfnCB);
         pme->m_pfnCB(pme->m_pvContext, &cbData);
      }
      else
      {
         // Draw the frame
         IDISPLAY_DrawFrame(pd, &rctFrame, AEE_FT_BOX, pme->m_c.cScrollbar);

         // Display Top/Bottom arrows if wrap scroll property is set
         if(WRAP_SCROLL(pme)){
            AEERect rc;

            // Never Executed!
            // Note that this is a 3x3 arrow. That's why CXARROW is used
            // for x as well as y calculation

            // draw top arrow
            int xa = rctFrame.x + ((rctFrame.dx - CXARROW)/2);
            SETAEERECT(&rc, xa + 1, rctFrame.y, 1, 1);
            IDISPLAY_FillRect(pd, &rc, pme->m_c.cFrame);

            SETAEERECT(&rc, xa, rctFrame.y + 1, CXARROW, 1);
            IDISPLAY_FillRect(pd, &rc, pme->m_c.cFrame);

            // draw bottom arrow
            SETAEERECT(&rc, xa + 1, rctFrame.y + rctFrame.dy - 1, 1, 1);
            IDISPLAY_FillRect(pd, &rc, pme->m_c.cFrame);

            SETAEERECT(&rc, xa, rctFrame.y + rctFrame.dy - 2, CXARROW, 1);
            IDISPLAY_FillRect(pd, &rc, pme->m_c.cFrame);
         }

         IDISPLAY_FillRect(pd, &rctThumb, pme->m_c.cScrollbarFill);
      }
   }
}

/*=====================================================================

Local Method - Gets rectangles of the scrollbar if it has one
pwLen and pwPos are not required and can be passed in as NULL.

======================================================================*/
static int Menu_GetScrollBarRects(CMenuCtl * pme, AEERect * prcFrame, AEERect * prcThumb, uint16 * pwPos, uint16 * pwLen)
{
   int         cx;
   int         nScrollItem, x, y, cy, nItems, nPageItems;
   uint16      nRange, nPos, nLen;

   if(IS_ICON_VIEW(pme)){

      if(!pme->m_bIconVScroll)
         return EFAILED;

      nItems = pme->m_nTotalRows;
      nPageItems = pme->m_nRows;
   }
   else{
      nItems = pme->m_nItems;
      nPageItems = pme->m_nPageItems;
   }
   nScrollItem = pme->m_nScrollItem;

   if (nItems > nPageItems) {
      // Adjust for frame size
      cx = IS_SB_OD(pme) ? pme->m_nSBWidth : (pme->m_nSBWidth - 2*AEE_FRAME_SIZE);
      x = (pme->m_rc.x + pme->m_rc.dx - cx);
      y = pme->m_rc.y;

      if(IS_MENU(pme))
         cy = nPageItems * pme->m_cyMaxItem;
      else
         cy = pme->m_rc.dy;

      // In an icon view, the scrollbar sits beside the entire icon window.  That
      // means everything EXCEPT the text area...
      if(IS_ICON_VIEW(pme)){
         if(pme->m_bHasText){
            uint16 cyText = Icon_GetMaxTextHeight(pme);
            cy -= cyText;
            if(ALIGN_TEXT_TOP(pme))
               y += cyText;
         }
      }
      else{       
         if(pme->m_pTitle || IS_TITLE_OD(pme)){
            if(!IS_MENU(pme))
               cy -= pme->m_rcTitle.dy;
            y += pme->m_rcTitle.dy;
         } 
      }    

      if (IS_SB_OD(pme))
         nRange = cy;
      else
      {
         nRange = cy - 2*AEE_FRAME_SIZE;
         if (WRAP_SCROLL(pme))
            nRange -= 2*CXARROW;
      }
    
      nRange = cy * 10;    // multiply by 10 for rounding
  
      nLen = (nRange * nPageItems)/nItems;      // Scroll Bar Length    
      nPos = (nRange * nScrollItem)/nItems;     // Scroll Bar Position
   
      nPos = (nPos + 5) / 10;     // rounding
      nLen = (nLen + 5) / 10;     // rounding

      if( pwPos ){
         *pwPos = nPos;
      }
      if( pwLen ){
         *pwLen = nLen;
      }
      SETAEERECT(prcFrame, x, y , cx, cy);
      if(WRAP_SCROLL(pme)){
         // Never executed!
         y += CXARROW;
      }


      // Adjust x, y and cx for scroll bar fill
      y += AEE_FRAME_SIZE;
      x += AEE_FRAME_SIZE;
      cx -= (AEE_FRAME_SIZE * 2);
      cy -= (AEE_FRAME_SIZE * 2);

      nLen = MAX(1,nLen);
      SETAEERECT(prcThumb, x, y + MIN(cy-nLen,nPos), cx, nLen);
      return AEE_SUCCESS;
   }
   return EFAILED;
}

/*=====================================================================

Local Method - Scrolls per where pen hit!

======================================================================*/
static void Menu_ScrollTimerCB(CMenuCtl *pme)
{
   pme->m_ptTracker.cbFlags &= ~PTRCK_GEN_TMRSET;
   Menu_ScrollByPos(pme, ((pme->m_ptTracker.cbHit & PTRCK_HIT_BELOW) ? 1 : -1), pme->m_ptTracker.ptPosition.x, pme->m_ptTracker.ptPosition.y);
}

/*=====================================================================

Local Method - Scrolls text in the Menu by direction and point
                  in a non-thumb area.

======================================================================*/
static boolean Menu_ScrollByPos(CMenuCtl * pme, int nDir, int16 wXPos, int16 wYPos)
{
   AEERect  rctFrame, rctThumb;

   if( !Menu_GetScrollBarRects(pme, &rctFrame, &rctThumb, NULL, NULL) ){
      boolean  bMoved = FALSE;

      if( PT_IN_RECT(wXPos, wYPos, rctFrame) ){
         // Selection offset from top of visible page
         int      nOffset;
         int      nScrl;   // New Scroll item
         uint16   wFocus;  // New Focus item

         // If the point is in the segment expected
         if( nDir > 0 ){
            if( wYPos > (rctThumb.y+rctThumb.dy) ){   // Already bound by SB Frame
               if(IS_ICON_VIEW(pme)){

                  if(!pme->m_bIconVScroll)
                     return FALSE;

                  nOffset  = pme->m_nSelect - (pme->m_nScrollItem*pme->m_nCols);
                  nScrl    = pme->m_nRows + pme->m_nScrollItem; // Next Page!

                  if( nOffset < 0 )
                     nOffset = 0;
                  wFocus   = ((uint16)nScrl*pme->m_nCols) + (uint16)nOffset;

                  // Scroll by full page view only
                  if((nScrl*pme->m_nCols) > (pme->m_nItems - (pme->m_nRows*pme->m_nCols)) ){
                     nScrl = ((pme->m_nItems+(pme->m_nCols-1))/pme->m_nCols) - pme->m_nRows;
                     // Fix the focus
                     if( wFocus > pme->m_nItems ){
                        // Keep focus where we were, page moved, selected item still on page
                        wFocus = pme->m_nSelect;
                     }else{
                        // Item no longer on page, focus to top of page
                        wFocus = (nScrl*pme->m_nCols) + (uint16)nOffset;
                     }
                  }
               }else{
                  nOffset = pme->m_nSelect - pme->m_nScrollItem;
                  nScrl    = pme->m_nPageItems + pme->m_nScrollItem; // Next Page!

                  if( nOffset < 0 )
                     nOffset = 0;
                  wFocus   = nScrl + (uint16)nOffset;   

                  // Scroll by full page view only
                  if(nScrl > (pme->m_nItems - pme->m_nPageItems) ){
                     nScrl = pme->m_nItems - pme->m_nPageItems;
                     // Fix the focus
                     if( wFocus > pme->m_nItems ){
                        // Keep focus where we were, page moved, selected item still on page
                        wFocus = pme->m_nSelect;
                     }else{
                        // Item no longer on page, focus to top of page
                        wFocus = (uint16)nScrl;
                     }
                  }
               }
               if( pme->m_nScrollItem != (uint16)nScrl 
                  || wFocus != pme->m_nSelect ){
                  bMoved = TRUE;
               }
            }
         }else{
            if( wYPos < rctThumb.y ){  // Already bound by SB Frame
               if(IS_ICON_VIEW(pme)){

                  if(!pme->m_bIconVScroll)
                     return FALSE;

                  nOffset  = pme->m_nSelect - (pme->m_nScrollItem*pme->m_nCols);
                  nScrl    = pme->m_nScrollItem - pme->m_nRows;

                  if( nScrl < 0 ){
                     nScrl = 0;

                     if( pme->m_nSelect >= (pme->m_nRows*pme->m_nCols) ){
                        wFocus = (uint16)nOffset;
                     }else{
                        wFocus = pme->m_nSelect;
                     }
                  }else{
                     // Keep focus as if a Page Up occurred, focus is same spot on prev screen
                     wFocus   = (nScrl*pme->m_nCols) + (uint16)nOffset;
                  }
               }else{
                  // Go back a page
                  nOffset  = pme->m_nSelect - pme->m_nScrollItem;
                  nScrl    = pme->m_nScrollItem - pme->m_nPageItems;

                  if( nScrl < 0 ){
                     // Not too far, back to the beginning
                     nScrl = 0;
                     // If the focus is off the screen bring it to the last item
                     if( pme->m_nSelect >= pme->m_nPageItems ){
                        wFocus = pme->m_nPageItems-1;
                     }else{
                        // Otherwise keep same item focused since it is still in screen
                        wFocus = pme->m_nSelect;
                     }
                  }else{
                     // Keep focus as if a Page Up occurred, focus is same spot on prev screen
                     wFocus   = (uint16)nScrl + (uint16)nOffset;
                  }
               }
               if( pme->m_nScrollItem != nScrl 
                  || wFocus != pme->m_nSelect ){
                  bMoved = TRUE;
               }
            }
         }
         if( bMoved ){
            uint32   dwProps = pme->m_dwProps;
            CMenuItem * pFocus;

            pme->m_nScrollItem = (uint16)nScrl;
            if( !ISHELL_SetTimer(pme->m_pIShell, pme->m_arPenRepeat.dwRate, (PFNNOTIFY)Menu_ScrollTimerCB, (void *)pme) ){
               pme->m_ptTracker.cbFlags |= PTRCK_GEN_TMRSET;
            }
            pme->m_dwProps &= ~MP_NO_REDRAW; // Prevent potential flicker
            if( (pFocus = GetItemByIdx(pme->m_pItemList, wFocus)) != NULL ){
               IMENUCTL_SetFocus((IMenuCtl *)pme, pFocus->nItemID);
            }
            pme->m_dwProps = dwProps;
            IMenuCtl_Redraw((IMenuCtl *)pme);
            if(IS_ICON_VIEW(pme)){
               // Icons need to redraw text, as focus is not moved!
               CMenuItem * pmi = GetItemByIdx(pme->m_pItemList, pme->m_nSelect);
               if(pmi){
                  if (IS_ITEM_OD(pmi))
                     Icon_DrawODItemText(pme, pmi, 0, TRUE);
                  else
                     Icon_DrawItemText(pme, pmi, 0, TRUE);
               }
            }
            return TRUE;
         }
      }
   }
   return FALSE;
}

/*=====================================================================

Local Method - Initializes background, text and frame colors with
CLR_USER_BACKGROUND, CLR_USER_TEXT and CLR_USER_FRAME.

======================================================================*/
static void Menu_InitColors(CMenuCtl * pme, RGBVAL * pBack, RGBVAL * pText, RGBVAL * pFrame)
{
   IDisplay * pd = pme->m_pIDisplay;

   *pBack = IDISPLAY_SetColor(pd, CLR_USER_BACKGROUND, pme->m_c.cBack);
   *pText = IDISPLAY_SetColor(pd, CLR_USER_TEXT, pme->m_c.cText);
   *pFrame = IDISPLAY_SetColor(pd, CLR_USER_FRAME, pme->m_c.cFrame);
}


/*=====================================================================

Local Method - Draws scrollbar arrows in a menu.

======================================================================*/
static void Menu_DrawArrows(CMenuCtl * pme, int x, int y, int xEnd)
{
   IDisplay *  pd = pme->m_pIDisplay;
   AEERect     rc;
   RGBVAL      c = pme->m_c.cText;

   // Draw left arrow using text color
   SETAEERECT(&rc, x+0, y+2, 1, 1);
   IDISPLAY_FillRect(pd, &rc, c);
   
   SETAEERECT(&rc, x+1, y+1, 1, 3);
   IDISPLAY_FillRect(pd, &rc, c);
   
   SETAEERECT(&rc, x+2, y+0, 1, 5);
   IDISPLAY_FillRect(pd, &rc, c);
   
   x = xEnd;
       
   // draw right arrow using text color  
   SETAEERECT(&rc, x+0, y+0, 1, 5);
   IDISPLAY_FillRect(pd, &rc, c);
   
   SETAEERECT(&rc, x+1, y+1, 1, 3);
   IDISPLAY_FillRect(pd, &rc, c);
   
   SETAEERECT(&rc, x+2, y+2, 1, 1);
   IDISPLAY_FillRect(pd, &rc, c);
}

/*=====================================================================

Local Method - Handles Selection type events for Menu. (AVK_SELECT and Pens)

======================================================================*/
static boolean Menu_HandleSelection(CMenuCtl * pme, int nSelect)
{
   if (pme->m_bSendCmd) {
      CMenuItem   * pItem;
      if((pItem = GetItemByIdx(pme->m_pItemList, nSelect)) != NULL){
         if( IS_MULTI_SEL(pme) ){
            pItem->bIsSel = !pItem->bIsSel;
            IMenuCtl_SetSelEx((IMenuCtl *)pme, pItem->nItemID, pItem->bIsSel);
            IMenuCtl_Redraw((IMenuCtl *)pme);
         }
         if(IS_TRI_STATE(pme)){
            pItem->bPressed = TRUE;
            IMenuCtl_Redraw((IMenuCtl *)pme);
            pItem->bPressed = FALSE;
         }
         if(pme->m_bResumeCmd){
            CALLBACK_Cancel(&pme->m_cbCommand);
            CALLBACK_Init(&pme->m_cbCommand,Menu_SendCommand,pme);
            ISHELL_Resume(pme->m_pIShell,&pme->m_cbCommand);
            return(TRUE);
         }
         return(ISHELL_HandleEvent(pme->m_pIShell, EVT_COMMAND, (uint16)pItem->nItemID, (uint32)pme));
      }
   }
   return FALSE;
}

/*=====================================================================

Local Method - Handles TAP events for a menu.

======================================================================*/
static uint16 Menu_HandleTap(CMenuCtl * pme, int x, int y)
{
   AEERect        rc;
   int            nIdx,nMax;
   SKDrawItem *   pdi = NULL;
   CMenuItem *    pi = NULL;
   AEECLSID       cls = pme->m_cls;

   // Ignore if control is empty or TAP was outside control rect 
   if(!pme->m_pItemList || !Intersects(&pme->m_rc,x,y))
      return(AVK_UNDEFINED);

   // Treat it as AVK_SELECT for AEECLSID_LISTCTL
   if(cls == AEECLSID_LISTCTL)
      return(AVK_SELECT);

   MEMSET(&rc, 0, sizeof(AEERect));
   nIdx = 0;

   if(cls == AEECLSID_SOFTKEYCTL){
      // Calculate Softkey Layout
      SK_CalcLayout(pme);

      pdi = &pme->m_draw[0];        // first draw item
      nMax = pme->m_nVisibleItems;  // visible items

      // SK has fixed y, dy
      rc.y = pme->m_rc.y;
      rc.dy = pme->m_rc.dy;
   }
   else{
      if(cls == AEECLSID_ICONVIEWCTL)
         nMax = pme->m_nItems;
      else
      {
         // For AEECLSID_MENUCTL, we need to examine items between scroll item
         // and (scroll item + items displayable on a page)
         nIdx  = pme->m_nScrollItem;
         nMax  = pme->m_nPageItems + nIdx;

         if(nMax > pme->m_nItems)
            nMax = pme->m_nItems;

         // Menu has fixed x, dx
         rc = pme->m_rc;

         if(pme->m_pTitle || IS_TITLE_OD(pme))
            rc.y += pme->m_rcTitle.dy;

         rc.dy = pme->m_cyMaxItem;  // Height of tallest item
         rc.y -= rc.dy;             // Initial y for AEECLSID_MENUCTL
      }
   }

   // For each item, calculate item cell and check if that intersects with TAP position.
   // If yes, we change pme->m_nSelect and return AVK_SELECT.
   for(; nIdx < nMax; nIdx++)
   {
      switch(cls)
      {
         case AEECLSID_ICONVIEWCTL:
            // Calculate grid rect
            Icon_CalcGridRect(pme, &rc, nIdx);
            break;

         case AEECLSID_SOFTKEYCTL:
            //Update pdi to point to next item
            pdi = &pme->m_draw[nIdx];
            rc.x = pdi->x;
            rc.dx = pdi->dx;

            // Must lookup the idx for the item...
            if(Intersects(&rc,x,y)){
               for(nIdx = 0, pi = pme->m_pItemList; pi != NULL; pi = pi->pNext, nIdx++){
                  if(pi == pdi->pmi){
                     pme->m_nSelect = nIdx;
                     return(AVK_SELECT);
                  }
               }
            }
            break;

         default:
            // Calculate y position
            rc.y += rc.dy;
      }
      if(cls != AEECLSID_SOFTKEYCTL && Intersects(&rc, x, y)){
         pme->m_nSelect = nIdx;
         return(AVK_SELECT);
      }         
   }

   // No menu items intersects with TAP position ...

   // Let's see if user tapped on left arrow. If so, move left and return AVK_LEFT
   SETAEERECT( &rc, pme->m_rc.x, pme->m_rc.y, 5, pme->m_rc.dy );
   if( Intersects( &rc, x, y ) ) {
     // tap on left arrow
     // if there are items not displayed, scroll display
     if( pme->m_nItems > pme->m_nPageItems ) {
       if(pme->m_nScrollItem == 0 )
         pme->m_nScrollItem = pme->m_nItems - pme->m_nPageItems;
       else
         pme->m_nScrollItem--;
     }

     return( AVK_LEFT );
   }

   // Let's see if user tapped on right arrow. If so, move left and return AVK_RIGHT
   SETAEERECT( &rc, pme->m_rc.x + pme->m_rc.dx - 5, pme->m_rc.y,5, pme->m_rc.dy );

   if( Intersects( &rc, x, y ) ) {
     // tap on right arrow
     // if there are items not displayed, scroll display
     if( pme->m_nItems > pme->m_nPageItems ) {
       if(pme->m_nScrollItem >=  pme->m_nItems - pme->m_nPageItems)
         pme->m_nScrollItem = 0;
       else
         pme->m_nScrollItem++;
     }

     return( AVK_RIGHT );
   }

   return(AVK_UNDEFINED);
}
         
/*=====================================================================

Local Method - Returns the style offset for a menu.

======================================================================*/
static void Menu_GetStyleOffsets(CMenuCtl * pme,int * pcx, int * pcy)
{
   int            nFrame = 0, cxMax = 0, cyMax = 0, cx, cy, i;
   AEEItemStyle * ps = pme->m_style;

   // For styles (Normal/Select), calculate max pixels added to width &
   // height by any of these
   for(i = 0; i < sizeof(pme->m_style)/sizeof(AEEItemStyle); i++, ps++){
      // Calculate pixels required to draw frame
		nFrame = IDISPLAY_DrawFrame(pme->m_pIDisplay, NULL, ps->ft, RGB_NONE);

      cx = (ps->xOffset + nFrame) * 2;
      cy = (ps->yOffset + nFrame) * 2;

      if(cx > cxMax)
         cxMax = cx;

      if(cy > cyMax)
         cyMax = cy;
   }

   pme->m_cxItemExtra = cxMax;      // Cache extra pixels added to width
   pme->m_cyItemExtra = cyMax;      // Cache extra pixels added to height
   
   *pcx = cxMax;                    // Return extra pixels added to width
   *pcy = cyMax;                    // Return extra pixels added to height
}

/*=====================================================================

Local Method - (Re)allocates a menu item.

======================================================================*/
static CMenuItem * Menu_AllocItem(CMenuCtl * pme, CMenuItem *piOrig, AECHAR * pText, const char * pszResImage, CMenuItem * piPrev)
{   
   int            nSize, nSizeBase, nStrSize;
   CMenuItem   *  pi;

   // If no resource file specified, use original's resource file if there
   if(!pszResImage && piOrig)
      pszResImage = piOrig->pszImage;

   // Text String Size
   if(pText)
      nStrSize = WSTRSIZE(pText);
   else
      nStrSize = 0;

   // Base Size
   nSize = nSizeBase = sizeof(CMenuItem) + nStrSize;

   // Image Resource File Name String Size
   if(pszResImage)
      nSize += STRLEN(pszResImage) + 1;

   // Allocate Memory
   pi = (CMenuItem *)MALLOC(nSize);

   if(pi){
      // If original is there, copy its content to new
      if(piOrig)
         MEMCPY(pi, piOrig, sizeof(CMenuItem));

      if(pszResImage){
         pi->pszImage = (const char *)pi + nSizeBase;
         STRCPY((char *)pi->pszImage, pszResImage);
      }
      else
         pi->pszImage = NULL;

      // Copy Item Text
      if(nStrSize)
      {
         WSTRCPY(pi->text, pText);
         if (piOrig)
            pi->cxText = IDISPLAY_MeasureText(pme->m_pIDisplay, pi->nFont, pi->text);
      }
      else
      {
         *pi->text = 0;
         pi->cxText = 0;
      }

      // Put it into the right spot in the list
      if(piPrev)
         piPrev->pNext = pi;
      else
        pme->m_pItemList = pi;

      if(piOrig){
         if( pme->m_pLastItem == piOrig ){
            pme->m_pLastItem = pi;  // Last item changed, our key is the pointer val!
         }
         FREE(piOrig);        // Free Original
      }else{
         pme->m_nItems++;     // Increase Item Count
         pme->m_pLastItem = pi;
      }
   }
   return(pi);
}


//===========================================================================
//
//
//
//                LIST ROUTINES
//
//
//
//===========================================================================
/*=====================================================================

Local Method - Returns List Item Rect

======================================================================*/
static void List_CalcItemRect(CMenuCtl * pme, AEERect * prc)
{
   IDisplay *  pd = pme->m_pIDisplay;

   // draw the title text
   *prc = pme->m_rc;

   if (pme->m_pTitle || IS_TITLE_OD(pme)){
      if (pme->m_pTitle)
         prc->x += IDISPLAY_MeasureText(pd, AEE_FONT_NORMAL, pme->m_pTitle) + LIST_TITLE_PAD;
      else
         // Title is owner draw
         prc->x += pme->m_rcTitle.dx + LIST_TITLE_PAD;
   }
}

/*=====================================================================

Local Method - Draws a list menu.

======================================================================*/
static boolean List_Draw(CMenuCtl * pme)
{
   AEERect     qrc;
   RGBVAL      clrBack, clrText;
   IDisplay *  pd = pme->m_pIDisplay;
   CMenuItem * p = GetItemByIdx(pme->m_pItemList, pme->m_nSelect);

   // erase previous menu
   IDISPLAY_FillRect(pd, &pme->m_rc, pme->m_c.cBack);

   clrBack = IDISPLAY_SetColor(pd, CLR_USER_BACKGROUND, pme->m_c.cTitle);
   clrText = IDISPLAY_SetColor(pd, CLR_USER_TEXT, pme->m_c.cTitleText);

   // draw the title text
   qrc = pme->m_rc;

   if (pme->m_pTitle || IS_TITLE_OD(pme)){
      if (pme->m_pTitle)
      {
         // Draw title on the appropriate side of the list menu
         if (ParagraphAlignment((AECHAR *)(pme->m_pTitle), WSTRLEN(pme->m_pTitle)) == IDF_ALIGN_RIGHT) {
            qrc.dx -= IDISPLAY_MeasureText(pd, AEE_FONT_NORMAL, pme->m_pTitle) + 5;
            IDISPLAY_DrawText(pd, AEE_FONT_NORMAL,pme->m_pTitle, -1, qrc.x + qrc.dx + 5, qrc.y+2,NULL,IDF_RECT_NONE);
         } else {
            IDISPLAY_DrawText(pd, AEE_FONT_NORMAL,pme->m_pTitle, -1, qrc.x, qrc.y+2,NULL,IDF_RECT_NONE);
            qrc.x += IDISPLAY_MeasureText(pd, AEE_FONT_NORMAL, pme->m_pTitle) + 5;
         }
      }
      else
      {
         // Title is owner draw

         AEEODCBData cbData;
         MEMSET(&cbData, 0, sizeof(AEEODCBData));

         cbData.nSize = sizeof(AEEODCBData);
         cbData.nAction = ODA_DRAW_TITLE;
         cbData.pIDisplay = pd;
         SETAEERECT(&cbData.rc, qrc.x, qrc.y, pme->m_rcTitle.dx, pme->m_rcTitle.dy);
         qrc.x += pme->m_rcTitle.dx + LIST_TITLE_PAD;

         // Draw Title
         BRIDLE_ASSERT_SAFE_PFN(pme->m_pfnCB);
         pme->m_pfnCB(pme->m_pvContext, &cbData);
      }
   }

   //If there is no item in the list, return
   if(!p)
	   return(FALSE);

   Menu_SetItemColor(pme, pd, (boolean)pme->m_bActive, NULL);

   if (IS_ITEM_OD(p))
   {   
      AEEODCBData cbData;

      // Initialize callback for owner draw item
         
      MEMSET(&cbData, 0, sizeof(AEEODCBData));

      cbData.nSize = sizeof(AEEODCBData);
      cbData.nAction = ODA_DRAW_ITEM;
      cbData.pIDisplay = pd;
      cbData.rc = qrc;
      cbData.nActionInfo = p->nItemID;
      cbData.nActionInfo <<= 16;
      if (pme->m_bActive)
         cbData.nActionInfo |= 0x00000001;

      // Draw Item 
      BRIDLE_ASSERT_SAFE_PFN(pme->m_pfnCB);
      pme->m_pfnCB(pme->m_pvContext, &cbData);
   }
   else
      // Draw Item Text
      IDISPLAY_DrawText(pme->m_pIDisplay, p->nFont,p->text, -1, qrc.x , qrc.y + 2, &qrc, IDF_RECT_FILL | ParagraphAlignment((AECHAR *)(p->text), WSTRLEN(p->text)));

   IDISPLAY_SetColor(pd, CLR_USER_BACKGROUND, clrBack);
   IDISPLAY_SetColor(pd, CLR_USER_TEXT, clrText);
   
   IDISPLAY_Update(pme->m_pIDisplay);
   return(TRUE);
}

/*=====================================================================

Local Method - Moves the selection in a list menu.

======================================================================*/
static void List_MoveSelect(CMenuCtl * pme, int nDir)
{
   uint16 nSaveSelect = pme->m_nSelect;

   if (nDir < 0) {   
      if (WRAP_SCROLL(pme) && (pme->m_nSelect == 0)) {
         pme->m_nSelect = pme->m_nItems-1;
      }
      else {
         if (pme->m_nSelect > 0)
            pme->m_nSelect--;
      }
   }
   else {
      if (WRAP_SCROLL(pme) && (pme->m_nSelect == pme->m_nItems-1)) {
         pme->m_nSelect = 0;
      }
      else {
         if (pme->m_nSelect < pme->m_nItems-1)
            pme->m_nSelect++;
      }
   }

   if (nSaveSelect != pme->m_nSelect){
      List_Draw(pme);
      Menu_NotifySelChange(pme);
   }
}


//===========================================================================
//
//
//
//                SOFTKEY ROUTINES
//
//
//
//===========================================================================
/*=====================================================================

Local Method - Moves the selection in a softkey menu

======================================================================*/
static boolean SK_MoveSelect(CMenuCtl * pme, int nDir)
{
   uint16 nSaveSelect = pme->m_nSelect;

   if (nDir < 0) {   
      if (pme->m_nSelect == 0){
         pme->m_nSelect = pme->m_nItems-1;
         pme->m_nScrollItem = (uint16)__MAX(0, (int)pme->m_nItems - (int)pme->m_nPageItems);
      }
      else
      if (pme->m_nSelect > 0) {
         pme->m_nSelect--;
         if (pme->m_nSelect < pme->m_nScrollItem)
            pme->m_nScrollItem = pme->m_nSelect;
      } 
   }
   else {
      if (pme->m_nSelect == pme->m_nItems-1) {
         pme->m_nSelect = 0;
         pme->m_nScrollItem = 0;
      }
      else
      if (pme->m_nSelect < pme->m_nItems-1) {
         pme->m_nSelect++;
         if (pme->m_nSelect - pme->m_nScrollItem > pme->m_nPageItems - 1)
            pme->m_nScrollItem++;
      }
   }

   if (nSaveSelect != pme->m_nSelect) {
      int i;

      // Scroll till current selection and scroll items are visible
      for (i = 0; i < pme->m_nPageItems; i++) {
         if (SK_CalcLayout(pme))
            break;
         pme->m_nScrollItem++;
      }

      // Draw
      if(!NO_REDRAW(pme))
         SK_Draw(pme);

      // Notify Change
      Menu_NotifySelChange(pme);
      return(TRUE);
   }
   
   return(FALSE);
}

/*=====================================================================

Local Method - Sets the SKDrawItem list for a softkey menu.  Called only
from SK_CalcLayout.

======================================================================*/
static int SK_SetDrawItems(CMenuCtl * pme, int nDir, int * pnVisibleItems, int * pnLastItem,boolean * pbNoFit)
{
   boolean        bNoFit = 0;
   int            i, x, dx,nMaxText;
   int            nRemain;
   int            nItem, nLastItem;
   CMenuItem *    p = NULL;
   SKDrawItem *   pdi = &pme->m_draw[0];

   // initial remaining space is full skb width
   nRemain = pme->m_rc.dx - CELL_SIDE_WIDTH;

   // initial x position for SK items
   x = pme->m_rc.x + CELL_LMARGIN;

   if (nDir < 0) {   // R -> L
      x += nRemain;  // Rightmost position to start drawing from
      nItem = nLastItem = pme->m_nSelect;       // First item to draw
   }
   else              // L -> R
      nItem = nLastItem = pme->m_nScrollItem;   // First item to draw

   // get starting menu item
   p = GetItemByIdx(pme->m_pItemList, nItem);

   // traverse item list from L->R or R->L

   for (i = 0; nRemain && p && i < pme->m_nPageItems; i++) {

      // Mark selected if item is selected
      pdi->bSelected = (nItem == pme->m_nSelect);

      // Size of any cell is the text width + frame + offset...
      dx = (IS_ITEM_OD(p) ? p->cxImage : (GetItemWidth(p) + pme->m_cxItemExtra));

   // see if item fits in remaining space

      if ((nRemain - dx) < 0) {           // Item doesn't fit

         // Max Text Space = Remaining Space - dot width - width added for style
         nMaxText = nRemain - (IS_ITEM_OD(p) ? dx : (pme->m_cxDotWidth + pme->m_cxItemExtra));

         if (p->pImage || nMaxText <= 0 || nRemain < MIN_CELL_WIDTH) 
            break;         // don't count item

         // We are clipping so we will fill the entire rect...

         dx = nRemain;

         // Width of truncated text
         pdi->xTruncatedExtent = IDISPLAY_MeasureTextEx(pme->m_pIDisplay, p->nFont, p->text, -1, nMaxText, &pdi->cbFits);

         bNoFit = 1;       // Doesn't fit
      }
      else
         pdi->xTruncatedExtent = 0;       // Items Fits

      // if going R->L, subtract cell width first to get x position
      if (nDir < 0)
         x -= dx;

      // add entry to draw item array

      pdi->x         = x;        // x position
      pdi->dx        = dx;       // width
      pdi->bTrunc    = bNoFit;   // Truncated
      pdi->pmi       = p;        // Item
      pdi++;

      // if going L->R, add cell width to get next x position
      if (nDir > 0)
         x += dx;

      nRemain -= dx;
      nLastItem = nItem;

      // Next item 
      if (nDir < 0) {
         if (nItem >= 0)
            p = GetItemByIdx(pme->m_pItemList, --nItem);
      }
      else {
         nItem++;
         p = p->pNext;
      }
   }

   if (pnVisibleItems)
      *pnVisibleItems = i;          // Number of visible items

   if (pnLastItem)
      *pnLastItem = nLastItem;      // Index of item added last

   if (pbNoFit)
      *pbNoFit = bNoFit;            // Flag to indicate that last item was truncated

   return nRemain;                  // Remaining space 
}

/*=====================================================================

Local Method - Handles the Pen events to map to hit data

======================================================================*/
static uint8 SK_HandlePen(CMenuCtl * pme, int16 wXPos, int16 wYPos, uint16 * pwExtraData, uint16 * pwIndex)
{
   SKDrawItem *   pdi   = NULL;
   AEERect        rct;
   int            i;

   // Looking to Fill in the Visible Items and their rects
   SK_CalcLayout(pme);
   // Each item's rect is inside a fixed y boundary
   rct.y = pme->m_rc.y;
   rct.dy = pme->m_rc.dy;
   for( i = 0; i < pme->m_nVisibleItems; i++ ){
      // Cycle through the items to see whether one was hit
      pdi = &pme->m_draw[i];
      rct.x = pdi->x;
      rct.dx = pdi->dx;

      if(PT_IN_RECT(wXPos,wYPos,rct)){
         int nIdx;
         // Hit! Now store the selection item for later use.
         CMenuItem * pItem = GetItemByIDEx(pme->m_pItemList, pdi->pmi->nItemID, NULL, &nIdx);
         if( pItem ){
            *pwIndex  = (uint16)nIdx;
            *pwExtraData = pdi->pmi->nItemID;
            return PTRCK_HIT_ITEM;
         }
         return 0;
      }
   }
   // Check right arrow first, as it is the most likely repeat hit candidate
   SETAEERECT(&rct, pme->m_rc.x + pme->m_rc.dx - ARROWMARGIN, pme->m_rc.y,ARROWMARGIN, pme->m_rc.dy);
   if( PT_IN_RECT(wXPos, wYPos, rct) ) {
      return PTRCK_HIT_BELOW;
   }
   // Now check the left arrow
   SETAEERECT(&rct, pme->m_rc.x, pme->m_rc.y, ARROWMARGIN, pme->m_rc.dy);
   if( PT_IN_RECT(wXPos, wYPos, rct) ) {
      return PTRCK_HIT_ABOVE;
   }
   return 0;
}
/*=====================================================================

Local Method - Recalculates the layout of a softkey menu.

======================================================================*/
static boolean SK_CalcLayout(CMenuCtl * pme)
{
   boolean bNoFit = 0;
   int i;
   int nRemain;
   int nLastItem;

   // we must not exceed the array bounds
   if (pme->m_nPageItems > MAX_SOFT_PAGE_ITEMS)
      pme->m_nPageItems = MAX_SOFT_PAGE_ITEMS;

   // traverse left->right
   nRemain = SK_SetDrawItems(pme, 1, &pme->m_nVisibleItems, &nLastItem, &bNoFit);

   // selected item is not visible, signal that a scroll is required
   if (pme->m_nSelect > nLastItem)
      return(FALSE);

   // if last item is the selected item and did not fit
   // traverse from right->left
   if (bNoFit && (nLastItem == pme->m_nSelect)) {
      nRemain = SK_SetDrawItems(pme, -1, &pme->m_nVisibleItems, &nLastItem, &bNoFit);
      // current scroll item is not visible, signal that a scroll is required
      if (nLastItem > pme->m_nScrollItem)
         return(FALSE);
   }

   // See if we should evenly distribute the buttons.  We do this if:
   // - There is space left; AND
   // - The number of items visible is > 0; AND
   // - (It's a text-only menu; OR It's an icon menu and there are more items to display (offscreen))

   if((nRemain && pme->m_nVisibleItems > 0) && (pme->m_bHasText || pme->m_nVisibleItems < pme->m_nItems)) {

      int cxPerItem = nRemain / pme->m_nVisibleItems;
      int j;
      nRemain %= pme->m_nVisibleItems;
      if (cxPerItem > 0) {
         for (i=0; i < pme->m_nVisibleItems; i++) {
            pme->m_draw[i].dx += cxPerItem;           // Increase Item Cell Width
            
            for (j=i+1; j < pme->m_nVisibleItems; j++)
               pme->m_draw[j].x += cxPerItem;         // Adjust cell start position of remaining cells
         }
      }
      if (nRemain) {
         for (i=0; i < pme->m_nVisibleItems; i++) {
            pme->m_draw[i].dx++;                      // Increment Item Cell Width
            
            for (j=i+1; j < pme->m_nVisibleItems; j++)
               pme->m_draw[j].x++;                    // Adjust cell start position of remaining cells

            if (--nRemain == 0) 
               break;
         }
      }
   }

   return(TRUE);
}

/*=====================================================================

Local Method - Returns Item Rect corresponding to the index

======================================================================*/
static void SK_CalcItemRect(CMenuCtl * pme, uint16 nIdx, AEERect* prc)
{
   int            i;
   SKDrawItem *   pdi = &pme->m_draw[0];
   CMenuItem *    pmi = NULL;
   CMenuItem *    pmiTemp = NULL;

   pmiTemp = GetItemByIdx(pme->m_pItemList, nIdx);
   
   for (i = 0; i < pme->m_nVisibleItems; i++, pdi++) {

      pmi = pdi->pmi;   // Item

      if (pmi->nItemID == pmiTemp->nItemID)
      {
         SETAEERECT(prc, pdi->x, pme->m_rc.y, pdi->dx, pme->m_rc.dy);
         return;
      }
   }
}

/*=====================================================================

Local Method - Draws the softkey menu.

======================================================================*/
static boolean SK_Draw(CMenuCtl * pme)
{
   int            i;
   AEERect        qrc;
   int            cx;                                      // x origin of text
   int            cyCell;                                  // height of cell
   int            yCell;                                   // y origin of cell
   int            yArrow;                                  // y origin of arrow
   int            xRight, xLeft;
   RGBVAL         clrBack, clrText,clrFrame;
   boolean        bSel;
   AEEFrameType   ft;
   SKDrawItem *   pdi = &pme->m_draw[0];
   IDisplay *     pd = pme->m_pIDisplay;
   CMenuItem *    pmi = NULL;

   cyCell      = pme->m_rc.dy;              
   yCell   = pme->m_rc.y;
   
   yArrow   = yCell + ((cyCell - 2) / 2); // Position arrow in the middle
   
   // initialize with menu colors
   Menu_InitColors(pme, &clrBack, &clrText, &clrFrame);

   // erase the previous soft key bar
   SETAEERECT(&qrc, pme->m_rc.x, yCell, pme->m_rc.dx, cyCell);
   IDISPLAY_FillRect(pd, &qrc, CLR_USER_BACKGROUND);

	// Make sure we draw the appropriate line across any empty tabs.  This is
	// necessary to cover the tab borders above the arrows and any empty (unpainted) cells
	ft = pme->m_style[0].ft;
   if(!IS_MULTI_IMAGE(pme) && (ft == AEE_FT_TAB_BOTTOM || ft == AEE_FT_TAB_TOP)){
		qrc = pme->m_rc;
      IDISPLAY_DrawFrame(pd, &qrc, ft, pme->m_c.cBack);
   }

   // draw each item
   for (i = 0; i < pme->m_nVisibleItems; i++, pdi++) {
      pmi = pdi->pmi;   // Item

      // Center the text in the rect...

      cx = pdi->dx;     // Cell Width

   // Reset the rect each time.  SKDrawItem bombs it...

      SETAEERECT(&qrc, pdi->x, yCell, cx, cyCell);

      bSel = (boolean)(pdi->bSelected && pme->m_bActive);

      if (IS_ITEM_OD(pmi))
         Menu_DrawODItem(pme, pmi, &qrc, bSel);
      else
         Menu_DrawItem(pme, pmi, &qrc, bSel, pdi->xTruncatedExtent);
   }
   
   // draw the arrows if multiple items

   if (pme->m_nItems > 1) {
      xLeft = pme->m_rc.x + 1;
      xRight = pme->m_rc.x + pme->m_rc.dx - CELL_RMARGIN + 1;
      Menu_DrawArrows(pme, xLeft, yArrow, xRight);
   }

   // Restore Colors
   ResetColors(pd, clrBack, clrText, clrFrame);

   IDISPLAY_Update(pd);
   
   return(TRUE);
}

//===========================================================================
//
//
//
//                ICON VIEW CODE
//
//
//
//===========================================================================
/*=====================================================================

Local Method - Handles keys of interest to the icon view menu.

======================================================================*/
static boolean Icon_HandleKey(CMenuCtl * pme, uint16 wKey)
{
   int            nSel, nLast, nMax, nDir, nRow, nCol, nNewRow;
   AEERect        rc, rcNew;
   boolean        bScroll;
   RGBVAL         clrBack, clrText, clrFrame;
   CMenuItem *    pmi = NULL;
   CMenuItem *    pmiNew = NULL;

   nSel = nLast = pme->m_nSelect;   // Index of current selection
   nMax = pme->m_nItems - 1;        // Index of last item

   switch(wKey){

      case AVK_RIGHT:
      case AVK_LEFT:

         // Only allow right/left if the displayed columns is > 1 OR there is only 1 row...
         if(pme->m_nCols <= 1 && pme->m_nRows > 1)
            return(TRUE);
   
         // Direction
         nDir = (wKey == AVK_RIGHT ? 1 : -1);

         // Change Selection
         nSel += nDir;

         // Wrap
         if(nSel < 0)
            nSel = nMax;
         else {
            if(nSel > nMax)
               nSel = 0;
         }

         // If MP_ICON_NO_CENTERSEL, don't keep position of selection fixed
         if (pme->m_bIconHScroll && (pme->m_nItems > pme->m_nCols) && (pme->m_dwProps & MP_ICON_NO_CENTERSEL))
         {
            if (nDir > 0)
            {
               if (nSel == 0)
                  pme->m_nScrollItem = 0;
               else {
                  if (nSel >= pme->m_nCols)
                     pme->m_nScrollItem++;
               }
            }
            else
            {
               if (nSel == pme->m_nItems - 1)
                  pme->m_nScrollItem = nSel - pme->m_nCols + 1;
               else
               {
                  if (nSel < pme->m_nScrollItem)
                     pme->m_nScrollItem--;
               }
            }
         }
         break;

      case AVK_DOWN:
      case AVK_UP:

         // Only allow up/down if the displayed rows > 1
         // Modified by dpark : return right away if col count is 0
         if(pme->m_bIconHScroll || pme->m_nCols == 0)
            return(TRUE);

         // Need to adjust selection by number of displayable cols
         nDir = pme->m_nCols;

         if(wKey == AVK_UP)
            nDir = -nDir;
         
         // Get the current row and col...
         nRow = nSel / pme->m_nCols;
         nCol = nSel % pme->m_nCols;

         // Adjust Selection
         nSel += nDir;

         // If the new selection is < 0, see if we can land on the same col in the last row...
         if(nSel < 0){
            nSel = ((pme->m_nTotalRows - 1) * pme->m_nCols) + nCol;

            // If we are still too high, put us on the last item...
            if(nSel > nMax)
               nSel = nMax;
         }
         else{

            // If the new sel is > max index, If so:
            // - See if we are on the last row.  If so, then wrap to row 0;
            // - See if we are above the last row.  If so, place on the last item...
            if(nSel > nMax){
               if(nRow == pme->m_nTotalRows - 1)
                  nSel = nCol;
               else
                  nSel = nMax;
            }
         }         
         break;

      default:
         return(FALSE);
   }

   // Let's not redraw if there is no change in index of current selection or
   // index of current selection looks goofy
   if(nSel == nLast || nSel < 0 || nSel >= pme->m_nItems)
      return(TRUE);

   // Re-draw for single-row of icons...
   if(pme->m_bIconHScroll){
      pme->m_nSelect = nSel;
      Icon_Draw(pme);
      Menu_NotifySelChange(pme);
      return(TRUE);
   }

   // Redraw for multi-line of icons...
   Icon_StopAnimation(pme);

   pmi = GetItemByIdx(pme->m_pItemList, nLast);    // Previous Selection
   pmiNew = GetItemByIdx(pme->m_pItemList, nSel);  // New Selection

   if(pmi == NULL || pmiNew == NULL)
      return(TRUE);

   Icon_CalcGridRect(pme, &rcNew, nSel);           // Get rect of new selection
   Icon_CalcGridRect(pme, &rc, nLast);             // Get rect of old selection

   bScroll = FALSE;

   nNewRow = nSel/pme->m_nCols;                    // Index of new selection row

   // If rect of new selection falls outside, we need to redraw entire control
   if(rcNew.y + pme->m_cyMaxItem >= pme->m_rc.y + pme->m_rc.dy - (pme->m_bHasText ? Icon_GetImageOffset(pme) : 0)){
      pme->m_nScrollItem = nNewRow - (pme->m_nRows - 1);
      bScroll = TRUE;
   }
   else{
      if(rcNew.y <= pme->m_rc.y + (pme->m_bHasText && ALIGN_TEXT_TOP(pme) ? Icon_GetImageOffset(pme) : 0)){
         pme->m_nScrollItem = nNewRow;
         bScroll = TRUE;
      }
   }
 
   pme->m_nSelect = nSel;

   if(bScroll)
      Icon_Draw(pme);   // Draw Entire Control
   else{
      // Just update the previous and current selection
      Menu_InitColors(pme, &clrBack, &clrText, &clrFrame);

      if (IS_ITEM_OD(pmi))
         Icon_DrawODItem(pme, pmi, &rc, FALSE);
      else
         Icon_DrawItem(pme, pmi, &rc, FALSE);
      
      if (IS_ITEM_OD(pmiNew))
         Icon_DrawODItem(pme, pmiNew, &rcNew, TRUE);
      else
         Icon_DrawItem(pme, pmiNew, &rcNew, TRUE);

      ResetColors(pme->m_pIDisplay, clrBack, clrText,clrFrame);
      IDISPLAY_Update(pme->m_pIDisplay);
   }

   if(nSel != nLast)
      Menu_NotifySelChange(pme);

   return(TRUE);
}

/*=====================================================================

Local Method - Calculates rect required to display nSel in grid view.

======================================================================*/
static void Icon_CalcGridRect(CMenuCtl * pme, AEERect * prc, int nSel)
{
   int   nRow;
   int   nCol,nFrame;

   nRow = (nSel / pme->m_nCols) - pme->m_nScrollItem;    // Item Row
   nCol = nSel % pme->m_nCols;                           // Item Col

   // Frame Size
   nFrame = (int)(pme->m_bHasText ? AEE_FRAME_SIZE : 0);

   // nSel rect
   SETAEERECT(prc,  pme->m_rc.x + nFrame + pme->m_xImagePad + (nCol * (pme->m_cxMaxItem + pme->m_xImagePad)),
                  pme->m_rc.y + nFrame + pme->m_yImagePad + (nRow * (pme->m_cyMaxItem + pme->m_yImagePad)),
                  pme->m_cxMaxItem, pme->m_cyMaxItem);


   // Adjust if text on top
   if(nFrame && ALIGN_TEXT_TOP(pme))
      prc->y += Icon_GetImageOffset(pme);
}

/*=====================================================================

Local Method - Returns the height of text for the icon menu.

======================================================================*/
static uint16 Icon_GetTextHeight(CMenuCtl * pme)
{
	uint16 cy = pme->m_cyFont;
	if(SINGLE_FRAME(pme))
		cy += (pme->m_style[0].yOffset * 2);
	return(cy);
}

/*=====================================================================

Local Method - Returns the height of tallest text as height may vary if
item text is owner drawn

======================================================================*/
static uint16 Icon_GetMaxTextHeight(CMenuCtl * pme)
{
   uint16 cyTextMax = 0;
   CMenuItem * pItem = pme->m_pItemList;

   if (OD_ITEMS_ALLOWED(pme))
   {
      while (pItem)
      {
         if (pItem->cyText > cyTextMax)
            cyTextMax = pItem->cyText;

         pItem = pItem->pNext;
      }
   }
   else
      cyTextMax = Icon_GetTextHeight(pme);

	return(cyTextMax);
}

/*=====================================================================

Local Method - Returns the item number that a point hit
If TRUE, pnItem is filled, otherwise not filled

======================================================================*/
static __inline boolean Icon_MapPointToItem(CMenuCtl * pme, int16 wXPos, int16 wYPos, int * pnItem, int * pnIdx)
{
   int nCol;
   int nRow;
   int nFrame  = (pme->m_bHasText ? AEE_FRAME_SIZE : 0);
   int nYAdj   = 0;

   // Calculate offset of images clicked in!
   if( nFrame && ALIGN_TEXT_TOP(pme) ){
      nYAdj = Icon_GetImageOffset(pme);
   }
   // Estimate Row and Column
   nCol = (wXPos - pme->m_rc.x - nFrame - pme->m_xImagePad)/(pme->m_cxMaxItem + pme->m_xImagePad);
   nRow = ((wYPos - nYAdj) - pme->m_rc.y - nFrame - pme->m_yImagePad)/(pme->m_cyMaxItem + pme->m_yImagePad);
   // If the Row and Column are on screen AND it is below the top aligned text
   if( nCol < pme->m_nCols && nRow < pme->m_nRows && wYPos > nYAdj ){
      AEERect  rctItem;
      int      nItem = ((nRow + pme->m_nScrollItem) * pme->m_nCols)+ nCol;

      // Check the rectangle of the estimated item
      Icon_CalcGridRect(pme, &rctItem, nItem);
      if( PT_IN_RECT(wXPos, wYPos, rctItem) ){
         // Hit it!
         CMenuItem *pItem = GetItemByIdx(pme->m_pItemList, nItem);
         if( pItem ){
            *pnItem = pItem->nItemID;
            *pnIdx = nItem;
            return TRUE;
         }
      }
   }
   return FALSE;
}

/*=====================================================================

Local Method - Turns off animation in an icon menu.

======================================================================*/
static void Icon_StopAnimation(CMenuCtl * pme)
{
   CMenuItem   *  pmi;
   boolean        bUnload;

   // Cancel our timers
   ISHELL_CancelTimer(pme->m_pIShell, NULL, pme);

   bUnload = (boolean)(SINGLE_FRAME(pme) ? TRUE : FALSE);

   for(pmi = pme->m_pItemList; pmi != NULL ; pmi = pmi->pNext)
   {      
      if(pmi->pImage)
      {
         // Stop animation
         IIMAGE_Stop(pmi->pImage);

         // Unload the image if we are in single-frame mode and it was
         // not passed directly to me..
         if(bUnload && pmi->pszImage)
            aee_releaseobj((void **)&pmi->pImage);
      }
   }
}
   
/*=====================================================================

Local Method - Redraws an icon menu.

======================================================================*/
static boolean Icon_Draw(CMenuCtl * pme)
{
   IDisplay *     pd = pme->m_pIDisplay;
   CMenuItem *    pmi = NULL;

   // Stop animation
   Icon_StopAnimation(pme);

   // If it's a single-frame update and we want the update deferred, only update the text and
   // delay Icon_DrawAllItems. Otherwise immediately call Icon_DrawAllItems
   if(SINGLE_FRAME(pme) && DELAY_IMAGE(pme)){
      pmi = GetItemByIdx(pme->m_pItemList, pme->m_nSelect);
      if(pmi){
         if (IS_ITEM_OD(pmi))
            Icon_DrawODItemText(pme, pmi, 0, TRUE);
         else
            Icon_DrawItemText(pme, pmi, 0, TRUE);
         ISHELL_SetTimer(pme->m_pIShell, pme->m_wMenuImageDelay , (PFNNOTIFY)(Icon_DrawAllItems), pme);
         IDISPLAY_Update(pd);
      }      
   }
   else
      Icon_DrawAllItems(pme);
   return(TRUE);
}

/*=====================================================================

Local Method - Calculates Index of first and last displayable items in
a horizontal scroll icon view menu

======================================================================*/
static void Icon_CalcStartEndItemIndx(CMenuCtl* pme, int* pnStart, int* pnEnd)
{
   int nCount, nCols, nColsLeft, nColsRight, i, nEnd;

   nCount = pme->m_nItems;
   nCols = pme->m_nCols;

   // Get Index of first and last displayable item
   if(nCount <= nCols)
   {
      // Start Index
      i = 0;

      // End Index
		if(nCount == 0)
		   nEnd = 0;
		else 
		   nEnd = nCount - 1;
   }
   else if (pme->m_dwProps & MP_ICON_NO_CENTERSEL)
   {
      // Start Index
      i = pme->m_nScrollItem;

      // End Index
      nEnd = i + pme->m_nCols - 1;
      if(nEnd >= nCount)
         nEnd -= nCount;
   }
   else{
      // If we are displaying a single row and MP_ICON_NO_CENTERSEL is not sel, 
      // attempt to draw the current selection in the middle of the screen...

      // See what col we want to show the selected item in...

      nColsLeft = nColsRight = nCols / 2;

      // If it's not an odd boundary, we need to backup 1...
      if(!(nCols & 0x0001)){
         nColsLeft--;
         nColsRight++;
      }

      // Now we need to get the start and end indexes...

      i = pme->m_nSelect - nColsLeft;
      if(i < 0)
         i += nCount;

      nEnd = pme->m_nSelect + nColsRight;
      if(nEnd >= nCount)
         nEnd -= nCount;
   }
   *pnStart = i;
   *pnEnd = nEnd;
}

/*=====================================================================

Local Method - Calculates Item Rect

======================================================================*/
static void Icon_CalcItemRect(CMenuCtl * pme, uint16 nIdx, AEERect* prc)
{
   CMenuItem   *  pmiTemp = NULL;
   CMenuItem   *  pmi = NULL;
   AEERect        rc;
   int            i, xStart, xPad, yPad, nFrame, yOffset;

   // Get Item
   pmiTemp = GetItemByIdx(pme->m_pItemList, nIdx);

   rc = pme->m_rc;

   nFrame = 0;

   if(pme->m_bHasText){
      yOffset = Icon_GetImageOffset(pme);    // Text Height + Frame Size
      rc.dy -= Icon_GetMaxTextHeight(pme);   // Adjust control rect height by text height

      // For top aligned text, frame should appear underneath text
      if(ALIGN_TEXT_TOP(pme))
         rc.y += yOffset;
   }
   else{
      yOffset = 0;
   }			

   xPad = pme->m_xImagePad;
   yPad = pme->m_yImagePad;
   xStart = pme->m_rc.x + nFrame + xPad;

   SETAEERECT(&rc, xStart, pme->m_rc.y + nFrame + yPad, pme->m_cxMaxItem, pme->m_cyMaxItem);

   // Adjust for space taken by text
   if(nFrame && ALIGN_TEXT_TOP(pme))
      rc.y += yOffset;

   if(pme->m_bIconHScroll){
      int nEnd, nCount;

      nCount = pme->m_nItems;

      Icon_CalcStartEndItemIndx(pme, &i, &nEnd);

      // Update Draw Rect if item is displayable
      while(1){

         pmi = GetItemByIdx(pme->m_pItemList, i);

         if (pmi->nItemID == pmiTemp->nItemID)
         {
            *prc = rc;
            return;
         }

         rc.x += pme->m_cxMaxItem + xPad;
   
         if(i == nEnd)
            break;

         if(++i >= nCount)
            i = 0;
      }
   }
   else{
      int nRow, nCol;

      // Fast-forward to the right item row...

      i = pme->m_nCols * pme->m_nScrollItem;
      pmi = GetItemByIdx(pme->m_pItemList, i);

      for(nCol = nRow = 0; pmi != NULL; pmi = pmi->pNext, i++){

         if (pmi->nItemID == pmiTemp->nItemID)
         {
            *prc = rc;
            return;
         }
         
         // Update the x coordinate...
         rc.x += pme->m_cxMaxItem + xPad;
         nCol++;

         // If we are falling out, reset x and update y
         if(nCol >= pme->m_nCols){
            nCol = 0;
            rc.x = xStart;
            rc.y += pme->m_cyMaxItem + yPad;

            // If we are falling out here, break as we have checked all displayable items
            if(++nRow >= pme->m_nRows)
               break;
         }
      }
   }
}

/*=====================================================================

Local Method - Redraws all icon menu items.

======================================================================*/
static void Icon_DrawAllItems(CMenuCtl * pme)
{
   IDisplay *     pd = pme->m_pIDisplay;
   CMenuItem   *  pmi = NULL;
   AEERect        rc;
   int            i, xStart, xPad, yPad, nFrame, yOffset;
   RGBVAL         clrBack, clrText, clrFrame;

   // Initialize Colors
   Menu_InitColors(pme,&clrBack, &clrText, &clrFrame);

   // Fill control rect with CLR_USER_BACKGROUND
   IDISPLAY_FillRect(pd, &pme->m_rc, CLR_USER_BACKGROUND);

   rc = pme->m_rc;

   if(pme->m_bHasText){
		yOffset = Icon_GetImageOffset(pme);    // Text Height + Frame Size
      rc.dy -= Icon_GetMaxTextHeight(pme);   // Adjust control rect height by text height

      // For top aligned text, frame should appear underneath text
      if(ALIGN_TEXT_TOP(pme))
         rc.y += yOffset;

      // Draw Frame
      IDISPLAY_DrawFrame(pd, &rc, AEE_FT_BOX, CLR_USER_BACKGROUND);
      nFrame = AEE_FRAME_SIZE;
   }
   else{
		yOffset = 0;
      nFrame = 0;
	}			

   xPad = pme->m_xImagePad;
   yPad = pme->m_yImagePad;
   xStart = pme->m_rc.x + nFrame + xPad;

   SETAEERECT(&rc, xStart, pme->m_rc.y + nFrame + yPad, pme->m_cxMaxItem, pme->m_cyMaxItem);

   // Adjust for space taken by text
   if(nFrame && ALIGN_TEXT_TOP(pme))
      rc.y += yOffset;

   if(pme->m_bIconHScroll){
      int nEnd, nCount;

      nCount = pme->m_nItems;

      Icon_CalcStartEndItemIndx(pme, &i, &nEnd);

      // Draw all of the items...
      while(1){

         pmi = GetItemByIdx(pme->m_pItemList, i);

         if (pmi)
         {
            if (IS_ITEM_OD(pmi))
               Icon_DrawODItem(pme, pmi, &rc, (boolean)(pme->m_bActive && (i == pme->m_nSelect)));
            else
               Icon_DrawItem(pme, pmi, &rc, (boolean)(pme->m_bActive && (i == pme->m_nSelect)));

            rc.x += pme->m_cxMaxItem + xPad;
         }
   
         if(i == nEnd)
            break;

         if(++i >= nCount)
            i = 0;
      }
      // Restore background, text and frame colors
      ResetColors(pd, clrBack, clrText,clrFrame);
   }
   else{
      int nCol, nRow;

   // Fast-forward to the right item row...

      i = pme->m_nCols * pme->m_nScrollItem;
      pmi = GetItemByIdx(pme->m_pItemList, i);

      for(nCol = nRow = 0; pmi != NULL; pmi = pmi->pNext, i++){

         if (IS_ITEM_OD(pmi))
            Icon_DrawODItem(pme, pmi, &rc, (boolean)(pme->m_bActive && (i == pme->m_nSelect)));
         else
            Icon_DrawItem(pme, pmi,&rc,(boolean)(pme->m_bActive && (i == pme->m_nSelect)));


         // Update the x coordinate...
         rc.x += pme->m_cxMaxItem + xPad;
         nCol++;

         // If we are falling out, reset x and update y
         if(nCol >= pme->m_nCols){
            nCol = 0;
            rc.x = xStart;
            rc.y += pme->m_cyMaxItem + yPad;

            // If we are falling out here, break as we have displayed all
            // we could display
            if(++nRow >= pme->m_nRows)
               break;
         }
      }

      // Must reset the colors before drawing the scrollbar
      ResetColors(pd, clrBack, clrText,clrFrame);

      // Draw scroll bar
      Menu_DrawScrollBar(pme);
   }
   IDISPLAY_Update(pd);
}

/*=====================================================================

Local Method - Redraws an icon menu item.

======================================================================*/
static void Icon_DrawItem(CMenuCtl * pme, CMenuItem * pmi, AEERect * prc, boolean bSel)
{
   AEERect           rc;
   boolean           bSelDraw, bSelMark, bCached;
   AEERasterOp       ro;
   AEEItemStyle *    ps = NULL;
   IImage *          pi = NULL;
   IDisplay *        pd = pme->m_pIDisplay;
	uint16			   cyText;

	if(!pmi)
		return;

   bSelMark = pme->m_bActive ? bSel : FALSE;

   pi = pmi->pImage;

	cyText = Icon_GetMaxTextHeight(pme);

   rc = *prc;
   ps = &pme->m_style[(bSelMark ? 1 : 0)];

   // If it's a single-frame icon view:
   //
   // - Draw the image UNSELECTED
   // - Draw the text SELECTED
   //

   // NOTE - Force reload of image if necessary...

   if(SINGLE_FRAME(pme)){

   // Must decrease the rect of the area by the text if necessary...

      if(pme->m_bHasText)
         rc.dy -= cyText;

      bSelDraw = FALSE;

      // Load Image & Calculate Size
      if(!pi && pmi->pszImage){
         pi = Menu_LoadImage(pme, pmi->pszImage, pmi->wImageID, &bCached);
         if(pi)
            Menu_SetupImage(pme, pmi, pi);
      }
   }
   else{
      bSelDraw = bSelMark;

      // Draw Frame
      IDISPLAY_DrawFrame(pd, &rc, ps->ft, (bSelMark ? pme->m_c.cSelBack : pme->m_c.cBack));

      // Adjust Rect for Style
      AdjustRect(&rc,ps);
   }

   if(pi){
      int      x, y, cx, cy;
      boolean  bLimit = FALSE;

      // Ensure image fits in. Limit the image size otherwise

      if(pmi->cxImage > rc.dx){
         cx = rc.dx;
         bLimit = TRUE;
      }
      else
         cx = pmi->cxImage;

      if(pmi->cyImage > rc.dy){
         cy = rc.dy;
         bLimit = TRUE;
      }
      else
         cy = pmi->cyImage;

      x = rc.x + ((rc.dx - cx) / 2);
      y = rc.y + ((rc.dy - cy) / 2);

      if(bLimit)
         IIMAGE_SetParm(pi, IPARM_SIZE, cx, cy);

      ro = ps->roImage;

      // Just copy if this isn't selected item or device is monochrome
      if(!bSelDraw && !pme->m_bColor)
         ro = AEE_RO_COPY;

		IIMAGE_SetParm(pi, IPARM_ROP, (int)ro, 0);

      // Make sure we reset the color of the background here.  This ensures that 
      // the image will be displayed in the correct color and will continue to do
      // so during animation...
      IDISPLAY_SetColor(pd, CLR_USER_BACKGROUND, (bSelDraw ? pme->m_c.cSelBack : pme->m_c.cBack));

      // Animate if image is animation and item is selected item. Draw otherwise.
      if(IS_ANIMATED(pme) && bSelMark)
         IIMAGE_Start(pi, x, y);
      else
         IIMAGE_Draw(pi, x ,y);
   } 

   // Draw Item Text
   Icon_DrawItemText(pme, pmi, cyText, bSel);
}

/*=====================================================================

Local Method - Requests draw for an owner drawn icon menu item.

======================================================================*/
static void Icon_DrawODItem(CMenuCtl * pme, CMenuItem * pmi, AEERect * prc, boolean bSel)
{
	uint16	   cyText;
   AEEODCBData cbData;
   IDisplay * pd = pme->m_pIDisplay;

	if(!pmi)
		return;

   cbData.rc = *prc;
   bSel &= pme->m_bActive;

	cyText = Icon_GetMaxTextHeight(pme);

   if(SINGLE_FRAME(pme)){
      if(pme->m_bHasText)
         cbData.rc.dy -= cyText;
   }

   // Make sure we set the color of the background in display context
   IDISPLAY_SetColor(pd, CLR_USER_BACKGROUND, (bSel ? pme->m_c.cSelBack : pme->m_c.cBack));

   // Initialize callback for owner draw item        
   MEMSET(&cbData, 0, sizeof(AEEODCBData));

   cbData.nSize = sizeof(AEEODCBData);
   cbData.nAction = ODA_DRAW_ITEM;
   cbData.pIDisplay = pd;
   cbData.nActionInfo = pmi->nItemID;
   cbData.nActionInfo <<= 16;
   if (bSel)
      cbData.nActionInfo |= 0x00000001;

   // Draw Item 
   BRIDLE_ASSERT_SAFE_PFN(pme->m_pfnCB);
   pme->m_pfnCB(pme->m_pvContext, &cbData);

   // Draw Item Text
   Icon_DrawODItemText(pme, pmi, cyText, bSel);
}

/*=====================================================================

Local Method - Redraws icon menu text.

======================================================================*/
static void Icon_DrawItemText(CMenuCtl * pme, CMenuItem * pmi, uint16 cyText, boolean bSel)
{
   AEERect        rc;
   IDisplay    *  pd = pme->m_pIDisplay;

   if(bSel && pme->m_bHasText){
      uint16 cyMaxText = Icon_GetMaxTextHeight(pme);

      if(!cyText)
	      cyText = cyMaxText;

      SETAEERECT(&rc, pme->m_rc.x, pme->m_rc.y, pme->m_rc.dx, cyText);

      // Adjust if text is at the bottom
      if(!ALIGN_TEXT_TOP(pme))
			rc.y += (pme->m_rc.dy - cyText);
   
      // We actually increment the size of the text by 1 pixel so that it hits the 
      // border of the box...
      rc.dy++;

      if(*pmi->text){

         uint16 adjust = (cyMaxText - Icon_GetTextHeight(pme))/2;

         // Adjust text rect for arrows
         if(!NO_ARROWS(pme) && pme->m_bIconHScroll){
            rc.x += (CXARROW + 1);        // Arrow + Space
            rc.dx -= 2*(CXARROW + 1);
         }

         // Set text/background color as SELECTED
         Menu_SetItemColor(pme, pd, TRUE,NULL);

         // Draw item text
         IDISPLAY_DrawText(pd, pmi->nFont, pmi->text, -1, rc.x, rc.y+adjust, &rc, IDF_RECT_FILL|IDF_ALIGN_CENTER|IDF_ALIGN_MIDDLE);

         // Draw Arrows
         if(pme->m_bIconHScroll && !NO_ARROWS(pme))
            Menu_DrawArrows(pme, pme->m_rc.x, rc.y + ((rc.dy - CYARROW)/2), rc.x + rc.dx + 1);
      }
      else
         IDISPLAY_FillRect(pd, &rc, pme->m_c.cSelBack);  // Fill with selection background color
   }
}


/*=====================================================================

Local Method - Calls draw for owner drawn icon menu text.

======================================================================*/
static void Icon_DrawODItemText(CMenuCtl * pme, CMenuItem * pmi, uint16 cyText, boolean bSel)
{
   AEERect        rc;
   IDisplay    *  pd = pme->m_pIDisplay;

   if(bSel && pme->m_bHasText){

      AEEODCBData cbData;

      if(!cyText)
	      cyText = Icon_GetMaxTextHeight(pme);

      SETAEERECT(&rc, pme->m_rc.x, pme->m_rc.y, pme->m_rc.dx, cyText);

      // Adjust if text is at the bottom
      if(!ALIGN_TEXT_TOP(pme))
			rc.y += (pme->m_rc.dy - cyText);
   
      // We actually increment the size of the text by 1 pixel so that it hits the 
      // border of the box...
      rc.dy++;

      // Adjust text rect for arrows
      if(!NO_ARROWS(pme) && pme->m_bIconHScroll){
         rc.x += (CXARROW + 1);        // Arrow + Space
         rc.dx -= 2*(CXARROW + 1);
      }

      // Set text/background color as SELECTED
      Menu_SetItemColor(pme, pd, TRUE,NULL);

      // Initialize cbData
      MEMSET(&cbData, 0, sizeof(AEEODCBData));

      cbData.nSize = sizeof(AEEODCBData);
      cbData.nAction = ODA_DRAW_ITEM_TEXT;
      cbData.pIDisplay = pd;
      cbData.rc = rc;
      cbData.nActionInfo = pmi->nItemID;
      cbData.nActionInfo <<= 16;
      if (bSel)
         cbData.nActionInfo |= 0x00000001;

      // Draw Item 
      BRIDLE_ASSERT_SAFE_PFN(pme->m_pfnCB);
      pme->m_pfnCB(pme->m_pvContext, &cbData);

      // Draw Arrows
      if(pme->m_bIconHScroll && !NO_ARROWS(pme))
         Menu_DrawArrows(pme, pme->m_rc.x, rc.y + ((rc.dy - CYARROW)/2), rc.x + rc.dx + 1);
   }
}

/*=====================================================================

Local Method - Recalculates the number of rows in an icon menu.

======================================================================*/
static void Icon_CalcRows(CMenuCtl * pme)
{
   uint16   cxMax,cyMax;

   // Single Frame has one one row/col
   if(SINGLE_FRAME(pme)){
      pme->m_nRows = pme->m_nCols = 1;
      pme->m_bIconHScroll = TRUE;
      return;
   }

   // We need height of tallest item
   if(!pme->m_cyMaxItem)
	   return;

   cyMax = pme->m_rc.dy;
   cxMax = pme->m_rc.dx;

   // If items have text - Adjust for frame and text
   if(pme->m_bHasText){
      cyMax -= (Icon_GetMaxTextHeight(pme) + (2*AEE_FRAME_SIZE));
      cxMax -= (AEE_FRAME_SIZE * 2);
   }

   // Number of displayable icon rows
   pme->m_nRows = cyMax / pme->m_cyMaxItem;

   // If there are more rows than displayable and displayable rows are
   // atleast 2, we show vertical scroll bar. Otherwise we set horizontal
   // scroll flag to indicate horizontal scrolling
   if(Icon_CalcCols(pme, cxMax) > pme->m_nRows && pme->m_nRows > 1){

      // Adjust available area for scroll bar width
      cxMax -= pme->m_nSBWidth;

      // Recalculate displayable cols and total rows
      Icon_CalcCols(pme, cxMax);

      pme->m_bIconVScroll = TRUE; // Vertical Scroll
   }
   else
      pme->m_bIconHScroll = (pme->m_nRows == 1 ? TRUE : FALSE);   // Horizontal Scroll
   
   // Calculate horizontal/vertical padding around icons
   pme->m_xImagePad = (cxMax - (pme->m_nCols * pme->m_cxMaxItem)) / (pme->m_nCols + 1);
   pme->m_yImagePad = (cyMax - (pme->m_nRows * pme->m_cyMaxItem)) / (pme->m_nRows + 1);
}

/*=====================================================================

Local Method - Recalculates the number of columns in an icon menu.

======================================================================*/
static int Icon_CalcCols(CMenuCtl * pme, int cx)
{
   int   nRows,nCols;

   // Need width of widest item
   if(! pme->m_cxMaxItem)
	   return 0;

   // Number of displayable icon cols
   nCols = cx / pme->m_cxMaxItem;

   // There is atleast one col
   if(nCols == 0)
      nCols = 1;

   // Cache number of displayable icon cols
   pme->m_nCols = nCols;
   
   // Let's calculate total number of rows in this view
   nRows = pme->m_nItems / nCols;

   if(pme->m_nItems % nCols)
      nRows++;

   return((pme->m_nTotalRows = nRows));
}



/*===========================================================================


  Calendar Menu Routines


===========================================================================*/
/*=====================================================================

Local Method - Draws the time in a calendar menu view.

======================================================================*/
static void Cal_DrawTime(CMenuCtl * pme, IDisplay * pd, uint16 wTimeMin, int x, int yOrig, int y)
{  
   int16       wHour, wMins, wSecs, wHSecs;
   boolean     bPM;
   char        szTemp[12], *psz, ch;
   AEERect     rc;

   // Load small font image
   if(!pme->m_pFont){
      pme->m_pFont = ISHELL_LoadResImage(pme->m_pIShell, AEECONTROLS_RES_FILE, AEE_IDB_SMALLFONTS);
      if(!pme->m_pFont)
         return;
      IIMAGE_SetParm(pme->m_pFont, IPARM_CXFRAME, IMAGE_WIDTH, 0);
   }

   // get time string
   aee_MSECSToHM((uint32)(wTimeMin * 60l) * 1000l, &wHour, &wMins, &wSecs, &wHSecs, &bPM);
   SPRINTF(szTemp,"%02d:%02d",wHour,wMins);

   // indicate PM
   if(bPM){
      SETAEERECT(&rc, x, yOrig, 2, 2);
      IDISPLAY_FillRect(pd, &rc, CLR_USER_FRAME);
   }

   x++;
   
   for(psz = szTemp; (ch = *psz) != 0; psz++){
      if(ch == ':'){    // Draw :
         SETAEERECT(&rc, x + 1, y, 2, 1);
         IDISPLAY_FillRect(pd, &rc, CLR_USER_TEXT);
         rc.y += 3;
         IDISPLAY_FillRect(pd, &rc, CLR_USER_TEXT);
      }
      else{             // Draw Time
         if(ch != '0' || psz != szTemp)
            IIMAGE_DrawFrame(pme->m_pFont, (int)(ch - '0'), x, y);
      }

      x += IMAGE_WIDTH;
   }
}

/*=====================================================================

Local Method - Resets the calendar menu.

======================================================================*/
static void Cal_Reset(CMenuCtl * pme)
{
   pme->m_dwProps &= ~(MP_CALENDAR|MP_AUTOSCROLLTIME);

   pme->m_yLastTimeMark = INVALID_MARK;

   ISHELL_CancelTimer(pme->m_pIShell, NULL, pme);

   aee_releaseobj((void **)&pme->m_pFont);
}

/*=====================================================================

Local Method - Recalculates the page layout of a cal menu. Updates m_nPageItems
with visible calendar menu items.

======================================================================*/
static void Cal_CalcPageItems(CMenuCtl * pme)
{
   CMenuItem * pp = GetItemByIdx(pme->m_pItemList, pme->m_nScrollItem);
   uint16      cyLeft,cyFont;

   if(IS_CALENDAR_VIEW(pme)){
      cyLeft = pme->m_rc.dy;
      cyFont = pme->m_cyFont;

      if(pme->m_pTitle || IS_TITLE_OD(pme))
         cyLeft -= pme->m_rcTitle.dy;

      pme->m_nPageItems = 0;
      for (; pp && cyLeft > 0; pp = pp->pNext){
         if(GetItemHeight(pp, &cyLeft, (uint16)cyFont))
            pme->m_nPageItems++;
      }
   }
}

/*=====================================================================

Local Method - Timer callback to auto-scroll a calendar menu based on time.

======================================================================*/
static void Cal_AutoScrollCB(CMenuCtl * pme)
{
   Cal_AutoScroll(pme, FALSE);
}

/*=====================================================================

Local Method - Auto-scrolls a calendar menu based on time.

======================================================================*/
static void Cal_AutoScroll(CMenuCtl * pme, boolean bForceSel)
{
   CMenuItem *    pi;
   uint16         i, wMinNow, wMins;

   // Don't scroll if told not to do so
   if(!AUTO_SCROLL(pme))
      return;

   // Find item corresponding to the current time
   pi = Cal_FindNowItem(pme, &wMinNow, &i);

   // No item corresponding to current time
   // If its a forced selection, draw again. Get out nevertheless.
   if(!pi){
      if(bForceSel)
         Menu_Draw(pme);
      return;
   }

   if(bForceSel || (i != pme->m_nSelect && !ITEM_IS_VISIBLE(pme, i))){
      pme->m_nSelect = pme->m_nScrollItem = i;
      Menu_Draw(pme);
   }

   // Show the time marker...
   wMins = Cal_ShowTimeMark(pme, pi, i, wMinNow);

   // Update as soon as minutes corresponding to one pixel size have lapsed
   if(wMins)
      ISHELL_SetTimer(pme->m_pIShell, (uint32)(wMins * (60l * 1000l)), (PFNNOTIFY)(Cal_AutoScrollCB), pme);
}

/*=====================================================================

Local Method - Returns the item closest to the current time.

======================================================================*/
static CMenuItem * Cal_FindNowItem(CMenuCtl * pme, uint16 * pwNow, uint16 * pwIdx)
{
   CMenuItem *    pi;
   uint16         i, wMinNow;
   JulianType     jdate;

   GETJULIANDATE(GETTIMESECONDS(), &jdate);
   wMinNow = (jdate.wHour * 60) + jdate.wMinute;

   for(i = 0; (pi = GetItemByIdx(pme->m_pItemList, i)) != NULL; i++){
      if(wMinNow >= pi->wCalStart && wMinNow < pi->wCalEnd)
         break;
   }
   *pwIdx = i;
   *pwNow = wMinNow;
   return(pi);
}

/*=====================================================================

Local Method - Draws the arrows in a calendar menu.

======================================================================*/
static void Cal_DrawArrow(CMenuCtl * pme, int y)
{
   IDisplay *  pd = pme->m_pIDisplay;
   AEERect       rc;
   int         xInc, i;

   SETAEERECT(&rc, (pme->m_rc.x + pme->m_rc.dx) - 2, y - (ARROW_LINES / 2), 1, 1);

   // Adjust x for scroll bar
   if (pme->m_nItems > pme->m_nPageItems) 
      rc.x -= pme->m_nSBWidth;

   xInc = -1;

   for(i = 0; i < ARROW_LINES; i++){

      if(rc.y >= pme->m_rc.y && rc.y < pme->m_rc.y + pme->m_rc.dy)
         IDISPLAY_InvertRect(pd, &rc);

      rc.y++;

      // Invert at center point
      if(i == ARROW_LINES / 2)
         xInc = -xInc;

      rc.dx -= xInc;
      rc.x += xInc;
   }
}

/*=====================================================================

Local Method - Shows the time marker in a calendar menu.

======================================================================*/
static uint16 Cal_ShowTimeMark(CMenuCtl * pme, CMenuItem * pi, uint16 i, uint16 wMinNow)
{
   uint16         wRange, wMinPerPixel, wCenter;
   IDisplay *     pd = pme->m_pIDisplay;
   int            y;

   // Find the entry in the list duration of which spans current time

   if(!pi && (pi = Cal_FindNowItem(pme, &wMinNow, &i)) == NULL)
      return(0);

   // Erase previous mark
   if(pme->m_yLastTimeMark != INVALID_MARK)
      Cal_DrawArrow(pme, pme->m_yLastTimeMark);

   wMinPerPixel = 0;

   if(pi->cyImage && ITEM_IS_VISIBLE(pme, i)){  // Draw new mark

      wRange = pi->wCalEnd - pi->wCalStart;  // Range of time in item...

      wMinPerPixel = wRange / pi->cyImage;

      if(!wMinPerPixel)
         wMinPerPixel++;

      wCenter = (wMinNow - pi->wCalStart) / wMinPerPixel;

      y = (int)(pi->y + (wCenter - 1));
      Cal_DrawArrow(pme, y);
   }
   else{
      if(i < pme->m_nScrollItem)
         wMinPerPixel = 15;      

      y = INVALID_MARK;
   }

   pme->m_yLastTimeMark = y;

   IDISPLAY_Update(pd);

   return(wMinPerPixel);
}

/*=====================================================================

Local Method - Draws a caledar menu item.

======================================================================*/
static void Cal_DrawItem(CMenuCtl * pme, CMenuItem * pi, int nIdx, boolean bClear)
{
   IDisplay *  pd = pme->m_pIDisplay;
   AEERect     rc;
   int         cx,cy;
   boolean     bActive = (boolean)(nIdx == pme->m_nSelect);

   Menu_SetItemColor(pme,pd,bActive,NULL);

   cx = pme->m_rc.dx;
   cy = pi->cyImage;

   if (pme->m_nItems > pme->m_nPageItems) 
      cx -= pme->m_nSBWidth;

   SETAEERECT(&rc, pme->m_rc.x, pi->y, cx, cy);

   if(bClear)
      IDISPLAY_FillRect(pd, &rc, RGB_NONE);
   
   // If this isn't active, draw a frame around
   if(!bActive){
      if(nIdx > pme->m_nScrollItem){
         rc.y--;
         rc.dy++;
      }
      IDISPLAY_FrameRect(pd, &rc);
      rc.y = pi->y;
      rc.dy = cy;
   }

   // WARNING - This will not work properly if the colors are inverted!!!!

   // Draw Time
   Cal_DrawTime(pme, pd, pi->wCalStart, rc.x , rc.y, rc.y + ((cy - IMAGE_WIDTH)/2));

   // If this is active, invert time string
   if(bActive){
      rc.dx = IMAGE_WIDTH * 6;
      IDISPLAY_InvertRect(pd, &rc);
      rc.dx = cx;
   }

   // Adjust for tims string
   rc.x += IMAGE_WIDTH * 6;
   rc.dx -= IMAGE_WIDTH * 6;

   // If not active, ensure that text doesn't touch frame
   if(!bActive){
      rc.x++;
      rc.y++;
      rc.dx -= 2;
      rc.dy -= 2;
   }

   // Draw Text
   IDISPLAY_DrawText(pd, pi->nFont, pi->text, -1, rc.x, rc.y, &rc,IDF_RECT_FILL|IDF_ALIGN_MIDDLE|ParagraphAlignment(pi->text, WSTRLEN(pi->text)));
}


/*===========================================================================





                 UTILITY ROUTINES






===========================================================================*/
/*=====================================================================

Local Method - Returns the item based upon the item ID.

======================================================================*/
static CMenuItem * GetItemByID(CMenuItem * pl, int nID, CMenuItem ** pLast)
{
   if(pLast)
      *pLast = NULL;
   for(; pl; pl = pl->pNext){
      if(pl->nItemID == nID)
         return(pl);
      if(pLast)
         *pLast = pl;
   }
   return(NULL);
}

/*=====================================================================

Local Method - Returns the item and its index based upon the item ID

======================================================================*/
static CMenuItem * GetItemByIDEx(CMenuItem * pl, int nID, CMenuItem ** pLast, int* pIndex)
{
   if(pLast)
      *pLast = NULL;
   if (pIndex)
      *pIndex = 0;
   for(; pl; pl = pl->pNext){
      if(pl->nItemID == nID)
         return(pl);
      if(pLast)
         *pLast = pl;
      if(pIndex)
         (*pIndex)++;
   }
   return(NULL);
}

/*=====================================================================

Local Method - Returns TRUE if the x/y intersects the rectangle.

======================================================================*/
static boolean Intersects(AEERect * prc, int x, int y)
{
   return(x >= prc->x && x < prc->x + prc->dx && y >= prc->y && y < prc->y + prc->dy);
}


/*=====================================================================

Local Method - Returns the item based upon the index value.

======================================================================*/
static CMenuItem * GetItemByIdx(CMenuItem * pl, int nIdx)
{
   int   i;

   for(i = 0; pl; pl = pl->pNext, i++){
      if(i == nIdx)
         return(pl);
   }

   return(NULL);
}

/*=====================================================================

Local Method - Resets the display colors.

======================================================================*/
static void ResetColors(IDisplay * pd, RGBVAL clrBack, RGBVAL clrText,RGBVAL clrFrame)
{
   IDISPLAY_SetColor(pd, CLR_USER_BACKGROUND, clrBack);
   IDISPLAY_SetColor(pd, CLR_USER_TEXT, clrText);
   IDISPLAY_SetColor(pd, CLR_USER_FRAME, clrFrame);
}


/*=====================================================================

Local Method - Returns the calendar menu item height in a menu. Also 
updates pwLeft with the space taken in drwaing specified item.

======================================================================*/
static uint16 GetItemHeight(CMenuItem * p, uint16 * pwLeft, uint16 wLine)
{
   uint16   wh,wDur,wl;

   wl = *pwLeft;

   wDur = p->wCalEnd - p->wCalStart;

   wh = wLine;

   if(wDur <= 30 && !p->text[0] && wl >= wLine + (IMAGE_WIDTH + 2))
      wh = IMAGE_WIDTH + 6;
   else{
      if(wDur >= 120)
         wh += wLine;
   }
   
   if(wh > wl){
      wh = wl;
      wl = 0;
   }
   else{
      wl -= wh;
      if(wl < wLine){
         wh += wl;
         wl = 0;
      }
   }

   *pwLeft = wl;
   return(wh);
}

/*=====================================================================

Local Method - Adjusts the size of the rect based upon the style.

======================================================================*/
static void AdjustRect(AEERect * prc, AEEItemStyle * ps)
{
   prc->x += ps->xOffset;
   prc->y += ps->yOffset;
   prc->dx -= (ps->xOffset * 2);
   prc->dy -= (ps->yOffset * 2);
}

/*=====================================================================

Local Method - Returns the width of a menu item

======================================================================*/
static int GetItemWidth(CMenuItem * pi)
{
   int   cx;

   cx = pi->cxText;  // Text Width

   // Add padding space and image width
   if(pi->cxImage){
      if(cx)
         cx += MENU_IMAGE_PAD;
      cx += pi->cxImage;
   }
   return(cx);
}
