Opened 7 years ago

Last modified 6 weeks ago

#16088 confirmed defect

best size is not updated after SetFont() with GTK >= 3.6

Reported by: pcor Owned by:
Priority: normal Milestone: 3.1.6
Component: wxGTK Version:
Keywords: gtk3 Cc: carandraug+dev@…, jpo234@…
Blocked By: Blocking:
Patch: no

Description

GTK+ 3.6 introduced caching of widget styling information. This cache is only updated at particular times of GTK's choosing. AFAICT there is no way to get the cache to be updated at any other time. So after setting a font with a non-default size, GetBestSize() will recalculate (and cache) a size which is still based on the old font. This breaks the common wxWidgets practice of doing layout and sizing during window initialization. Here is a simple example using the minimal sample:

--- samples/minimal/minimal.cpp (revision 76148)
+++ samples/minimal/minimal.cpp (working copy)
@@ -172,6 +172,22 @@
     CreateStatusBar(2);
     SetStatusText("Welcome to wxWidgets!");
 #endif // wxUSE_STATUSBAR
+
+    wxString msg = wxS("The quick brown fox jumps over the lazy dog. The slow brown fox strolls around it.");
+    wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
+
+    wxStaticText* original = new wxStaticText(this, wxID_ANY, msg);
+    sizer->Add(original);
+
+    wxStaticText* large = new wxStaticText(this, wxID_ANY, msg);
+    wxFont font = large->GetFont();
+    font.SetPointSize(font.GetPointSize() * 3);
+    large->SetFont(font);
+    sizer->Add(large);
+
+    SetSizer(sizer);
+    sizer->Layout();
+    sizer->Fit(this);
 }
 
 

There appears to be no way to fix this. The best we can do is invalidate the best size when we know the GTK+ style information has been updated. This would allow user code to work around the problem, for example by re-doing the layout and sizing from a wxShowEvent handler.

Attachments (1)

wx-large-font.png download (3.8 KB) - added by carandraug 14 months ago.
example not sized properly for font

Download all attachments as: .zip

Change History (20)

comment:1 Changed 7 years ago by PC

(In [76149]) for GTK+ 3.6 and later, invalidate cached best size when GTK's style cache is updated, see #16088

comment:2 Changed 5 years ago by Paul Cornett <paulcor@…>

In 101c43d0aa703a843f177c449fad73951de199c3/git-wxWidgets:

Partial workaround for stale styling information with GTK3

We can trigger size events when we know the style cache has been updated.
See #16088

comment:3 Changed 5 years ago by Paul Cornett <paulcor@…>

In 4206e4ce1c9da067f23a7798f6e55c0c14878e69/git-wxWidgets:

Partial workaround for stale styling information with GTK3

We can trigger size events when we know the style cache has been updated.
See #16088

(backport of 101c43d0aa703a843f177c449fad73951de199c3)

Changed 14 months ago by carandraug

example not sized properly for font

comment:4 Changed 14 months ago by carandraug

  • Cc carandraug+dev@… added

I'm still affected by what I think is this issue. On this ticket description and history there's mention of commits with partial workarounds but I don't get what they're doing and how to work around it on my side.

Maybe my bug is different? I'm on GTK 3.24.5 (Debian 10.3 - latest Debian stable) and wxWidgets 3.1.3. I have this minimal example:

#include <wx/wx.h>

class App : public wxApp
{
public:
    virtual bool OnInit();
};

IMPLEMENT_APP(App)

bool App::OnInit()
{
  wxFrame* frame = new wxFrame (NULL, wxID_ANY, "frame label");

  wxStaticText* label = new wxStaticText(frame, wxID_ANY, "");
  label->SetLabel(wxGetLibraryVersionInfo().GetVersionString());
  wxFont font = label->GetFont();
  for (int i = 0; i < 3; i++)
    font.MakeLarger();
  label->SetFont(font);

  wxBoxSizer* sizer = new wxBoxSizer (wxVERTICAL);
  sizer->Add(label, wxSizerFlags().Expand());
  frame->SetSizerAndFit(sizer);

  frame->Show(true);
  return true;
}

