* @brief Headless save to the given path (no file dialog). */
| 1893 | * @brief Headless save to the given path (no file dialog). |
| 1894 | */ |
| 1895 | bool DataModel::ProjectModel::apiSaveJsonFile(const QString& path) |
| 1896 | { |
| 1897 | if (path.isEmpty()) |
| 1898 | return false; |
| 1899 | |
| 1900 | if (m_title.isEmpty()) { |
| 1901 | qWarning() << "[ProjectModel] Project title cannot be empty"; |
| 1902 | return false; |
| 1903 | } |
| 1904 | |
| 1905 | if (groupCount() <= 0) { |
| 1906 | qWarning() << "[ProjectModel] Project needs at least one group"; |
| 1907 | return false; |
| 1908 | } |
| 1909 | |
| 1910 | const bool hasImageGroup = std::any_of(m_groups.begin(), m_groups.end(), [](const Group& g) { |
| 1911 | return g.widget == QLatin1String("image"); |
| 1912 | }); |
| 1913 | |
| 1914 | if (datasetCount() <= 0 && !hasImageGroup) { |
| 1915 | qWarning() << "[ProjectModel] Project needs at least one dataset"; |
| 1916 | return false; |
| 1917 | } |
| 1918 | |
| 1919 | QString finalPath = path; |
| 1920 | if (!finalPath.endsWith(QStringLiteral(".ssproj"), Qt::CaseInsensitive)) |
| 1921 | finalPath += QStringLiteral(".ssproj"); |
| 1922 | |
| 1923 | m_filePath = finalPath; |
| 1924 | return finalizeProjectSave(); |
| 1925 | } |
| 1926 | |
| 1927 | /** |
| 1928 | * @brief Serializes the complete project state to a QJsonObject. |