Text Mode Menu Converter

HMG Samples and Enhancements

Moderator: Rathinagiri

Post Reply
HGAutomator
Posts: 191
Joined: Thu Jul 16, 2020 5:42 pm
DBs Used: DBF

Text Mode Menu Converter

Post by HGAutomator »

The following has zero practical use, as far as I can tell. But it was a tangent exploration thas was fun to play with.

Takes Menu Items specified in a form

{ { "First Menu item", "First Message" }, { "Second Menu item", "Second Message" }, { "Third Menu Item", "Third Message" }, etc. }

, and calculates rows and columns to generate Horizontal, Vertical, or Random menus.

Compile:

call ..\..\batch\compile.bat MultMenu /C /LE c:\minigui\Harbour\lib\hbnf /C /LE c:\minigui\Harbour\lib\SUPER %2 %3 %4 %5 %6 %7 %8 %9

Code: Select all

#Include "MiniGui.ch"
#Include "hbinkey.ch"
#Include "inkey.ch"

Function MultMenu
Local MainMenu_a := { ;
                      {'Add', "Add Something", },;
                      {'Open', "Open Something" },;
                      {'Edit', "Change Something" },;
                      {'Delete', "Delete Something"},;
                      {'Reset', "Reset Everything"},;
                      {'Quit', "Get the heck out" }}

Local CompleteMenu_a := { ;
                      {1,1 ,  "Add",          2, 1, "Add something"},;
                      {1,5 ,  "Open",         2, 1, "Open something"},;
                      {1,10 , "Delete",       2, 1, "Delete something"},;
                      {1,17 , "Change Date",  2, 1, "Change the current Date"},;
                      {1,29 , "List",         2, 1, "Display List"},;
                      {1,34 , "Purge",        2, 1, "Purge something"},;
                      {1,40 , "Purge 2",      2, 1, "Purge something else"},;
                      {1,48 , "Purge 3",      2, 1, "Purge something one more time"},;
                      {1,57 , 'Quit',         2, 1, 'Get da heck out'}}


SETCOLOR( "W+/B,B/W,,W+/B")
cls(23, chr(177))

ComputedArray_a := Fill_in_Coordinates_for_Menu_Array( MainMenu_a, "HORIZONTALBAR" )
nSelected := Execute_MenuArray(ComputedArray_a,1,.t.)
cls()

ComputedArray_a := Fill_in_Coordinates_for_Menu_Array( MainMenu_a, "VERTICALBAR" )
nSelected := Execute_MenuArray(ComputedArray_a,1,.t.)
cls()

ComputedArray_a := Fill_in_Coordinates_for_Menu_Array( MainMenu_a, "RANDOMMENU" )
nSelected := Execute_MenuArray(ComputedArray_a,1,.t.)
cls()


nSelected := Execute_MenuArray(CompleteMenu_a,1,.t.)
cls()



Return Nil



