Skip to content

Commit 074fc44

Browse files
author
andy
committed
Merge remote-tracking branch 'origin/main' into wip/org-summary
2 parents f382c28 + e130403 commit 074fc44

6 files changed

Lines changed: 217 additions & 21 deletions

File tree

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
type: rule
3+
title: "Codereg dialogs use the theme's colours and font like the C++ windows; no fixed colours in globals.py"
4+
area: codereg
5+
paths: ["scripts/codereg/globals.py", "scripts/codereg/templates.py", "tests/look/codereg_shot.py"]
6+
issues: [210]
7+
---
8+
`globals.py` holds no legacy colours any more: inputs and panels are the
9+
theme's (`wx.NullColour`), read-only is `SYS_COLOUR_BTNFACE` (as
10+
`ewxStyledWindow::getReadonlyColor`), and the warning/error flash of the
11+
message box uses the `ewxThemeColours` BAD/UNSURE tints in light and dark.
12+
Fonts are the theme's GUI font at 10 pt plus the Font Size step, read from
13+
`$ECCE_REALUSERHOME/.ECCE/EcceGlobal` (`FontSize`, `UseSystemFont`) since
14+
the dialogs are separate processes; keep that in step with
15+
`ewxStyledWindow::getFontSizeStep`. `tests/look/codereg_shot.py` renders
16+
the real dialogs headless in light/dark (`--flash warning|error`).

‎scripts/codereg/globals.py‎

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,11 @@ class Globals:
2929
MaxFloatDefault = 1e100
3030
MinIntDefault = -1000000
3131
MaxIntDefault = 1000000
32-
BackgroundColour = wx.Colour(224, 225, 225) # #e0e1e1
33-
InputColour = wx.Colour(249, 221, 153) #f9dd99
34-
ReadonlyColour = wx.Colour(183, 184, 186) #b7b8ba
32+
# The theme draws everything except read-only fields, as the C++
33+
# windows do (ewxStyledWindow, #210); wx.NullColour hands a widget
34+
# back to the theme.
35+
InputColour = wx.NullColour
36+
ReadonlyColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE)
3537

3638
FdIn = "restore.in"
3739
PortOut = 0
@@ -79,18 +81,47 @@ class Globals:
7981
# to wait after a key click event before performing validation
8082
TimerLength = 2000
8183

84+
@staticmethod
85+
def ThemeFont(size):
86+
font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
87+
font.SetPointSize(size)
88+
return font
89+
90+
@staticmethod
91+
def FontSizes():
92+
"""(base point size, step) from the shared EcceGlobal preferences."""
93+
prefs = {}
94+
home = os.environ.get("ECCE_REALUSERHOME", os.path.expanduser("~"))
95+
try:
96+
with open(os.path.join(home, ".ECCE", "EcceGlobal")) as f:
97+
for line in f:
98+
key, sep, value = line.partition(":")
99+
if sep:
100+
prefs[key.strip().lower()] = value.strip()
101+
except OSError:
102+
pass
103+
base = 10
104+
if prefs.get("usesystemfont", "false").lower() in ("true", "1"):
105+
base = wx.SystemSettings.GetFont(
106+
wx.SYS_DEFAULT_GUI_FONT).GetPointSize()
107+
try:
108+
step = {0: -1, 2: 2, 3: 4}.get(int(prefs.get("fontsize", 1)), 0)
109+
except ValueError:
110+
step = 0
111+
return base, step
112+
82113
def __init__(self, values):
83-
self.ErrorColour = wx.Colour(255, 75, 85)
84-
self.WarningColour = wx.Colour(255, 255, 198)
85-
# wxPython Classic's wx.Font(pointSize=.., family=.., style=..,
86-
# weight=.., face=..) keyword form doesn't match any Phoenix
87-
# overload (the keyword is "faceName", not "face", and mixing
88-
# legacy keyword names trips up Phoenix's overload resolution
89-
# entirely -- confirmed via a live TypeError listing all 7
90-
# candidate overloads rejecting it). wx.FontInfo is the modern,
91-
# non-deprecated, unambiguous way to build a wx.Font in Phoenix.
92-
self.FontDefault = wx.Font(wx.FontInfo(8).FaceName("Helvetica").Bold())
93-
self.ScriptFontDefault = wx.Font(wx.FontInfo(8).FaceName("Helvetica"))
114+
# Same tints as ewxThemeColours::statusTint (BAD, UNSURE), light and
115+
# dark; the message box keeps the theme's text colour.
116+
dark = wx.SystemSettings.GetAppearance().IsDark()
117+
self.ErrorColour = wx.Colour("#5c1f1f" if dark else "#ffdddd")
118+
self.WarningColour = wx.Colour("#4d3a10" if dark else "#fff1cc")
119+
120+
# The theme's font at ECCE's 10 pt, moved by the Font Size
121+
# preference, as ewxStyledWindow::applyFont does.
122+
base, step = self.FontSizes()
123+
self.FontDefault = self.ThemeFont(base + step)
124+
self.ScriptFontDefault = self.ThemeFont(base + step - 1)
94125

