Reports from Runs: the one-call reports for a batch of runs, asked
for in the window.
visualdynamics.run_report writes the report each controller run's
type calls for, one per run, and a script passes it the runs, a
geometry, a destination and a marking. This dialog asks for the same
things, so File → Reports from Runs… is that call and nothing else: the
window runs run_report with the answers on its runner for long verbs,
with the progress bar and Cancel.
Each run is listed with the report it will get, read from the file the
way run_report reads it (report_kind), and how much of its end to
read (Last). The report can be changed or the run left out, so a run
that does not belong with the rest (a modal survey in a folder of
random runs, for example) is seen and left out here, instead of the
batch being refused after the button is pressed. A random-and-sine run
gets two reports, and the button counts reports, not runs.
Classes:
| Name |
Description |
RunReportsDialog |
The runs, the report each gets and how much of it to read, the
|
Functions:
| Name |
Description |
run_kind |
(kind, label) for one run: the one-call report it gets and its
|
ask_run_reports |
Show the dialog, modal. Returns its choices, or None when it is
|
Classes
RunReportsDialog
RunReportsDialog(runs: list[str], parent: QWidget | None = None)
Bases: QDialog
The runs, the report each gets and how much of it to read, the
geometry, where the reports go, and their marking.
choices() returns the answers as run_report's keywords: runs
(those not left out), kinds and last (each a dict by run),
path (a folder, or None for beside each run), geometry (a path
or None) and marking.
Methods:
| Name |
Description |
choices |
The answers, as run_report's keywords.
|
Source code in src/visualdynamics/gui/run_reports.py
| def __init__(self, runs: list[str], parent: QWidget | None = None) -> None:
super().__init__(parent)
self.setWindowTitle('Reports from Runs')
self.kinds: list[tuple[str, str | None, str]] = [
(path, *run_kind(path)) for path in runs]
self.geometry: str | None = None
self.folder: str | None = None
layout = QVBoxLayout(self)
self.heading: QLabel = QLabel()
layout.addWidget(self.heading)
# a run a row: its name, the report it gets (detected, or Leave
# out when it has none) and how much of its end to read
self.table: QTableWidget = QTableWidget(len(self.kinds), 3)
self.table.setHorizontalHeaderLabels(['Run', 'Report', 'Last'])
self.table.verticalHeader().setVisible(False)
self.table.horizontalHeader().setSectionResizeMode(
0, QHeaderView.ResizeMode.Stretch)
self.reports: list[QComboBox] = []
self.lasts: list[QDoubleSpinBox] = []
for row, (path, kind, said) in enumerate(self.kinds):
name = QTableWidgetItem(os.path.basename(path) if kind
else f'{os.path.basename(path)} — {said}')
# the reason in full: a narrow column cuts it off
name.setToolTip(path if kind else f'{path}\n{said}')
name.setFlags(name.flags() & ~Qt.ItemFlag.ItemIsEditable)
self.table.setItem(row, 0, name)
box = QComboBox()
for label, value in CHOICES:
box.addItem(label, value)
# seen and left out, not hidden: the reader should know
# which file of the campaign was passed over, and why
box.setCurrentIndex(box.findData(kind) if kind
else len(CHOICES) - 1)
box.currentIndexChanged.connect(self._restate)
self.table.setCellWidget(row, 1, box)
self.reports.append(box)
span = _seconds_box()
self.table.setCellWidget(row, 2, span)
self.lasts.append(span)
self.table.resizeColumnsToContents()
layout.addWidget(self.table)
# most random reports want only the end of the run: one number
# for all of them, each row still its own to change
self.every_random: QDoubleSpinBox = _seconds_box()
self.every_random.valueChanged.connect(self._last_for_random)
every = QHBoxLayout()
every.addWidget(QLabel('Last, for every random report'))
every.addWidget(self.every_random)
every.addStretch(1)
layout.addLayout(every)
form = QFormLayout()
self.geometry_label: QLabel = QLabel('None')
choose_geometry = QPushButton('Choose…')
choose_geometry.clicked.connect(self._choose_geometry)
clear_geometry = QPushButton('None')
clear_geometry.clicked.connect(lambda: self._set_geometry(None))
row = QHBoxLayout()
row.addWidget(self.geometry_label, 1)
row.addWidget(choose_geometry)
row.addWidget(clear_geometry)
form.addRow('Geometry', row)
# beside each run is what `run_report` does with no path, so it
# is the default here too; one folder is the other answer
self.beside: QRadioButton = QRadioButton('Beside each run')
self.beside.setChecked(True)
self.in_folder: QRadioButton = QRadioButton('In one folder')
self.folder_label: QLabel = QLabel('')
choose_folder = QPushButton('Choose…')
choose_folder.clicked.connect(self._choose_folder)
self.in_folder.toggled.connect(
lambda on: on and self.folder is None and self._choose_folder())
where = QVBoxLayout()
where.addWidget(self.beside)
folder_row = QHBoxLayout()
folder_row.addWidget(self.in_folder)
folder_row.addWidget(self.folder_label, 1)
folder_row.addWidget(choose_folder)
where.addLayout(folder_row)
form.addRow('Write', where)
self.marking: QLineEdit = QLineEdit(DEFAULT_MARKING)
self.marking.setToolTip('The banner across the top and bottom of '
'every page of every report')
form.addRow('Marking', self.marking)
layout.addLayout(form)
buttons = QDialogButtonBox()
self.write_button: QPushButton = buttons.addButton(
'', QDialogButtonBox.ButtonRole.AcceptRole)
buttons.addButton(QDialogButtonBox.StandardButton.Cancel)
buttons.accepted.connect(self.accept)
buttons.rejected.connect(self.reject)
layout.addWidget(buttons)
self._restate()
|
Methods:
choices
choices() -> dict[str, Any]
The answers, as run_report's keywords.
Source code in src/visualdynamics/gui/run_reports.py
| def choices(self) -> dict[str, Any]:
"""The answers, as `run_report`'s keywords."""
picked = [row for row in self._picked() if row[1]]
return {
'runs': [path for path, _kind, _last in picked],
'kinds': {path: kind for path, kind, _last in picked},
'last': {path: last for path, _kind, last in picked},
# a trailing separator: `run_report` reads a path ending in
# one as a folder, existing or not
'path': (os.path.join(self.folder, '')
if self.in_folder.isChecked() and self.folder else None),
'geometry': self.geometry,
'marking': self.marking.text().strip() or DEFAULT_MARKING,
}
|
Functions:
run_kind
run_kind(path: str) -> tuple[str | None, str]
(kind, label) for one run: the one-call report it gets and its
name in the list, or None and the reason it gets none.
Source code in src/visualdynamics/gui/run_reports.py
| def run_kind(path: str) -> tuple[str | None, str]:
"""(kind, label) for one run: the one-call report it gets and its
name in the list, or None and the reason it gets none."""
from ..io.rattlesnake import project_type
from ..project import report_kind
try:
kind = report_kind(path)
except ValueError:
try:
declared = project_type(path)
except (OSError, ValueError):
declared = None
return None, (f'{declared}: no one-call report' if declared
else 'not a controller run')
except OSError:
return None, 'cannot be read'
return kind, KIND_NAMES[kind]
|
ask_run_reports
ask_run_reports(parent: QWidget | None, runs: list[str]) -> dict[str, Any] | None
Show the dialog, modal. Returns its choices, or None when it is
canceled.
Source code in src/visualdynamics/gui/run_reports.py
| def ask_run_reports(parent: QWidget | None, runs: list[str]
) -> dict[str, Any] | None:
"""Show the dialog, modal. Returns its choices, or None when it is
canceled."""
dialog = RunReportsDialog(runs, parent)
if dialog.exec() == QDialog.DialogCode.Accepted:
return dialog.choices()
return None
|