---------------------------------------------------------
The Liberty Basic Newsletter - Issue #45 - JUL 99
"Knowledge is a gift we receive from others."
		- Michael T. Rankin
---------------------------------------------------------
In this issue:

Menus - Part 2



In future issues:

Block Structured Code 
        AND
Development of Large Programs with LB
	by Herman

---------------------------------------------------------
Menu API calls:  advanced
	Add/Remove SubMenus and Menu Items.
	Make popup menu columns.
	Track popup menus.
	Adding a Most Recently Used list to a menu.
---------------------------------------------------------
Menu API calls:  advanced
Add/Remove SubMenus and Menu Items.

Menu Items may be removed with a call to DeleteMenu.  
This will not work with SubMenus.  A Menu Item that has 
been DELETED may be INSERTED again later.

[delete.menu.item]
calldll #user, "DeleteMenu",_
  hSubMenu as word,_       'handle of SubMenu
  menu.id as short,_       'ID of menu item to delete
  _MF_BYCOMMAND as word,_  'use ID rather than position
  result as ushort


Menu Items and SubMenus may be removed permanently with a call 
to RemoveMenu.    Remember that you must make a call to DrawMenu 
to redraw the menu bar, anytime you make modifications to the 
Menu Bar or SubMenus:

[remove.sub.menu]
calldll #user, "RemoveMenu",_
  hMenu as word,_         'handle of Menu Bar
  hSubMenu as short,_     'handle of SubMenu to remove
  _MF_BYCOMMAND as word,_ 'flags
  result as ushort

calldll #user, "DrawMenuBar",_
  hWnd as word,_   'handle of window containing Menu Bar
  result as void


[remove.menu.item]
calldll #user, "RemoveMenu",_
  hSubMenu as word,_      'handle of SubMenu 
  menu.id,_               'handle of Menu Item to remove
  _MF_BYCOMMAND as word,_ 'flags
  result as ushort

YOU MAY ONLY INSERT MENU ITEMS THAT WERE DELETED PREVIOUSLY 
with a call to DeleteMenu.  You may NOT insert Menu Items 
that were deleted with RemoveMenu.  You may NOT insert items 
that were not initialized with Liberty BASIC MENU commands.  
If you do not want an item to be included at the start of a 
program, you must list it in your LB menu and call DeleteMenu 
at the start of your program.  When the item is needed, then 
a call is made to InsertMenu.  Note that you also need to 
specify the string for the Menu Item, even if you named it 
earlier with LB commands.  This technique will be used again 
later in this document.  This call only works with Menu Items, 
not with SubMenus.

[insert.menu.item]
Menu.flags=_MF_STRING or _MF_BYCOMMAND

calldll #user, "InsertMenu",_
 hSubMenu as word,_      'handle of SubMenu
 menu.id as short,_      'ID of item to insert
 Menu.flags as word,_    'flags
 menu.id as word,_       'ID of item to insert
 "Item Title" as ptr,_   'the text string for the Menu Item
 results as ushort


Make popup menu columns.
Liberty BASIC provides us with an easy way to make a 
horizontal dividing line within our popup menus.  We can 
call on the API to divide menus into columns - either with 
or without a visible dividing line.  This again uses a call 
to ModifyMenu.  It requires that we have used LB commands 
to specify a Menu Item, which we then transform into the 
column break we need.  We can have multiple columns if we 
insert multiple breaks.  The flag used in the call indicates 
whether there will be a visible dividing line between columns. 
If the flag is  _MF_MENUBARBREAK  columns are formed with lines 
between. If the flag is  _MF_MENUBREAK columns are formed 
without lines between.

[make.menu.columns.dividing.lines]
FLAGS = _MF_BYCOMMAND OR _MF_STRING OR _MF_MENUBARBREAK
calldll #user, "ModifyMenu",_
  hSubMenu as word,_   'handle of submenu
  menu.id as word,_    'ID of Menu Item
  FLAGS as word,_      'flags
  menu.id as word,_    'ID of Menu Item
 "" as ptr,_           'no string needed here!
 results as ushort