95126
# PARAMETER INITIALIZED CONSTANTS
96127
try:

‎scripts/codereg/templates.py‎

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,6 @@ class EcceSubPanel(wx.Panel):
314314
def __init__(self, parent):
315315
wx.Panel.__init__(self, parent, id = -1, style = wx.TAB_TRAVERSAL)
316316
self.SetFont(EcceGlobals.FontDefault)
317-
self.SetBackgroundColour(EcceGlobals.BackgroundColour)
318317
self.Settings = parent.Settings
319318
self.CheckButtonEnables = parent.CheckButtonEnables
320319
self.OnChanges = parent.OnChanges
@@ -340,7 +339,6 @@ def __init__(self, parent, helpURL=""):
340339
self.panelSizer = EccePanelSizer()
341340
self.SetSizer(self.panelSizer)
342341
self.SetFont(EcceGlobals.FontDefault)
343-
self.SetBackgroundColour(EcceGlobals.BackgroundColour)
344342
self.Settings = []
345343

346344
parent.Bind(wx.EVT_CLOSE, self.OnClose)
@@ -358,7 +356,6 @@ def AddButtons(self):
358356
# The footer is the frame's, not this panel's, so it does not scroll.
359357
self.footer = wx.Panel(self.GetParent(), style = wx.TAB_TRAVERSAL)
360358
self.footer.SetFont(EcceGlobals.FontDefault)
361-
self.footer.SetBackgroundColour(EcceGlobals.BackgroundColour)
362359
self.footerSizer = EccePanelSizer()
363360
self.footer.SetSizer(self.footerSizer)
364361
frameSizer = wx.BoxSizer(wx.VERTICAL)
@@ -1481,7 +1478,6 @@ def __init__(self, parent, rootPanel=0, column=2):
14811478
EcceGlobals.BorderDefault)
14821479
self.SetSizer(self.panelSizer)
14831480
self.SetFont(EcceGlobals.FontDefault)
1484-
self.SetBackgroundColour(EcceGlobals.BackgroundColour)
14851481
self.Settings = rootPanel.Settings
14861482
self.CheckButtonEnables = rootPanel.CheckButtonEnables
14871483
self.OnChanges = rootPanel.OnChanges

