From 155c090807c35765cb3b52d5b99ac3d42fd58c3b Mon Sep 17 00:00:00 2001 From: Matthew Asplund Date: Sat, 24 Oct 2026 18:20:45 -0600 Subject: [PATCH 1/2] Avoid hidden GTK child allocation while scrolling summary --- src/apps/organizer/CalcMgr.C | 25 +++++++++++++ src/apps/organizer/CalculationContextPanel.C | 37 +++++++++++++------- src/apps/organizer/ProgressBox.C | 16 ++++++--- tests/apps/summary_test.py | 8 ++++- 4 files changed, 69 insertions(+), 17 deletions(-) diff --git a/src/apps/organizer/CalcMgr.C b/src/apps/organizer/CalcMgr.C index f4921ff8c..f68631b5c 100644 --- a/src/apps/organizer/CalcMgr.C +++ b/src/apps/organizer/CalcMgr.C @@ -510,6 +510,7 @@ bool CalcMgr::Create( wxWindow* parent, wxWindowID id, const wxString& caption, * collapse events during it, then the tree * size resize the Organizer window * firstvisible the label of the tree's top visible row (its scroll) + * scrollsummary scroll the calculation summary to its bottom and back * showbox scroll the summary panel so that the Progress box is at * its top * progressbox the shown summary's Progress box: shown or not, its @@ -1064,6 +1065,30 @@ void CalcMgr::runTestCommand(const string& line) for (int i = 0; i < 5; i++) wxYield(); } outcome = box ? "scrolled" : "no box"; + } else if (command == "scrollsummary") { + CalculationContextPanel *panel = + dynamic_cast(p_contextPanel); + for (int i = 0; i < 5; i++) wxYield(); + if (panel) { + const wxSize virtualSize = panel->GetVirtualSize(); + const wxSize clientSize = panel->GetClientSize(); + int unitY = 1; + panel->GetScrollPixelsPerUnit(0, &unitY); + const int maxY = std::max(0, (virtualSize.y - clientSize.y + + std::max(1, unitY) - 1) / + std::max(1, unitY)); + if (maxY > 0) { + panel->Scroll(-1, maxY); + for (int i = 0; i < 5; i++) wxYield(); + panel->Scroll(-1, 0); + for (int i = 0; i < 5; i++) wxYield(); + outcome = "scrolled to " + std::to_string(maxY) + " and back"; + } else { + outcome = "no scroll range"; + } + } else { + outcome = "no calculation panel"; + } } else if (command == "progressbox") { CalculationContextPanel *panel = dynamic_cast(p_contextPanel); diff --git a/src/apps/organizer/CalculationContextPanel.C b/src/apps/organizer/CalculationContextPanel.C index 418d940ef..74470a7af 100644 --- a/src/apps/organizer/CalculationContextPanel.C +++ b/src/apps/organizer/CalculationContextPanel.C @@ -72,6 +72,22 @@ static bool basisToolOff(Resource *res, ResourceTool *tool) return code && code->getBasisSetToolHidden(); } +// wxGTK's fixed-position child container may still allocate widgets hidden +// through a sizer. Collapse inactive controls in the sizer while keeping the +// native child GTK-visible at zero size. +static void setSizerChildVisible(wxWindow *window, bool visible) +{ + wxSizer *sizer = window->GetContainingSizer(); + if (visible) { + if (sizer) sizer->Show(window, true); + window->Show(true); + } else { + if (sizer) sizer->Show(window, false); + window->Show(true); + window->SetSize(0, 0, 0, 0); + } +} + /** @@ -290,6 +306,8 @@ void CalculationContextPanel::updateProgress() const bool show = p_progressBox->wanted(); if (show != GetSizer()->IsShown(p_progressSizer)) { GetSizer()->Show(p_progressSizer, show); + p_progressBox->Show(true); + if (!show) p_progressBox->SetSize(0, 0, 0, 0); Layout(); FitInside(); } else if (show && resized) { @@ -438,6 +456,8 @@ void CalculationContextPanel::createControls() p_progressSizer->Add(p_progressBox, wxSizerFlags(1).Expand().Border()); GetSizer()->Add(p_progressSizer, wxSizerFlags().Expand().Border()); GetSizer()->Hide(p_progressSizer); + p_progressBox->Show(true); + p_progressBox->SetSize(0, 0, 0, 0); } } // end of ToolPropertyView for loop @@ -668,18 +688,11 @@ void CalculationContextPanel::setPropValue(ResourceProperty* resProp) p_inpProvProps->findValue(resProp->getQName(), valuestr); } - // some properties will have no value, but we want them to be displayed on the - // panel anyway, so show the user a place-holder - if (valuestr == "") { - if (! resProp->getDisplay()) // do we force the label/prop's display? - { - label->Hide(); - prop->Hide(); - } - } else { - label->Show(); - prop->Show(); - } + // Empty metadata fields take no sizer space, while their GTK widgets remain + // visible at zero size so scrolling cannot allocate a hidden child. + const bool show = !valuestr.empty() || resProp->getDisplay(); + setSizerChildVisible(label, show); + setSizerChildVisible(prop, show); // the property might need special formatting or other considerations based // on its type diff --git a/src/apps/organizer/ProgressBox.C b/src/apps/organizer/ProgressBox.C index a213d7d14..743cc35a1 100644 --- a/src/apps/organizer/ProgressBox.C +++ b/src/apps/organizer/ProgressBox.C @@ -57,11 +57,13 @@ ProgressBox::ProgressBox(wxWindow *parent) ewxConfig::getConfig(INIFILE)->Read(FOLDKEY, &p_collapsed); p_energyLabel = new wxStaticText(this, wxID_ANY, ""); ewxMakeSelectable(p_energyLabel); - p_energyLabel->Hide(); + p_energyLabel->SetSize(0, 0, 0, 0); + p_energyLabel->Show(true); p_energyLabel->Bind(wxEVT_CONTEXT_MENU, &ProgressBox::OnContextMenu, this); for (int i = 0; i < 3; i++) { p_buttons[i] = new wxButton(this, wxID_ANY, ACTIONS[i]); - p_buttons[i]->Hide(); + p_buttons[i]->SetSize(0, 0, 0, 0); + p_buttons[i]->Show(true); p_buttons[i]->Bind(wxEVT_BUTTON, [this, i](wxCommandEvent&) { if (p_action) p_action(i); }); @@ -683,8 +685,14 @@ wxSize ProgressBox::layout(wxDC *dc) } } if (!dc && !buttonsShown) - for (int i = 0; i < 3; i++) p_buttons[i]->Hide(); - if (!dc && !energyShown) p_energyLabel->Hide(); + for (int i = 0; i < 3; i++) { + p_buttons[i]->SetSize(0, 0, 0, 0); + p_buttons[i]->Show(true); + } + if (!dc && !energyShown) { + p_energyLabel->SetSize(0, 0, 0, 0); + p_energyLabel->Show(true); + } return size; } diff --git a/tests/apps/summary_test.py b/tests/apps/summary_test.py index fe325470a..b50204261 100755 --- a/tests/apps/summary_test.py +++ b/tests/apps/summary_test.py @@ -68,7 +68,7 @@ def organizer(display, url, png, env, args=()): work = tempfile.mkdtemp(prefix="ecce-summary-") cmds = os.path.join(work, "commands") with open(cmds, "w") as handle: - handle.write("summary %s\nsnap %s\n" % (url, png)) + handle.write("summary %s\nscrollsummary\nsnap %s\n" % (url, png)) try: result = apps.run(display, "organizer", args=args, windowTimeout=60, settle=40, @@ -82,11 +82,17 @@ def organizer(display, url, png, env, args=()): def check(label, result, expect): log = result.log or "" done = re.search(r"ECCE_TEST_ORGANIZER: summary \S+: (.*)", log) + scrolled = re.search(r"ECCE_TEST_ORGANIZER: scrollsummary: scrolled to ([1-9]\d*) and back", + log) problem = None if result.crashed: problem = "CRASHED (%s)" % result.signalName elif not done: problem = "the summary command never answered" + elif not scrolled: + problem = "the summary panel did not finish scrolling" + elif "gtk_widget_set_allocation" in log: + problem = "GTK tried to allocate a hidden summary widget while scrolling" else: got = parse(done.group(1)) wrong = ["%s=%r (want %r)" % (k, got.get(k), v) From f5764a170d163b952bbebb13ad6d86c04c2e5278 Mon Sep 17 00:00:00 2001 From: Matthew Asplund Date: Sun, 25 Oct 2026 17:49:23 -0600 Subject: [PATCH 2/2] Fix GTK activation and Builder MO rendering --- include/viz/FixedPixelText.H | 54 ++++++++++++++++---------- src/apps/basistool/WxBasisToolApp.C | 4 +- src/apps/builder/Builder.C | 2 +- src/apps/builder/BuilderApp.C | 6 ++- src/apps/calced/CalcEdApp.C | 4 +- src/apps/launcher/WxLauncherApp.C | 6 --- src/apps/organizer/CalcMgrApp.C | 4 +- src/viz/propsgcommands/IsoSurfaceCmd.C | 2 +- src/wxgui/jms/WxJMSMessageDispatch.C | 16 ++------ src/wxviz/viewer/SGViewer.C | 30 +++----------- 10 files changed, 54 insertions(+), 74 deletions(-) diff --git a/include/viz/FixedPixelText.H b/include/viz/FixedPixelText.H index 18cc12b73..ffc790d91 100644 --- a/include/viz/FixedPixelText.H +++ b/include/viz/FixedPixelText.H @@ -1,15 +1,17 @@ #ifndef FIXEDPIXELTEXT_H #define FIXEDPIXELTEXT_H +#include #include -// Draw a bitmap string with a fixed screen-space advance between glyphs. -// Drawing glyphs under a window-coordinate projection prevents camera zoom -// from changing the raster advance used by glBitmap. -inline void drawFixedPixelBitmapText(GLuint fontOffset, const char *text, - GLdouble glyphAdvance) +// Draw the fixed 9x15 font with screen-space pixels. Drawing each set bit +// as geometry avoids OpenGL's glBitmap path, which can allocate temporary +// textures in software renderers while drawing display lists. +inline void drawFixedPixelText(const GLubyte *const *glyphs, + const char *text, + GLdouble glyphAdvance) { - if (!text || !*text) + if (!glyphs || !text || !*text) return; GLboolean valid = GL_FALSE; @@ -27,9 +29,6 @@ inline void drawFixedPixelBitmapText(GLuint fontOffset, const char *text, GLint matrixMode; glGetIntegerv(GL_MATRIX_MODE, &matrixMode); - glPushAttrib(GL_PIXEL_MODE_BIT); - glPixelZoom(1.0f, 1.0f); - glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); @@ -41,23 +40,38 @@ inline void drawFixedPixelBitmapText(GLuint fontOffset, const char *text, glLoadIdentity(); const GLdouble windowZ = 1.0 - 2.0 * raster[2]; - for (const unsigned char *character = - reinterpret_cast(text); - *character != '\0'; ++character) { - const GLdouble windowX = raster[0] + glyphAdvance * - (character - reinterpret_cast(text)); - glRasterPos3d(windowX, raster[1], windowZ); - glGetBooleanv(GL_CURRENT_RASTER_POSITION_VALID, &valid); - if (!valid) - break; - glCallList(fontOffset + *character); + glBegin(GL_QUADS); + for (std::size_t index = 0; text[index] != '\0'; ++index) { + const unsigned char code = static_cast(text[index]); + const GLubyte *glyph = glyphs[code]; + if (!glyph) continue; + + // The first byte is the glyph width; the bitmap is stored as two + // most-significant-bit-first bytes for each of its 16 rows. The + // visible font is 9x15, as in the former glBitmap display lists. + const GLubyte *bitmap = glyph + 1; + const GLdouble originX = raster[0] + glyphAdvance * index; + for (int row = 0; row < 15; ++row) { + const unsigned int bits = + (static_cast(bitmap[row * 2]) << 8) | + static_cast(bitmap[row * 2 + 1]); + for (int column = 0; column < 9; ++column) { + if ((bits & (0x8000u >> column)) == 0) continue; + const GLdouble x = originX + column; + const GLdouble y = raster[1] + row; + glVertex3d(x, y, windowZ); + glVertex3d(x + 1, y, windowZ); + glVertex3d(x + 1, y + 1, windowZ); + glVertex3d(x, y + 1, windowZ); + } + } } + glEnd(); glPopMatrix(); glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(matrixMode); - glPopAttrib(); } #endif diff --git a/src/apps/basistool/WxBasisToolApp.C b/src/apps/basistool/WxBasisToolApp.C index 689ce2c27..667be32bc 100644 --- a/src/apps/basistool/WxBasisToolApp.C +++ b/src/apps/basistool/WxBasisToolApp.C @@ -20,6 +20,7 @@ #include "dsm/ResourceDescriptor.H" #include "wxgui/WxFeedback.H" +#include "wxgui/ewxWindowUtils.H" #include "WxBasisToolApp.H" #include "WxBasisTool.H" @@ -191,9 +192,8 @@ void WxBasisToolApp::invokeMCB(JMSMessage& msg) this->setPollContext(urlstr); p_basisToolFrame->Iconize(false); - p_basisToolFrame->Raise(); p_basisToolFrame->Show(true); - p_basisToolFrame->SetFocus(); + ewxRaiseWindow(p_basisToolFrame); p_basisToolFrame->RequestUserAttention(); } } diff --git a/src/apps/builder/Builder.C b/src/apps/builder/Builder.C index 651505594..8359b2923 100644 --- a/src/apps/builder/Builder.C +++ b/src/apps/builder/Builder.C @@ -1517,7 +1517,7 @@ void Builder::setContext(const string& url, const bool& force) bool newContext = false; // Make sure it is raised/uniconified for popup dialogs - Raise(); + ewxRaiseWindow(this); if (p_calculation) { if (p_calculation->getURL()==url && !force) { diff --git a/src/apps/builder/BuilderApp.C b/src/apps/builder/BuilderApp.C index da400b9cf..d9a30e5cd 100644 --- a/src/apps/builder/BuilderApp.C +++ b/src/apps/builder/BuilderApp.C @@ -319,7 +319,6 @@ void BuilderApp::invokeMCB(JMSMessage& msg) if (action == "" && urlstr.empty()) { // "New Structure": no calculation, keep the default context - p_builder->Raise(); } else if (action == "") { @@ -552,7 +551,10 @@ void BuilderApp::invokeMCB(JMSMessage& msg) registerTopShell(p_builder); p_builder->Iconize(false); -#ifdef __WXMSW__ +#ifdef __WXGTK__ + p_builder->Show(true); + ewxRaiseWindow(p_builder); +#elif defined(__WXMSW__) p_builder->Show(true); ewxRaiseWindow(p_builder); #else diff --git a/src/apps/calced/CalcEdApp.C b/src/apps/calced/CalcEdApp.C index 20d9f6831..14e3fe6a8 100644 --- a/src/apps/calced/CalcEdApp.C +++ b/src/apps/calced/CalcEdApp.C @@ -9,6 +9,7 @@ #include "dsm/EDSIServerCentral.H" #include "dsm/ResourceDescriptor.H" +#include "wxgui/ewxWindowUtils.H" #include "CalcEdApp.H" #include "CalcEd.H" @@ -148,8 +149,7 @@ void CalcEdApp::invokeMCB(JMSMessage& msg) p_calced->Iconize(false); showEditor(); - p_calced->Raise(); - p_calced->SetFocus(); + ewxRaiseWindow(p_calced); p_calced->RequestUserAttention(); } else { showEditor(); diff --git a/src/apps/launcher/WxLauncherApp.C b/src/apps/launcher/WxLauncherApp.C index 5ef5e463e..df3fc3116 100644 --- a/src/apps/launcher/WxLauncherApp.C +++ b/src/apps/launcher/WxLauncherApp.C @@ -189,15 +189,9 @@ void WxLauncherApp::invokeMCB(JMSMessage& msg) this->setPollContext(url); p_launchFrame->Iconize(false); -#ifdef __WXMSW__ p_launchFrame->Show(true); ewxRaiseWindow(p_launchFrame); -#else - p_launchFrame->Raise(); - p_launchFrame->Show(true); - p_launchFrame->SetFocus(); p_launchFrame->RequestUserAttention(); -#endif } } diff --git a/src/apps/organizer/CalcMgrApp.C b/src/apps/organizer/CalcMgrApp.C index f92640a50..788d7334a 100644 --- a/src/apps/organizer/CalcMgrApp.C +++ b/src/apps/organizer/CalcMgrApp.C @@ -31,6 +31,7 @@ #include "dsm/EDSIFactory.H" #include "wxgui/ewxSplash.H" +#include "wxgui/ewxWindowUtils.H" #include "wxgui/WxJMSMessageDispatch.H" #include "wxgui/WxJMSSubscriber.H" #include "wxgui/WxState.H" @@ -286,9 +287,8 @@ void CalcMgrApp::invokeMCB(JMSMessage& msg) const string app = msg.getProperty("appname"); if (!app.empty() && app != "Organizer") return; p_calcMgr->Iconize(false); - p_calcMgr->Raise(); p_calcMgr->Show(true); - p_calcMgr->SetFocus(); + ewxRaiseWindow(p_calcMgr); p_calcMgr->RequestUserAttention(); } diff --git a/src/viz/propsgcommands/IsoSurfaceCmd.C b/src/viz/propsgcommands/IsoSurfaceCmd.C index a2804fe8e..ee7118e71 100644 --- a/src/viz/propsgcommands/IsoSurfaceCmd.C +++ b/src/viz/propsgcommands/IsoSurfaceCmd.C @@ -180,7 +180,7 @@ bool IsoSurfaceCmd::execute() } lattice->coord.setValues(0, resX+resY+resZ, coords); - delete coords; + delete[] coords; } diff --git a/src/wxgui/jms/WxJMSMessageDispatch.C b/src/wxgui/jms/WxJMSMessageDispatch.C index b984dcbac..f1f47ef64 100644 --- a/src/wxgui/jms/WxJMSMessageDispatch.C +++ b/src/wxgui/jms/WxJMSMessageDispatch.C @@ -38,6 +38,7 @@ #include "dsm/ResourceDescriptor.H" #include "dsm/ResourceTool.H" +#include "wxgui/ewxWindowUtils.H" #include "wxgui/WxJMSMessageDispatch.H" // KLS 9/6/06 @@ -329,19 +330,8 @@ void WxJMSMessageDispatch::notifyReady() void WxJMSMessageDispatch::raiseAllWindows() { for (int win=p_topShells.size()-1; win>=0; win--) { - // wxTopLevelWindow * topLevelWin = - // dynamic_cast(p_topShells[win]); - // if (topLevelWin) { - // topLevelWin->Iconize(false); - // topLevelWin->Raise(); - // topLevelWin->Show(true); - // topLevelWin->SetFocus(); - // topLevelWin->RequestUserAttention(); - // } - // else { - (p_topShells[win])->Raise(); - (p_topShells[win])->Show(true); - // } + p_topShells[win]->Show(true); + ewxRaiseWindow(p_topShells[win]); } } diff --git a/src/wxviz/viewer/SGViewer.C b/src/wxviz/viewer/SGViewer.C index cd5dfbd51..565f3e00e 100644 --- a/src/wxviz/viewer/SGViewer.C +++ b/src/wxviz/viewer/SGViewer.C @@ -574,32 +574,14 @@ void SGViewer::myCallbackRoutine(void *userData, SoAction *action) { SGViewer *viewer = (SGViewer*)userData; + // freeglut_font_data.H also declares fontOffset for other drawables, but + // this viewer now draws its fixed font as geometry instead of display lists. + (void)fontOffset; + // only render the floor during GLRender actions: if(!action->isOfType(SoGLRenderAction::getClassTypeId())) return; - static int firstTime = 1; - if (firstTime) { - - GLint curAlignment; - glGetIntegerv(GL_UNPACK_ALIGNMENT,&curAlignment); - - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - - fontOffset = glGenLists (256); - for (GLuint i = 32; i < 256; i++) { - glNewList(i+fontOffset, GL_COMPILE); - // rasters defined in viz/MoGLFonts.h - a hardwired font array - //glBitmap(8, 13, 0.0, 2.0, 10.0, 0.0, rasters[i-32]); - //glBitmap(9, 15, 0.0, 0.0, 9.0, 0.0, Fixed9x15_Character_Map[i]+1); - glBitmap(9, 15, 0.0, 0.0, 9.0, 0.0, Fixed9x15_Character_Map[i]+1); - glEndList(); - } - firstTime = 0; - glPixelStorei(GL_UNPACK_ALIGNMENT, curAlignment); - } - - // If this is not done, the color/lighting of objects farther down the // scenegraph will not have correct lighting. calling // SoGLLazyElement::sendAllMaterial(state) works also but might be overkill @@ -652,9 +634,7 @@ void SGViewer::myCallbackRoutine(void *userData, SoAction *action) vv.getPlanePoint(vv.getNearDist()+ 0.1 * vv.getDepth(), pos).getValue(x,y,z); glRasterPos3f(x,y,z); - glPushAttrib (GL_LIST_BIT); - drawFixedPixelBitmapText(fontOffset, text, 10.0); - glPopAttrib (); + drawFixedPixelText(Fixed9x15_Character_Map, text, 10.0); } glPopAttrib();