Skip to content

Commit aa90c5a

Browse files
author
andy
committed
Merge origin/main into wip/help-shots
2 parents 29f3f6b + a8db287 commit aa90c5a

4 files changed

Lines changed: 194 additions & 4 deletions

File tree

‎CMakeLists.txt‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1847,6 +1847,15 @@ if(PYTHON3_EXECUTABLE)
18471847
set_tests_properties(apps_calcimport PROPERTIES TIMEOUT 1200
18481848
SKIP_RETURN_CODE 77)
18491849

1850+
# The Builder with a calculation's MOs panel open in every panel layout,
1851+
# on a 1366x768 screen: viewer and panel on screen and inside the window.
1852+
add_test(NAME apps_panel_layouts
1853+
COMMAND ${PYTHON3_EXECUTABLE}
1854+
${CMAKE_CURRENT_SOURCE_DIR}/tests/apps/panel_layouts_test.py
1855+
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tests/apps)
1856+
set_tests_properties(apps_panel_layouts PROPERTIES TIMEOUT 900
1857+
SKIP_RETURN_CODE 77)
1858+
18501859
# Every app's main window and every codereg Theory/Runtype dialog fits a
18511860
# small screen (#189): measured on private Xvfb screens, no window may
18521861
# extend past the screen. Same installed tree and isolation as `apps`.

‎docs/claude/wx-viewer/windows-fit-small-screens-via-fittodisplay.md‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
type: rule
33
title: "Every ewxFrame/ewxDialog is capped to its display by `fitToScreen()`; codereg dialogs scroll in `EccePanel` (#189)"
44
area: wx-viewer
5-
paths: ["src/wxgui/ewxClasses/ewxWindowUtils.C", "src/inv/wxinv/SoWxRenderArea.C", "scripts/codereg/templates.py", "tests/apps/smallscreen_test.py"]
5+
paths: ["src/wxgui/ewxClasses/ewxWindowUtils.C", "src/inv/wxinv/SoWxRenderArea.C", "tests/apps/panel_layouts_test.py", "scripts/codereg/templates.py", "tests/apps/smallscreen_test.py"]
66
issues: [187, 189]
77
---
88
**`ewxFrame::Show()` and `ewxDialog::Show()/ShowModal()` call
@@ -26,8 +26,13 @@ up to three items under it), or the sizer passed to `fitToScreen(row)`.
2626
at its virtual size, and the Builder's AUI sizer reports a minimum
2727
17 000 104 px tall; on macOS arm64 the GL canvas inside it aborted the
2828
first paint in `CGLSetSurface` (SkyLight `CGRectContainsRect` assertion).
29-
GTK clips the child window, so Linux never shows it. `SoWxRenderArea`
30-
also refuses to grow past its display's client area.
29+
On Linux it showed too: the viewer was laid out ~6800 px tall with the
30+
molecule below the window and the MOs panel off screen (1366x768: every
31+
layout; 1700x1100: all but Classic). `SoWxRenderArea` also refuses to
32+
grow past its display's client area. `tests/apps/panel_layouts_test.py`
33+
(ctest `apps_panel_layouts`) opens the MOs panel in every layout at
34+
1366x768 and fails for a pane outside the window or no longer the
35+
frame's child (`ECCE_PANEL_METRICS` pane lines).
3136
- An app that grows the window itself (`CalcEd::update*Fields()`) calls
3237
`fitToScreen()` after `SetSizeHints(this)`; the hint alone sets a
3338
minimum above the display.

‎src/apps/builder/Builder.C‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4868,7 +4868,7 @@ void Builder::updatePropertyMenus()
48684868
}
48694869