[make.menu.columns.noline]
FLAGS = _MF_BYCOMMAND OR _MF_STRING OR _MF_MENUBREAK
calldll #user, "ModifyMenu",_
  hSubMenu as word,_   'handle of submenu
  menu.id as word,_    'ID of Menu Item
  FLAGS as word,_      'flags
  menu.id as word,_    'ID of Menu Item
 "" as ptr,_           'no string needed here!
 results as ushort



Track popup menus.

Once you have initialized a menu with LB commands, you 
can have the popup menu appear on the screen anywhere you 
like - even outside of the program's window!  This 
procedure requires a call to TrackPopupMenu.  You can 
even remove the Menu Bar or the SubMenu on the Menu Bar 
and still have access to the popup menu.  This is most 
frequently activated in applications by a click of the 
right mouse button, but the choice is up to the programmer.  

There is one tricky part about positioning the popup menu.  
Since it can appear outside of a window, the X, Y coordinates 
used to position it are SCREEN coordinates, not CLIENT 
coordinates.  First we must make a call to ClientToScreen, 
which converts the client coordinates of any point on the 
screen to screen coordinates.  In the following example, 
the POINTAPI.x.struct is filled by the ClientToScreen call 
with the screen position of the left side of the window.  
If you want your popup to appear where the mouse is located, 
just add MouseX to this X position, as in the example below.  
The same is true for the Y position.  The POINTAPI.y.struct 
variable is filled with the coordinate of the upper side of 
the window.  Add this to MouseY to have the popup menu appear 
at the current Y position of the mouse.


[track.popup]
struct POINTAPI,_
x As short,_
y As short

calldll #user, "ClientToScreen",_
  hWnd As word,_         'handle of window
  POINTAPI as struct,_   'struct to be filled by the call
  r as void

'*****open menu at mouse coords:
mx = POINTAPI.x.struct+MouseX
my = POINTAPI.y.struct+MouseY


calldll #user, "TrackPopupMenu",_
  hSubMenu as word,_  'handle of SubMenu (popup menu)
  0 as word,_         'flag for alignment - this is the default
  mx as word,_        'upper left x position for menu
  my as word,_        'upper left y position for menu
  0 as word,_         'reserved - always 0
  hWnd as word,_      'handle of window
  0 as long,_         'not used in LB
  r as ushort




 
Adding a Most Recently Used list to a menu.

Many applications append a list of most recently opened files 
to the bottom of the File Menu.  There is a way to do this in 
Liberty BASIC.  It is not possible to call AppendMenu 
successfully in LB, because there is no way to associate 
the new Menu Item with a branch label within the program.  
It can be done however, by setting up the File Menu with dummy 
items where the recently opened files will later appear.  You 
must get the ID's of these items and call DeleteMenu for each 
of them.  After a file is opened by the user of your program, 
a call is made to InsertMenu, adding one of the dummy items 
back and changing its name to be the name of the file.  If you 
have already reinserted all dummy items, then you will need to 
replace a Menu Item with the new file name with a call to ModifyMenu.
The branch label associated with the Menu Item ID must contain 
contain or access the procedure that utilizes the file.

This would work as follows.  The menu would contain one or more 
dummy items:

menu #win, "&File", "&Open",[open],"Dummy One",[file.one]

When the user chooses the option to Open a file, a filedialog would 
allow him/her to choose a file to open:

filedialog "Choose file", "*.*", fileName$

You would then call InsertMenu to insert a previously deleted 
dummy item:

Menu.flags=_MF_STRING or _MF_BYCOMMAND
calldll #user, "InsertMenu",_
 hSubMenu as word,_   'handle of SubMenu
 menu.id as short,_   'ID of item to insert
 Menu.flags as word,_ 'flags
 menu.id as word,_    'ID of item to insert
 fileName$ as ptr,_   'the text string for the Menu Item
 results as ushort

After this, the program would continue on to the branch label 
that utilizes the chosen file:

GOTO [file.one]