‎tests/look/codereg_shot.py‎

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
#!/usr/bin/env python3
2+
"""Render the code-registration Theory/Runtype Details dialogs to PNGs.
3+
4+
tests/look/codereg_shot.py OUTDIR [--theme light|dark|both] [--tag NAME]
5+
[--codes orca,nwchem,...] [--scripts DIR]
6+
7+
Runs the real dialog scripts (unmodified) on a private Xvfb, grabs the
8+
frame once it is mapped, and writes OUTDIR/<tag>-<code>-<theory|runtype>-<theme>.png.
9+
Used to compare the dialogs against the C++ windows (#210).
10+
"""
11+
import argparse
12+
import os
13+
import socket
14+
import subprocess
15+
import sys
16+
import tempfile
17+
import time
18+
19+
HERE = os.path.dirname(os.path.abspath(__file__))
20+
ROOT = os.path.dirname(os.path.dirname(HERE))
21+
22+
# code -> (theory script, runtype script, category, theory, runtype)
23+
CASES = {
24+
"orca": ("orcatheory.py", "orcaruntype.py", "DFT", "RDFT", "Geometry"),
25+
"nwchem": ("nedtheory.py", "nedruntype.py", "SCF", "RHF", "Geometry"),
26+
"gaussian16": ("ged16theory.py", "ged16runtype.py", "DFT", "RDFT",
27+
"Geometry"),
28+
"mopac": ("mopactheory.py", "mopacruntype.py", "SE", "RPM7", "Energy"),
29+
"qe": ("qetheory.py", "qeruntype.py", "PW", "PW", "Energy"),
30+
"gromacs": ("gromacstheory.py", "gromacsruntype.py", "MD", "MD", "Energy"),
31+
}
32+
33+
INNER = r'''
34+
import os, sys, subprocess
35+
sys.path.insert(0, os.environ["CODEREG_DIR"])
36+
os.chdir(os.environ["CODEREG_DIR"])
37+
import templates
38+
from templates import wx
39+
out = os.environ["SHOT_OUT"]
40+
def finalize(self):
41+
import time
42+
self.Fit()
43+
self.Show(True)
44+
flash = os.environ.get("SHOT_FLASH")
45+
if flash:
46+
panel = [c for c in self.GetChildren()
47+
if isinstance(c, templates.EccePanel)][0]
48+
panel.SetStatusText("Value out of range: hold the colour for the shot",
49+
flash)
50+
panel.colorTimer.Stop()
51+
panel.messageTimer.Stop()
52+
end = time.time() + 1.5
53+
while time.time() < end:
54+
wx.GetApp().Yield()
55+
time.sleep(0.05)
56+
r = self.GetScreenRect()
57+
subprocess.run(["import", "-window", "root", out + ".root.png"])
58+
subprocess.run(["convert", out + ".root.png", "-crop",
59+
"%dx%d+%d+%d" % (r.width, r.height, r.x, r.y),
60+
"+repage", out])
61+
os.unlink(out + ".root.png")
62+
templates.EcceFrame.Finalize = finalize
63+
import runpy
64+
runpy.run_path(os.environ["SHOT_SCRIPT"], run_name="__main__")
65+
'''
66+
67+
68+
def main():
69+
ap = argparse.ArgumentParser()
70+
ap.add_argument("outdir")
71+
ap.add_argument("--theme", default="light")
72+
ap.add_argument("--tag", default="after")
73+
ap.add_argument("--codes", default=",".join(CASES))
74+
ap.add_argument("--scripts", default=os.path.join(ROOT, "scripts/codereg"))
75+
ap.add_argument("--flash", choices=("warning", "error"), default=None,
76+
help="show a status message with that flash colour")
77+
ap.add_argument("--fontsize", default=None,
78+
help="write FONTSIZE:<n> to the test preferences")
79+
o = ap.parse_args()
80+
os.makedirs(o.outdir, exist_ok=True)
81+
sys.path.insert(0, os.path.join(ROOT, "tests", "apps"))
82+
import xdisplay
83+
work = tempfile.mkdtemp(prefix="codereg-shot")
84+
os.makedirs(os.path.join(work, ".ECCE"))
85+
if o.fontsize:
86+
with open(os.path.join(work, ".ECCE", "EcceGlobal"), "w") as f:
87+
f.write("FONTSIZE:\t%s\n" % o.fontsize)
88+
sink = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
89+
sink.bind(("127.0.0.1", 0))
90+
port = str(sink.getsockname()[1])
91+
status = 0
92+
with xdisplay.Display(screen="1400x1000x24") as xv:
93+
time.sleep(1)
94+
themes = ["light", "dark"] if o.theme == "both" else [o.theme]
95+
for theme in themes:
96+
gtk = "Adwaita:dark" if theme == "dark" else "Adwaita"
97+
for code in o.codes.split(","):
98+
th, rt, cat, theory, run = CASES[code]
99+
for kind, script in (("theory", th), ("runtype", rt)):
100+
png = os.path.join(o.outdir, "%s-%s-%s-%s.png"
101+
% (o.tag, code, kind, theme))
102+
restore = os.path.join(work, "restore.in")
103+
open(restore, "w").write("END_GUIValues\n")
104+
argv = [restore, port, "GUIValues", "Writable",
105+
"DebugOff", cat, theory, run, "shot", "0", "C1",
106+
"10", "1", "0", "5", "5", "6"]
107+
env = dict(os.environ, DISPLAY=xv.name, GTK_THEME=gtk,
108+
ECCE_REALUSERHOME=work, HOME=work,
109+
CODEREG_DIR=o.scripts, SHOT_OUT=png,
110+
SHOT_SCRIPT=os.path.join(o.scripts, script),
111+
PYTHONDONTWRITEBYTECODE="1",
112+
SHOT_FLASH=o.flash or "")
113+
r = subprocess.run([sys.executable, "-c", INNER] + argv,
114+
env=env, capture_output=True, text=True, errors="replace",
115+
timeout=120)
116+
if not os.path.exists(png):
117+
print("FAILED %s %s %s rc=%s\n%s%s" % (code, kind, theme, r.returncode, r.stdout,
118+
r.stderr[-1500:]))
119+
status = 1
120+
else:
121+
print(png)
122+
return status
123+
124+
125+
if __name__ == "__main__":
126+
sys.exit(main())