48704870
// ECCE_PANEL_METRICS=<file>: once the layout has settled, write the
4871-
// 3-D viewer's width and the window's, for the layout screenshots.
4871+
// 3-D viewer's width and the window's, and every shown pane's place.
48724872
static bool panelMetricsStarted = false;
48734873
const char *metricsPath = getenv("ECCE_PANEL_METRICS");
48744874
if (metricsPath != 0 && !panelMetricsStarted && p_calculation != 0 &&
@@ -4891,6 +4891,19 @@ void Builder::updatePropertyMenus()
48914891
panes.Item(i).rect.height, GetClientSize().y);
48924892
}
48934893
}
4894+
// One line per shown pane: where AUI put it, and whether its window
4895+
// is still the frame's own child (tests/apps/panel_layouts_test.py).
4896+
for (size_t i = 0; f && i < panes.GetCount(); ++i) {
4897+
const wxAuiPaneInfo &p = panes.Item(i);
4898+
if (!p.IsShown() || p.IsToolbar() || !p.window) continue;
4899+
fprintf(f, "pane \"%s\" %d %d %d %d %s %s\n",
4900+
p.name.ToStdString().c_str(), p.rect.x, p.rect.y,
4901+
p.rect.width, p.rect.height,
4902+
p.window->IsShownOnScreen() ? "onscreen" : "hidden",
4903+
p.window->GetParent() == this ? "docked" : "reparented");
4904+
}
4905+
if (f) fprintf(f, "client %d %d\n", GetClientSize().x,
4906+
GetClientSize().y);
48944907
if (f) fclose(f);
48954908
});
48964909
timer->StartOnce(12000);

