Extract the shared _export_actions builder from add_export_actions so the same dialog/file-naming/collection logic can target either a toolbar (DA-12, DA-07) or a QMenu (for toolbars on a width budget). The builder's docstring documents that the handlers must stay closures: a closure slot is stored strongly by the connection, so the actions anchor the parent window's Python wrapper for the life of the C++ action - bound-method slots are stored weakly, and rewiring them as window methods lets an otherwise-unreferenced window be GC'd mid-test, cascading C++ deletion into child dialogs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2.9 KiB
2.9 KiB
Spreadsheet Export
Any data screen can be exported to a real Excel .xlsx workbook: a metadata block
(station context), then the grid exactly as shown, including alarm colors. Two toolbar
actions — Export This Tab… (one sheet) and Export All Tabs… (one sheet per tab).
How it works
cim_suite/core/export/— Qt-free engine:Sheet/Celldata types andwrite_xlsx(path, sheets).cim_suite/core/ui/table_tab.py—TableTab.export_sheet()snapshots a tab's grid (text + per-cell colors) into aSheet.cim_suite/core/ui/export_action.py—add_export_actions(...)adds the two toolbar actions;add_export_menu_actions(...)adds the same pair to aQMenuinstead (for toolbars on a width budget — IOModbus's "Export ▾" menu button uses it); both share one action builder.collect_sheet/collect_allturn tabs into sheets and stamp metadata.
Export adoption checklist (per module)
- Existing TableTab-based tabs export for free. Adding a column or field to a tab
needs no export change —
export_sheet()reads whatever columns the table has. - A new module must, once, call
add_export_actions(toolbar, tabs, window, metadata_provider, file_prefix=..., last_dir=..., remember_dir=...)in its toolbar (oradd_export_menu_actions(menu, ...)to put the pair in a menu) and supply ametadata_providerreturning the device-context dict. Without this the module has no export. - A non-
TableTabscreen (a form of custom widgets, a chart, a side panel) has no grid to walk. It must implement its ownexport_sheet() -> Sheet | None, or it is intentionally excluded (returnNone/ omit the method, and it's skipped). DA-12 History dialog (BL-D5, 2026-06-05): the per-sensor History dialog is a custom chart + table screen. It exports the data table to.xlsxvia the sharedsave_sheets_dialoghelper and the chart to PNG — both available from the dialog's own toolbar. This covers the "every data screen exports" requirement for this screen. - A tab that needs to emit several worksheets (not just one) may implement the
optional
export_sheets() -> list[Sheet](plural) hook instead — each returnedSheetis self-titled and becomes its own worksheet in Export All Tabs. When present it takes precedence overexport_sheet()(collect_sheetsinexport_action.py). The DA-07 ChannelsTab uses it to emit one sheet per configured device, so Export-All covers every device's channels regardless of which one the tab is currently showing. - Data shown outside the table (e.g. a value only in a header label) is not
captured by the default grid-walker. Surface it in the grid or override
export_sheet().
What is intentionally NOT captured
The stylesheet-driven alternating-row banding lives in QSS, not on the cells, so it is correctly ignored. Only colors set directly on items (alarm fills) are exported.