which leads to the following which I believe is caused because it does not take into account the resized font:

example not sized properly for font

If I do not increase the font size, then I get the string "wxWidgets 3.1.3". I have also found this issue also affects StatusBar.

Am I doing something wrong? How do I work around this?

comment:5 Changed 14 months ago by pcor

  • Status changed from new to confirmed

One possibility is to add a wxEVT_SHOW handler.

 
 IMPLEMENT_APP(App)
 
+static void onShow(wxShowEvent& e)
+{
+    wxWindow* win = static_cast<wxWindow*>(e.GetEventObject());
+    win->GetSizer()->SetSizeHints(win);
+    win->Unbind(wxEVT_SHOW, onShow);
+}
+
 bool App::OnInit()
 {
   wxFrame* frame = new wxFrame (NULL, wxID_ANY, "frame label");
+  frame->Bind(wxEVT_SHOW, onShow);
 
   wxStaticText* label = new wxStaticText(frame, wxID_ANY, "");
   label->SetLabel(wxGetLibraryVersionInfo().GetVersionString());

comment:6 follow-up: Changed 14 months ago by vadz

Could/should we have such wxEVT_SHOW handler in wxGTK itself? We'd need to remember if the size of the frame had been explicitly set to something else though, to avoid overriding it with the fitting values, but this ought to be possible.

And this still wouldn't help if you do something depending on the best size computed using the wrong font in your code, but it should cover most of the realistic use cases in which we just want the initial layout to be correct.

comment:7 in reply to: ↑ 6 Changed 14 months ago by pcor

Replying to vadz:

Could/should we have such wxEVT_SHOW handler in wxGTK itself?

I don't know... the frame sizing code is already very complicated. There are so many ways the sizing can happen. Maybe we could somehow record that some form of wxSizer::Fit() had been called. But I strongly suspect this could be a whack-a-mole situation.

Last edited 14 months ago by pcor (previous) (diff)

comment:8 Changed 14 months ago by vadz

Yes, I agree with everything you say but OTOH we still need some way of making this work. Can we provide some new GTK-compatible API for this? E.g. some new event in which the initial layout could be done, once everything is available?

comment:9 Changed 14 months ago by Paul Cornett <paulcor@…>

In f655a52fb/git-wxWidgets:

Allow wxSizer::Fit() to work properly when called from TLW ctor on GTK3

Style information affecting sizes may not be updated by GTK until TLW is shown
See #16088

comment:10 Changed 2 months ago by Vadim Zeitlin <vadim@…>

In 9c0a8be1d/git-wxWidgets:

Merge branch 'gtk-initial-size'

Fix initial size of TLWs in wxGTK when using both sizers and explicit
SetSize() calls.

See https://github.com/wxWidgets/wxWidgets/pull/2322

See #16088, #19134.

comment:11 Changed 2 months ago by vadz

  • Resolution set to fixed
  • Status changed from confirmed to closed

This was fixed by f655a52fba3109570ec7cdf137c2173c7921edd2 and, importantly, remains fixed in 3.1.5 as I've confirmed by testing the changes of 9c0a8be1dc32063d91ed1901fd5fcd54f4f955a1

comment:12 Changed 8 weeks ago by jpo234

  • Cc jpo234@… added
  • Resolution fixed deleted
  • Status changed from closed to reopened

This is still broken for the attached test program when one switches to the second panel of a wxSimplebook.

comment:13 Changed 8 weeks ago by jpo234

  • Cc jpo234@… removed

It's still broken for the following test case:

#include <wx/wx.h>
#include <wx/simplebook.h>

class MyApp : public wxApp
{
public:
	virtual bool OnInit();
};
class MyFrame : public wxFrame
{
protected:
	wxSimplebook* m_simplebook1;
	wxPanel* m_panel1;
	wxStaticText* m_staticText1;
	wxPanel* m_panel2;
	wxStaticText* m_staticText2;
	wxButton* m_button1;

	int m_book_page;

	// Virtual event handlers, overide them in your derived class
	virtual void OnButtonClick(wxCommandEvent& event) {
		m_book_page = (m_book_page == 0) ? 1 : 0;
		m_simplebook1->ChangeSelection(m_book_page);
	}
public:
	MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size);
};


