Skip to content

visualdynamics.gui.run_reports

run_reports

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