| 2643 | } |
| 2644 | |
| 2645 | void MainWindow::loadLayoutFromPath(const QString& path) { |
| 2646 | // 1. Open + parse |
| 2647 | QFile file(path); |
| 2648 | if (!file.open(QIODevice::ReadOnly)) { |
| 2649 | MessageBox::warning(this, tr("Load Layout"), tr("Cannot open '%1' for reading.").arg(path)); |
| 2650 | return; |
| 2651 | } |
| 2652 | QDomDocument doc; |
| 2653 | const QDomDocument::ParseResult parse_result = doc.setContent(&file); |
| 2654 | if (!parse_result) { |
| 2655 | file.close(); |
| 2656 | MessageBox::warning( |
| 2657 | this, tr("Load Layout"), |
| 2658 | tr("'%1' is not a valid PJ4 layout: %2 (line %3, col %4)") |
| 2659 | .arg(path, parse_result.errorMessage) |
| 2660 | .arg(parse_result.errorLine) |
| 2661 | .arg(parse_result.errorColumn)); |
| 2662 | return; |
| 2663 | } |
| 2664 | file.close(); |
| 2665 | |
| 2666 | // Forward-compat schema check. pj4_version is written by xmlSaveState |
| 2667 | // as an integer string; if we encounter a layout from a newer PJ4 |
| 2668 | // that introduced incompatible schema changes, warn but proceed — |
| 2669 | // unknown elements are silently ignored downstream anyway. We never |
| 2670 | // hard-fail on this; the user can always re-save under the current |
| 2671 | // schema. |
| 2672 | const QDomElement root = doc.documentElement(); |
| 2673 | bool version_ok = false; |
| 2674 | const int version = root.attribute(QStringLiteral("pj4_version"), QStringLiteral("0")).toInt(&version_ok); |
| 2675 | if (version_ok && version > kLayoutSchemaVersion) { |
| 2676 | emitDiagnostic( |
| 2677 | DiagnosticLevel::kWarning, "Layout", "schema-newer", |
| 2678 | tr("Layout '%1' was saved by a newer PJ4 (pj4_version=%2 > %3); loading best-effort.") |
| 2679 | .arg(QFileInfo(path).fileName()) |
| 2680 | .arg(version) |
| 2681 | .arg(kLayoutSchemaVersion)); |
| 2682 | } |
| 2683 | |
| 2684 | // 2. Source-bound layouts may reload their original file; generic layouts |
| 2685 | // (and source-bound ones whose file is missing or where the user opts out) |
| 2686 | // bind straight to the currently-loaded data. The binding attribute records |
| 2687 | // the save-time intent; this is the load-time override. |
| 2688 | const QString binding = root.attribute(QStringLiteral("binding"), QStringLiteral("source")); |
| 2689 | const QDir layout_dir(QFileInfo(path).absoluteDir()); |
| 2690 | const QList<layout_xml::DataSourceRef> replays = layout_xml::extractDataSource(doc, layout_dir); |
| 2691 | // Set when the user chose "Reload original": the data loads (possibly async on |
| 2692 | // a worker), so the layout apply below must wait for the load queue to drain. |
| 2693 | bool reload_requested = false; |
| 2694 | if (binding != QStringLiteral("generic") && !replays.empty()) { |
| 2695 | // Classify each referenced file: already loaded (skip), missing on disk |
| 2696 | // (warn + skip), or reloadable. A file counts as already loaded only while |
| 2697 | // the catalog has data — a remembered-but-cleared source must reload. |
| 2698 | const auto& loaded = session_->sessionManager().loadedSources(); |
| 2699 | const bool catalog_has_data = !session_->catalogModel().isEmpty(); |
| 2700 | QList<layout_xml::DataSourceRef> pending; |
| 2701 | for (const auto& replay : replays) { |
| 2702 | if (replay.resolved_path.isEmpty()) { |
nothing calls this directly
no test coverage detected