wxIMPLEMENT_APP(MyApp);

bool MyApp::OnInit()
{
	MyFrame* frame = new MyFrame("Hello World", wxPoint(50, 50), wxSize(450, 340));
	frame->Show(true);
	return true;
}

MyFrame::MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size)
	: wxFrame(NULL, wxID_ANY, title, pos, size)
{
	this->SetSizeHints(wxDefaultSize, wxDefaultSize);

	wxBoxSizer* bSizer1;
	bSizer1 = new wxBoxSizer(wxVERTICAL);

	m_simplebook1 = new wxSimplebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0);
	m_panel1 = new wxPanel(m_simplebook1, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
	wxBoxSizer* bSizer2;
	bSizer2 = new wxBoxSizer(wxVERTICAL);

	m_staticText1 = new wxStaticText(m_panel1, wxID_ANY, wxT("Panel 1"), wxDefaultPosition, wxDefaultSize, 0);
	m_staticText1->Wrap(-1);
	m_staticText1->SetFont(wxFont(15, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString));

	bSizer2->Add(m_staticText1, 0, wxALL, 5);


	m_panel1->SetSizer(bSizer2);
	m_simplebook1->AddPage(m_panel1, wxT("a page"), false);
	m_panel2 = new wxPanel(m_simplebook1, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
	wxBoxSizer* bSizer3;
	bSizer3 = new wxBoxSizer(wxVERTICAL);

	m_staticText2 = new wxStaticText(m_panel2, wxID_ANY, wxT("Panel 2"), wxDefaultPosition, wxDefaultSize, 0);
	m_staticText2->Wrap(-1);
	m_staticText2->SetFont(wxFont(30, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString));

	bSizer3->Add(m_staticText2, 0, wxALL, 5);


	m_panel2->SetSizer(bSizer3);
	m_simplebook1->AddPage(m_panel2, wxT("a page"), false);

	bSizer1->Add(m_simplebook1, 1, wxEXPAND | wxALL, 5);

	m_button1 = new wxButton(this, wxID_ANY, wxT("Switch"), wxDefaultPosition, wxDefaultSize, 0);
	bSizer1->Add(m_button1, 0, wxALL, 5);


	this->SetSizer(bSizer1);

	this->Centre(wxBOTH);

	// Connect Events
	m_button1->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MyFrame::OnButtonClick), NULL, this);

	m_book_page = m_simplebook1->GetSelection();
}

comment:14 Changed 8 weeks ago by vadz

  • Milestone set to 3.1.6
  • Status changed from reopened to confirmed

Thanks, I can reproduce the problem. Will try to debug it, unless someone already sees what it is (and how to fix it).

comment:15 Changed 6 weeks ago by pcor

