Restoring GUI state after window is closed

Hello everyone!

I am using PGM for designing a user interface for a synth, and I am having a problem recovering the GUI state when the GUI is redrawn.

In particular, I have a combobox that fills up at runtime with filenames obtained from a user-selected directory. The ComboBox is defined in the XML. I find it using a pointer to the builder and populate it with file entries.

   if (auto* item = builder_ptr->findGuiItemWithId(GUI_IDs::models)) 
      {
      std::cout << "Found ComboBox" << std::endl;
      if (auto* combo_box =
              dynamic_cast<juce::ComboBox*>(item->getWrappedComponent())) 
        {
        combo_box->clear(false);  // Remove all previous items
        int menu_idx = 1;
        for (std::string menuentry : _guiconfig.modelnames) 
          {
          combo_box->addItem(menuentry, menu_idx);
          menu_idx++;
          }

I can keep the list in the valueTree or a Processor-owned vector, but my question is, how can I manage to re-populate it when the plugin GUI is redrawn?

For instance, in REAPER, the plugin is redrawn every time I close and open back the plugin window, or every time I switch between plugins connected on the same track.

I know that foleys::GuiItem has an update() callback, but I cannot fetch any non-GUI related information from there.

Is there any callback that can be handled to re-populate the combobox using values from the valueTree that are visible to the Processor too?

Thanks so much!
Fran

Hi Fran,
This is a good question. The update() is indeed the wrong place like you mentioned. That is called only when the GUI is rebuilt to set all properties from the ValueTree that declares the whole GUI machinery.

I haven’t designed any dynamic ComboBox content yet, it seems it deserves a built in interface.
However if you look at FoleysSynth in the examples, it populates a Listbox with preset names, so it could serve as example.

Basically I would create a kind of back-end in the processor that you then publish via

magicState.createAndAddObject<FileListBackend>("files");

// and retrieve it in your bespoke GuiItem that has a combo box in update()
fileList = magicState.getObjectOfType<FileListBackend>("files");

That way you can add a combobox that has access to the backend in the processor.

I hope that gets you further…

Hey Daniel,

First of all, thank you so much for your quick response!
It literally got me there, and it is working now :slight_smile:

The only thing is that I am not sure I pulled it off in an elegant way.
I have an issue with the combobox.onChange() lambda function that I am setting during the GuiItem::update() method (the factory for the combobox)

The lamba function loads a patch and needs access to the processor.

This is what I do:

combobox.onChange = [&] {
    MyProcessor *proc = (MyProcessor*)magicBuilder.getMagicState().getProcessor();
    if(proc)
    {
        const unsigned int selected_entry = combobox.getSelectedId() - 1;
        // Stop Audio Processing Thread
        proc->suspendProcessing(true);
        proc->load_patch(selected_entry);
        proc->suspendProcessing(false);
    }
};

Is there any other way to avoid handling the processor pointer in such a way? Should I be using a ComboBoxAttachment or a different approach to store the lamda in the valueTree (if that’s possible) instead of resetting it every single time update() is called?

Thank you again!
Fran

Awesome, glad you got it working so quickly!

That’s exactly how I would have did it.
The capture is safe, because the magicBuilder and the comboBox will outlive the lambda.
And the cast is fine, since there is only one processor type in the project.

Style-wise I would prefer auto and dynamic_cast, but that is just aesthetics:

if (auto* proc = dynamic_cast<MyProcessor*>(magicBuilder.getMagicState().getProcessor())
{
    const auto selected_entry = combobox.getSelectedId() - 1;
    // ...
}

Note that I would avoid unsigned int, since it can be easier reasoned about the value.
Imagine getSelectedId() returns 0 (which it does when nothing is selected), then it loads the path no 65535.

Cheers,
Daniel

Hey Daniel,

I have taken your suggestions into account :slight_smile:

I have another question, the GUI state (such as the patch list) persists when the window is redrawn, but the attributes I create with createAndAddObject, for instance magicState.createAndAddObject<FileListBackend>("files"); are lost once I close and open the DAW back.

I noted that the parameters I declare in a ParameterLayout at createParameterLayout() persist between even when I close and open back the DAW.

Is there a way to store the items created with magicState.createAndAddObject() so that they persist between DAWs runs?

Thanks so much again!

Fran

The MagicState has a ValueTree that is automatically serialised in the getStateInformation().
There are three parts:

  • the parameters are stored in the schema compatible to the AudioProcessorValueTreeState (in the subclass MagicProcessorState, which is default in the MagicProcessor)
  • a sub tree “properties” that can be connected from GUI and from the processor via getPropertyAsValue("path:value")
  • getValueTree() is the root of the xml, where you can add more children if you need

In addition you find an ApplicationSettingsFile in the MagicState, which will be shared from all plugin instances, e.g. for settings

Hope that helps