‎tools/offload/README.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,9 @@ exit status is the command's (77 is SKIP). Examples:
3737

3838
`--host auto` (the default) prefers tellurium for `build`/`ctest` and radium
3939
for `teaching`/`apps`, takes the other if the first has no free slot, and
40-
queues on the first reachable host if both are full. **tellurium is off at
41-
night (after 20:30)**: a host that does not answer within 5 s is skipped. If
40+
queues on the first reachable host if both are full. **tellurium is someone's gaming computer and is used only in its owner's hours** (until 2026-10-08 14:00, then evenings 17-21 and weekend mornings 07-12; a job starts only with an hour left, see `tellurium_free` in the script). radium starts nothing from 17:00 to 21:00
41+
(in use 18-21). A host that does not answer within 5 s
42+
is skipped. If
4243
no host is reachable the script exits 3 and says so; run the command locally
4344
then. Slots are `flock` files in `<work root>/locks`, so waiting jobs queue
4445
instead of failing. One job per ref at a time (the build dir is per ref).

‎tools/offload/run-offload.sh‎

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,39 @@ free() {
2424
ssh -o BatchMode=yes -o ConnectTimeout=5 "$1" "$(conf "$1"); n=0; mkdir -p \$OFF/locks;
2525
for i in \$(seq 1 \$SLOTS); do ( exec 9>\$OFF/locks/slot\$i; flock -n 9 ) && n=\$((n+1)); done; echo \$n" 2>/dev/null || echo -1
2626
}
27+
# tellurium is someone's gaming computer: use it only in the hours its
28+
# owner allows, and start a job only when an hour of the window is left.
29+
# Until 2026-10-08 14:00 any time before 20:30; after that weekday and
30+
# weekend evenings 17-21 and weekend mornings 07-12.
31+
tellurium_free() {
32+
local now day hm
33+
now=$(date +%s); day=$(date +%u); hm=$((10#$(date +%H) * 60 + 10#$(date +%M)))
34+
if [ "$now" -lt "$(date -d '2026-10-08 13:00' +%s)" ]; then
35+
[ "$hm" -le $((19 * 60 + 30)) ]; return
36+
fi
37+
[ "$hm" -ge $((17 * 60)) ] && [ "$hm" -le $((20 * 60)) ] && return 0
38+
[ "$day" -ge 6 ] && [ "$hm" -ge $((7 * 60)) ] && [ "$hm" -le $((11 * 60)) ]
39+
}
40+
# radium is in use on evenings 18-21: start nothing from 17:00 to 21:00.
41+
radium_free() {
42+
local hm=$((10#$(date +%H) * 60 + 10#$(date +%M)))
43+
[ "$hm" -lt $((17 * 60)) ] || [ "$hm" -ge $((21 * 60)) ]
44+
}
45+
host_free() { case $1 in tellurium) tellurium_free ;; radium) radium_free ;; *) true ;; esac; }
46+
if [ "$host" != auto ] && ! host_free "$host"; then
47+
echo "offload: $host is not available now (owner's hours)" >&2
48+
exit 3
49+
fi
2750
if [ "$host" = auto ]; then
2851
# Builds prefer tellurium, test suites radium. A host that does not
29-
# answer (tellurium is off at night) is skipped; if every reachable host
52+
# answer, or is outside its owner's hours, is skipped; if every reachable host
3053
# is full the job queues on the first reachable one.
3154
case $2 in build|ctest) order="tellurium radium" ;; *) order="radium tellurium" ;; esac
3255
host= queue=
3356
for h in $order; do
57+
if ! host_free "$h"; then
58+
echo "offload: $h outside its hours, skipped" >&2; continue
59+
fi
3460
n=$(free "$h")
3561
[ "$n" -ge 0 ] || { echo "offload: $h unreachable, skipped" >&2; continue; }
3662
[ -n "$queue" ] || queue=$h

0 commit comments

Comments
 (0)