Patch to minimal sample which reproduces the problem:

  • samples/minimal/minimal.cpp

    diff --git a/samples/minimal/minimal.cpp b/samples/minimal/minimal.cpp
    index 470e765423..f71afae69f 100644
    a b wxIMPLEMENT_APP(MyApp); 
    112112// the application class
    113113// ----------------------------------------------------------------------------
    114114
     115#include "wx/simplebook.h"
     116static wxSimplebook* book;
     117
    115118// 'Main program' equivalent: the program execution "starts" here
    116119bool MyApp::OnInit()
    117120{
    bool MyApp::OnInit() 
    123126    // create the main application window
    124127    MyFrame *frame = new MyFrame("Minimal wxWidgets App");
    125128
     129    book = new wxSimplebook(frame);
     130    wxWindow* page = new wxWindow(book, wxID_ANY);
     131    wxWindow* text = new wxStaticText(page, wxID_ANY, "First Page");
     132    wxFont font(text->GetFont());
     133    font.SetFractionalPointSize(font.GetFractionalPointSize() * 2);
     134    text->SetFont(font);
     135    wxSizer* sizer = new wxBoxSizer(wxVERTICAL);
     136    sizer->Add(text);
     137    page->SetSizer(sizer);
     138    book->AddPage(page, "page 1");
     139    page = new wxWindow(book, wxID_ANY);
     140    text = new wxStaticText(page, wxID_ANY, "Second Page");
     141    text->SetFont(font);
     142    sizer = new wxBoxSizer(wxVERTICAL);
     143    sizer->Add(text);
     144    page->SetSizer(sizer);
     145    book->AddPage(page, "page 2");
     146    frame->SetClientSize(400, 200);
     147
    126148    // and show it (the frames, unlike simple controls, are not shown when
    127149    // created initially)
    128150    frame->Show(true);
    void MyFrame::OnQuit(wxCommandEvent& WXUNUSED(event)) 
    188210
    189211void MyFrame::OnAbout(wxCommandEvent& WXUNUSED(event))
    190212{
     213    book->ChangeSelection(1 - book->GetSelection());
     214    return;
    191215    wxMessageBox(wxString::Format
    192216                 (
    193217                    "Welcome to %s!\n"

comment:16 Changed 6 weeks ago by Paul Cornett <paulcor@…>

In 3217a4e8a/git-wxWidgets:

Fix best size for windows which are hidden when TLW is shown with GTK3

GTK3's style cache is not updated for hidden windows until after they are shown
See #16088

comment:17 Changed 6 weeks ago by jpo234

  • Cc jpo234@… added

@Paul: I got the patch from https://github.com/wxWidgets/wxWidgets/commit/3217a4e8a26bf30758c00852738c0612ca931909.diff and cleanly applied it to 3.1.5. I now get the following SEGV in my application:

Thread 1 "hettras-console" received signal SIGSEGV, Segmentation fault.
0x00007ffff79a96c5 in wxWindow::GTKSizeRevalidate (this=0x555555ea7720) at ../src/gtk/window.cpp:5881
5881                    if (win->m_needSizeEvent)
(gdb) bt
#0  0x00007ffff79a96c5 in wxWindow::GTKSizeRevalidate() (this=0x555555ea7720) at ../src/gtk/window.cpp:5881
#1  0x00007ffff7992392 in wxTopLevelWindowGTK::Show(bool) (this=0x555555ea7720, show=true) at ../src/gtk/toplevel.cpp:1161
#2  0x00007ffff7991944 in wxTopLevelWindowGTK::ShowFullScreen(bool, long) (this=0x555555ea7720, show=true) at ../src/gtk/toplevel.cpp:924
#3  0x00007ffff79fc78a in wxFrame::ShowFullScreen(bool, long) (this=0x555555ea7720, show=true, style=31) at ../src/gtk/frame.cpp:115
#4  0x0000555555b2922d in HETTRASConsoleApp::OnInit() ()
#5  0x0000555555b29525 in wxAppConsoleBase::CallOnInit() ()
#6  0x00007ffff74a16a6 in wxEntry(int&, wchar_t**) (argc=@0x7ffff761f090: 1, argv=0x555555d48b90) at ../src/common/init.cpp:488
#7  0x00007ffff74a17c2 in wxEntry(int&, char**) (argc=@0x7fffffffde7c: 1, argv=0x7fffffffdf78) at ../src/common/init.cpp:516
#8  0x0000555555b28fd7 in main ()
(gdb) p win
$1 = (wxWindow *) 0x0

Just to make sure I built a fresh download of the current master and got the same SEGV.
Please let me know whether this is enough information.

Last edited 6 weeks ago by jpo234 (previous) (diff)

comment:18 Changed 6 weeks ago by Paul Cornett <paulcor@…>

In a3b7244ef/git-wxWidgets:

Fix possible crash after 3217a4e8a2

3217a4e8a2 (Fix best size for windows which are hidden when TLW is shown
with GTK3, 2021-04-30) did not account for possibility that window needing
revalidated best size is also the TLW.
See #16088

comment:19 Changed 6 weeks ago by jpo234

Yes! With the latest patch applied my application now works with wxGTK3.

Note: See TracTickets for help on using tickets.