| 3036 | } |
| 3037 | |
| 3038 | void MainWindow::saveLayoutToPath(const QString& path, bool include_data_source) { |
| 3039 | QDomDocument doc = xmlSaveState(); |
| 3040 | // Record the binding intent so load knows whether to reload the original |
| 3041 | // file (source-bound) or adopt the currently-loaded dataset (generic). The |
| 3042 | // data-source element below is only embedded for source-bound layouts. |
| 3043 | doc.documentElement().setAttribute( |
| 3044 | QStringLiteral("binding"), include_data_source ? QStringLiteral("source") : QStringLiteral("generic")); |
| 3045 | if (include_data_source) { |
| 3046 | const QDir layout_dir(QFileInfo(path).absoluteDir()); |
| 3047 | QDomElement ds = appendDataSourceElement(doc, layout_dir); |
| 3048 | if (!ds.isNull()) { |
| 3049 | doc.documentElement().appendChild(ds); |
| 3050 | } |
| 3051 | } |
| 3052 | // Always save right-panel state — pure UI chrome, no privacy cost, |
| 3053 | // not gated by Save Data Source. |
| 3054 | doc.documentElement().appendChild(saveRightPanelState(doc)); |
| 3055 | // Always save the remaining widget state — pure UI chrome, not gated |
| 3056 | // by Save Data Source. |
| 3057 | doc.documentElement().appendChild(ui_->leftPanel->saveSourcesState(doc)); |
| 3058 | doc.documentElement().appendChild(ui_->curveListPanel->saveListState(doc)); |
| 3059 | doc.documentElement().appendChild(saveChromeState(doc)); |
| 3060 | // NOTE: <data_processors> is already emitted by xmlSaveState() (it is part of THE |
| 3061 | // snapshot now, shared with undo/redo); do not append it again here or the layout |
| 3062 | // would carry two copies. |
| 3063 | // Timeline view chrome (zoom/scroll/name-column/snap). Pure UI, not gated by |
| 3064 | // Save Data Source; the per-source offsets/order ride <fileInfo> separately. |
| 3065 | doc.documentElement().appendChild(saveSourceTimelineViewState(doc)); |
| 3066 | // QSaveFile gives us write-temp + rename atomicity: a partial write |
| 3067 | // (disk full, signal, broken NFS) leaves the user's prior layout |
| 3068 | // untouched. commit() does the rename; cancelWriting() abandons the |
| 3069 | // tempfile. A QFile::Truncate path would have destroyed prior state |
| 3070 | // before completing the write. |
| 3071 | QSaveFile file(path); |
| 3072 | if (!file.open(QIODevice::WriteOnly)) { |
| 3073 | MessageBox::warning(this, tr("Save Layout"), tr("Cannot open '%1' for writing.").arg(path)); |
| 3074 | return; |
| 3075 | } |
| 3076 | const QByteArray bytes = doc.toByteArray(2); |
| 3077 | if (file.write(bytes) != bytes.size()) { |
| 3078 | const QString err = file.errorString(); |
| 3079 | file.cancelWriting(); |
| 3080 | MessageBox::warning(this, tr("Save Layout"), tr("Failed to write layout to '%1': %2").arg(path, err)); |
| 3081 | return; |
| 3082 | } |
| 3083 | if (!file.commit()) { |
| 3084 | MessageBox::warning( |
| 3085 | this, tr("Save Layout"), tr("Failed to finalize layout '%1': %2").arg(path, file.errorString())); |
| 3086 | return; |
| 3087 | } |
| 3088 | recordRecentLayout(path); |
| 3089 | emitDiagnostic(DiagnosticLevel::kInfo, "Layout", "saved", tr("Saved layout: %1").arg(QFileInfo(path).fileName())); |
| 3090 | } |
| 3091 | |
| 3092 | QDomElement MainWindow::saveDataProcessors(QDomDocument& doc) const { |
| 3093 | QDomElement element = doc.createElement(QStringLiteral("data_processors")); |
nothing calls this directly
no test coverage detected