TabbedComponent how to manage UndoManager?


#1

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 ?


#2

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…


#3

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


#4

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

#5

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

#6

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

#7

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 ?


#8

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…


#9

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

#10

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?


#11

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

#12

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

#13

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


#14

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()
    { ...

#15

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


#16

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 !