Adding/preserving check-marks in context-menu

Using the code below I have a menu-item with two “sub-menues” and choosing one of them the “aOrder” variable will be set and the menu keeps their check-marks (which one is selected).

menu->addChild(createIndexPtrSubmenuItem("Order",
    {"Scale->Offset", "Offset->Scale"},
    &module->aOrder
));

But I would like to call a function/method in stead of just setting a variable. So in stead I did this:

menu->addChild(createSubmenuItem("Set order ", "",
	[=](Menu* menu) {
		menu->addChild(createMenuItem("Scale->Offset", "", [=]() {
			module->setOrder(module->aOrder, module->scaleOffset);
			}));
		menu->addChild(createMenuItem("Offset->Scale", "", [=]()  {
			module->setOrder(module->aOrder, module->offsetScale);
			}));
	}
));

And here is the method that called:

void setOrder(order& whichOrder, order value) {
	mustProcessParams = true;
	whichOrder = value;
	//TODO: update lights
}

This second way if calling the method works fine (it does what I need it to do), except for the checkmarks. Any ideas how I can implement checkmarks for this 2nd method, so the context-menu (first time its opened) actually shows that the first item is “selected by default” (it want to show a check-mark), and when the user manually choose one of the menu-items, that item should then show the checkmark and the other item should not?

Use create createCheckMenuItem, the first lambda is the “getter”, the second one is the “setter”.

E.g. For a menu in the root of the context

menu->addChild(createCheckMenuItem("Display follows modulated Model", "",
	[=]() {return module->bDisplayModulatedModel; }, /* This reads the value directly; but can be a function: bool getMenuItemStatus() */
	[=]() {module->toggleModulatedDisplay(); /* This uses a function to set the item; but it can be set directly module->myMenuItemSetting=myValue */ }));

If you feel like going old school… check here:

Thanks for posting I look forward putting it to a test (my way of saying “see if I can get it working”). However I am currently fighting to first fix another issue I have with the menu. My module “SignModule” (with widget “SignModuleWidget”) inherit from my base module “InfNoiseModule” (and widget “InfNoiseWidget”). That way I can put common features in “InfNoiseModule”/“InfNoiseWidget” that I want to use in all/some of my modules. One thing I try to do is to add some common menu-items in my “InfNoiseWidget”, and then add some additional menu-items in the desendants like “SignModuleWidget”. Here is the code of the “SignModuleWidget” what tries to invoke the “appendContextMenu” method of “InfNoiseWidget”:

void appendContextMenu(Menu* menu) override {
    InfNoiseWidget::appendContextMenu(menu);  // <<---
    SignModule* module = dynamic_cast<SignModule*>(this->module);
    assert(module);

    if (!menuSepAdded)
 	    menu->addChild(new MenuSeparator);

    std::vector<std::string> InvRangeNames = getVoltInvRangesNames();
    menu->addChild(createIndexPtrSubmenuItem("Inversion-range", InvRangeNames,
        &module->inversionRange));

    std::vector<std::string> trueNames = getTrueDetectVoltNames();
    menu->addChild(createIndexPtrSubmenuItem("Gate-true detection", trueNames, 
       &module->trueDetect));

    std::vector<std::string> voltNames = getVoltValuesNames();
    menu->addChild(createIndexPtrSubmenuItem("Gate-On level", voltNames, 
       &module->gateOn));
    menu->addChild(createIndexPtrSubmenuItem("Gate-Off level", voltNames, 
       &module->gateOff));
}

Here is the “appendContextMenu” of “InfNoiseWidget”, that I try to invoke from the above code (I must do something wrong?):

void appendContextMenu(Menu* menu) override {
    InfNoiseModule* module = dynamic_cast<InfNoiseModule*>(this->module);
    assert(module);

    if (module->haveProcQuality || module->haveOutClipRange)
    {
        menu->addChild(new MenuSeparator);
        menuSepAdded = true;
        
        if (module->haveProcQuality)
        {
            menu->addChild(createIndexPtrSubmenuItem("Process quality", processQualityNames,
                &module->procQuality));
        }
        if (module->haveOutClipRange)
        {
            std::vector<std::string> clipRangeNames = getVoltRangesNames(true);
            menu->addChild(createIndexPtrSubmenuItem("Clipping range", clipRangeNames,
                &module->outClipRange));
        }
    }
}

