Skip to content

visualdynamics.gui.findings_pane

findings_pane

The findings register in the report editor's pane (report feature findings).

Shown under section 9's words when the deviations section is selected and the report has the findings register on. A list of the findings entered, by kind and number, each with the parts it still owes; a kind and Add to enter one; and, for the finding chosen, its run, channel, band and status, its four parts in the author's words, and a row that reads a draft of its fact off a comparison the project holds, with the draft shown beside Accept and Discard until the author decides.

The pane decides nothing. Every change is emitted as requested, and the editor makes it through report.findings, which refuses what cannot be entered; the pane is built again to show the outcome, with the refusal's words when there was one.

Classes:

Name Description
FindingsPane

The register's list, the chosen finding's fields, and its draft.

Classes

FindingsPane

FindingsPane(entries: Sequence[Mapping[str, Any]], selected: str | None, measured: Sequence[str] = (), specifications: Sequence[str] = (), message: str = '', parent: QWidget | None = None)

Bases: QWidget

The register's list, the chosen finding's fields, and its draft.

requested carries one change as a dict with 'action': 'select', 'add' (with 'kind'), 'remove', 'edit' (with 'fields'), 'draft' (with 'measured', 'specification' and 'limits'), 'accept' or 'discard', each but 'add' with the finding's 'number'.

Methods:

Name Description
flush

Enter whatever has been typed in the parts and not entered

Source code in src/visualdynamics/gui/findings_pane.py
def __init__(self, entries: Sequence[Mapping[str, Any]],
             selected: str | None, measured: Sequence[str] = (),
             specifications: Sequence[str] = (), message: str = '',
             parent: QWidget | None = None) -> None:
    super().__init__(parent)
    self._loading = True
    #: the register's findings, in its order, as `report.findings` reads them
    self.entries: list[Mapping[str, Any]] = list(entries)
    chosen = [entry for entry in self.entries
              if entry['number'] == selected]
    #: the finding whose fields are shown, or None
    self.entry: Mapping[str, Any] | None = chosen[0] if chosen else None
    layout = QVBoxLayout(self)
    layout.setContentsMargins(0, 0, 0, 0)
    layout.setSpacing(4)
    layout.addWidget(QLabel('<b>Findings register</b>'))
    intro = QLabel('Each finding in four parts. A blank part is owed '
                   'on the page; None above says a kind has none.')
    intro.setWordWrap(True)
    layout.addWidget(intro)
    self.list: QListWidget = QListWidget()
    for entry in self.entries:
        owed = core.owed(entry)
        label = f'{entry["number"]}  {entry["kind"].capitalize()}'
        if owed:
            named = [core.FOURTH[entry['kind']].lower()
                     if part == 'approval' else part for part in owed]
            label += f'  (owes {", ".join(named)})'
        if entry['draft'] is not None:
            label += '  [draft waiting]'
        item = QListWidgetItem(label)
        item.setData(Qt.ItemDataRole.UserRole, entry['number'])
        self.list.addItem(item)
    if self.entry is not None:
        self.list.setCurrentRow(self.entries.index(self.entry))
    self.list.currentRowChanged.connect(self._chosen)
    self.list.setMaximumHeight(120)
    layout.addWidget(self.list)
    adding = QHBoxLayout()
    self.kind_box: QComboBox = QComboBox()
    for kind in core.KINDS:
        self.kind_box.addItem(kind.capitalize(), kind)
    self.add_button: QPushButton = QPushButton('Add')
    self.add_button.setToolTip('Enter a finding of this kind under the '
                               'next number of its kind')
    self.add_button.clicked.connect(lambda: self.requested.emit(
        {'action': 'add', 'kind': self.kind_box.currentData()}))
    self.remove_button: QPushButton = QPushButton('Remove')
    self.remove_button.setEnabled(self.entry is not None)
    self.remove_button.clicked.connect(lambda: self._ask('remove'))
    adding.addWidget(self.kind_box, 1)
    adding.addWidget(self.add_button)
    adding.addWidget(self.remove_button)
    layout.addLayout(adding)
    #: the chosen finding's line fields and part editors, by field
    self.lines: dict[str, QLineEdit] = {}
    self.parts: dict[str, QPlainTextEdit] = {}
    self.status_box: QComboBox | None = None
    self.measured_box: QComboBox | None = None
    self.specification_box: QComboBox | None = None
    self.limits_box: QComboBox | None = None
    self.draft_button: QPushButton | None = None
    self.accept_button: QPushButton | None = None
    self.discard_button: QPushButton | None = None
    self._timer: QTimer = QTimer(self)
    self._timer.setSingleShot(True)
    self._timer.setInterval(PART_DEBOUNCE_MS)
    self._timer.timeout.connect(self.flush)
    if self.entry is not None:
        layout.addWidget(self._fields(self.entry, measured,
                                      specifications))
    self.note: QLabel = QLabel(message)
    self.note.setWordWrap(True)
    self.note.setStyleSheet('color: #c0392b')
    self.note.setVisible(bool(message))
    layout.addWidget(self.note)
    self._loading = False
Methods:
flush
flush() -> None

Enter whatever has been typed in the parts and not entered yet. The pane does this itself once typing pauses for PART_DEBOUNCE_MS, and the editor before anything else.

Source code in src/visualdynamics/gui/findings_pane.py
def flush(self) -> None:
    """Enter whatever has been typed in the parts and not entered
    yet. The pane does this itself once typing pauses for
    `PART_DEBOUNCE_MS`, and the editor before anything else."""
    self._timer.stop()
    if self.entry is None:
        return
    changed = {part: edit.toPlainText()
               for part, edit in self.parts.items()
               if edit.toPlainText().strip() != self.entry[part].strip()}
    if changed:
        self._edit(changed)