This method was "invented" by Alyce.  It works.  It has some 
drawbacks, though.  The files are added back in order of 
menu item placement, so if many files are opened, the most 
recently opened file might actually appear in the middle or 
at the bottom of the list.  The other problem is the length 
of the file name.  Professional applications truncate this 
name if it is long.  You can see why, if you use the sample 
program to open a file that is buried several directories 
deep.  It makes for a pretty wide popup menu!

Brosco has solved these problems with his MRU menu list set
of reusable subroutines.

After the user chooses a file, the path and filename should 
be truncated for a more professional look.

MaxLen=20 
if len(fileName$) > MaxLen then
   fileName$ = left$( fileName$, 3) + " ... " + _
         right$( fileName$, MaxLen - 8)
end if

The fileName$ "c:\lb14w\programs\brosco\samples\just4fun.bas"  
would instead become "c:\...just4fun.bas"

The next operation to perform is to see if the fileName$ is 
already on the menu list.  To make this easy, the list is 
maintained in an array.  If the name is on the list, and is 
in the first position, then no other operation needs to be 
performed.  If it is on the list, but not in the first 
position, the list must be rearranged to bring the fileName$ 
to the first postion and move the other files into their new 
positions.  If it is not on the list, it must be added in 
place of the oldest file$ on the list, if the list is full.  
If the list is not full, then a previously deleted dummy Menu 
Item must be replaced with a call to InsertMenu.  The string$ 
of this inserted Menu Item will be the truncated fileName$ and 
it will be placed at the top of the list - as the first item 
in the list array.

Whew!  It gets pretty complicated!  If you don't care to puzzle 
out the exact code to perform these operations, you don't need 
to do so.  Brosco has written a set of reusuable subroutines 
that you can cut and paste into your program.  There is a 
section for you to initialize the routines with your own values, 
such as position in the menu of the first MRU item, number of 
MRU items to list and maximum length for file names.  See 
"menumru.bas," which is included at the end of this document.

One last note:  upon closing of the program, you may write 
the file names from the list into an ini file that will be 
read when the program is next opened, and used to fill the 
MRU list.  If you choose to do this, be sure to use COMPLETE 
filenames in the ini file, not the truncated versions!

MENUMRU.BAS
' Demo of Menu MRU (Most Recently Used) list
' Original code by Alyce Watson
' Reusable subroutine by Brosco
' Version 1.00 - May 1998
'
    NOMAINWIN
    UpperLeftX = 20
    UpperLeftY = 20
    WindowWidth = 600 : WindowHeight = 300

    Menu #MenuList, "File", _
        "Open",[open], _                ' SubMenuItem 0
        "Exit", [Menu.List.Exit], _     ' SubMenuItem 1
        |, _                            ' SubMenuItem 2
        "Item One",[menu.one], _        ' SubMenuItem 3
        "Item Two",[menu.two], _        ' SubMenuItem 4
        "Item Three",[menu.three], _    ' SubMenuItem 5
        "Item Four",[menu.four]         ' SubMenuItem 6

    STATICTEXT #MenuList.SText, "Add a list to a menu.  Choose File/Open to add files to the list.", 10, 100, 200, 80

    open "Adding a MRU list to a menu..." for window as #MenuList
    print #MenuList, "!TRAPCLOSE [Menu.List.Exit]"

