* @brief Validates and saves the project, optionally prompting for a path; the * path-accepted handler defers via a queued invoke because the macOS NSSavePanel * KVO callback must unwind before re-entering the model. */
| 1837 | * KVO callback must unwind before re-entering the model. |
| 1838 | */ |
| 1839 | bool DataModel::ProjectModel::saveJsonFile(const bool askPath) |
| 1840 | { |
| 1841 | if (!validateProject(m_suppressMessageBoxes)) |
| 1842 | return false; |
| 1843 | |
| 1844 | if (jsonFilePath().isEmpty() || askPath) { |
| 1845 | auto* dialog = new QFileDialog(qApp->activeWindow(), |
| 1846 | tr("Save Serial Studio Project"), |
| 1847 | jsonProjectsPath() + "/" + title() + ".ssproj", |
| 1848 | tr("Serial Studio Project Files (*.ssproj)")); |
| 1849 | |
| 1850 | dialog->setAcceptMode(QFileDialog::AcceptSave); |
| 1851 | dialog->setFileMode(QFileDialog::AnyFile); |
| 1852 | dialog->setAttribute(Qt::WA_DeleteOnClose); |
| 1853 | |
| 1854 | auto accepted = std::make_shared<bool>(false); |
| 1855 | connect(dialog, &QFileDialog::fileSelected, this, [this, accepted](const QString& path) { |
| 1856 | if (path.isEmpty()) |
| 1857 | return; |
| 1858 | |
| 1859 | *accepted = true; |
| 1860 | |
| 1861 | QMetaObject::invokeMethod( |
| 1862 | this, |
| 1863 | [this, path]() { |
| 1864 | QString finalPath = path; |
| 1865 | if (!finalPath.endsWith(QStringLiteral(".ssproj"), Qt::CaseInsensitive)) |
| 1866 | finalPath += QStringLiteral(".ssproj"); |
| 1867 | |
| 1868 | const QString chosenTitle = QFileInfo(finalPath).completeBaseName(); |
| 1869 | if (m_title == tr("Untitled Project") && !chosenTitle.isEmpty() |
| 1870 | && chosenTitle != m_title) { |
| 1871 | m_title = chosenTitle; |
| 1872 | Q_EMIT titleChanged(); |
| 1873 | } |
| 1874 | |
| 1875 | m_filePath = finalPath; |
| 1876 | (void)finalizeProjectSave(); |
| 1877 | }, |
| 1878 | Qt::QueuedConnection); |
| 1879 | }); |
| 1880 | |
| 1881 | connect(dialog, &QFileDialog::finished, this, [this, accepted](int) { |
| 1882 | Q_EMIT saveDialogCompleted(*accepted); |
| 1883 | }); |
| 1884 | |
| 1885 | dialog->open(); |
| 1886 | return false; |
| 1887 | } |
| 1888 | |
| 1889 | return finalizeProjectSave(); |
| 1890 | } |
| 1891 | |
| 1892 | /** |
| 1893 | * @brief Headless save to the given path (no file dialog). |
no test coverage detected