ArduinoMenu icon indicating copy to clipboard operation
ArduinoMenu copied to clipboard

Dynamic menu on a function

Open Garito opened this issue 5 years ago • 7 comments

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

Garito avatar Dec 20 '20 06:12 Garito

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
);

neu-rah avatar Dec 20 '20 07:12 neu-rah

Hi! Thanks for your response but my issue is when I put this code in a function (the function's signature I guess?)

Garito avatar Dec 20 '20 13:12 Garito

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

neu-rah avatar Dec 20 '20 23:12 neu-rah

So how you plan to fill, for instance, the scanned available wifis so the user can choose?

Garito avatar Dec 20 '20 23:12 Garito

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

neu-rah avatar Dec 21 '20 04:12 neu-rah

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;
}

neu-rah avatar Dec 21 '20 04:12 neu-rah

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?

Garito avatar Dec 21 '20 12:12 Garito