// From Super.lib
FUNCTION cls(ncColorAtt,Fill_Character_s)
local Color_s
Color_s   := iif(valtype(ncColorAtt)=="N",at2char(ncColorAtt),ncColorAtt)
Fill_Character_s := repl( iif(Fill_Character_s#nil,Fill_Character_s," "),9 )
dispbox(0,0,maxrow(),maxcol(),Fill_Character_s,Color_s)
RETURN ''


Function At2char(nColor)
local ForeGround_a   := {"N","B","G","BG","R","RB","GR","W",;
                  "N+","B+","G+","BG+","R+","RB+","GR+","W+"}
local BackGround_a   := {"N","B","G","BG","R","RB","GR","W",;
                  "N*","B*","G*","BG*","R*","RB*","GR*","W*"}
local nFore         := nColor%16
local nBack         := INT(nColor/16)
local cForeground   := ForeGround_a[nFore+1]
local cBackGround   := BackGround_a[nBack+1]
return ( cForeground+'/'+cBackGround )


// Originally Rat_Menu2() From SuperLib
FUNCTION Execute_MenuArray(Menus_a,nStart,Menu_First_Letter_Executes_b)
/*
  Menus_a should be a nested array of menu arrays Menu_a.
  Each menu array should be of the form
  { MenuRow, MenuColumn, MenuItem, MessageRow, MessageColumn, Message }

  nStart - The Nth Prompt to start with.  Defaults to the first prompt

	Menu_First_Letter_Executes_b -
		If Menu_First_Letter_Executes_b is True, then a single first letter and ENTER will return.
		If Menu_First_Letter_Executes_b is False, then two first letters are required.
*/

Local Normal_Color_s := ""
Local Enhanced_Color_s := ""
local NthOption_n := 0
Local NthOption_a := {}
Local Message_Row_n := 0
local Selected_Item_n := 0
local Previous_Selected_Item_n := 0
local LastKeyPressed_n
Local nFound
Local nMrow
Local nMcol
Local nMouseFound
local FirstLettersOfMenus_s := ""
local LastKeyPressed_c
Local Key_cb					 // SETKEY Code Block
local Cursor_n := setcursor(0)

Normal_Color_s   				:= Token(Setcolor(),",",1)
Enhanced_Color_s    			:= Token(Setcolor(),",",2)
NthOption_n := 0
NthOption_a := {}


// Return the current selected item, the starting item or the last item, whichever is less.
Selected_Item_n := min( iif(nStart#nil,max(nStart,1),1), len(Menus_a) )
Previous_Selected_Item_n := Selected_Item_n

Menu_First_Letter_Executes_b := iif(Menu_First_Letter_Executes_b#nil,Menu_First_Letter_Executes_b,.t.)

// Initial Display of all the menu items
AEVal( Menus_a, { | NthOption_a, NthOption_n   | DisplayCurrentMenuItem( NthOption_a, NthOption_n, Normal_Color_s, Enhanced_Color_s ) } )

// Concatenate all first letters of the menus into a single string
FirstLettersOfMenus_s := ConcatenateAllFirstLetters_of_Menus("ConcatenateFirstLetters",Menus_a,"")

while .t.

	hb_DispOutAtBox( Menus_a[Previous_Selected_Item_n,1], Menus_a[Previous_Selected_Item_n,2], Menus_a[Previous_Selected_Item_n,3], Normal_Color_s )
   Previous_Selected_Item_n      := Selected_Item_n
	hb_DispOutAtBox( Menus_a[Previous_Selected_Item_n,1], Menus_a[Previous_Selected_Item_n,2], Menus_a[Previous_Selected_Item_n,3], Enhanced_Color_s )

	// TODO: Customize background of message depending on type of menu
	// 		 Specifically, change the background fill characters for the left of the message.
	// Clear Message
	hb_DispOutAtBox( Menus_a[Previous_Selected_Item_n,4], Menus_a[Previous_Selected_Item_n,5], Repl( chr(177), MaxCol() ), Normal_Color_s )
	// Display Message
	hb_DispOutAtBox( Menus_a[Previous_Selected_Item_n,4], Menus_a[Previous_Selected_Item_n,5], Menus_a[Previous_Selected_Item_n,6], Normal_Color_s )

   LastKeyPressed_n := rat_event(0)
   LastKeyPressed_c := upper(chr(LastKeyPressed_n))
   do case
   case LastKeyPressed_n == K_LEFT .or. LastKeyPressed_n==K_UP
		hb_DispOutAtBox( Menus_a[Previous_Selected_Item_n,4], Menus_a[Previous_Selected_Item_n,5], Repl( chr(177), MaxCol() ), Normal_Color_s )
      Selected_Item_n := IIF(Selected_Item_n=1,len(Menus_a),Selected_Item_n-1)
   case LastKeyPressed_n == K_RIGHT .or. LastKeyPressed_n == K_DOWN
		hb_DispOutAtBox( Menus_a[Previous_Selected_Item_n,4], Menus_a[Previous_Selected_Item_n,5], Repl( chr(177), MaxCol() ), Normal_Color_s )
      Selected_Item_n := IIF(Selected_Item_n=len(Menus_a),1,Selected_Item_n+1)
	Case IsThisCharacterContainedInThatString( LastKeyPressed_c, FirstLettersOfMenus_s )
      if (nFound := AT(LastKeyPressed_c,subst(FirstLettersOfMenus_s,Selected_Item_n)) ) > 0
        Selected_Item_n := nFound+Selected_Item_n-1
      else
        Selected_Item_n := at(LastKeyPressed_c,FirstLettersOfMenus_s)
      endif
      if Menu_First_Letter_Executes_b .or. Selected_Item_n==Previous_Selected_Item_n
        keyboard chr(13)
      endif
   case LastKeyPressed_n = K_ENTER .or. LastKeyPressed_n==K_PGUP .or. LastKeyPressed_n== K_PGDN
      exit
   case LastKeyPressed_n = K_ESC
      Selected_Item_n := 0
      exit
    case ( (Key_cb := SetKey(LastKeyPressed_n)) <> NIL )
      Eval(Key_cb,"",0,"")
   endcase
end
setcursor(Cursor_n)
return Selected_Item_n


Procedure DisplayCurrentMenuItem( NthOption_a, NthOption_n, Normal_Color_s, Enhanced_Color_s	)
hb_DispOutAtBox( NthOption_a[1], NthOption_a[2], NthOption_a[3], Normal_Color_s )
Return

Function ConcatenateFirstLetters(FirstLettersOfMenus_s,NthOption_a)
	FirstLettersOfMenus_s += upper(left(NthOption_a[3],1 ))
Return FirstLettersOfMenus_s


/*
	Map functions were written by a generous fellow Harbour traveler in one of the google groups.
	For an explanation of each Map function, see
	 https://www.freecodecamp.org/news/javascript-map-reduce-and-filter-explained-with-examples/
*/


// Returns an array, whose elements are the value of xFunc applied to each element in aArg1
//  aArg2, aArg3, aArg4, ... aArgnN have not been tested yet.
// AMap( <xFunc, [,aArg1] [, aArgN])
function AMap(...)
   local aOut := {}
   local aTemp
   local aArgs := hb_aParams()
   local nArgCount := len(aArgs) // func is the first arg so skip it.
   local xFunc := iif(len(aArgs)>0,aArgs[1],nil)
   local nArgInd
   local lObject := hb_isObject(xFunc)
   local nArgStart := iif(lObject,3,2)
   local nResCount := iif(nArgCount>1,len(aArgs[2]),0)
   local nResInd
   local oObj := iif(lObject,xFunc,nil)
   if lObject
      xFunc := aArgs[2]
   endif
   for nResInd:=1 to nResCount
      aTemp := {}
      for nArgInd:=nArgStart to nArgCount
         aAdd(aTemp,aArgs[nArgInd][nResInd])
      next
      if lObject
         aAdd(aOut, HB_ExecFromArray(oObj,xFunc,aTemp))
      else
         aAdd(aOut, HB_ExecFromArray(xFunc,aTemp))
      endif
   next
return aOut


/*
	Applies function Reduction_Function_x to FirstValueToBeApplied_x and first element of Parameters_a,
	then applies Reduction_Function_x to the result of the first application and the second element of Parameters_a,
	then applies Reduction_Function_x to the result of the previous application and the third element of Parameters_a, etc,
	until it arrives at a single result which is the accumulation of the functiona applied to all consecutive elements of the array.

	AReduce( "Add", { 1, 2, 3 } )
		=> 6 ( 0 + 1 + 2 + 3 = 6 )
	AReduce( "ConcatenateAllFirstLetters_of_Menus", { "What", "the", "heck" } )
		=> "Wth" ( Concatenate "" with "W" with "t" with "h"   )

	Returns same type as FirstValueToBeApplied_x accumulated from each element
*/
function AReduce(Reduction_Function_x,Parameters_a,FirstValueToBeApplied_x)
   local StartingElement_n := 1
   local nLength := len(Parameters_a)
   local nInd
   if hb_isNil(FirstValueToBeApplied_x) .and. nLength>0
      StartingElement_n := 2
      FirstValueToBeApplied_x := Parameters_a[1]
   endif

	hb_ForNext( StartingElement_n, nLength, { |nInd| FirstValueToBeApplied_x := HB_ExecFromArray( Reduction_Function_x, {FirstValueToBeApplied_x,Parameters_a[nInd]}) } )

return FirstValueToBeApplied_x

/*
  Applies constraint function xFunc to each parameter of Parameters_a,
  , returning an array with elements that satisfy the constraint.

  See
	https://www.phptutorial.net/php-tutorial/php-array-filter/
		and
	https://en.wikipedia.org/wiki/Filter_(higher-order_function)

	for illustrative examples and explanations

*/
Function aFilter(xFunc,Parameters_a)
   local aOut := {}
   local nLength := len(Parameters_a)
   local nInd

   for nInd := 1 to nLength
      if HB_ExecFromArray(xFunc,{Parameters_a[nInd]})
         aAdd(aOut,Parameters_a[nInd])
      endif
   next
return aOut

Function IsThisCharacterContainedInThatString( ThisCharacter_s, ThatString_s )
Local ThisCharacterContainedInThatString_b := .F.
ThisCharacterContainedInThatString_b := ThisCharacter_s $ ThatString_s
Return ThisCharacterContainedInThatString_b


Function Fill_in_Coordinates_for_Menu_Array( MenuArray_a, Menu_Type_s )
/*
  Convert a menu array without coordinates like
  	{ MenuItem, Message }
  to a menu array with Row and Column coordinates like
  	{ MenuRow, MenuColumn, MenuItem, MessageRow, MessageColumn, Message }
*/
Local CompleteMenuArray_a := {}
Do Case
Case Menu_Type_s == "HORIZONTALBAR"
	CompleteMenuArray_a := AMap( "ConvertMenu_to_HorizontalBar", MenuArray_a )
Case Menu_Type_s == "VERTICALBAR"
	CompleteMenuArray_a := AMap( "ConvertMenu_to_VerticalBar", MenuArray_a )
Case Menu_Type_s == "RANDOMMENU"
	CompleteMenuArray_a := AMap( "ConvertMenu_to_RandomMenu", MenuArray_a )
End Case
Return CompleteMenuArray_a



Function ConvertMenu_to_HorizontalBar( MenuItem_a )
/*
  MenuItem_a has the structure
  { MenuItem, Message }
*/
Local HorizontalMenuItem_a := {}
Local Row_n := 1
Local MenuItem_s := ""
Local Message_s := ""
STATIC Column_n := 0

MenuItem_s := Trim( MenuItem_a[ 1 ] )
Message_s := Trim( MenuItem_a[ 2 ] )

// Menu item
AADD( HorizontalMenuItem_a, Row_n )
AADD( HorizontalMenuItem_a, Column_n )
AADD( HorizontalMenuItem_a, MenuItem_s )

// Messages associated with each Menu item
AADD( HorizontalMenuItem_a, Row_n + 1 )
AADD( HorizontalMenuItem_a, 0 )
AADD( HorizontalMenuItem_a, Message_s )

// Calculate Next Column, which will be the previous column plus the length of the current menu item plus the length of one space.
Column_n := Column_n + Len( Trim( MenuItem_s ) ) + 1

Return HorizontalMenuItem_a


Function ConvertMenu_to_VerticalBar( MenuItem_a )
/*
  MenuItem_a has the structure
  { MenuItem, Message }
*/
Local VerticalMenuItem_a := {}
Local Column_n := 1
Local MenuItem_s := ""
Local Message_s := ""
STATIC Row_n := 0

MenuItem_s := Trim( MenuItem_a[ 1 ] )
Message_s := Trim( MenuItem_a[ 2 ] )

// Menu item
AADD( VerticalMenuItem_a, Row_n )
AADD( VerticalMenuItem_a, Column_n )
AADD( VerticalMenuItem_a, MenuItem_s )

// Messages associated with each Menu item
AADD( VerticalMenuItem_a, Row_n )
AADD( VerticalMenuItem_a, Column_n + Len( MenuItem_s ) + 2 )
AADD( VerticalMenuItem_a, Message_s )

// Calculate Next Row, which will be the previous Row plus 1.
Row_n := Row_n + 1
Return VerticalMenuItem_a


/*
	Please do not ask me, why I built something as insane and useless
	 as a menu picklist located at random points on the screen.
	It just sounded like it would be fun to see.
*/
Function ConvertMenu_to_RandomMenu( MenuItem_a )
/*
  MenuItem_a has the structure
  { MenuItem, Message }
*/
#DEFINE TEMPORARYRANDOMROWLIMIT 15				// Maximum number of row coordinates
#DEFINE TEMPORARYRANDOMCOLUMNLIMIT 72			// Maximum number of column coordinates
/*	TODO - The maximum number of rows and columns used to extract the random rows and columns,
	 should be a factor of the total number of menu items.
*/
Local RandomMenuItem_a := {}
Local RandomRow_n := 0
Local RandomColumn_n := 0

Local Column_n := 1
Local MenuItem_s := ""
Local Message_s := ""

RandomRow_n := hb_RandomInt( TEMPORARYRANDOMROWLIMIT )
RandomColumn_n := hb_RandomInt( TEMPORARYRANDOMCOLUMNLIMIT )

MenuItem_s := Trim( MenuItem_a[ 1 ] )
Message_s := Trim( MenuItem_a[ 2 ] )

// Menu item
AADD( RandomMenuItem_a, RandomRow_n )
AADD( RandomMenuItem_a, RandomColumn_n )
AADD( RandomMenuItem_a, MenuItem_s )
// Messages associated with each Menu item
AADD( RandomMenuItem_a, RandomRow_n )
AADD( RandomMenuItem_a, RandomColumn_n + Len( MenuItem_s ) + 2 )
AADD( RandomMenuItem_a, Message_s )

Return RandomMenuItem_a



Function ConcatenateAllFirstLetters_of_Menus( ConcatenateFirstLetters_s, Menus_a, FirstValue_s )
Local FirstLettersOfMenus_s := ""
FirstLettersOfMenus_s := AReduce("ConcatenateFirstLetters",Menus_a, FirstValue_s )
Return FirstLettersOfMenus_s
[attachment=0]MultiMenu.jpg[/attachment]
Attachments
MultiMenu.jpg
MultiMenu.jpg (113.46 KiB) Viewed 1006 times
User avatar
serge_girard
Posts: 3168
Joined: Sun Nov 25, 2012 2:44 pm
DBs Used: 1 MySQL - MariaDB
2 DBF
Location: Belgium
Contact:

Re: Text Mode Menu Converter

Post by serge_girard »

Pure nostalgia?!
There's nothing you can do that can't be done...
HGAutomator
Posts: 191
Joined: Thu Jul 16, 2020 5:42 pm
DBs Used: DBF

Re: Text Mode Menu Converter

Post by HGAutomator »

Yes, and the feeling of Flow when using the Text Mode and Keyboard, as opposed to draaaaaggging the mouse to get things done.

serge_girard wrote: Tue Dec 21, 2021 11:09 am Pure nostalgia?!
Post Reply