| 479 | } |
| 480 | |
| 481 | QDomElement PlotWidget::xmlSaveState(QDomDocument& doc) const { |
| 482 | QDomElement plot_element = doc.createElement(QStringLiteral("plot")); |
| 483 | plot_element.setAttribute(QStringLiteral("id"), state_id_); |
| 484 | plot_element.setAttribute( |
| 485 | QStringLiteral("mode"), isXYPlot() ? QStringLiteral("XYPlot") : QStringLiteral("TimeSeries")); |
| 486 | plot_element.setAttribute(QStringLiteral("line_width"), lineWidthToString(lineWidth())); |
| 487 | // Style and width are plot-level properties (every curve shares them; only |
| 488 | // colour is per-curve), so they are saved once on the <plot>, not per <curve>. |
| 489 | plot_element.setAttribute(QStringLiteral("style"), curveStyleToString(curveStyle())); |
| 490 | plot_element.setAttribute(QStringLiteral("title"), qwtPlot()->title().text()); |
| 491 | plot_element.setAttribute( |
| 492 | QStringLiteral("tracker_enabled"), tracker_enabled_ ? QStringLiteral("true") : QStringLiteral("false")); |
| 493 | |
| 494 | // Skip the <range> element when the canvas has not yet computed a real |
| 495 | // viewport (e.g. drop happened immediately before save) -- a degenerate |
| 496 | // rect would restore as a zero-width window and hide everything. Without |
| 497 | // <range>, xmlLoadState falls back to zoomOut(), which auto-fits. |
| 498 | const QRectF rect = currentBoundingRect(); |
| 499 | if (rect.left() != rect.right() && rect.top() != rect.bottom()) { |
| 500 | QDomElement range_element = doc.createElement(QStringLiteral("range")); |
| 501 | range_element.setAttribute(QStringLiteral("bottom"), QString::number(rect.bottom(), 'f', 6)); |
| 502 | range_element.setAttribute(QStringLiteral("top"), QString::number(rect.top(), 'f', 6)); |
| 503 | // The X axis of a time-series plot is TIME: persist it in ABSOLUTE seconds, |
| 504 | // not the display-relative seconds the Qwt axis speaks (display = absolute - |
| 505 | // offset; see pj_runtime/Time.h). PJ4's display offset is per-dataset and |
| 506 | // live, so a display-relative range only frames the right instant for the |
| 507 | // offset present at save time — storing absolute makes the restored range |
| 508 | // correct regardless of the offset state at load (the "Use time offset" |
| 509 | // toggle, reloaded data, a layout shared between machines). The time axis is |
| 510 | // ALWAYS stored absolute — no marker is written; on load the plot MODE |
| 511 | // (time-series vs XY) is what decides whether to undo the offset. XY plots' X is |
| 512 | // a value, not time, so they keep their raw axis coordinates. |
| 513 | if (isXYPlot()) { |
| 514 | range_element.setAttribute(QStringLiteral("left"), QString::number(rect.left(), 'f', 6)); |
| 515 | range_element.setAttribute(QStringLiteral("right"), QString::number(rect.right(), 'f', 6)); |
| 516 | } else { |
| 517 | const double offset_sec = displayOffsetSeconds(); |
| 518 | range_element.setAttribute(QStringLiteral("left"), QString::number(rect.left() + offset_sec, 'f', 6)); |
| 519 | range_element.setAttribute(QStringLiteral("right"), QString::number(rect.right() + offset_sec, 'f', 6)); |
| 520 | } |
| 521 | plot_element.appendChild(range_element); |
| 522 | } |
| 523 | |
| 524 | // A curve is identified by its stable topic+field path (PJ::LayoutXml), not |
| 525 | // the engine's opaque per-load catalog key. On load — layout file or undo/redo |
| 526 | // snapshot alike — the path is re-resolved against the current dataset(s) to |
| 527 | // the concrete key (PJ::LayoutXml::rebindCurveKeys), so the saved form stays |
| 528 | // valid across reloads and similar datasets. |
| 529 | const auto write_stable_path = [&](QDomElement& element, const QString& topic_attr, const QString& field_attr, |
| 530 | const QString& key) { |
| 531 | if (catalog_ == nullptr) { |
| 532 | return; |
| 533 | } |
| 534 | if (const auto descriptor = catalog_->curveDescriptor(key); descriptor.has_value()) { |
| 535 | element.setAttribute(topic_attr, descriptor->topic_name); |
| 536 | element.setAttribute(field_attr, descriptor->field_path); |
| 537 | } |
| 538 | }; |
no test coverage detected