LCDMenuModule

SwordfishUser.LCDMenuModule History

Hide minor edits - Show changes to output

Changed lines 1-2 from:
%center%http://www.sfcompiler.co.uk/wiki/uploads/AndyO/menu.png
to:
%rframe width=300px%http://www.sfcompiler.co.uk/wiki/uploads/AndyO/menu.png
A module for displaying a hierarchical menu system on a character LCD.

The module handles scrolling the menus and navigating up and down the menu levels. It triggers an event in the main program when the user selects a menu item which requires an action other then opening another menu. All the menu data is stored in Const arrays so the module uses very little RAM. It works with LCD displays from 1 to many lines and has various options to control how the menus are displayed.

To accompany the module is a basic utility - Menu Builder - which allows you to design the menu structure using a tree-view and then generates all the Const array declarations for you.

Added lines 12-14:
[[http://www.sfcompiler.co.uk/wiki/uploads/AndyO/LCDMenuModule.pdf | Download module description and instructions doc »]]

%lfloat% http://www.sfcompiler.co.uk/wiki/uploads/StevenWright/download.gif
Deleted lines 17-19:
[[http://www.sfcompiler.co.uk/wiki/uploads/AndyO/LCDMenuModule.pdf | Download PDF instructions »]]

%lfloat% http://www.sfcompiler.co.uk/wiki/uploads/StevenWright/download.gif
Changed lines 21-22 from:
[[http://www.sfcompiler.co.uk/wiki/uploads/AndyO/RunningSampleProgram.pdf | Download instructions for running sample code »]]
to:
[[http://www.sfcompiler.co.uk/wiki/uploads/AndyO/RunningSampleProgram.pdf | Download instructions for running example code »]]
Added line 29:
Changed lines 1-2 from:
%centre%http://www.sfcompiler.co.uk/wiki/uploads/AndyO/menu.png
to:
%center%http://www.sfcompiler.co.uk/wiki/uploads/AndyO/menu.png
Added lines 1-2:
%centre%http://www.sfcompiler.co.uk/wiki/uploads/AndyO/menu.png
Changed lines 6-20 from:
to:
%lfloat% http://www.sfcompiler.co.uk/wiki/uploads/StevenWright/download.gif
[[http://www.sfcompiler.co.uk/wiki/uploads/AndyO/LCDMenuModule.pdf | Download PDF instructions »]]

%lfloat% http://www.sfcompiler.co.uk/wiki/uploads/StevenWright/download.gif
[[http://www.sfcompiler.co.uk/wiki/uploads/AndyO/MenuModuleSample.zip | Download example code »]]

%lfloat% http://www.sfcompiler.co.uk/wiki/uploads/StevenWright/download.gif
[[http://www.sfcompiler.co.uk/wiki/uploads/AndyO/RunningSampleProgram.pdf | Download instructions for running sample code »]]

%lfloat% http://www.sfcompiler.co.uk/wiki/uploads/StevenWright/download.gif
[[http://www.sfcompiler.co.uk/wiki/uploads/AndyO/MenuBuilder.zip | Download Menu Builder Utility »]]

%lfloat% http://www.sfcompiler.co.uk/wiki/uploads/StevenWright/download.gif
[[http://www.sfcompiler.co.uk/wiki/uploads/AndyO/MenuBuilderUtility.pdf | Download Menu Builder Utility instructions »]]

Added lines 1-6:
!!!Downloads

%lfloat% http://www.sfcompiler.co.uk/wiki/uploads/StevenWright/download.gif
[[http://www.sfcompiler.co.uk/wiki/uploads/AndyO/Menu.zip | Download Menu module & updated LCD module »]]

Changed lines 696-697 from:
   If (PointerLine >= LCDLines - 1 Or PointerPosition = LastItemNumber - FirstItemNumber) And CanScroll = True Then
to:
   If (PointerLine >= LCDLines - 1 Or PointerPosition = LastItemNumber - FirstItemNumber) And
     CanScroll = True Then
Changed lines 282-283 from:
Public Const mnuMenuName(5) As String = ("MAIN MENU", "FLASH SETTINGS", "CONFIRM RESET", "LED1 Frequency", "LED2 Frequency")
to:
Public Const mnuMenuName(5) As String = ("MAIN MENU", "FLASH SETTINGS", "CONFIRM RESET",
"LED1 Frequency", "LED2 Frequency")
Changed lines 289-293 from:
"Stop Flashing", "Flash Settings", "Reset", "LED1 Frequency", "LED2 Frequency", "Back", "No", "Yes", "1Hz", "2Hz", "10Hz", "Back", "1Hz",
"2Hz", "10Hz", "Back")
Public Const mnuItemAction(17) As Byte = (255, 255, 1, 2, 3, 4, 0, 0, 255, 255, 255, 255, 1, 255, 255, 255, 1)

to:
"Stop Flashing", "Flash Settings", "Reset", "LED1 Frequency", "LED2 Frequency", "Back", "No",
"Yes", "1Hz", "2Hz", "10Hz", "Back", "1Hz", "2Hz", "10Hz", "Back")
Public Const mnuItemAction(17) As Byte = (255, 255, 1, 2, 3, 4, 0, 0, 255,
255, 255, 255, 1, 255, 255, 255, 1)

Changed lines 287-289 from:
Public Const mnuItemName(17) As String = ("Start Flashing", "Stop Flashing", "Flash Settings", "Reset", "LED1 Frequency", "LED2 Frequency", "Back", "No", "Yes", "1Hz", "2Hz", "10Hz", "Back", "1Hz", "2Hz", "10Hz", "Back")
to:
Public Const mnuItemName(17) As String = ("Start Flashing",
"Stop Flashing", "Flash Settings", "Reset", "LED1 Frequency", "LED2 Frequency", "Back", "No", "Yes", "1Hz", "2Hz", "10Hz", "Back", "1Hz",
"2Hz", "10Hz", "Back")
Changed lines 722-723 from:
   ElseIf PointerLine < LCDLines And PointerPosition + FirstItemNumber + ScrollPosition < LastItemNumber Then
to:
   ElseIf PointerLine < LCDLines And
     PointerPosition + FirstItemNumber + ScrollPosition < LastItemNumber Then
Changed lines 729-730 from:
       #If (Menu_LCDLines < 4 Or Menu_ScrollWrapping = True) And Menu_ShowScrollIndicators = True
to:
       #If (Menu_LCDLines < 4 Or Menu_ScrollWrapping = True) And
         Menu_ShowScrollIndicators = True
Changed line 1 from:
!!!Sample Code
to:
!!!Menu Module Sample Code
Deleted lines 2-6:

=]

!!!Menu Module
=code [=
Changed line 5 from:
*  Name    : LCD Menu Module                                                  *
to:
*  Name    : LCD Menu Module Sample Program                                    *
Changed line 9 from:
*  Date    : 13/07/2008                                                        *
to:
*  Date    : 5 October 2008                                                    *
Changed line 11 from:
*  Notes  : See doc "LCD Menu Module.pdf"                                   *
to:
*  Notes  : See separate doc - "Running the Sample Program.pdf"              *
Deleted lines 12-48:
*          : Menu data stored in Const arrays which are declared in seperate  *
*          : module indicated by #define MenuDataModule = "filename.BAS" in    *
*          : main program                                                      *
*          :                                                                  *
*          : Requires modified LCD.bas module.  Following sub should be placed *
*          : after sub "WriteAt":                                              *
*                                                                              *
*  -------------------------------------------------------------------------  *
    Public Sub WriteConstStringAt(pY, pX As Byte, ByRefConst pText As String) 
                                                                             
        SetLocationY(pY)                                                     
        SetLocationX(pX)                                                     
        MoveTo()                                                             
                                                                             
        EECON1 = 0
        EECON1.7 = 1
   
        TABLEPTR = @pText
       
        ASM
            TBLRD*+
        End ASM
       
        While TABLAT <> 0
           
            WriteItem(TABLAT)
           
            ASM
                TBLRD*+
            End ASM
       
        Wend
   
    End Sub
*  -------------------------------------------------------------------------  *
*                                                                              *
*                                                                              *
Deleted lines 15-24:
Module Menu

Include "LCD.bas"

#ifdef MenuDataModule
    Include MenuDataModule
#else
    #error "No MenuDataModule defined in main program"
#endif

Changed line 17 from:
'User definable options
to:
'Device, Clock and Config directives
Changed lines 20-34 from:
#option Menu_LCDLines = 4                  'Number of lines on LCD display

#option Menu_ShowMenuTitle = True          'Display Menu Name at top of screen?
#option Menu_ShowScrollIndicators = True    'Show up and down scroll indicators?
#option Menu_ScrollWrapping = False        'Scroll wraps from top to bottom?

#option Menu_PointerCharacter = 126        'Character used as pointer


#if Menu_LCDLines = 1 And Menu_ShowMenuTitle = True

    #error "Cannot show menu titles on 1 line LCD"
   
#endif

to:
Device = 18F2520
Clock = 20

'Disable MCLR on Pin 1 (RE3)
Config MCLRE = OFF

Changed line 28 from:
'Variable and Const Declarations
to:
'Module options & Includes
Changed lines 31-76 from:
'---Types

Type TEvent = Event()


'---Bring options into module
 
Private Const
    LCDLines As Byte = Menu_LCDLines,
    PointerCharacter As Byte = Menu_PointerCharacter


'---Custom Characters (only loaded if option Menu_ShowScrollIndicators = True)

#If Menu_ShowScrollIndicators = True

    'Character definitions for Up Arrow and Down Arrow scroll indicators
    Private Const ScrollIndicators(16) As Byte=($04,$0E,$1F,$00,$00,$00,$00,$00,
                                                $00,$00,$00,$00,$00,$1F,$0E,$04)
   
    Private Const UpArrow = 0
    Private Const DownArrow = 1

#EndIf
 
 
'---Private Variables
   
Private Dim FItemActionEvent As TEvent  'Holds pointer to event code in main
                                        'program which is run when an "action"
                                        'menu item is selected

Private Dim PointerPosition As Byte    'Position of pointer character (0 = 1st)
Private Dim PointerLine As Byte        'Actual LCD line of pointer       
Private Dim ScrollPosition As Byte      'Number of lines the menu is scrolled by

Private Dim CurrentMenu As Byte        'Index number of current menu
Private Dim ParentID As Byte            'Index number of parent of current menu
Private Dim FirstItemNumber As Byte    'First item number for current menu
Private Dim LastItemNumber As Byte      'Last item number for current menu


'---Public Variables

Public Dim SelectedMenuItem As Byte    'Index number of selected menu item

to:
'---Menu module options
#define MenuDataModule = "SampleDataModule.bas"
#option Menu_ShowMenuTitle = True
#option Menu_ShowScrollIndicators = True
#option Menu_ScrollWrapping = False
#option Menu_LCDLines = 4       

'---LCD module options
#option LCD_DATA = PORTA.0
#option LCD_RS = PORTA.4
#option LCD_EN = PORTA.5

'---Includes
Include "LCD.bas"
Include "Utils.bas"
Include "Menu.bas"                  'Menu Module           
Include "SampleDataModule.bas"      'Menu Data Module

Changed line 51 from:
'Helper Subs and Functions (Private - can't be called from main program)
to:
'Variable and Const Declarations
Added lines 54-81:
Dim UpButton As PORTB.4                     
Dim DownButton As PORTB.5         
Dim SelectButton As PORTB.6       
                                   
Dim LED1 As PORTB.0               
Dim LED2 As PORTB.1               

Dim UpButtonCount As Byte          '}Counters used for button debounce -
Dim DownButtonCount As Byte        '}Counters track how long each button has
Dim SelectButtonCount As Byte      '}been pressed and
Dim NoButtonCount As Byte          '}how long no button has been pressed

Const DebounceCount As Byte = 2    'Default debounce delay in units of 5ms


Dim LED1Delay As Byte              '}Delay between on/off toggle of LEDs in
Dim LED2Delay As Byte              '}units of 5mS

Dim LED1Count As Byte              '}Counters used to track time to next on/off
Dim LED2Count As Byte              '}toggle of LEDs

Dim DoFlash As Boolean              'True = flash LEDs, False = don't flash LEDs


'===============================================================================
'Interrupts and Events
'-------------------------------------------------------------------------------

Changed lines 83-85 from:
'Name:      DrawMenu(pMenuNumber as byte)    
'Purpose:  Draws current
menu, scrolled by ScrollPosition amount
'
              
to:
'Name:      Event - MenuItemSelected
'Purpose:
  Event called by menu module whenever 'action' menu item is selected
'
          Executes item action and then displays root menu
'
'Notes:    Variable 'Menu.SelectedMenuItem' holds index number of menu item.
'
'          Aliases to menu items used in Select...Case structure are declared
'          in the Menu Data Module - "SampleDataModule.bas".
'
Changed lines 94-107 from:
Private Sub DrawMenu()

Dim LineNumber As Byte                    'Line number of LCD to write at
Dim ItemNumber As Byte
                     'Index of menu item number to write

#If Menu_ScrollWrapping = True              '}
                                            '}If scroll wrapping enabled then
    Dim ItemsDrawn As Byte                  '}also need to keep track of number
                                            '}of menu items drawn on LCD
    ItemsDrawn = 0                          '}
                                            '}
#EndIf

    LCD.Cls
to:
Event MenuItemSelected()

   Select Menu.SelectedMenuItem

    Case mnuMainMenu_StartFlashing
           
           DoFlash = True
Changed lines 102-105 from:
   LineNumber = 1
    ItemNumber = FirstItemNumber + ScrollPosition
   
    #If Menu_ShowMenuTitle = True         'If option enabled, show Menu Title
to:
   Case mnuMainMenu_StopFlashing
 
         
          DoFlash = False
Changed lines 106-109 from:
       LCD.WriteConstStringAt(1, 1, mnuMenuName(CurrentMenu))
        LineNumber = 2
 
     
    #Endif
to:
   Case mnuConfirmReset_Yes
   
 
           LCD.Cls
           LCD.WriteAt(1,1,"Resetting...")
            DelayMS(500)
            ASM
                reset
            End ASM
Added lines 115-117:
   Case mnuLed1Frequency_1hz
               
            LED1Delay = 100
Changed lines 119-127 from:
   'Loop which keeps writing menu items until the following conditions exist -
    '
    '  Scroll Wrapping Enabled: write menu items (wrapping from last menu item
    '  back to first menu item as necessary) until either the number of LCD
    '  lines has been reached or all menu items have been written (ItemsDrawn =
    '  LastItemNumber - FirstItemNumber)
    '
    '  Scroll Wrapping Not Enabled: write items until the number of LCD lines
    '  has been reached or the last menu item has been written
to:
   Case mnuLed1Frequency_2hz
   
   
      LED1Delay = 50
Changed lines 123-132 from:
   Repeat

        LCD.WriteConstStringAt(LineNumber, 2, mnuItemName(ItemNumber))
       
        Inc(LineNumber)
        Inc(ItemNumber)
       
    #If Menu
_ScrollWrapping = True
       
        Inc(ItemsDrawn)
to:
   Case mnuLed1Frequency_10hz
Changed lines 125-129 from:
       If ItemNumber > LastItemNumber Then '}
                                            '}Scroll wrapping enabled so wrap
            ItemNumber = FirstItemNumber    '}from last menu item to the first
                                            '}menu item
        EndIf                              '}
to:
           LED1Delay = 10
Changed lines 127-131 from:
   Until LineNumber > LCDLines Or ItemsDrawn > LastItemNumber - FirstItemNumber
           
    #Else
       
    Until LineNumber > LCDLines Or ItemNumber > LastItemNumber
to:
   Case mnuLed2Frequency_1hz
   
            LED2Delay = 100   
Changed lines 131-133 from:
   #EndIf
to:
   Case mnuLed2Frequency_2hz
   
            LED2Delay = 50
Added lines 135-137:
   Case mnuLed2Frequency_10hz
   
            LED2Delay = 10
Changed lines 139-149 from:
   'If Menu_ShowScrollIndicators option enabled then show scroll indicators
    'depending on the following conditions -
    '
    '  Scroll Wrapping Enabled: Show both Up and Down arrows if the number of
    '  menu items exceeds the number of available LCD lines
    '
    '  Scroll Wrapping Not Enabled: Show Up arrow if menu can be scrolled up,
    '  Down arrow if there are more menu items to display
    '
    'Note: Number of available LCD lines and position of Up arrow depends on
    '      whether Menu_ShowMenuTitle is enabled or not
to:
   EndSelect
Changed lines 141-180 from:
   #If Menu_ShowScrollIndicators = True
to:
   Menu.ShowMenu                          'Show root menu

End Event


'===============================================================================
'Main Program
'-------------------------------------------------------------------------------

'Turn off analogue functions
SetAllDigital

'Initialise vars
LED1Delay = 100                            '}Both LEDs set to 1Hz flash
LED2Delay = 100                            '}frequency (500mS between toggles)

LED1Count = 0
LED2Count = 0

UpButtonCount = 0
DownButtonCount = 0
SelectButtonCount = 0
NoButtonCount = 0       

DoFlash = True


DelayMS(100)                                'Let things settle
                                           
Menu.Initialise(MenuItemSelected)          'Initialise Menu module and let it
                                            'know the name of the event handler

Menu.ShowMenu                              'Show the root menu

Low(LED1)                                  '}Make both LED pins outputs and
Low(LED2)                                  '}turn off


While True                                  'Endless loop which executes every
    DelayMS(5)                              '5mS
Changed lines 182-194 from:
       #If Menu_ScrollWrapping = True
to:
  
    'Flash the LEDs
    If DoFlash = True Then                  'If the LEDs should be flashing:
   
        Inc(LED1Count)                      '}Increase both LED counters by 1
        Inc(LED2Count)                      '}

        If LED1Count >= LED1Delay Then      '}If LED1 count has reached the delay
                                            '}value:
            Toggle(LED1)                    '}Toggle the LED and
            LED1Count = 0                  '}Reset counter to 0
                                            '}
        EndIf                              '}
Changed lines 196-341 from:
           #If Menu_ShowMenuTitle = True
to:
       If LED2Count >= LED2Delay Then      '}If LED2 count has reached the delay
                                            '}value:
            Toggle(LED2)                    '}Toggle the LED and
            LED2Count = 0                  '}Reset counter to 0
                                            '}
        EndIf                              '}

    EndIf


    'Check and debounce buttons
    If NoButtonCount = DebounceCount Then  '}If no button has been pressed for
                                            '}10mS:
                                            '}
        If UpButton = 1 Then                '}Check each button and if a button
                                            '}is pressed then increase that
            Inc(UpButtonCount)              '}button's counter by 1
                                            '}
        ElseIf DownButton = 1 Then          '}
                                            '}
            Inc(DownButtonCount)            '}
                                            '}
        ElseIf SelectButton = 1 Then        '}
                                            '}
            Inc(SelectButtonCount)          '}
                                            '}
        EndIf                              '}

    ElseIf (PORTB And %01110000) = 0 Then  '}If less than 10mS since the last
                                            '}button push and no button is being
        Inc(NoButtonCount)                  '}pressed then increase the
                                            '}No-button counter by 1
    EndIf                                  '}
                                           
                                           
    If UpButtonCount = DebounceCount Then  '}For each button, if the counter
                                            '}has reached 2 (i.e. the button has
        UpButtonCount = 0                  '}been pushed for 10mS) then:
        NoButtonCount = 0                  '}
        Menu.MoveUp                        '}Reset that button's counter,
                                            '}Reset the No-button counter, and
    EndIf                                  '}Call the appropriate Menu sub -
                                            '}i.e. Up, Down or SelectItem
    If DownButtonCount = DebounceCount Then '}
                                            '}
        DownButtonCount = 0                '}
        NoButtonCount = 0                  '}
        Menu.MoveDown                      '}
                                            '}
    EndIf                                  '}
                                            '}
    If SelectButtonCount = DebounceCount Then '}
                                            '}
        SelectButtonCount = 0              '}
        NoButtonCount = 0                  '}
        Menu.SelectItem                    '}
                                            '}
    EndIf                                  '}

Wend
=]

!!!Menu Module Sample Code Data
=code [=
{
********************************************************************************
*  Name    : LCD Menu Sample Data Module                                      *
*  Author  : AndyO                                                            *
*  Notice  : Copyright (c) 2008 AndyO                                          *
*          : All Rights Reserved                                              *
*  Date    : 04/09/2008                                                        *
*  Version : 1.0                                                              *
*  Notes  : For use with LCD Menu Module sample program.                      *
*          :                                                                  *
*          : All Const declarations generated by Menu Builder utility.        *
*          :                                                                  *
********************************************************************************
}

Module SampleDataModule


'===============================================================================
'Menu Header & Menu Item Data
'-------------------------------------------------------------------------------

Public Const mnuMenuName(5) As String = ("MAIN MENU", "FLASH SETTINGS", "CONFIRM RESET", "LED1 Frequency", "LED2 Frequency")
Public Const mnuMenuItemStart(5) As Byte = (0, 4, 7, 9, 13)
Public Const mnuMenuItemEnd(5) As Byte = (3, 6, 8, 12, 16)
Public Const mnuParentMenuID(5) As Byte = (0, 0, 0, 1, 1)

Public Const mnuItemName(17) As String = ("Start Flashing", "Stop Flashing", "Flash Settings", "Reset", "LED1 Frequency", "LED2 Frequency", "Back", "No", "Yes", "1Hz", "2Hz", "10Hz", "Back", "1Hz", "2Hz", "10Hz", "Back")
Public Const mnuItemAction(17) As Byte = (255, 255, 1, 2, 3, 4, 0, 0, 255, 255, 255, 255, 1, 255, 255, 255, 1)


'===============================================================================
'Menu Item Aliases - used by Select Case structure in main program
'-------------------------------------------------------------------------------

Public Const mnuMainMenu_StartFlashing = 0
Public Const mnuMainMenu_StopFlashing = 1
Public Const mnuConfirmReset_Yes = 8
Public Const mnuLed1Frequency_1hz = 9
Public Const mnuLed1Frequency_2hz = 10
Public Const mnuLed1Frequency_10hz = 11
Public Const mnuLed2Frequency_1hz = 13
Public Const mnuLed2Frequency_2hz = 14
Public Const mnuLed2Frequency_10hz = 15
=]

!!!Menu Module
=code [=
{
********************************************************************************
*  Name    : LCD Menu Module                                                  *
*  Author  : AndyO                                                            *
*  Notice  : Copyright (c) 2008 AndyO                                          *
*          : All Rights Reserved                                              *
*  Date    : 13/07/2008                                                        *
*  Version : 1.0                                                              *
*  Notes  : See doc "LCD Menu Module.pdf"                                    *
*          :                                                                  *
*          : Menu data stored in Const arrays which are declared in seperate  *
*          : module indicated by #define MenuDataModule = "filename.BAS" in    *
*          : main program                                                      *
*          :                                                                  *
*          : Requires modified LCD.bas module.  Following sub should be placed *
*          : after sub "WriteAt":                                              *
*                                                                              *
*  -------------------------------------------------------------------------  *
    Public Sub WriteConstStringAt(pY, pX As Byte, ByRefConst pText As String) 
                                                                             
        SetLocationY(pY)                                                     
        SetLocationX(pX)                                                     
        MoveTo()                                                             
                                                                             
        EECON1 = 0
        EECON1.7 = 1
   
        TABLEPTR = @pText
       
        ASM
            TBLRD*+
        End ASM
       
        While TABLAT <> 0
Changed lines 343-348 from:
              If LastItemNumber - FirstItemNumber > LCDLines - 1 Then
               
                    LCD.WriteAt
(LCDLines, 1, DownArrow)
                    LCD.WriteAt(2, 1, UpArrow)
                   
            #Else
to:
           WriteItem(TABLAT)
Changed lines 345-352 from:
               If LastItemNumber - FirstItemNumber > LCDLines Then
               
                   LCD.WriteAt(LCDLines, 1, DownArrow)
                    LCD.WriteAt(1, 1, UpArrow)
                   
            #EndIf
               
                EndIf
to:
           ASM
                TBLRD*+
            End ASM
Changed line 349 from:
       #Else
to:
       Wend
Changed lines 351-388 from:
         If ItemNumber - 1 < LastItemNumber Then
           
                LCD.WriteAt(LCDLines, 1, DownArrow)
           
            EndIf
         
         
            If ScrollPosition > 0 Then
               
                #If Menu_ShowMenuTitle = True
                   
                   LCD.WriteAt(2, 1, UpArrow)
               
               
#Else
 
           
                   LCD.WriteAt(1, 1, UpArrow)
              
               #EndIf
              
           EndIf
          
        #EndIf
       
    #EndIf

End Sub

'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'Name:      DrawPointer(pOldPosition as byte)     
'Purpose:  Deletes pointer at pOldPosition and draws pointer at PointerPosition
'
'Notes:    When pointer is at first menu item on screen, PointerPosition = 0
'
'          Pointer character determined by option
"Menu_PointerCharacter",
'          default = 126 (right arrow)
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Private Sub DrawPointer(pOldPosition As Byte)
to:
   End Sub
*
  -------------------------------------------------------------------------  *
                                                                            *
*
                                                                            *
********************************************************************************
}

Module Menu


Include "LCD.bas"

#ifdef MenuDataModule
    Include MenuDataModule
#else
    #error "No MenuDataModule defined in main program"
#endif

'===============================================================================
'User definable options
'-------------------------------------------------------------------------------

#option Menu_LCDLines = 4
                  'Number of lines on LCD display

#option Menu_ShowMenuTitle = True
          'Display Menu Name at top of screen?
#option Menu_ShowScrollIndicators = True
    'Show up and down scroll indicators?
#option Menu_ScrollWrapping = False
        'Scroll wraps from top to bottom?

#option Menu_PointerCharacter = 126
        'Character used as pointer


#if Menu_LCDLines = 1 And Menu_ShowMenuTitle = True

    #error "Cannot show menu titles on 1 line LCD"
Changed lines 385-405 from:
   'Actual LCD line which pointer is drawn on depends on whether
    'Menu_ShowMenuTitle is enabled or not
       
   
#If Menu_ShowMenuTitle Then
   
        PointerLine = PointerPosition + 2

        LCD.WriteAt(pOldPosition + 2, 1, " ")
        LCD.WriteAt(PointerLine, 1, PointerCharacter)
       
    #Else
   
        PointerLine = PointerPosition + 1
       
        LCD.WriteAt(pOldPosition + 1, 1, " ")
        LCD.WriteAt(PointerLine, 1, PointerCharacter)
       
    #EndIf

End Sub

to:
#endif
Changed line 388 from:
'Public Subs and Functions
to:
'Variable and Const Declarations
Changed lines 391-401 from:
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'Name:
     ShowMenu(pMenuNumber as byte = 0)    
'Purpose:  Display specified menu on LCD
'
         
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Public Sub ShowMenu
(pMenuNumber As Byte = 0)

    'Reset scroll and pointer positions
    ScrollPosition = 0
    PointerPosition = 0
to:
'---Types

Type TEvent = Event
()


'---Bring options into module

Private Const
    LCDLines As Byte = Menu_LCDLines,
    PointerCharacter As Byte = Menu_PointerCharacter


'---Custom Characters (only loaded if option Menu_ShowScrollIndicators = True)

#If Menu_ShowScrollIndicators = True

    'Character definitions for Up Arrow and Down Arrow scroll indicators
    Private Const ScrollIndicators(16) As Byte=($04,$0E,$1F,$00,$00,$00,$00,$00,
                                                $00,$00,$00,$00,$00,$1F,$0E,$04)
Changed lines 411-416 from:
   'Set values for first item number, last item number & parentID for current
    'menu
    CurrentMenu = pMenuNumber
    ParentID = mnuParentMenuID(CurrentMenu)
    FirstItemNumber = mnuMenuItemStart(CurrentMenu)
    LastItemNumber = mnuMenuItemEnd(CurrentMenu)
to:
   Private Const UpArrow = 0
    Private Const DownArrow = 1

#EndIf

 
'---Private Variables
Changed lines 419-424 from:
   'Display menu & pointer
   DrawMenu
 
  DrawPointer(0)

End Sub

to:
Private Dim FItemActionEvent As TEvent  'Holds pointer to event code in main
                                       'program which is run when an "action"
                                        'menu item is selected

Private Dim PointerPosition As Byte    'Position of pointer character (0 = 1st)
Private Dim PointerLine As Byte        'Actual LCD line of pointer       
Private Dim ScrollPosition As Byte      'Number of lines the menu is scrolled by

Private Dim CurrentMenu As Byte        'Index number of current menu
Private Dim ParentID As Byte            'Index number of parent of current menu
Private Dim FirstItemNumber As Byte    'First item number for current menu
Private Dim LastItemNumber As Byte      'Last item number for current menu


'---Public Variables

Public Dim SelectedMenuItem As Byte    'Index number of selected menu item

'===============================================================================
'Helper Subs and Functions (Private - can't be called from main program)
'-------------------------------------------------------------------------------

Changed lines 442-444 from:
'Name:      MoveDown(   
'Purpose:  Move pointer to next menu item, scrolling the menu if necessary
'
to:
'Name:      DrawMenu(pMenuNumber as byte)     
'Purpose:  Draws current menu, scrolled by ScrollPosition amount
'               
Changed lines 447-451 from:
Public Sub MoveDown()

Dim CanScroll As Boolean

    'First, determine if the menu can be scrolled down and set CanScroll -
to:
Private Sub DrawMenu()

Dim LineNumber As Byte                      'Line number of LCD to write at
Dim ItemNumber As Byte                      'Index of menu item number to write

#If Menu_ScrollWrapping = True              '}
                                            '}If scroll wrapping enabled then
    Dim ItemsDrawn As Byte                  '}also need to keep track of number
                                            '}of menu items drawn on LCD
    ItemsDrawn = 0                          '}
                                            '}
#EndIf

    LCD.Cls
   
    LineNumber = 1
    ItemNumber = FirstItemNumber + ScrollPosition
   
    #If Menu_ShowMenuTitle = True          'If option enabled, show Menu Title
   
        LCD.WriteConstStringAt(1, 1, mnuMenuName(CurrentMenu))
        LineNumber = 2
       
    #Endif
   
   
    'Loop which keeps writing menu items until the following conditions exist -
Changed lines 475-477 from:
   '  If Scroll Wrapping Not Enabled: Menu may only be scrolled down if the
    '  number of menu items left to display won't fit on the number of
   available LCD lines
to:
   '  Scroll Wrapping Enabled: write menu items (wrapping from last menu item
    '  back to first menu item as necessary) until either the number of LCD
 
  '  lines has been reached or all menu items have been written (ItemsDrawn =
    '  LastItemNumber - FirstItemNumber)
Changed lines 480-487 from:
   '  Note: Number of available lines depends on whether Menu_ShowMenuTitle is
 
  '  enabled or not
    '
    '  If Scroll Wrapping Enabled: Can always scroll down
 
    #If Menu_ScrollWrapping = False
 
        CanScroll = False
to:
   '  Scroll Wrapping Not Enabled: write items until the number of LCD lines
    '  has been reached or the last menu item has been written
Changed lines 483-485 from:
       #If Menu_ShowMenuTitle = True Then
to:
   Repeat

       LCD.WriteConstStringAt(LineNumber, 2, mnuItemName(ItemNumber))
Changed lines 487-493 from:
           If FirstItemNumber + ScrollPosition + LCDLines - 2 < LastItemNumber Then
           
                CanScroll = True
               
            EndIf
           
        #Else
to:
       Inc(LineNumber)
        Inc(ItemNumber)
Changed lines 490-496 from:
            If FirstItemNumber + ScrollPosition + LCDLines - 1 < LastItemNumber Then
           
                CanScroll = True
               
            EndIf
           
        #EndIf
to:
   #If Menu_ScrollWrapping = True
Changed line 492 from:
   #Else
to:
      Inc(ItemsDrawn)
Changed lines 494-502 from:
       CanScroll = True
to:
       If ItemNumber > LastItemNumber Then '}
                                            '}Scroll wrapping enabled so wrap
            ItemNumber = FirstItemNumber    '}from last menu item to the first
                                            '}menu item
        EndIf                              '}
           
    Until LineNumber > LCDLines Or ItemsDrawn > LastItemNumber - FirstItemNumber
           
    #Else
Added lines 504-505:
   Until LineNumber > LCDLines Or ItemNumber > LastItemNumber
   
Changed lines 507-509 from:
      

    'Scroll the menu if the following conditions exist -
to:
  
   
    'If Menu_ShowScrollIndicators option enabled then show scroll indicators
    'depending on the following conditions
-
Changed lines 512-518 from:
   '  Menu can be scrolled down and pointer is on or below the second last
    '  line of the display
    '
  OR
    '  Menu can be scrolled down and pointer is on the last menu item - this
   allows scroll wrapping

    If (PointerLine >= LCDLines - 1 Or PointerPosition = LastItemNumber - FirstItemNumber) And CanScroll = True Then
to:
   '  Scroll Wrapping Enabled: Show both Up and Down arrows if the number of
 
  '  menu items exceeds the number of available LCD lines
   '
    '   Scroll Wrapping Not Enabled: Show Up arrow if menu can be scrolled up,
 
  '  Down arrow if there are more menu items to display
    '
    'Note: Number of available LCD lines and position of Up arrow depends on
    '      whether Menu_ShowMenuTitle is enabled or not
Changed lines 521-523 from:
        Inc(ScrollPosition)
to:
   #If Menu_ShowScrollIndicators = True
   
        #If Menu_ScrollWrapping = True
Changed lines 525-530 from:
       #If Menu_ScrollWrapping = True
      
        'If scroll wrapping and we've scrolled to the bottom then start again at
        'the top
       
            If ScrollPosition > (LastItemNumber - FirstItemNumber) Then
to:
            #If Menu_ShowMenuTitle = True
Changed line 527 from:
               ScrollPosition = 0
to:
               If LastItemNumber - FirstItemNumber > LCDLines - 1 Then
Changed lines 529-532 from:
           EndIf
to:
                  LCD.WriteAt(LCDLines, 1, DownArrow)
                    LCD.WriteAt(2, 1, UpArrow)
                   
            #Else
Changed lines 534-541 from:
       #EndIf
to:
              If LastItemNumber - FirstItemNumber > LCDLines Then
               
                    LCD.WriteAt(LCDLines, 1, DownArrow)
                    LCD.WriteAt(1, 1, UpArrow)
                   
           
#EndIf
               
               
EndIf
Changed lines 543-545 from:
       DrawMenu()
        DrawPointer(PointerPosition)
       
to:
       #Else
Changed lines 545-561 from:
   'If not scrolling the menu then move the pointer down if the following
    'conditions exist -
    '
    '  Pointer is not on the last line of the display
    '  AND
    '  Pointer isn't on the last menu item
   
    ElseIf PointerLine < LCDLines And PointerPosition + FirstItemNumber + ScrollPosition < LastItemNumber Then
   
        Inc(PointerPosition)
       
        'Fix to ensure Up Arrow scroll indicator is shown on smaller displays
        'when pointer moves down from top line
        #If (Menu_LCDLines < 4 Or Menu_ScrollWrapping = True) And Menu_ShowScrollIndicators = True
       
            DrawMenu()
            DrawPointer(PointerPosition)
to:
            If ItemNumber - 1 < LastItemNumber Then
Changed lines 547-549 from:
       #Else
       
            DrawPointer(PointerPosition - 1
)
to:
               LCD.WriteAt(LCDLines, 1, DownArrow)
Added lines 549-565:
           EndIf
         
         
            If ScrollPosition > 0 Then
               
                #If Menu_ShowMenuTitle = True
                   
                    LCD.WriteAt(2, 1, UpArrow)
               
                #Else
               
                    LCD.WriteAt(1, 1, UpArrow)
               
                #EndIf
               
            EndIf
           
Changed lines 567-569 from:
  
    End If   
to:
      
 
  #EndIf
Changed lines 573-574 from:
'Name:      MoveUp(   
'Purpose:  Move pointer to previous menu item, scrolling menu if necessary
to:
'Name:      DrawPointer(pOldPosition as byte)     
'Purpose:  Deletes pointer at pOldPosition and draws pointer at PointerPosition
Added lines 576-579:
'Notes:    When pointer is at first menu item on screen, PointerPosition = 0
'
'          Pointer character determined by option "Menu_PointerCharacter",
'          default = 126 (right arrow)
Changed lines 582-596 from:
Public Sub MoveUp()

 
  'Routine decides whether to scroll the menu up or just move the pointer up
    'depending on the current state:
    '
    'If there is a free LCD line above the pointer (i.e. one that doesn't have
    'the menu title or a scroll indicator in it then move the pointer up;
    '
    'Otherwise, if the menu can be scrolled (either because scroll wrapping is
    'on or because the current scroll position > 0) then scroll the menu up;
    '
    'If the menu can't be scrolled then move the pointer up, overwriting any
    'scroll-up indicator.  (If necessary, re-draw the scroll-down indicator).

    #If Menu_ShowMenuTitle = True
to:
Private Sub DrawPointer(pOldPosition As Byte)
   
    'Actual LCD line which pointer is drawn on depends on whether
    'Menu_ShowMenuTitle is enabled or not
Changed lines 587-592 from:
        #If Menu_ShowScrollIndicators = True
to:
   #If Menu_ShowMenuTitle Then
 
       PointerLine = PointerPosition + 2

        LCD.WriteAt(pOldPosition + 2, 1, " ")
        LCD.WriteAt(PointerLine, 1, PointerCharacter)
Changed lines 594-596 from:
           If PointerLine > 3 Then
          
        #Else
to:
   #Else
   
        PointerLine = PointerPosition + 1
Changed lines 598-599 from:
           If PointerLine > 2 Then
to:
       LCD.WriteAt(pOldPosition + 1, 1, " ")
       LCD.WriteAt(PointerLine, 1, PointerCharacter)
Changed lines 601-661 from:
       #EndIf
to:
   #EndIf

End Sub

'===============================================================================
'Public Subs and Functions
'-------------------------------------------------------------------------------

'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'Name:      ShowMenu(pMenuNumber as byte = 0)     
'Purpose:  Display specified menu on LCD
'       
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Public Sub ShowMenu(pMenuNumber As Byte = 0)

    'Reset scroll and pointer positions
    ScrollPosition = 0
    PointerPosition = 0
   
    'Set values for first item number, last item number & parentID for current
    'menu
    CurrentMenu = pMenuNumber
    ParentID = mnuParentMenuID(CurrentMenu)
    FirstItemNumber = mnuMenuItemStart(CurrentMenu)
    LastItemNumber = mnuMenuItemEnd(CurrentMenu)
   
    'Display menu & pointer
    DrawMenu
    DrawPointer(0)

End Sub

'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'Name:      MoveDown()     
'Purpose:  Move pointer to next menu item, scrolling the menu if necessary
'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Public Sub MoveDown()

Dim CanScroll As Boolean

    'First, determine if the menu can be scrolled down and set CanScroll -
    '
    '  If Scroll Wrapping Not Enabled: Menu may only be scrolled down if the
    '  number of menu items left to display won't fit on the number of
    '  available LCD lines
    '
    '  Note: Number of available lines depends on whether Menu_ShowMenuTitle is
    '  enabled or not
    '
    '  If Scroll Wrapping Enabled: Can always scroll down
 
    #If Menu_ScrollWrapping = False
 
        CanScroll = False
   
        #If Menu_ShowMenuTitle = True Then
       
            If FirstItemNumber + ScrollPosition + LCDLines - 2 < LastItemNumber Then
Changed lines 663-664 from:
               Dec(PointerPosition)
               DrawPointer(PointerPosition + 1)
to:
               CanScroll = True
Changed lines 665-667 from:
       #If Menu_ScrollWrapping = True
to:
            EndIf
           
        #Else
Changed lines 669-675 from:
           Else
 
            
                If ScrollPosition > 0 Then
                   Dec(ScrollPosition)
                Else
                    ScrollPosition = LastItemNumber - FirstItemNumber
                EndIf
to:
           If FirstItemNumber + ScrollPosition + LCDLines - 1 < LastItemNumber Then
           
              CanScroll = True
Deleted lines 672-674:
               DrawMenu()
                DrawPointer(PointerPosition)
               
Changed lines 675-688 from:
       #Else               
                   
            ElseIf ScrollPosition > 0 Then
           
                Dec(ScrollPosition)
                DrawMenu()
                DrawPointer(PointerPosition)
               
           
            ElseIf PointerLine > 2 Then
           
                Dec(PointerPosition)
               
                #If Menu_LCDLines < 4 And Menu_ShowScrollIndicators = True
to:
       #EndIf
Deleted lines 676-688:
                   DrawMenu()
                    DrawPointer(PointerPosition)
                   
                #Else
               
                    DrawPointer(PointerPosition + 1)
                   
                #EndIf
                       
            EndIf
           
        #EndIf
       
Changed lines 678-679 from:

        #If Menu_ShowScrollIndicators = True
to:
  
       CanScroll = True
Changed lines 681-683 from:
          If PointerLine > 2 Then
           
       
#Else
to:
   #EndIf
Changed lines 683-694 from:
            If PointerLine > 1 Then
to:

    'Scroll the menu if the following conditions exist -
 
   '
    '  Menu can be scrolled down and pointer is on or below the second last
    '  line of the display
    '  OR
    '  Menu can be scrolled down and pointer is on the last menu item - this
    '  allows scroll wrapping

    If (PointerLine >= LCDLines - 1 Or PointerPosition = LastItemNumber - FirstItemNumber) And CanScroll = True Then
   
        Inc(ScrollPosition)
Changed lines 696-699 from:
       #EndIf
 
          
                Dec(PointerPosition)
                DrawPointer(PointerPosition + 1)
to:
       #If Menu_ScrollWrapping = True
Added lines 698-699:
       'If scroll wrapping and we've scrolled to the bottom then start again at
        'the top
Changed lines 701-707 from:
       #If Menu_ScrollWrapping = True
to:
            If ScrollPosition > (LastItemNumber - FirstItemNumber) Then
           
                ScrollPosition = 0
               
            EndIf
           
        #EndIf
Changed lines 709-735 from:
           Else
             
                If ScrollPosition > 0 Then
                    Dec(ScrollPosition)
                Else
                    ScrollPosition = LastItemNumber - FirstItemNumber
                EndIf
               
                DrawMenu()
                DrawPointer(PointerPosition)
               
            EndIf
           
        #Else                       
           
            ElseIf ScrollPosition > 0 Then
           
                Dec(ScrollPosition)
                DrawMenu()
                DrawPointer(PointerPosition)
               
           
            ElseIf PointerLine > 1 Then
           
                Dec(PointerPosition)
               
                #If Menu_LCDLines < 4 And Menu_ShowScrollIndicators = True
to:
       DrawMenu()
        DrawPointer(PointerPosition)
Changed lines 712-723 from:
                    DrawMenu()
                   DrawPointer(PointerPosition)
     
           
     
          #Else
 
            
                   DrawPointer(PointerPosition + 1)
                   
                #EndIf
               
            EndIf
           
        #EndIf
to:
  
    'If not scrolling the menu then move the pointer down if the following
    'conditions exist -
    '
    '  Pointer is not on the last line of the display
    '  AND
    '  Pointer isn't on the last menu item
   
    ElseIf PointerLine < LCDLines And PointerPosition + FirstItemNumber + ScrollPosition < LastItemNumber Then
   
        Inc(PointerPosition)
Changed lines 724-725 from:
   #EndIf
to:
      'Fix to ensure Up Arrow scroll indicator is shown on smaller displays
        'when pointer moves down from top line
       
#If (Menu_LCDLines < 4 Or Menu_ScrollWrapping = True) And Menu_ShowScrollIndicators = True
       
            DrawMenu()
            DrawPointer(PointerPosition)
           
        #Else
       
            DrawPointer(PointerPosition - 1)
           
        #
EndIf
  
    End If   

Changed lines 742-743 from:
'Name:      Back()     
'Purpose:  Show parent of current menu
to:
'Name:      MoveUp()     
'Purpose:  Move pointer to previous menu item, scrolling menu if necessary
Changed lines 747-770 from:
Public Sub Back()

    ShowMenu(ParentID)

End Sub

'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'Name:   
  SelectItem()     
'Purpose:  Carries out action of selected menu item.  If menu item opens
'
         another menu then that menu is drawn.  If menu item is an 'action'
'     
     item then the event in the main program which handles the action is
'   
      called
'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Public Sub SelectItem()

Dim ItemAction As Byte

    SelectedMenuItem = FirstItemNumber + ScrollPosition + PointerPosition
   
 
  #If Menu_ScrollWrapping = True
   
        If SelectedMenuItem > LastItemNumber Then
to:
Public Sub MoveUp()

    'Routine decides whether to scroll the menu up or just move the pointer up
   
'depending on the current state:
    '
    'If there is a free LCD line above the pointer (i.e. one that doesn't have
    'the menu title or a scroll indicator in it then move the pointer up;
    '
   
'Otherwise, if the menu can be scrolled (either because scroll wrapping is
    'on or because the current scroll position > 0) then scroll the menu up;
    '
    'If the menu can't be scrolled then move the pointer up, overwriting any
    'scroll-up indicator.  (If necessary, re-draw the scroll-down indicator).

   #If Menu_ShowMenuTitle = True
       
        #If Menu_ShowScrollIndicators = True
       
            If PointerLine > 3
Then
Changed lines 767-771 from:
           SelectedMenuItem = SelectedMenuItem - (LastItemNumber - FirstItemNumber) - 1
to:
       #Else
       
           If PointerLine > 2 Then
       
        #EndIf
Changed lines 773-783 from:
       EndIf
   
    #EndIf
   
    ItemAction = mnuItemAction(SelectedMenuItem)


    If ItemAction = 255 Then
   
        ParentID = CurrentMenu 'Means 'back' command will return to correct menu
       FItemActionEvent()    'Call event handler in main program
to:
               Dec(PointerPosition)
                DrawPointer(PointerPosition + 1)
               
       #If Menu_ScrollWrapping = True
Changed lines 778-780 from:
   Else
   
        ShowMenu(ItemAction)   
to:
           Else
             
                If ScrollPosition > 0 Then
                    Dec(ScrollPosition)
                Else
                    ScrollPosition = LastItemNumber - FirstItemNumber
                EndIf
               
                DrawMenu()
                DrawPointer(PointerPosition)
               
            EndIf
           
        #Else               
                   
            ElseIf ScrollPosition > 0 Then
           
                Dec(ScrollPosition)
                DrawMenu()
                DrawPointer(PointerPosition)
               
           
            ElseIf PointerLine > 2 Then
           
                Dec(PointerPosition)
               
                #If Menu_LCDLines < 4 And Menu_ShowScrollIndicators = True
Changed lines 806-807 from:
   EndIf
to:
                   DrawMenu()
                    DrawPointer(PointerPosition)
                   
                #Else
               
                    DrawPointer(PointerPosition + 1)
                   
                #EndIf
                       
            EndIf
           
        #EndIf
       
    #Else

        #If Menu_ShowScrollIndicators = True
       
            If PointerLine > 2 Then
           
        #Else
       
            If PointerLine > 1 Then
       
        #EndIf
           
                Dec(PointerPosition)
                DrawPointer(PointerPosition + 1)
       
       
        #If Menu_ScrollWrapping = True
       
            Else
             
                If ScrollPosition > 0 Then
                    Dec(ScrollPosition)
                Else
                    ScrollPosition = LastItemNumber - FirstItemNumber
                EndIf
               
                DrawMenu()
                DrawPointer(PointerPosition)
               
            EndIf
           
        #Else                       
           
            ElseIf ScrollPosition > 0 Then
           
                Dec(ScrollPosition)
                DrawMenu()
                DrawPointer(PointerPosition)
               
           
            ElseIf PointerLine > 1 Then
           
                Dec(PointerPosition)
               
                #If Menu_LCDLines < 4 And Menu_ShowScrollIndicators = True
       
                    DrawMenu()
                    DrawPointer(PointerPosition)
                   
                #Else
               
                    DrawPointer(PointerPosition + 1)
                   
                #EndIf
               
            EndIf
           
        #EndIf
       
    #EndIf

Changed lines 883-885 from:
'Name:      Initialise(pItemActionEvent as TEvent)     
'Purpose:  Sets name of main program event handler and loads custom scroll
'          characters into LCD
to:
'Name:      Back(   
'Purpose:  Show parent of current menu
Changed lines 888-897 from:
Public Sub Initialise(pItemActionEvent As TEvent)

    FItemActionEvent = pItemActionEvent
   
    #If Menu_ShowScrollIndicators = True Then

        LCD.Write
(ScrollIndicators)
  
    #EndIf

to:
Public Sub Back()

    ShowMenu(ParentID)
Added lines 894-953:
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'Name:      SelectItem()     
'Purpose:  Carries out action of selected menu item.  If menu item opens
'          another menu then that menu is drawn.  If menu item is an 'action'
'          item then the event in the main program which handles the action is
'          called
'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Public Sub SelectItem()

Dim ItemAction As Byte

    SelectedMenuItem = FirstItemNumber + ScrollPosition + PointerPosition
   
    #If Menu_ScrollWrapping = True
   
        If SelectedMenuItem > LastItemNumber Then
           
            SelectedMenuItem = SelectedMenuItem - (LastItemNumber - FirstItemNumber) - 1
           
        EndIf
   
    #EndIf
   
    ItemAction = mnuItemAction(SelectedMenuItem)


    If ItemAction = 255 Then
   
        ParentID = CurrentMenu 'Means 'back' command will return to correct menu
        FItemActionEvent()    'Call event handler in main program
       
    Else
   
        ShowMenu(ItemAction)   
       
    EndIf

End Sub

'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'Name:      Initialise(pItemActionEvent as TEvent)     
'Purpose:  Sets name of main program event handler and loads custom scroll
'          characters into LCD
'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Public Sub Initialise(pItemActionEvent As TEvent)

    FItemActionEvent = pItemActionEvent
   
    #If Menu_ShowScrollIndicators = True Then

        LCD.Write(ScrollIndicators)
   
    #EndIf

End Sub

Changed line 6 from:
!!!Module
to:
!!!Menu Module
Added lines 658-1193:

!!!Updated LCD Module
=code [=
{
****************************************************************
*  Name    : LCD.BAS - Modified                                *
*  Author  : David John Barker - modified by AndyO            *
*  Notice  : Copyright (c) 2006 Mecanique                      *
*          : All Rights Reserved                              *
*  Date    : 17/07/2008                                        *
*  Version : 1.2 - Added "WriteConstStringAt" function for    *
*            sending String Const Array to LCD without using  *
*            RAM - uses ByRefConst method of parameter passing *
*          : 1.1 - Implements optional RW pin for testing the  *
*            device busy flag, rather than using fixed delays  *
*          : 1.0 - Release                                    *
*  Notes  : Supports Hitachi HD44780 LCD controller          *
****************************************************************
}
Module LCD

Include "system.bas"
Include "utils.bas" 

// by default, the module assumes RW is not used and tied to ground - if the
// user specifies a RW pin, then data read code is linked in...
#if IsOption(LCD_RW)
  #define _RW_USED
  #if Not IsValidPortPin(LCD_RW)
      #error LCD_RW, "Invalid option. RW must be a valid port pin."
  #endif 
#endif

// user has specified a data port, check to see if it is 8
// bit or 4 bit interface...
#if IsOption(LCD_DATA)
  #ifndef LCD_DATA@
      #define _LCD_INTERFACE_08
  #endif 
#endif

// default module option - user options can override these values...
#option LCD_DATA = PORTB.4
#option LCD_RS = PORTB.3
#option LCD_EN = PORTB.2
#option LCD_COMMAND_US = 2000
#option LCD_DATA_US = 50
#option LCD_INIT_DELAY = 100

// validate data port...
#if IsOption(LCD_DATA)
  #if Not IsValidPort(LCD_DATA)
      #error LCD_DATA, "Invalid option. DATA must be a valid port name."
  #endif
 
  // has the user specified a DATA port bit? If so, then
  // we need to validate and set for 4 bit interface...
  #ifdef LCD_DATA@ Then
      #if Not (LCD_DATA@ in (0,4))
        #error LCD_DATA@, "Invalid option. DATA pin must be 0 or 4."     
      #endif
      #if LCD_DATA@ = 4
        #define _LCD_UPPER
      #endif   
  #endif
#endif 

// create TRIS, based on the DATA port option...
#option _LCD_DATA_TRIS = GetTRIS(LCD_DATA)   

// validate RS pin...
#if IsOption(LCD_RS) And Not IsValidPortPin(LCD_RS)
  #error LCD_RS, "Invalid option. RS must be a valid port pin."
#endif

// validate EN pin...
#if IsOption(LCD_EN) And Not IsValidPortPin(LCD_EN)
  #error LCD_EN, "Invalid option. EN must be a valid port pin."
#endif

// validate command delay...
#if IsOption(LCD_COMMAND_US) And Not (LCD_COMMAND_US in (1 to 65535))
  #error LCD_COMMAND_US, "Invalid option. Command delay must be between 1 and 65535 us."
#endif

// validate data delay...
#if IsOption(LCD_DATA_US) And Not (LCD_DATA_US in (1 to 255))
  #error LCD_DATA_US, "Invalid option. Data delay must be between 1 and 255 us."
#endif

// validate initialisation delay...
#if IsOption(LCD_INIT_DELAY)
  #if Not (LCD_INIT_DELAY in (0 to 1000))
      #error LCD_INIT_DELAY, "Invalid option. LCD initialize delay must be between 0 and 1000 (ms)."
  #endif
#endif

// public command constants...
Public Const
  cmdCGRAM          = %01000000,
  cmdDDRAM          = %10000000,
  cmdClear          = %00000001,
  cmdHome            = %00000010,
  cmdCursorOff      = %00001100,
  cmdCursorOn        = %00001110, 
  cmdBlinkOn        = %00001101,
  cmdBlinkOff        = %00001100,
  cmdMoveLeft        = %00010000,
  cmdMoveRight      = %00010100

// bring delay options into the module...
Const
  DataUS = LCD_DATA_US,
  CommandUS = LCD_COMMAND_US,
  LCDDelay = LCD_INIT_DELAY 

// bring port and pin options into the module...
Dim
  Data As LCD_DATA,          // DATA port
  TRISData As _LCD_DATA_TRIS, // DATA port TRIS
  RS As LCD_RS.LCD_RS@,      // register select
  EN As LCD_EN.LCD_EN@,      // enable read
  FPosX, FPosY As Byte        // local x, y coordinates 

// RW specified...
#ifdef _RW_USED
Dim RW As LCD_RW.LCD_RW@
#endif
{
****************************************************************************
* Name    : StrobeEN (PRIVATE)                                            *
* Purpose : Strobe read enable                                            *
****************************************************************************
}
Inline Sub StrobeEN()
  EN = 1
ASM-
  Nop
End ASM
  EN = 0
End Sub
{
****************************************************************************
* Name    : MakeOutput (PRIVATE)                                          *
* Purpose : Make data pins output                                          *
****************************************************************************
}
#ifdef _RW_USED
Sub MakeOutput()
  #ifdef _LCD_INTERFACE_08
      TRISData = $00
  #else
      #ifdef _LCD_UPPER
      TRISData = TRISData And $0F
      #else
      TRISData = TRISData And $F0
      #endif
  #endif
End Sub
{
****************************************************************************
* Name    : MakeInput (PRIVATE)                                            *
* Purpose : Make data pins input                                          *
****************************************************************************
}
Sub MakeInput()
  #ifdef _LCD_INTERFACE_08
      TRISData = $FF
  #else
      #ifdef _LCD_UPPER
      TRISData = TRISData Or $F0
      #else
      TRISData = TRISData Or $0F
      #endif
  #endif
End Sub
{
****************************************************************************
* Name    : GetData (PRIVATE)                                              *
* Purpose : Read Data port value. For 4 bit interface, value is returned  *
*        : in LSNibble                                                    *
****************************************************************************
}
Function GetData() As Byte
  EN = 1
  ASM-
      bra $ + 2
      bra $ + 2
  End ASM
  result = Data
  #ifndef _LCD_INTERFACE_08
      #ifdef _LCD_UPPER
      result = result >> 4
      #else
      result = result And $0F
      #endif
  #endif
  EN = 0
End Function
{
****************************************************************************
* Name    : ReadByte (PRIVATE)                                            *
* Purpose : Read a data byte from the device                              *
****************************************************************************
}
Function ReadByte() As Byte
  MakeInput
  RS = 0
  RW = 1
  #ifdef _LCD_INTERFACE_08
      result = GetData
  #else 
      result = GetData << 4                // high nibble
      result = result Or (GetData And $0F)  // low nibble
  #endif
  RW = 0
  MakeOutput
End Function
{
****************************************************************************
* Name    : WaitFor (PRIVATE)                                              *
* Purpose : Wait for device busy flag to clear                            *
****************************************************************************
}
Sub WaitFor()
  Repeat
  Until (ReadByte And $80) = 0
End Sub 
#endif // _RW_USED
{
****************************************************************************
* Name    : SetData (PRIVATE)                                              *
* Purpose : Sets DATA port value                                          *
****************************************************************************
}
Sub SetData(pData As Byte)
  #ifdef _LCD_INTERFACE_08
  Data = pData
  #else
      #ifdef _LCD_UPPER
      Data = Data And $0F
      Data = Data Or (pData And $F0)
      #else
      Data = Data And $F0
      Data = Data Or (pData And $0F)
      #endif
  #endif
  StrobeEN
  ClrWDT
End Sub
{
****************************************************************************
* Name    : SetDDRAM (PRIVATE)                                            *
* Purpose : Set DDRAM address                                              *
****************************************************************************
}
Sub SetDDRAM(pAddr As Byte)
  #ifdef _RW_USED
  WaitFor
  #endif
 
  RS = 0
  #ifdef _LCD_INTERFACE_08
  SetData(pAddr Or %10000000)
  #else
      #ifdef _LCD_UPPER
      SetData(pAddr Or %10000000)
      SetData(pAddr << 4)
      #else
      SetData((pAddr Or %10000000) >> 4)
      SetData(pAddr)
      #endif
  #endif 
 
  #ifndef _RW_USED
  DelayUS(DataUS)
  #endif
  ClrWDT
End Sub
{
****************************************************************************
* Name    : Command                                                        *
* Purpose : Write command to LCD                                          *
****************************************************************************
}
Public Sub Command(pCommand As Byte)
  #ifdef _RW_USED
  WaitFor
  #endif
 
  RS = 0
  #ifdef _LCD_INTERFACE_08
  SetData(pCommand)
  #else
      #ifdef _LCD_UPPER
      SetData(pCommand)
      SetData(pCommand << 4)
      #else
      SetData(pCommand >> 4)
      SetData(pCommand)
      #endif
  #endif 
 
  #ifndef _RW_USED
  DelayUS(CommandUS)
  #endif
  ClrWDT
End Sub
{
****************************************************************************
* Name    : MoveCursor                                                    *
* Purpose : Move the cursor to line and column                            *
****************************************************************************
}
Public Sub MoveCursor(pLine, pCol As Byte)
  Dec(pCol)
  Select pLine
      Case 1 : SetDDRAM(pCol)
      Case 2 : SetDDRAM($40 + pCol)
      Case 3 : SetDDRAM($14 + pCol)
      Case 4 : SetDDRAM($54 + pCol)
  End Select
End Sub
{
****************************************************************************
* Name    : WriteItem (OVERLOAD)                                          *
* Purpose : Write a single byte to the LCD                                *
****************************************************************************
}
Sub WriteItem(pData As Byte)
  #ifdef _RW_USED
  WaitFor
  #endif
 
  RS = 1
  #ifdef _LCD_INTERFACE_08
  SetData(pData)
  #else
      #ifdef _LCD_UPPER
      SetData(pData)
      SetData(pData << 4)
      #else
      SetData(pData >> 4)
      SetData(pData)
      #endif
  #endif 
 
  #ifndef _RW_USED
  DelayUS(DataUS)
  #endif
  ClrWDT
End Sub
{
****************************************************************************
* Name    : WriteItem (OVERLOAD)                                          *
* Purpose : Write a string to the LCD                                      *
****************************************************************************
}
Sub WriteItem(pText As String)
  Dim TextPtr As POSTINC0
  Dim Text As INDF0
  FSR0 = AddressOf(pText)
  While Text <> 0
      WriteItem(TextPtr)
  Wend
End Sub
{
****************************************************************************
* Name    : WriteItem (OVERLOAD)                                          *
* Purpose : Initialise the CGRAM with constant data array. Eight          *
*        : programmable characters are available (0..7).                  *
****************************************************************************
}
Sub WriteItem(ByRefConst pBitmap() As Byte)
  Dim Index As Byte
  Command(cmdCGRAM)
  For Index = 0 To Bound(pBitmap)
      WriteItem(pBitmap(Index))
  Next
  Command(cmdDDRAM)
End Sub
{
****************************************************************************
* Name    : Write (COMPOUND)                                              *
* Purpose : Calls one or more WriteItem() subroutines                      *
****************************************************************************
}
Public Compound Sub Write(WriteItem)
{
****************************************************************************
* Name    : MoveTo (PRIVATE)                                              *
* Purpose : Moves cursor to module private x, y location                  *
****************************************************************************
}
Sub MoveTo()
  MoveCursor(FPosY, FPosX)
End Sub
{
****************************************************************************
* Name    : SetLocationX (PRIVATE)                                        *
* Purpose : Set cursor x                                                  *
****************************************************************************
}
Sub SetLocationX(pX As Byte)
  FPosX = pX
End Sub
{
****************************************************************************
* Name    : SetLocationY (PRIVATE)                                        *
* Purpose : Set cursor y                                                  *
****************************************************************************
}
Sub SetLocationY(pY As Byte)
  FPosY = pY
End Sub
{
****************************************************************************
* Name    : WriteAt (COMPOUND)                                            *
* Purpose : Calls one or more WriteItem() subroutines at location x, y    *
****************************************************************************
}
Public Compound Sub WriteAt(SetLocationY, SetLocationX, MoveTo, WriteItem) 


{
****************************************************************************
* Name    : WriteConstStringAt(pY, pX As Byte,ByRefConst pText As String)  *
* Purpose : Writes a string stored in a Const Array                        *
****************************************************************************
}
Public Sub WriteConstStringAt(pY, pX As Byte, ByRefConst pText As String)

    SetLocationY(pY)
    SetLocationX(pX)
    MoveTo()
   
    EECON1 = 0
    EECON1.7 = 1

    TABLEPTR = @pText
   
    ASM
        TBLRD*+
    End ASM
   
    While TABLAT <> 0
       
        WriteItem(TABLAT)
       
        ASM
            TBLRD*+
        End ASM
   
    Wend

End Sub


{
****************************************************************************
* Name    : Cls                                                            *
* Purpose : Clear the LCD screen                                          *
****************************************************************************
}
Public Sub Cls()
  Command(cmdClear)
  DelayMS(30)
End Sub
{
****************************************************************************
* Name    : DoInitSequence (PRIVATE)                                      *
* Purpose : Performs a initialization sequence                            *
****************************************************************************
}
Sub DoInitSequence(pValue As Byte)
  SetData(pValue)
  DelayMS(5)
  SetData(pValue)
  DelayUS(160)
  SetData(pValue)
  DelayUS(160)
End Sub
{
****************************************************************************
* Name    : Initialize (PRIVATE)                                          *
* Purpose : Initialize the LCD                                            *
****************************************************************************
}
Sub Initialize() 
  // wait for LCD to stabilise...
  SetAllDigital
  DelayMS(LCDDelay)
 
  // read write...
  #ifdef _RW_USED
  Low(RW)
  #endif
 
  // set RS and EN to low output...
  Low(RS)
  Low(EN)
  DelayMS(15) 
 
  // initialise for 8 bit interface...
  #ifdef _LCD_INTERFACE_08
      Data = $00
      TRISData = $00
      DoInitSequence($30)
      Command(%00111000) // DL = 1, N = 1, F = 0
 
  // initialise for 4 bit interface...
  #else
      #ifdef _LCD_UPPER
      Data = Data And $0F
      TRISData = TRISData And $0F
      DoInitSequence($30)
      SetData($20)
      DelayUS(160)
      #else
      Data = Data And $F0
      TRISData = TRISData And $F0
      DoInitSequence($03)
      SetData($02)
      DelayUS(160)
      #endif
      Command(%00101000) // DL = 0, N = 1, F = 0
  #endif
  Command(%00001000)    // D = 0, C = 0, B = 0
  Command(%00000001)    // Clear
  Command(%00000110)    // ID = 1, S = 0
  Command(%00001100)    // D = 1, C = 0, B = 0
End Sub

// initialise the module...
Initialize
=]
Added lines 1-657:
!!!Sample Code
=code [=

=]

!!!Module
=code [=
{
********************************************************************************
*  Name    : LCD Menu Module                                                  *
*  Author  : AndyO                                                            *
*  Notice  : Copyright (c) 2008 AndyO                                          *
*          : All Rights Reserved                                              *
*  Date    : 13/07/2008                                                        *
*  Version : 1.0                                                              *
*  Notes  : See doc "LCD Menu Module.pdf"                                    *
*          :                                                                  *
*          : Menu data stored in Const arrays which are declared in seperate  *
*          : module indicated by #define MenuDataModule = "filename.BAS" in    *
*          : main program                                                      *
*          :                                                                  *
*          : Requires modified LCD.bas module.  Following sub should be placed *
*          : after sub "WriteAt":                                              *
*                                                                              *
*  -------------------------------------------------------------------------  *
    Public Sub WriteConstStringAt(pY, pX As Byte, ByRefConst pText As String) 
                                                                             
        SetLocationY(pY)                                                     
        SetLocationX(pX)                                                     
        MoveTo()                                                             
                                                                             
        EECON1 = 0
        EECON1.7 = 1
   
        TABLEPTR = @pText
       
        ASM
            TBLRD*+
        End ASM
       
        While TABLAT <> 0
           
            WriteItem(TABLAT)
           
            ASM
                TBLRD*+
            End ASM
       
        Wend
   
    End Sub
*  -------------------------------------------------------------------------  *
*                                                                              *
*                                                                              *
********************************************************************************
}

Module Menu

Include "LCD.bas"

#ifdef MenuDataModule
    Include MenuDataModule
#else
    #error "No MenuDataModule defined in main program"
#endif

'===============================================================================
'User definable options
'-------------------------------------------------------------------------------

#option Menu_LCDLines = 4                  'Number of lines on LCD display

#option Menu_ShowMenuTitle = True          'Display Menu Name at top of screen?
#option Menu_ShowScrollIndicators = True    'Show up and down scroll indicators?
#option Menu_ScrollWrapping = False        'Scroll wraps from top to bottom?

#option Menu_PointerCharacter = 126        'Character used as pointer


#if Menu_LCDLines = 1 And Menu_ShowMenuTitle = True

    #error "Cannot show menu titles on 1 line LCD"
   
#endif

'===============================================================================
'Variable and Const Declarations
'-------------------------------------------------------------------------------

'---Types

Type TEvent = Event()


'---Bring options into module
 
Private Const
    LCDLines As Byte = Menu_LCDLines,
    PointerCharacter As Byte = Menu_PointerCharacter


'---Custom Characters (only loaded if option Menu_ShowScrollIndicators = True)

#If Menu_ShowScrollIndicators = True

    'Character definitions for Up Arrow and Down Arrow scroll indicators
    Private Const ScrollIndicators(16) As Byte=($04,$0E,$1F,$00,$00,$00,$00,$00,
                                                $00,$00,$00,$00,$00,$1F,$0E,$04)
   
    Private Const UpArrow = 0
    Private Const DownArrow = 1

#EndIf
 
 
'---Private Variables
   
Private Dim FItemActionEvent As TEvent  'Holds pointer to event code in main
                                        'program which is run when an "action"
                                        'menu item is selected

Private Dim PointerPosition As Byte    'Position of pointer character (0 = 1st)
Private Dim PointerLine As Byte        'Actual LCD line of pointer       
Private Dim ScrollPosition As Byte      'Number of lines the menu is scrolled by

Private Dim CurrentMenu As Byte        'Index number of current menu
Private Dim ParentID As Byte            'Index number of parent of current menu
Private Dim FirstItemNumber As Byte    'First item number for current menu
Private Dim LastItemNumber As Byte      'Last item number for current menu


'---Public Variables

Public Dim SelectedMenuItem As Byte    'Index number of selected menu item

'===============================================================================
'Helper Subs and Functions (Private - can't be called from main program)
'-------------------------------------------------------------------------------

'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'Name:      DrawMenu(pMenuNumber as byte)     
'Purpose:  Draws current menu, scrolled by ScrollPosition amount
'               
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Private Sub DrawMenu()

Dim LineNumber As Byte                      'Line number of LCD to write at
Dim ItemNumber As Byte                      'Index of menu item number to write

#If Menu_ScrollWrapping = True              '}
                                            '}If scroll wrapping enabled then
    Dim ItemsDrawn As Byte                  '}also need to keep track of number
                                            '}of menu items drawn on LCD
    ItemsDrawn = 0                          '}
                                            '}
#EndIf

    LCD.Cls
   
    LineNumber = 1
    ItemNumber = FirstItemNumber + ScrollPosition
   
    #If Menu_ShowMenuTitle = True          'If option enabled, show Menu Title
   
        LCD.WriteConstStringAt(1, 1, mnuMenuName(CurrentMenu))
        LineNumber = 2
       
    #Endif
   
   
    'Loop which keeps writing menu items until the following conditions exist -
    '
    '  Scroll Wrapping Enabled: write menu items (wrapping from last menu item
    '  back to first menu item as necessary) until either the number of LCD
    '  lines has been reached or all menu items have been written (ItemsDrawn =
    '  LastItemNumber - FirstItemNumber)
    '
    '  Scroll Wrapping Not Enabled: write items until the number of LCD lines
    '  has been reached or the last menu item has been written
   
    Repeat

        LCD.WriteConstStringAt(LineNumber, 2, mnuItemName(ItemNumber))
       
        Inc(LineNumber)
        Inc(ItemNumber)
       
    #If Menu_ScrollWrapping = True
       
        Inc(ItemsDrawn)
   
        If ItemNumber > LastItemNumber Then '}
                                            '}Scroll wrapping enabled so wrap
            ItemNumber = FirstItemNumber    '}from last menu item to the first
                                            '}menu item
        EndIf                              '}
           
    Until LineNumber > LCDLines Or ItemsDrawn > LastItemNumber - FirstItemNumber
           
    #Else
       
    Until LineNumber > LCDLines Or ItemNumber > LastItemNumber
   
    #EndIf
   
   
    'If Menu_ShowScrollIndicators option enabled then show scroll indicators
    'depending on the following conditions -
    '
    '  Scroll Wrapping Enabled: Show both Up and Down arrows if the number of
    '  menu items exceeds the number of available LCD lines
    '
    '  Scroll Wrapping Not Enabled: Show Up arrow if menu can be scrolled up,
    '  Down arrow if there are more menu items to display
    '
    'Note: Number of available LCD lines and position of Up arrow depends on
    '      whether Menu_ShowMenuTitle is enabled or not
   
    #If Menu_ShowScrollIndicators = True
   
        #If Menu_ScrollWrapping = True
       
            #If Menu_ShowMenuTitle = True
           
                If LastItemNumber - FirstItemNumber > LCDLines - 1 Then
               
                    LCD.WriteAt(LCDLines, 1, DownArrow)
                    LCD.WriteAt(2, 1, UpArrow)
                   
            #Else
           
                If LastItemNumber - FirstItemNumber > LCDLines Then
               
                    LCD.WriteAt(LCDLines, 1, DownArrow)
                    LCD.WriteAt(1, 1, UpArrow)
                   
            #EndIf
               
                EndIf
       
        #Else
   
            If ItemNumber - 1 < LastItemNumber Then
           
                LCD.WriteAt(LCDLines, 1, DownArrow)
           
            EndIf
         
         
            If ScrollPosition > 0 Then
               
                #If Menu_ShowMenuTitle = True
                   
                    LCD.WriteAt(2, 1, UpArrow)
               
                #Else
               
                    LCD.WriteAt(1, 1, UpArrow)
               
                #EndIf
               
            EndIf
           
        #EndIf
       
    #EndIf

End Sub

'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'Name:      DrawPointer(pOldPosition as byte)     
'Purpose:  Deletes pointer at pOldPosition and draws pointer at PointerPosition
'
'Notes:    When pointer is at first menu item on screen, PointerPosition = 0
'
'          Pointer character determined by option "Menu_PointerCharacter",
'          default = 126 (right arrow)
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Private Sub DrawPointer(pOldPosition As Byte)
   
    'Actual LCD line which pointer is drawn on depends on whether
    'Menu_ShowMenuTitle is enabled or not
       
    #If Menu_ShowMenuTitle Then
   
        PointerLine = PointerPosition + 2

        LCD.WriteAt(pOldPosition + 2, 1, " ")
        LCD.WriteAt(PointerLine, 1, PointerCharacter)
       
    #Else
   
        PointerLine = PointerPosition + 1
       
        LCD.WriteAt(pOldPosition + 1, 1, " ")
        LCD.WriteAt(PointerLine, 1, PointerCharacter)
       
    #EndIf

End Sub

'===============================================================================
'Public Subs and Functions
'-------------------------------------------------------------------------------

'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'Name:      ShowMenu(pMenuNumber as byte = 0)     
'Purpose:  Display specified menu on LCD
'       
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Public Sub ShowMenu(pMenuNumber As Byte = 0)

    'Reset scroll and pointer positions
    ScrollPosition = 0
    PointerPosition = 0
   
    'Set values for first item number, last item number & parentID for current
    'menu
    CurrentMenu = pMenuNumber
    ParentID = mnuParentMenuID(CurrentMenu)
    FirstItemNumber = mnuMenuItemStart(CurrentMenu)
    LastItemNumber = mnuMenuItemEnd(CurrentMenu)
   
    'Display menu & pointer
    DrawMenu
    DrawPointer(0)

End Sub

'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'Name:      MoveDown()     
'Purpose:  Move pointer to next menu item, scrolling the menu if necessary
'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Public Sub MoveDown()

Dim CanScroll As Boolean

    'First, determine if the menu can be scrolled down and set CanScroll -
    '
    '  If Scroll Wrapping Not Enabled: Menu may only be scrolled down if the
    '  number of menu items left to display won't fit on the number of
    '  available LCD lines
    '
    '  Note: Number of available lines depends on whether Menu_ShowMenuTitle is
    '  enabled or not
    '
    '  If Scroll Wrapping Enabled: Can always scroll down
 
    #If Menu_ScrollWrapping = False
 
        CanScroll = False
   
        #If Menu_ShowMenuTitle = True Then
       
            If FirstItemNumber + ScrollPosition + LCDLines - 2 < LastItemNumber Then
           
                CanScroll = True
               
            EndIf
           
        #Else
       
            If FirstItemNumber + ScrollPosition + LCDLines - 1 < LastItemNumber Then
           
                CanScroll = True
               
            EndIf
           
        #EndIf
       
    #Else
   
        CanScroll = True
       
    #EndIf
       

    'Scroll the menu if the following conditions exist -
    '
    '  Menu can be scrolled down and pointer is on or below the second last
    '  line of the display
    '  OR
    '  Menu can be scrolled down and pointer is on the last menu item - this
    '  allows scroll wrapping

    If (PointerLine >= LCDLines - 1 Or PointerPosition = LastItemNumber - FirstItemNumber) And CanScroll = True Then
   
        Inc(ScrollPosition)
       
        #If Menu_ScrollWrapping = True
       
        'If scroll wrapping and we've scrolled to the bottom then start again at
        'the top
       
            If ScrollPosition > (LastItemNumber - FirstItemNumber) Then
           
                ScrollPosition = 0
               
            EndIf
           
        #EndIf
       
        DrawMenu()
        DrawPointer(PointerPosition)
       
   
    'If not scrolling the menu then move the pointer down if the following
    'conditions exist -
    '
    '  Pointer is not on the last line of the display
    '  AND
    '  Pointer isn't on the last menu item
   
    ElseIf PointerLine < LCDLines And PointerPosition + FirstItemNumber + ScrollPosition < LastItemNumber Then
   
        Inc(PointerPosition)
       
        'Fix to ensure Up Arrow scroll indicator is shown on smaller displays
        'when pointer moves down from top line
        #If (Menu_LCDLines < 4 Or Menu_ScrollWrapping = True) And Menu_ShowScrollIndicators = True
       
            DrawMenu()
            DrawPointer(PointerPosition)
           
        #Else
       
            DrawPointer(PointerPosition - 1)
           
        #EndIf
   
    End If   

End Sub

'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'Name:      MoveUp()     
'Purpose:  Move pointer to previous menu item, scrolling menu if necessary
'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Public Sub MoveUp()

    'Routine decides whether to scroll the menu up or just move the pointer up
    'depending on the current state:
    '
    'If there is a free LCD line above the pointer (i.e. one that doesn't have
    'the menu title or a scroll indicator in it then move the pointer up;
    '
    'Otherwise, if the menu can be scrolled (either because scroll wrapping is
    'on or because the current scroll position > 0) then scroll the menu up;
    '
    'If the menu can't be scrolled then move the pointer up, overwriting any
    'scroll-up indicator.  (If necessary, re-draw the scroll-down indicator).

    #If Menu_ShowMenuTitle = True
       
        #If Menu_ShowScrollIndicators = True
       
            If PointerLine > 3 Then
           
        #Else
       
            If PointerLine > 2 Then
       
        #EndIf
           
                Dec(PointerPosition)
                DrawPointer(PointerPosition + 1)
               
        #If Menu_ScrollWrapping = True
       
            Else
             
                If ScrollPosition > 0 Then
                    Dec(ScrollPosition)
                Else
                    ScrollPosition = LastItemNumber - FirstItemNumber
                EndIf
               
                DrawMenu()
                DrawPointer(PointerPosition)
               
            EndIf
           
        #Else               
                   
            ElseIf ScrollPosition > 0 Then
           
                Dec(ScrollPosition)
                DrawMenu()
                DrawPointer(PointerPosition)
               
           
            ElseIf PointerLine > 2 Then
           
                Dec(PointerPosition)
               
                #If Menu_LCDLines < 4 And Menu_ShowScrollIndicators = True
       
                    DrawMenu()
                    DrawPointer(PointerPosition)
                   
                #Else
               
                    DrawPointer(PointerPosition + 1)
                   
                #EndIf
                       
            EndIf
           
        #EndIf
       
    #Else

        #If Menu_ShowScrollIndicators = True
       
            If PointerLine > 2 Then
           
        #Else
       
            If PointerLine > 1 Then
       
        #EndIf
           
                Dec(PointerPosition)
                DrawPointer(PointerPosition + 1)
       
       
        #If Menu_ScrollWrapping = True
       
            Else
             
                If ScrollPosition > 0 Then
                    Dec(ScrollPosition)
                Else
                    ScrollPosition = LastItemNumber - FirstItemNumber
                EndIf
               
                DrawMenu()
                DrawPointer(PointerPosition)
               
            EndIf
           
        #Else                       
           
            ElseIf ScrollPosition > 0 Then
           
                Dec(ScrollPosition)
                DrawMenu()
                DrawPointer(PointerPosition)
               
           
            ElseIf PointerLine > 1 Then
           
                Dec(PointerPosition)
               
                #If Menu_LCDLines < 4 And Menu_ShowScrollIndicators = True
       
                    DrawMenu()
                    DrawPointer(PointerPosition)
                   
                #Else
               
                    DrawPointer(PointerPosition + 1)
                   
                #EndIf
               
            EndIf
           
        #EndIf
       
    #EndIf

End Sub

'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'Name:      Back()     
'Purpose:  Show parent of current menu
'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Public Sub Back()

    ShowMenu(ParentID)

End Sub

'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'Name:      SelectItem()     
'Purpose:  Carries out action of selected menu item.  If menu item opens
'          another menu then that menu is drawn.  If menu item is an 'action'
'          item then the event in the main program which handles the action is
'          called
'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Public Sub SelectItem()

Dim ItemAction As Byte

    SelectedMenuItem = FirstItemNumber + ScrollPosition + PointerPosition
   
    #If Menu_ScrollWrapping = True
   
        If SelectedMenuItem > LastItemNumber Then
           
            SelectedMenuItem = SelectedMenuItem - (LastItemNumber - FirstItemNumber) - 1
           
        EndIf
   
    #EndIf
   
    ItemAction = mnuItemAction(SelectedMenuItem)


    If ItemAction = 255 Then
   
        ParentID = CurrentMenu 'Means 'back' command will return to correct menu
        FItemActionEvent()    'Call event handler in main program
       
    Else
   
        ShowMenu(ItemAction)   
       
    EndIf

End Sub

'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'Name:      Initialise(pItemActionEvent as TEvent)     
'Purpose:  Sets name of main program event handler and loads custom scroll
'          characters into LCD
'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Public Sub Initialise(pItemActionEvent As TEvent)

    FItemActionEvent = pItemActionEvent
   
    #If Menu_ShowScrollIndicators = True Then

        LCD.Write(ScrollIndicators)
   
    #EndIf

End Sub

'===============================================================================
'Module Initialisation
'-------------------------------------------------------------------------------
=]