However when I make my module and load it into the Rack, all I see are the menu-items added directly by the “Sign”-module. The two menu-items that the “InfNoiseWidget” should add are nowhere to be seen. Removing the if’s, so the two items are always added have no effect. If I manually copy the menu-creation code from “InfNoiseWidget” (e.g. the following 3 lines) directly into the “appendContextMenu” method of “SignModuleWidget” the menu is added just fine:

std::vector<std::string> clipRangeNames = getVoltRangesNames(true);
menu->addChild(createIndexPtrSubmenuItem("Clipping range", clipRangeNames,
  &module->outClipRange));

Any ideas what I do wrong? I am extreeeeemly rusty when it comes to C++, so I am guessing I try to invoke the “appendContextMenu” of “InfNoiseWidget” wrong, or it have to do with how the “appendContextMenu” of “InfNoiseWidget” is casting module to “InfNoiseModule” … or something else?

For an option on how to do that, probably with less pain check here:

Another thing that comes to mind, though I haven’t tested it:

If one module descends from another, split the menu create methods:

Base module has its own menu created by say

void createBaseMenu(Menu *menu, *Module module) {
// Make your menu here.
}

base module has its own appendContextMenu that calls createBaseMenu

void appendContextMenu(Menu *menu) override { 
  createBaseMenu(menu, module);
}

The descendant module has its own method to create its menu:

void createDescendantMenu(Menu *menu, *Module module) {
// Make the child's menu here.
}

And its own appendContextMenu override that calls both the parent’s and its own methods:

void appendContextMenu(Menu *menu) override { 
  createBaseMenu(menu, module);
  createDescendantMenu(menu, module);
}

Make sure to cast Module outside the createBaseMenu and createDescendantMenu methods.

Once again @Bloodbat a big thank you. Yesterday I was thinking of trying something along your last suggestion (with “createBaseMenu”). I’ve added a “appendInfNoiseMenuItems” that I call from decendants “appendContextMenu” method and it works fine.

Regarding my first/initial question I decided on another implementation that better fits the way I am doing things. The way I am setting up my modules I’ll have them check various knob-positions, counting connected input/output, do certain calculations (based on knob-positions, and so on) either every 256 cycles, or if “mustProcessParams” is set true (e.g. done in “onReset” or when first loading up the module). In the same manner my modules have a process-quality setting (bit-pattern), which define how often the process “needs to run” (e.g. a slow-moving LFO don’t need to update at each cycle). This process is also “forced” execute if/when “processParams” have been executed:

void process(const ProcessArgs& args) override {
    bool doProcessParams = mustProcessParams || ((cycle256 & patternProcessParams) == patternProcessParams);
    if (doProcessParams)
        processParams(ProcessArgs& args);

    bool doProcess = (doProcessParams ||
        ((cycle256 & processQualityPatterns[procQuality]) == processQualityPatterns[procQuality]));
    if (doProcess && portsInUse > 0) {   
        // Only perform when "needed" ("portsInUse" is set by processParams)
    }
}

What I wanted to acomplish regarding my first/inital question, was to both set a variable (based on menu-selection) and at the same time set “mustProcessParams” to true (to force “processParams” to be executed). However “processParams” don’t need to be force-executed as soon as a menu-item is selected (every 256 cycles, is still 187 times per second at 48 hKz). But the new setting of that vairable (controled by the menu) should not “change” until after “processParams” have been executed, in order to prevent the process to use new values (set by menu-selection) until after “processParams” have been executed.

Hence I’ve decided on this following struct (to use for these menu-item values), that both have the “actual” value (that is currently used by process), and it have a “requested” value that is set/read by the menu. When the user selects a menu-item, only the “requested” value is changed, and process keeps using the “actual” value (that at this time remains unchanged). It is then the responsibility of “processParams” to update the actual value (set it to the requested):

template <typename T>
struct actReqValue {
    T act;  // Actual value used by process (e.g. each cycle)
    T req;  // E.g. Set/Read by menu-items (updates "act" each 256 cycles)

    actReqValue(T value) : act(value), req(value) {}

    inline void UpdateActual() {
        act = req;
    }

    inline bool needsUpdate() {
        return act != req;
    }
}

By both having the “actual” and “requested”-values, I can in some cases limit the work that “processParams” have to perform:

void processParams(ProcessArgs& args) {
    if (someVar.needsUpdate())
    {
        someVar.UpdateActual(); 
        // perform various calculations based on the value of "someVar.act"
    }
    
    otherVar.UpdateActual(); // nothing to calculate, just update in case it was changed
}
1 Like