From c63d47bc9451daf2e2509fe400a13d2e547fbdaa Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 20 Jan 2018 18:07:23 +0100 Subject: [PATCH] Fix broken resize handling in wxGTK wxCollapsiblePane Almost totally rewrite the resize-after-toggle code in this control so that it actually makes sense and works, especially in the situations when the contents of wxCollapsiblePane changes, for example when it contains another wxCollapsiblePane inside it which can be collapsed or expanded. Previously, this didn't work at all in wxGTK and the inner pane always remained at its minimal, collapsed size. Now it does, after doing the following: First, replace the completely useless DoGetBestSize() which just did the same thing as its base class implementation with the code actually determining the best size of the window that was previously hidden inside gtk_collapsiblepane_expanded_callback() for some reason. Note that the comment about "not caching the best size" in the old code was very out of date and was probably left over from the days when GetBestSize() itself was virtual, as caching is not done in DoGetBestSize() anyhow, but in GetBestSize() itself. So, second, do fix the best size invalidation by doing it explicitly whenever the pane is toggled. And, relatedly, do not set the min size of anything because this overrides the layout computations and is never reset/invalidated, unlike the best size, even if the best size of the pane changes, e.g. because of a change to its contents. Finally, remove many thoroughly outdated comments, e.g. the one speaking about OnStateChange() which doesn't exist at all in this implementation. --- src/gtk/collpane.cpp | 84 +++++++++++++++----------------------------- 1 file changed, 29 insertions(+), 55 deletions(-) diff --git a/src/gtk/collpane.cpp b/src/gtk/collpane.cpp index a2007f6662..e02c0be1da 100644 --- a/src/gtk/collpane.cpp +++ b/src/gtk/collpane.cpp @@ -51,62 +51,28 @@ gtk_collapsiblepane_expanded_callback(GObject * WXUNUSED(object), GParamSpec * WXUNUSED(param_spec), wxCollapsiblePane *p) { - // NB: unlike for the "activate" signal, when this callback is called, if - // we try to query the "collapsed" status through p->IsCollapsed(), we - // get the right value. I.e. here p->IsCollapsed() will return false if - // this callback has been called at the end of a collapsed->expanded - // transition and viceversa. Inside the "activate" signal callback - // p->IsCollapsed() would return the wrong value! - - wxSize sz; - if ( p->IsExpanded() ) - { - // NB: we cannot use the p->GetBestSize() or p->GetMinSize() functions - // here as they would return the size for the collapsed expander - // even if the collapsed->expanded transition has already been - // completed; we solve this problem doing: - - sz = p->m_szCollapsed; - - wxSize panesz = p->GetPane()->GetBestSize(); - sz.x = wxMax(sz.x, panesz.x); - sz.y += gtk_expander_get_spacing(GTK_EXPANDER(p->m_widget)) + panesz.y; - } - else // collapsed - { - // same problem described above: using p->Get[Best|Min]Size() here we - // would get the size of the control when it is expanded even if the - // expanded->collapsed transition should be complete now... - // So, we use the size cached at control-creation time... - sz = p->m_szCollapsed; - } - - // VERY IMPORTANT: - // just calling - // p->OnStateChange(sz); - // here would work work BUT: - // 1) in the expanded->collapsed transition it provokes a lot of flickering - // 2) in the collapsed->expanded transition using the "Change status" wxButton - // in samples/collpane application some strange warnings would be generated - // by the "clearlooks" theme, if that's your theme. - // - // So we prefer to use some GTK+ native optimized calls, which prevent too many resize - // calculations to happen. Note that the following code has been very carefully designed - // and tested - be VERY careful when changing it! - - // 1) need to update our size hints - // NB: this function call won't actually do any long operation - // (redraw/relayout/resize) so that it's flicker-free - p->SetMinSize(sz); + // When the pane is expanded or collapsed, its best size changes, so it + // needs to be invalidated in any case. + p->InvalidateBestSize(); if (!p->HasFlag(wxCP_NO_TLW_RESIZE)) { wxTopLevelWindow * top = wxDynamicCast(wxGetTopLevelParent(p), wxTopLevelWindow); + + // If we want to automatically resize the entire TLW to adopt to the + // new pane size, we also need to invalidate the cached best sizes of + // all the intermediate windows to ensure that it's recalculated + // correctly when doing the layout below. + for ( wxWindow* w = p->GetParent(); w != top; w = w->GetParent() ) + { + w->InvalidateBestSize(); + } + if ( top && top->GetSizer() ) { // 2) recalculate minimal size of the top window - sz = top->GetSizer()->CalcMin(); + const wxSize sz = top->GetSizer()->CalcMin(); if (top->m_mainWidget) { @@ -184,9 +150,10 @@ bool wxCollapsiblePane::Create(wxWindow *parent, gtk_expander_new_with_mnemonic(wxGTK_CONV(GTKConvertMnemonics(label))); g_object_ref(m_widget); - // see the gtk_collapsiblepane_expanded_callback comments to understand why - // we connect to the "notify::expanded" signal instead of the more common - // "activate" one + // Connect to the "notify::expanded" signal instead of the more common + // "activate" one in order to use the new state in our callback, which is + // more convenient e.g. because calling GetBestSize() returns the suitable + // size for the new state. g_signal_connect(m_widget, "notify::expanded", G_CALLBACK(gtk_collapsiblepane_expanded_callback), this); @@ -204,7 +171,7 @@ bool wxCollapsiblePane::Create(wxWindow *parent, m_pPane->SetBackgroundColour(bg); // remember the size of this control when it's collapsed - m_szCollapsed = GetBestSize(); + m_szCollapsed = GTKGetPreferredSize(m_widget); return true; } @@ -213,9 +180,16 @@ wxSize wxCollapsiblePane::DoGetBestSize() const { wxASSERT_MSG( m_widget, wxT("DoGetBestSize called before creation") ); - // notice that we do not cache our best size here as it changes - // all times the user expands/hide our pane - return GTKGetPreferredSize(m_widget); + wxSize sz = m_szCollapsed; + + if ( IsExpanded() ) + { + const wxSize panesz = m_pPane->GetBestSize(); + sz.x = wxMax(sz.x, panesz.x); + sz.y += gtk_expander_get_spacing(GTK_EXPANDER(m_widget)) + panesz.y; + } + + return sz; } void wxCollapsiblePane::Collapse(bool collapse)