‎tests/apps/panel_layouts_test.py‎

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
#!/usr/bin/env python3
2+
"""
3+
The Builder on a calculation with its MOs panel open, in every panel layout
4+
(View > Panel layout), on a laptop-sized screen.
5+
6+
For each layout the Builder opens the water fixture with ECCE_PANEL_MODE,
7+
ECCE_OPEN_PANEL=MOs and ECCE_PANEL_METRICS, and the metrics must show the
8+
viewer and the MOs pane on screen, inside the window, at a usable size, and
9+
every pane still a child of the frame. "default" sets no ECCE_PANEL_MODE,
10+
so it is the layout a new install gets (list + detail).
11+
12+
The case this was written for: fitting the window to the screen (#189)
13+
moved the AUI frame's panes into a scrolled window, after which the viewer
14+
was laid out ~6800 px tall with the molecule off screen and the MOs panel
15+
below the window's edge.
16+
17+
Same installed tree, isolation and services as run_tests.py (ECCE_TEST_HOME,
18+
ECCE_TEST_WRAPPERS). Exit 77 (skip) without an install, Xvfb or fixture.
19+
"""
20+
21+
import os
22+
import re
23+
import shutil
24+
import sys
25+
import tempfile
26+
27+
HERE = os.path.dirname(os.path.abspath(__file__))
28+
sys.path.insert(0, HERE)
29+
30+
import apps # noqa: E402
31+
import fixture # noqa: E402
32+
import run_tests # noqa: E402
33+
import xdisplay # noqa: E402
34+
35+
LAYOUTS = [("classic", "classic"), ("stacked", "stacked"),
36+
("accordion", "accordion"), ("detail", "detail"),
37+
("default", "detail")]
38+
SCREEN = "1366x768x24"
39+
PANEL = "MOs"
40+
MIN_VIEWER = 200 # px, each way
41+
MIN_PANEL = 80 # px high: the MOs pane's own minimum
42+
43+
failures = []
44+
45+
46+
def readMetrics(path):
47+
"""{'mode': str, 'client': (w, h), 'panes': {name: (x, y, w, h, on, docked)}}"""
48+
out = {"mode": None, "client": None, "panes": {}}
49+
if not os.path.exists(path):
50+
return out
51+
with open(path) as handle:
52+
for line in handle:
53+
m = re.match(r'pane "([^"]+)" (-?\d+) (-?\d+) (\d+) (\d+) (\w+) (\w+)',
54+
line)
55+
if m:
56+
out["panes"][m.group(1)] = (
57+
int(m.group(2)), int(m.group(3)), int(m.group(4)),
58+
int(m.group(5)), m.group(6) == "onscreen",
59+
m.group(7) == "docked")
60+
elif line.startswith("client "):
61+
out["client"] = tuple(int(v) for v in line.split()[1:3])
62+
elif line.startswith("mode "):
63+
out["mode"] = line.split()[1]
64+
return out
65+
66+
67+
def paneProblem(metrics, name, minW, minH):
68+
pane = metrics["panes"].get(name)
69+
if pane is None:
70+
return "%s pane not shown" % name
71+
x, y, w, h, onscreen, docked = pane
72+
cw, ch = metrics["client"]
73+
if not docked:
74+
return "%s pane moved out of the frame" % name
75+
if not onscreen:
76+
return "%s pane is not on screen" % name
77+
if x < 0 or y < 0 or x + w > cw or y + h > ch:
78+
return ("%s pane at %dx%d+%d+%d is outside the %dx%d window"
79+
% (name, w, h, x, y, cw, ch))
80+
if w < minW or h < minH:
81+
return "%s pane is only %dx%d" % (name, w, h)
82+
return None
83+
84+
85+
def installCalc():
86+
"""The water fixture in the logged-in user's own home: in the fixture
87+
account the Builder stops at an "ECCE Authentication" dialog."""
88+
user = fixture.realUser()
89+
fixture.ensureRealUserAccount()
90+
target = os.path.join(fixture.stateDir(), "htdocs", "Ecce", "users",
91+
user, "panel-layouts-water")
92+
shutil.rmtree(target, ignore_errors=True)
93+
shutil.copytree(os.path.join(fixture.FIXTURES, "calc-water-vib"), target,
94+
symlinks=True)
95+
return "http://localhost:%d/Ecce/users/%s/panel-layouts-water" % (
96+
fixture.dataserverPort(), user)
97+
98+
99+
def check(display, url, label, mode, expected):
100+
metricsPath = tempfile.mktemp(prefix="ecce-panels-", suffix=".txt")
101+
env = {"ECCE_OPEN_PANEL": PANEL, "ECCE_PANEL_METRICS": metricsPath}
102+
if mode != "default":
103+
env["ECCE_PANEL_MODE"] = mode
104+
authPath = fixture.authFile(
105+
os.path.join(fixture.stateHome(), ".ECCE", "auth.pipe"),
106+
user=fixture.realUser())
107+
# The metrics are written 12 s after the calculation's panels exist.
108+
result = apps.run(display, "builder",
109+
args=("-pipe", authPath, "-context", url),
110+
windowTimeout=60, settle=40, env=env)
111+
metrics = readMetrics(metricsPath)
112+
try:
113+
os.unlink(metricsPath)
114+
except OSError:
115+
pass
116+
log = result.log or ""
117+
problem = None
118+
if result.crashed:
119+
problem = "CRASHED (%s)" % result.signalName
120+
elif not result.sawWindow:
121+
problem = "no window"
122+
elif metrics["client"] is None:
123+
problem = "no layout metrics written"
124+
elif metrics["mode"] != expected:
125+
problem = "layout %s, expected %s" % (metrics["mode"], expected)
126+
else:
127+
moved = [n for n, p in metrics["panes"].items() if not p[5]]
128+
problem = ("panes moved out of the frame: " + ", ".join(moved)
129+
if moved else
130+
paneProblem(metrics, "Viewer", MIN_VIEWER, MIN_VIEWER) or
131+
paneProblem(metrics, PANEL, 1, MIN_PANEL))
132+
if problem is None:
133+
print("ok %s" % label)
134+
return
135+
failures.append(label)
136+
print("FAIL %s: %s\n%s" % (label, problem, run_tests._tail(log, 15)))
137+
138+
139+
def main():
140+
xdisplay.SCREEN = SCREEN
141+
142+
def checkApp(display, name, results, verbose=False):
143+
if not fixture.available():
144+
results.notes.append("no calculation fixture checked in; skipped")
145+
return
146+
url = installCalc()
147+
restorePrefs = fixture.settleUpgradeNotices()
148+
try:
149+
for mode, expected in LAYOUTS:
150+
check(display, url, "%s layout, %s panel" % (mode, PANEL),
151+
mode, expected)
152+
finally:
153+
restorePrefs()
154+
if failures:
155+
results.fail("panels", "%d of %d layouts wrong: %s"
156+
% (len(failures), len(LAYOUTS), ", ".join(failures)))
157+
run_tests.checkApp = checkApp
158+
sys.argv = [sys.argv[0], "--app", "builder"] + sys.argv[1:]
159+
return run_tests.main()
160+
161+
162+
if __name__ == "__main__":
163+
sys.exit(main())

0 commit comments

Comments
 (0)