Dynamic menu on a function
Hi I'm trying to use the library without macros like this:
prompt* mainData[] = {
new prompt("Op 1", doNothing, enterEvent),
new prompt("Op 2", doNothing, enterEvent),
};
menuNode& mainMenu = *new menuNode("T:F Tumaka Config", sizeof(mainData) / sizeof(prompt*), mainData);
navNode path[MAX_DEPTH];
navRoot nav(mainMenu, path, MAX_DEPTH, in, out);
which works fine but if I try to put the code in a function like
menuNode& createMainMenu() {
prompt* mainData[] = {
new prompt("Op 1", doNothing, enterEvent),
new prompt("Op 2", doNothing, enterEvent),
};
menuNode& mainMenu = *new menuNode("T:F Tumaka Config", sizeof(mainData) / sizeof(prompt*), mainData);
return mainMenu;
}
menuNode & mainMenu = createMainMenu();
navNode path[MAX_DEPTH];
navRoot nav(mainMenu, path, MAX_DEPTH, in, out);
it fails like a champ (the hardware reboots in a bucle) It is, probably, a newbie error but I can't see it
Can you point me the error, please? Thanks
Hi!
from some working code... hope i'm not missing copy/paste some detail
prompt* fxChan_data[]{
new menuValue<fxChan>(lang[txtOff],0),
new menuValue<fxChan>(lang[txtfxChan1],1),
new menuValue<fxChan>(lang[txtfxChan2],2),
new menuValue<fxChan>(lang[txtfxChan3],3)
};
prompt* settingsMenu_data[]{
new menuField<typeof(settings.standbyTime)>(settings.standbyTime,lang[txtStandby],"min.",2,5,1,0,setStandbyTime,enterEvent,wrapStyle),
new textField(lang[txtKey],settings.key,1,&menu_base64In,checkKey,exitEvent),
new select<FxChan>(lang[txtFx],settings.Fx,len(FxChan_data),FxChan_data),
new menuField<typeof(settings.FxMax)>(settings.FxMax,lang[txtFxMax],"%",0,10,1,0.1,doNothing,noEvent,wrapStyle),
new toggle<bool>(lang[txtAux],settings.Aux,len(togData),togData),
&back
};
menuNode settingsMenu(
lang[txtSettings],
len(settingsMenu_data),
settingsMenu_data
);
Hi! Thanks for your response but my issue is when I put this code in a function (the function's signature I guess?)
menu items cannot be allocated locally on a functions as they must exist after the function exit...
you are using new that allows it but the array that holds them is local... and C++ will require you to delete the items allocated with new inside the function, or its a memory leak. Its ok to do so globally because they will never be (or need to be) deleted by going out of scope
So how you plan to fill, for instance, the scanned available wifis so the user can choose?
The best strategy will depend on how exactly will your function be used...
a) generate different independent menus (worst case) b) populate same same menu (refreshing) c) populate a menu once
for c you don't have to worry with deletes, as it should be ok for the items to live forever...
for both b and c one can have an external variable prompt* to hold the items or allocate the pointer inside the function (using new), to a it must be allocated inside function for every menu to be independent
on cases a and b deleting the old/unused values is essential, otherwise you will have a memory leak
this example will let you draw a menu from an array of string or any other source https://github.com/neu-rah/ArduinoMenu/blob/master/examples/targetSel/targetSel/targetSel.ino
this might help, i'm using it but not checked all corner cases yet, so use with caution (feedback appreciated)
dynMem.h
/* -*- C++ -*- */
#ifndef MENU_DYN_MEM_H
#define MENU_DYN_MEM_H
#include <menu.h>
using namespace Menu;
//menu memory management ==========================================================
//copy initial static data
//can and should skip this if initial is created with new ;)
result initMenu(menuNode& m);
//insert given menu item at index n (n<=sz)
result insItem(menuNode& m,int n,prompt* o);
//delete nth menu item (n<sz)
result delItem(menuNode& m,int n);
//delet a dynamic created item
result delItem(prompt* m);
result delItem(menuNode* m);
//some aux functions to create dynamic menus ====================
//count parameters at compile time
template<typename O,typename ... OO> constexpr size_t count(O o) {return 1;}
template<typename O,typename ... OO> constexpr size_t count(O o, OO ... oo) {return count<OO...>(oo...)+1;}
//WARNING: you have to delete the menu if deallocating!
template<typename ... OO>
menuNode* newMenu(char* title,OO ... oo) {
uint8_t sz=count(oo...);
menuNode* r=new menuNode(title,sz,new prompt*[sz]());
prompt* data[]{(prompt*)oo...};
for(int n=0;n<sz;n++)
((menuNodeShadow*)r->shadow)->data[n]=data[n];
return r;
}
#endif
dynMem.cpp
#include "dynMem.h"
enum MenuMem {Copy,Ins,Del};//the operations!
//the secret work task! doing memory allocation and copy
template<MenuMem op>
static prompt** mkMenu(menu::prompt** src,uint8_t sz,uint8_t at=0,menu::prompt* ins=NULL) {
int nsz=sz+(op==Copy?0:op==Ins?1:-1);
//static as this is our secret function, used outside here it will leak memory!
//because people calling it MUST be aware of it allocating memory but NOT managing it.
//so the caller is reponsable for the management!
menu::prompt** tmp=new menu::prompt*[nsz];
for(int o=0,p=0;(op==Del&&o==at)?o++:0,op==Ins?p<nsz:o<sz;o++,p++) {
if (op==Ins&&o==at) {
tmp[p]=ins;
p++;
}
tmp[p]=src[o];
}
return tmp;
}
//copy initial static data
//can and should skip this if initial is created with new ;)
result initMenu(menuNode& m) {
//just copy the data for dynamic ram, then we can and SHOULD manage it
((menuNodeShadow*)m.shadow)->data=mkMenu<Copy>(((menuNodeShadow*)m.shadow)->data,m.sz());
return proceed;
}
result insItem(menuNode& m,int n,prompt* o) {
if (n>m.sz()) return quit;
prompt** oldData=((menuNodeShadow*)m.shadow)->data;
((menuNodeShadow*)m.shadow)->data=mkMenu<Ins>(((menuNodeShadow*)m.shadow)->data,m.sz(),n,o);
((menuNodeShadow*)m.shadow)->sz++;
delete[](oldData);
return proceed;
}
//TODO: Test this case! or disable it
result delItem(menuNode& m,int n) {
if (n>=m.sz()) return quit;
prompt** oldData=((menuNodeShadow*)m.shadow)->data;
((menuNodeShadow*)m.shadow)->data=mkMenu<Del>(((menuNodeShadow*)m.shadow)->data,m.sz(),n);
((menuNodeShadow*)m.shadow)->sz--;
delete[](oldData);
return proceed;
}
result delItem(prompt* m) {
delete m->shadow->text;
m->isMenu()?delete((menuNode*)m):delete(m);
return proceed;
}
//TODO: test this case! or disable it
result delItem(menuNode* m) {
for(int n=0;n<m->sz();n++)
delItem(&m->operator[](n));
delete [] (((menuNodeShadow*)m->shadow)->data);
delete m;
return proceed;
}
That seems promising (an example of the usage and its almost a documentation. I suggest to follow up the wifi case)
Now we only need to figure out the function signatures to use it with functions... How do you think we can do it?