TabbedComponent how to manage UndoManager?

Well, I got a Filter Window with a tabbedComponent and for each child component (filter 1 / 2 …) I wanna send the element number (OSC 1 / 2 / 3 / 4) and the undoManager.
I try to make a class ElementComponent and implement setElement (int number, &UndoManage um) and I hope I can use
tabs.getTabContentComponent(i)->setElement(number,undomanager);
But it doesn’t work !
Someone have an idea ?

There are two methods to achieve this:
a) putting the information into a ValueTree property, and react to a ValueTree::Listener, that would also fire on undo, or
b) implement an UndoableAction, that you can push to the undo stack of the UndoManager. This action needs a WeakReference to the tabs, and the previous selected tab. Now you simply implement the abstract undo() and perform() methods.

But a UX question would be, if you really want selecting a tab to be undoable… I personally would reserve undo for editing actions…

But a UX question would be, if you really want selecting a tab to be undoable… I personally would reserve undo for editing actions…

You’re right and I don’t wanna undoable the tabs, just I wanna send the General &undoManager and the element number to my tab->child filter editor

I’m afraid I don’t understand the question… if it is a compiler/syntax problem, can you post the exact code you tried to use?
Preferably surround your code with three backticks ` like this:

    ```
    int main (int, char**)
    {
        return 0;
    }
    ```

It’s my Fault, I’m not really clear :wink:
In the vue I have a setElementNumber I wanna send it to the child to use the valueTree with my general UndoManager and the element to refer to the good filter properties

//==============================================================================

struct TabFilter  : public TabbedComponent
{
    TabFilter (bool isRunningComponenTransformsDemo)
    : TabbedComponent (TabbedButtonBar::TabsAtTop)
    {
        auto colour = findColour (ResizableWindow::backgroundColourId);
        addTab (TRANS("Common "),     colour, new CommonFilter (), true);
        addTab (TRANS("Filter 1"),     colour, new Filter1 (), true);
        addTab (TRANS("Filter 2"),     colour, new Filter2 (), true);
                                                              
    }
    
 
    
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TabFilter)
};

//=============================================================================

/*
*/
class FilterVue    : public Component, public TextButton::Listener
{
public:
    FilterVue(bool isRunningComponenTransformsDemo = false)
    :tabs (isRunningComponenTransformsDemo)
    {
        // In your constructor, you should add any child components, and
        // initialise any special settings that your component needs.
           addAndMakeVisible(tabs);
        addAndMakeVisible(btClose);
        btClose.addListener(this);


    }

    ~FilterVue()
    {
    //    removeKeyListener(this);
       
        btClose.removeListener(this);
 
        
    }

    void setElementNumber ( int number, UndoManager& undoman)
    {
        intNumber = number;
    //    tabs.getTabContentComponent(1) ;
   //     Logger::writeToLog("Filter nbre tabs: " + String( tabs.getNumTabs()) );
        for(auto i =0; i>tabs.getNumTabs();i++)
        {
           // tabs.getTabContentComponent(i)->setElementNumber(number,undoman);
    
        }

What springs to the eye, is the for loop. The condition is, when the block is executed, not when it is stopped:

You meant probably to write this:

for (auto i=0; i<tabs.getNumTabs(); ++i)

The getTabbedComponent returns a Component, so you need to use dynamic_cast to cast it to your subclass, that allows setElement():

if (auto* filterTab = dynamic_cast<FilterVue*> (tabs.getTabContentComponent(i)))
{
    filterTab->setElementNumber (number, undoman);
}

Oh, you’re right for the loop !

I don’t know what is a dynamic_cast, but I’m gonna try.
Is it a way to access to a setter and a getter if it exist when the binary is compiled ?

dynamic_cast will try to convert the pointer type to a more concrete (inherited) type.
Say you have a component Foo:

class Foo : public Component 
{
public:
    void bar() { /* ... */ }
};

Component* component = new Foo();
component->bar();   // component cannot bar()
auto* foo = dynamic_cast<Foo*> (component);
foo->bar();        // foo can bar()

but careful, if the component is not in fact a Foo pointer, dynamic_cast will return nullptr…

Great ! So I need to implement ii like this ?

   for(auto i =0; i<tabs.getNumTabs();i++)
        {
           // tabs.getTabContentComponent(i)->setElementNumber(number,undoman);
            if (auto* filterTab = dynamic_cast<FilterVue*> (tabs.getTabContentComponent(i)))
            {
                filterTab->setElementNumber (number, undoman);
            }
        }

the call tabs.getTabContentComponent(i) returns a Component*, that doesn’t understand the methods, that you added for the classes CommonFilter or Filter1.
Either you create a common base class and use polymorphism, or you need to try each possible subclass for each tab. Ideally you have one filter class, that you instanciate 3 times with different parameters.

If you design classes, try to figure out, what is common, and what is different. Maybe CommonFilter, Filter1 and Filter2 can be one class?

Like this class And all my three vue inherit from it ?

class ElementComponent    : public Component
{
public:
    ElementComponent()
    {
        // In your constructor, you should add any child components, and
        // initialise any special settings that your component needs.
    }
    
    ~ElementComponent()
    {
        
    }
    void setElementNumber ( int element, UndoManager& um)
    {
        Logger::writeToLog( "ElementComponent setElement");
    }
    void paint (Graphics& g) override
    {
        /* This demo code just fills the component's background and
         draws some placeholder text to get you started.
         
         You should replace everything in this method with your own
         drawing code..
         */
        
        g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));   // clear the background
        
    }

    
    void resized() override
    {
        
    }
    
private:
    
    
    
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ElementComponent)
};

Exactly, and now you can do:

for(auto i = 0; i < tabs.getNumTabs(); ++i)
{
    if (auto* element = dynamic_cast< ElementComponent*> (tabs.getTabContentComponent(i)))
    {
        element->setElementNumber (number, undoman);
    }
}
1 Like

Yes I do It and it work ! You are the best !!!

I don’t know why I can’t override my setElementNumber function in each filter ?
I make them like this:

class CommonFilter    : public ElementComponent, public TextButton::Listener
{
public:
    CommonFilter()
    { ...

setElementNumber is not virtual in the class you are inheriting from?

1 Like

Oh Yess ! Just add virtual void setElementNumber in the class definition and it’s ok ! Like the Resize function in the component class.
Many thanks !
I got a lot of things to learn …

Really cool, I can now refer my slider value to the valueTree and set the undoManager !