' *****  These statements are required to Initialise [MenuMRU] ********
'
    open "user.dll" for dll as #user ' must be OPENed before first Call
    MenuMRU.hWnd = hwnd(#MenuList)  ' also use GetParent if Graphics window
    MenuMRU.Filename$ = ""      ' "" to initialise the function
    MenuMRU.MaxLen = 20         ' Maximum Filename length in the Menu
    MenuMRU.MenuItem = 0        ' First menu Item on the MenuBar
    MenuMRU.FirstSub = 3        ' 4th Submenu item = 3
    MenuMRU.LastSub = 6         ' 7th SubMenu Item = 6
    gosub [MenuMRU]
'
' ***** End [MenuMRU initialisation **********************


[Menu.List.Loop]
    Input a$
    Goto [Menu.List.Loop]

'***************** Open a file and add it to the MRU list
[open]
    filedialog "Choose file", "*.*", MenuMRU.Filename$
    if MenuMRU.Filename$="" then goto [Menu.List.Loop]
[Process.File]
    gosub [MenuMRU]         ' ***** Add Filename to MRU list
'
' Your file processing goes here!
'
    notice "File chosen is ";MenuMRU.Filename$
    Goto [Menu.List.Loop]


'
' When the user Selects a file from the MRU list -
' retrieve it from the MenuMRU.File$ array.
'
[menu.one]
    MenuMRU.Filename$ = MenuMRU.Files$(1)
    Goto [Process.File] ' Goto UPDATE of the MRU list - then user processing

[menu.two]
    MenuMRU.Filename$ = MenuMRU.Files$(2)
    Goto [Process.File]

[menu.three]
    MenuMRU.Filename$ = MenuMRU.Files$(3)
    Goto [Process.File]

[menu.four]
    MenuMRU.Filename$ = MenuMRU.Files$(4)
    Goto [Process.File]

[Menu.List.Exit]
    close #MenuList
    close #user
  END

' *************** Reusable subroutine: MenuMRU starts here ***********
'
' Subroutines:
' [MenuMRU] - Maintain a Menu of Most REcently Used (MRU) filenames.
'
'   INPUT:
'       MenuMRU.hWnd = hwnd of the Window with the Menu ( use
'                      GetParent if its a GRAPHICS Window)
'       MenuMRU.Filename$ - Set to "" on the first call to initialise
'                       the MRU array - subsequent calls have the
'                       Filename to be maintained in the  MRU array
'       MenuMRU.MaxLen - Maximum Filename length to be used in the
'                       Menu display.  (Filename will be abbreviated).
'       MenuMRU.MenuItem - The Position of the MenuItem on the MenuBar.
'                       Note - the first item is 0, the second=1, etc
'       MenuMRU.FirstSub - The position of the first MRU SubMenuItem in
'                       the list.
'                       Note - the first item is 0, the second=1, etc
'       MenuMRU.LastSub - Position of the last MRU SubMenuItem in the list.
'                       Note - the first item is 0, the second=1, etc
'
'   OUTPUT:
'       MenuMRU.Files$( - An array of the MRUs (Most Recently Used) filenames.
'
'   ASSUMPTIONS:
'       1) "user.dll" has been OPENned as #user
'       2) #user will be CLOSEd by the program
'
[MenuMRU]
                    ' First time - Dim the arrays and Delete the Menu Items
    if MenuMRU.dimmed = 0 then
        MenuMRU.dimmed = 1
        menuMRU.numMa = 0           ' Number of Menu slots used
        menuMRU.numM = MenuMRU.LastSub - MenuMRU.FirstSub + 1
        dim MenuMRU.Files$(10)
        dim menuMRU.subM(10, 2) ' subM(i,1) = Item.id, subM(i,2) = Inserted Flag
        redim MenuMRU.Files$(menuMRU.numM)
        redim menuMRU.subM(menuMRU.numM, 2)
        gosub [menuMRU.delete]
        if MenuMRU.MaxLen < 20 then MenuMRU.MaxLen = 20  'Minimum Filename size
        menuMRU.ddir$ = DefaultDir$ + "\"
        end if
    if MenuMRU.Filename$ = "" then return

    menuMRU.found = 0       ' Test if file is in list
    for menuMRU.i = 1 to menuMRU.numMa
        if MenuMRU.Filename$ = MenuMRU.Files$(menuMRU.i) then
            menuMRU.found = menuMRU.i
            menuMRU.i = menuMRU.numMa
            end if
        next menuMRU.i

    if menuMRU.found = 1 then return    'Already first in list

    if menuMRU.found <> 0 then          ' Move it to Top of list
        menuMRU.work$ = MenuMRU.Files$(menuMRU.found)
        for menuMRU.i = menuMRU.found to 2 step -1
            MenuMRU.Files$(menuMRU.i) = MenuMRU.Files$(menuMRU.i-1)
            next menuMRU.i
        MenuMRU.Files$(1) = menuMRU.work$
        gosub [menuMRU.updateMenu]
        return
        end if

    if menuMRU.numMa < menuMRU.numM then    ' Add it to MRU list
        menuMRU.numMa = menuMRU.numMa + 1
        for menuMRU.i = menuMRU.numMa to 2 step -1
            MenuMRU.Files$(menuMRU.i) = MenuMRU.Files$(menuMRU.i-1)
            next menuMRU.i
        MenuMRU.Files$(1) = MenuMRU.Filename$
        gosub [menuMRU.updateMenu]
        return
        end if

                    ' Delete oldest entry - then Add new Filename
   for menuMRU.i = menuMRU.numM to 2 step -1
        MenuMRU.Files$(menuMRU.i) = MenuMRU.Files$(menuMRU.i-1)
        next menuMRU.i
    MenuMRU.Files$(1) = MenuMRU.Filename$
    gosub [menuMRU.updateMenu]

    return

[menuMRU.updateMenu]    ' Used internally by MenuMRU
    for menuMRU.i = 1 to menuMRU.numMa
        menuMRU.id = menuMRU.subM(menuMRU.i, 1)
        menuMRU.file$ = MenuMRU.Files$(menuMRU.i)
        if upper$(left$(menuMRU.file$, len(menuMRU.ddir$))) = _
                        upper$(menuMRU.ddir$) then
            menuMRU.file$ = right$(menuMRU.file$, _
                len(menuMRU.file$) - len(menuMRU.ddir$))
            end if
        if len(menuMRU.file$) > MenuMRU.MaxLen then
            menuMRU.file$ = left$(menuMRU.file$, 3) + " ... " + _
                    right$(menuMRU.file$, MenuMRU.MaxLen - 8)
            end if
        if menuMRU.subM(menuMRU.i, 2) = 0 then
            menuMRU.subM(menuMRU.i, 2) = 1
            menuMRU.insert.Flags=_MF_STRING or _MF_BYCOMMAND
            calldll #user, "InsertMenu",_
                MenuMRU.FileMenu as word,_
                menuMRU.id as short,_
                menuMRU.insert.Flags as word,_
                menuMRU.id as word,_
                menuMRU.file$ as ptr,_
                menuMRU.results as ushort
        else
            calldll #user, "ModifyMenu",_
                MenuMRU.FileMenu as word,_
                menuMRU.id as word,_
                menuMRU.insert.Flags as word,_
                menuMRU.id as word,_
                menuMRU.file$ as ptr,_
                menuMRU.results as ushort
            end if
        next menuMRU.i
    return

[menuMRU.delete]        ' Used internally by MenuMRU
                        '*************GetMenu gets handle of menu bar
    calldll #user, "GetMenu", _
        MenuMRU.hWnd as word, MenuMRU.hMenu as word

                        '*************get handle of submenus
    calldll #user, "GetSubMenu", _
        MenuMRU.hMenu as word, _
        MenuMRU.MenuItem as short, _
        MenuMRU.FileMenu as word

                        '*************gets ID number for a menu item
    for menuMRU.i = MenuMRU.FirstSub to MenuMRU.LastSub
        calldll #user, "GetMenuItemID", _
            MenuMRU.FileMenu as word, _
            menuMRU.i as short, _
            menuMRU.id as word
        menuMRU.subM(menuMRU.i - MenuMRU.FirstSub + 1, 1) = menuMRU.id
        next menuMRU.i

                        '*************Deletes the menuItems from the subMenu
    for menuMRU.i = 1 to menuMRU.numM
        menuMRU.id = menuMRU.subM(menuMRU.i, 1)
        calldll #user, "DeleteMenu",_
            MenuMRU.FileMenu as word,_
            menuMRU.id as short,_
            _MF_BYCOMMAND as word,_
            menuMRU.results as ushort
        next menuMRU.i
    return

' ****** End of MenuMRU Subroutine ************************************
---------------------------------------------------------
 Newsletter compiled and edited by: Brosco and Alyce.
 Comments, requests or corrections: Hit 'REPLY' now!
            mailto:brosco@orac.net.au
                       or
           mailto:awatson@mail.wctc.net
---------------------------------------------------------