Child component resize Top-Level DocumentWindow problem


#1

Hello guys,

I have some resizable child components inside a container component, which should resize their Top-Level DocumentWindow if the childs change size. Setup is like this (parent->child):

 

DocumentWindow -> ContainerComponent -> ItemComponent -> ResizableEdgeComponent

 

ItemComponents can be resized with their ResizableEdgeComponent. They are watched for size changes by their ContainerComponent like this:

ContainerComponent::childBoundsChanged( Component* component )
{
    int w = sumChildWidths();
    setSize( w, 800 );
}


ContainerComponent::resized()
{    
    // reorder their placement from l to r
    for( ItemComponent* i: components ) {
        i->setBounds(...);
    }
}

DocumentWindow is set to resize itself if ContainerComponent changes size:

DocumentWindow::DocumentWindow()
{
    DocumentWindow->setContentOwned( new ContainerComponent(), true ); // true enables auto-resize
}

 

Now, if I resize the child components, it works... except when scaling them sth. below 200px or so, it hits an assert inside Component:

void Component::setBounds( const int x, const int y, int w, int h )
{
...
// It's a very bad idea to try to resize a window during its paint() method!
jassert (! (flags.isInsidePaintCall && wasResized && isOnDesktop()));
}

Looking at the stack trace, ContainerComponent::childBoundsChanged() seems indeed to have been called twice, once for the mouse interaction and once again after the DocumentWindow was repainted.

I could only replicate this on the Mac, Windoes looks fine... is this known somehow? Should I file a bug report or write a simple proof of concept .cpp?

Best,

Christoph


#2

Nothing in JUCE itself should cause a call to setBounds from inside a paint() call, so in theory, the assertion shouldn't be triggered unless you're doing something silly! A short proof-of-concept piece of example code that reproduces it would be what we'd need to see, I think...


#3

Here you go...

Actually the assertion only gets hit when the width of the resizing component is small. I therefore added just one resizable ItemComponent to the container.

The problem might be related to the title bar of DocumentWindow,  which imposes a minimum width on it... if the child component gets smaller than that, it hits the assert.

Could not reproduce it on Windows and assert only happens intermittently (but still reliably fast). Thank you for looking into this!

 

#include "../JuceLibraryCode/JuceHeader.h"


class ItemComponent :    public Component
{
public:
    ItemComponent( const String& name )
    {
        setName( name );
        
        constrainer.setMinimumWidth( 100 );
        resizer = new ResizableEdgeComponent( this, &constrainer, ResizableEdgeComponent::Edge::rightEdge );
        addAndMakeVisible( resizer.get() );
    }
    
    
    ~ItemComponent()
    {
        resizer = nullptr;
    }
    
    
    void paint( Graphics& g ) override
    {
        g.fillAll( Colours::yellow );
        g.drawText( getName(), 0, 0, getWidth(), 30, Justification::left );
    }
    
    
    void resized() override
    {
        resizer->setBounds( getWidth() - 5, 0, 5, getHeight() );
    }
    
    
private:
    ComponentBoundsConstrainer constrainer;
    ScopedPointer<ResizableEdgeComponent> resizer;
    
    
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR( ItemComponent );
};



class ContainerComponent :    public Component
{
public:
    ContainerComponent()
    {
    }
    
    
    void update()
    {
        int w = 0;
        for( Component* c : components ) {
            w += c->getWidth();
        }
        setSize( w, getHeight() );
    }
    
    
    void resized() override
    {
        int x = 0;
        for( Component* c : components ) {
            c->setBounds( x, 0, c->getWidth(), getHeight() );
            x += c->getWidth();
        }
    }
    
    
    void createItemComponent()
    {
        ItemComponent* c = new ItemComponent( String( "item " + String ( components.size() ) ) );
        components.add( c );
        addAndMakeVisible( c );
        
        // lazy initial position, corrected inside resized()
        c->setBounds( 0, 0, 200, 500 );
        update();
    }
    
    
    void childBoundsChanged( Component* child ) override
    {
        update();
    }
    
    
private:
    OwnedArray<ItemComponent> components;
    
    
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR( ContainerComponent );
};




class Window :    public DocumentWindow
{
public:
    Window() :
    DocumentWindow( "Window", Colours::green, DocumentWindow::closeButton )
    {
        container = new ContainerComponent();
        container->setSize( 400, 500 );    // will resize after first item created
        setContentOwned( container, true );
        centreWithSize( getWidth(), getHeight() );
        setVisible( true );
        
        container->createItemComponent();    // if you add more than one, the assert does not get hit
    }
    
    
    void closeButtonPressed() override
    {
        JUCEApplication::getInstance()->systemRequestedQuit();
    }
    
    
private:
    ScopedPointer<ContainerComponent> container;
    
    
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR( Window );
};



class JuceOSXDocumentWindowBugApplication  : public JUCEApplication
{
public:
    JuceOSXDocumentWindowBugApplication() {}
    const String getApplicationName() override       { return ProjectInfo::projectName; }
    const String getApplicationVersion() override    { return ProjectInfo::versionString; }
    bool moreThanOneInstanceAllowed() override       { return true; }
    
    
    void initialise (const String& commandLine) override
    {
        window = new Window();
    }
    
    
    void shutdown() override
    {
        window = nullptr;
    }
    
    void systemRequestedQuit() override
    {
        quit();
    }
    
    
    void anotherInstanceStarted (const String& commandLine) override
    {
    }
    
    
private:
    ScopedPointer<Window> window;
};

//==============================================================================
// This macro generates the main() routine that launches the app.
START_JUCE_APPLICATION (JuceOSXDocumentWindowBugApplication)


#4

Thanks - this was a pretty obscure edge-case, but I've added a fix for it now!


#5

Nothing in JUCE itself should cause a call to setBounds from inside a paint()

This seems to not be the case in TreeView::ContentComponent:

void paint (Graphics& g) override
{
    if (owner.rootItem != nullptr)
    {
        owner.recalculateIfNeeded();

where recalculateIfNeeded ends up resizing. This causing intermittent hangs in release and asserts in debug when on the resize we reposition a popup window.

Thanks!