diff --git a/.github/actions/initialize-build-environment/action.yml b/.github/actions/initialize-build-environment/action.yml index f92910ef..12e10df1 100644 --- a/.github/actions/initialize-build-environment/action.yml +++ b/.github/actions/initialize-build-environment/action.yml @@ -39,11 +39,11 @@ runs: HOMEBREW_NO_INSTALL_CLEANUP: 1 run: | brew update - brew install ninja + brew install ninja ccache imagemagick create-dmg - name: Install Qt uses: jurplel/install-qt-action@v4 with: version: ${{ inputs.qt_version }} - modules: qt5compat qtshadertools + modules: qt5compat qtscxml qtshadertools cache: true diff --git a/.github/actions/initialize-vcpkg/action.yml b/.github/actions/initialize-vcpkg/action.yml index e19dd5ab..1d083ef5 100644 --- a/.github/actions/initialize-vcpkg/action.yml +++ b/.github/actions/initialize-vcpkg/action.yml @@ -31,7 +31,7 @@ runs: QT_DIR: ${{ env.Qt_ROOT_DIR }}/lib/cmake/Qt6 Qt6_DIR: ${{ env.Qt_ROOT_DIR }}/lib/cmake/Qt6 VCPKG_CMAKE_RELEASE_BUILD_TYPE: "RelWithDebInfo" - VCPKG_KEEP_ENV_VARS: "QT_DIR;Qt6_DIR;VCPKG_CMAKE_RELEASE_BUILD_TYPE;VCPKG_BUILD_TYPE" + VCPKG_KEEP_ENV_VARS: "QT_DIR;Qt6_DIR;VCPKG_CMAKE_RELEASE_BUILD_TYPE;VCPKG_BUILD_TYPE;MACOSX_DEPLOYMENT_TARGET" shell: pwsh run: | if (!(Test-Path $env:VCPKG_DEFAULT_BINARY_CACHE)) { diff --git a/.github/instructions/viewmodel-binding.instructions.md b/.github/instructions/viewmodel-binding.instructions.md new file mode 100644 index 00000000..50eddd12 --- /dev/null +++ b/.github/instructions/viewmodel-binding.instructions.md @@ -0,0 +1,103 @@ +# View-Model ↔ Document Binding Prompt + +Use this prompt to guide new view-model bindings for future document elements. Keep property specifics out; focus on state design, controller-driven transitions, transactions, and synchronization patterns. Derive patterns from existing implementations (e.g., `TrackViewModelContextData`, `LabelViewModelContextData`, `TempoViewModelContextData`). + +## Core Goals +- Mirror document collections to view-model collections with clear ownership and mapping tables in both directions. +- Drive UI interactions through controllers; never mutate document state directly from QML. +- Guard against infinite loops by conditioning view→doc updates on active states. +- Wrap mutating operations in transactions with start/commit/abort semantics tied to state entry/exit. + +- Instrument every state with entry/exit logging (use `qCInfo` with a per-context logging category) to trace flows during debugging, even for states without handlers. + +## State Machine Design +- Build a `QStateMachine` with explicit states for idle, rubber-band selection, move/drag, edit/adjust flows, and per-property operations as needed. +- Keep transitions driven by signals emitted from interaction controllers and internal guards (e.g., started/not-started, commit/abort, finish). +- Example (move flow, list rotation): + - `idle` → `movePending` on `moveTransactionWillStart` (from controller). + - `movePending` → `moveProcessing` on `moveTransactionStarted`; → `idle` on `moveTransactionNotStarted`. + - `moveProcessing` → `moveCommitting` on `moveTransactionWillCommit`; → `moveAborting` on `moveTransactionWillAbort`. + - `moveCommitting`/`moveAborting` → `idle` (reset guards). +- Example (edit flow, text-like): + - `idle` → `namePending` on `nameTransactionWillStart`. + - `namePending` → `nameProgressing` on `nameTransactionStarted`; → `idle` if not started. + - `nameProgressing` → `nameCommitting` on commit; → `nameAborting` on abort. + - `nameCommitting`/`nameAborting` → `idle`. +- Rubber-band selection: `idle` → `rubberBandDragging` on start, back to `idle` on finish. + +## Controller-Driven Transitions +- Wire controller signals to emit local signals that the state machine consumes (two patterns): + 1) **Started/Committed/Aborted**: drag/move and text edits use started/commit/abort triplets. + 2) **Started/Finished**: toggle/slider/height edits use started/finished pairs. +- Set the current target item/index when the controller signals a start; clear it on commit/finish/abort handlers. +- For rotations or list moves: only propagate view→doc when in the move-processing state; otherwise apply doc→view rotations. + +## Transactions +- Begin a transaction when entering the corresponding "pending" state. Abort immediately if the controller could not start. +- On commit states: if the new value differs, write to the document and `commitTransaction` with a descriptive label; otherwise `abortTransaction`. +- On abort states: always `abortTransaction` and reset local guards (`target`, flags like `moveChanged`). +- Track whether any change occurred during move/drag (`moveChanged`) to avoid committing no-op transactions. + +## Synchronization Patterns +- Maintain bidirectional maps: `doc -> view` and `view -> doc`. Insert/remove bindings on collection signals (`itemInserted`/`itemRemoved`), not "aboutTo" when you need the item fully constructed. +- When binding a new item: + - Create the view-model item, insert into both maps and the view-model collection at the correct index. + - Connect doc→view signals to update view items, guarded by equality checks. + - Connect view→doc signals but gate them with state checks (only honor during the relevant progressing/doing states; otherwise revert the view to the doc value). + - Initialize view properties from the doc model after wiring connections. +- Selection sync: listen to document selection model `itemSelected` and mark the view item selected; initialize selection for pre-selected items after binding. +- Rotation sync: doc→view rotations apply when *not* moving; view→doc rotations apply only while the move state is active, and should mark a change flag. + +## Example Snippets +- **Doc→View guarded update** (avoid loops): + ```cpp + connect(control, &ControlType::propertyChanged, viewItem, [=](auto value) { + if (viewItem->property() == value) return; + viewItem->setProperty(value); + }); + ``` +- **View→Doc gated by state**: + ```cpp + connect(viewItem, &ViewType::propertyChanged, docItem, [=] { + if (!stateMachine->configuration().contains(propertyProgressingState)) { + viewItem->setProperty(docItem->property()); + return; + } + // defer actual write to commit handler + }); + ``` +- **Transaction commit handler**: + ```cpp + void ContextData::onNameCommittingStateEntered() { + if (!target || nameTxId == Invalid) { target = {}; return; } + auto viewItem = viewMap.value(target); + if (viewItem->name() == target->name()) { + tx->abortTransaction(nameTxId); + } else { + target->setName(viewItem->name()); + tx->commitTransaction(nameTxId, tr("Renaming item")); + } + nameTxId = {}; target = {}; + } + ``` +- **Rotate handling**: + ```cpp + connect(docList, &List::rotated, this, [=](int l, int m, int r) { + if (stateMachine->configuration().contains(moveProcessingState)) return; + viewList->rotate(l, m, r); + }); + connect(viewList, &ViewList::rotated, this, [=](int l, int m, int r) { + if (!stateMachine->configuration().contains(moveProcessingState)) return; + moveChanged = true; + docList->rotate(l, m, r); + }); + ``` + +## Implementation Checklist +- Define states and transitions before binding to controllers; start the state machine immediately. +- Create controllers via context helper methods; hook all relevant signals to emit local transition signals and set the current target. +- Bind document collections first, then replay existing selection to the view. +- For each commit/finish handler: compare values, write document, commit transaction; otherwise abort. Always reset `target` and flags. +- Keep all strings ASCII; add concise comments only where non-obvious. + +Use this prompt verbatim when extending bindings to new document elements to maintain consistent interaction, transaction, and synchronization behavior across the codebase. diff --git a/.github/workflows/dev-build.yml b/.github/workflows/dev-build.yml index 03dca6ce..7dd6dc8c 100644 --- a/.github/workflows/dev-build.yml +++ b/.github/workflows/dev-build.yml @@ -16,6 +16,7 @@ on: - 'LICENSE' - 'crowdin.yml' - '.github/**' + - '**/translations/*.ts' workflow_dispatch: inputs: identifier: @@ -40,16 +41,16 @@ jobs: os: - windows-2025 # - ubuntu-24.04 - # - macos-15 - + - macos-15 env: - QT_VERSION: 6.9.2 + QT_VERSION: 6.10.1 VCPKG_REF: 74e6536215718009aae747d86d84b78376bf9e09 INNOSETUP_REF: is-6_5_4 VERSION_IDENTIFIER: ${{ github.sha }}${{ github.event.inputs.identifier && '.' || '' }}${{ github.event.inputs.identifier }} BUILD_DIR: ${{ github.workspace }}/build/build INSTALL_DIR: ${{ github.workspace }}/build/install CCACHE_DIR: ${{ github.workspace }}/build/ccache + MACOSX_DEPLOYMENT_TARGET: 13.0 runs-on: ${{ matrix.os }} @@ -87,7 +88,10 @@ jobs: -InstallDir $env:INSTALL_DIR ` -VersionIdentifier $env:VERSION_IDENTIFIER ` ${{ github.event.inputs.use_ccache == 'true' && '-CCache' || '' }} - Write-Output ARTIFACT_NAME=$($output.ApplicationName)_$($output.Semver -replace '[\.\-\+]', '_') >> $env:GITHUB_ENV + Write-Output ARTIFACT_NAME=$($output.ApplicationName)_$($output.Semver -replace '[\.\-\+]', '_')_${{ runner.os }}_${{ runner.arch }} >> $env:GITHUB_ENV + Write-Output APPLICATION_SEMVER=$($output.Semver) >> $env:GITHUB_ENV + Write-Output APPLICATION_DISPLAY_NAME=$($output.ApplicationDisplayName) >> $env:GITHUB_ENV + Write-Output APPLICATION_NAME=$($output.ApplicationName) >> $env:GITHUB_ENV Write-Output INSTALLER_FILE_BASE=$($output.InstallerFileBase) >> $env:GITHUB_ENV - name: Save CCache cache @@ -102,11 +106,22 @@ jobs: $output = & ./scripts/ci/Collect-Symbol-Files.ps1 -VcpkgRootDir $env:VCPKG_ROOT_DIR -InstallDir $env:INSTALL_DIR Write-Output SYMBOL_FILES_PATH=$($output.Path) >> $env:GITHUB_ENV - - name: Pack + - name: Create InnoSetup installer (Windows) + if: ${{ runner.os == 'Windows' }} run: | $output = & ./scripts/ci/Pack.ps1 -BuildDir $env:BUILD_DIR -InstallerFileBase $env:INSTALLER_FILE_BASE -InnoSetupCommit $env:INNOSETUP_REF Write-Output PACKAGE_PATH=$($output.Path) >> $env:GITHUB_ENV + - name: Create DMG installer (macOS) + if: ${{ runner.os == 'macOS' }} + run: | + $output = & ./scripts/ci/Create-DMG.ps1 ` + -AppPath $(Join-Path $env:INSTALL_DIR $env:APPLICATION_NAME'.app') ` + -Semver $env:APPLICATION_SEMVER ` + -ApplicationDisplayName $env:APPLICATION_DISPLAY_NAME ` + -InstallerFileBase $env:INSTALLER_FILE_BASE + Write-Output PACKAGE_PATH=$($output) >> $env:GITHUB_ENV + - name: Upload symbol files uses: actions/upload-artifact@v4 with: diff --git a/crowdin.yml b/crowdin.yml index 14e32c6a..35758df7 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -1,13 +1,5 @@ files: - - source: /src/plugins/coreplugin/res/translations/Core_en_US.ts - translation: /src/plugins/coreplugin/res/translations/Core_%locale_with_underscore%.ts - - source: /src/plugins/audio/res/translations/org.diffscope.audio_en_US.ts - translation: /src/plugins/audio/res/translations/org.diffscope.audio_%locale_with_underscore%.ts - - source: /src/plugins/welcomewizard/res/translations/org.diffscope.welcomewizard_en_US.ts - translation: /src/plugins/welcomewizard/res/translations/org.diffscope.welcomewizard_%locale_with_underscore%.ts - - source: /src/plugins/achievement/res/translations/org.diffscope.achievement_en_US.ts - translation: /src/plugins/achievement/res/translations/org.diffscope.achievement_%locale_with_underscore%.ts - - source: /src/plugins/maintenance/res/translations/org.diffscope.maintenance_en_US.ts - translation: /src/plugins/maintenance/res/translations/org.diffscope.maintenance_%locale_with_underscore%.ts - - source: /src/libs/application/uishell/share/translations/uishell_en_US.ts - translation: /src/libs/application/uishell/share/translations/uishell_%locale_with_underscore%.ts + - source: '/**/translations/*_en_US.ts' + translation: '/%original_path%/%file_name%_%locale_with_underscore%.ts' + translation_replace: + '_en_US': '' diff --git a/scripts/ci/Build.ps1 b/scripts/ci/Build.ps1 index d7bee6de..0afa4394 100644 --- a/scripts/ci/Build.ps1 +++ b/scripts/ci/Build.ps1 @@ -78,6 +78,8 @@ Write-Host "Semver: $semver" $installerFileBase = "${applicationName}_$($semver -replace '[\.\-\+]', '_')_installer" +$depsDir = (Get-ChildItem -Path $(Join-Path $VcpkgRootDir installed) | Where-Object {$_.Name -ne "vcpkg"})[0].FullName + cmake -S . -B $(Resolve-Path $BuildDir) -G Ninja ` -DCMAKE_BUILD_TYPE=RelWithDebInfo ` "-DCMAKE_TOOLCHAIN_FILE=$(Join-Path $VcpkgRootDir scripts/buildsystems/vcpkg.cmake)" ` @@ -85,6 +87,8 @@ cmake -S . -B $(Resolve-Path $BuildDir) -G Ninja ` "-DCMAKE_CXX_COMPILER_LAUNCHER=$($CCache ? 'ccache' : '')" ` -DCMAKE_MSVC_DEBUG_INFORMATION_FORMAT=Embedded ` -DCK_ENABLE_CONSOLE:BOOL=FALSE ` + -DQT_NO_PRIVATE_MODULE_WARNING:BOOL=ON ` + "-DQMSETUP_APPLOCAL_DEPS_PATHS_RELWITHDEBINFO=$(Join-Path $depsDir lib)" ` -DAPPLICATION_INSTALL:BOOL=ON ` -DAPPLICATION_CONFIGURE_INSTALLER:BOOL=ON ` -DINNOSETUP_USE_UNOFFICIAL_LANGUAGE:BOOL=ON ` diff --git a/scripts/ci/Collect-Symbol-Files.ps1 b/scripts/ci/Collect-Symbol-Files.ps1 index 5c4e9206..914b3c52 100644 --- a/scripts/ci/Collect-Symbol-Files.ps1 +++ b/scripts/ci/Collect-Symbol-Files.ps1 @@ -47,6 +47,7 @@ if ($IsWindows) { } dsymutil $dllFile.FullName -o "$pdbTargetDirectory/$($dllFile.Name).dSYM" strip -S $dllFile.FullName + codesign --force --sign - $dllFile.FullName } else { Write-Host "Skip: $dllFile" } diff --git a/scripts/ci/Create-DMG.ps1 b/scripts/ci/Create-DMG.ps1 new file mode 100644 index 00000000..c4dd5544 --- /dev/null +++ b/scripts/ci/Create-DMG.ps1 @@ -0,0 +1,122 @@ +param ( + [Parameter(Mandatory)] + [string]$AppPath, + + [Parameter(Mandatory)] + [string]$Semver, + + [Parameter(Mandatory)] + [string]$ApplicationDisplayName, + + [Parameter(Mandatory)] + [string]$InstallerFileBase +) + +$BackgroundSrcDir = "src/app/share/dmg" + +$Bg1x = Join-Path $BackgroundSrcDir "dmg_background.png" +$Bg2x = Join-Path $BackgroundSrcDir "dmg_background@2x.png" + +if (!(Test-Path $Bg1x) -or !(Test-Path $Bg2x)) { + throw "dmg_background.png and dmg_background@2x.png do not exist in $BackgroundSrcDir" +} + +if (!(Test-Path $AppPath)) { + throw "App bundle not exist: $AppPath" +} + +# Temporary directory +$TempDir = Join-Path ([System.IO.Path]::GetTempPath()) ("dmg-build-" + [System.Guid]::NewGuid()) +New-Item -ItemType Directory -Path $TempDir | Out-Null + +$Bg1xOut = Join-Path $TempDir "dmg_background.png" +$Bg2xOut = Join-Path $TempDir "dmg_background@2x.png" +$BgTiff = Join-Path $TempDir "dmg_background.tiff" +$AppBundleName = "$ApplicationDisplayName.app" +$AppBundlePath = Join-Path $TempDir $AppBundleName + +$VersionText = "Version $Semver" + +try { + # ----------------------------- + # Step 1: Preprocess background + # ----------------------------- + + # 1x image + & magick ` + "$Bg1x" ` + -gravity south ` + -pointsize 12 ` + -fill "rgba(37,37,37,0.25)" ` + -annotate +0+8 "$VersionText" ` + "$Bg1xOut" | Write-Host + + if ($LASTEXITCODE -ne 0) { + throw "ImageMagick failed to process dmg_background.png" + } + + # 2x image (scaled) + & magick ` + "$Bg2x" ` + -gravity south ` + -pointsize 24 ` + -fill "rgba(37,37,37,0.25)" ` + -annotate +0+16 "$VersionText" ` + "$Bg2xOut" | Write-Host + + if ($LASTEXITCODE -ne 0) { + throw "ImageMagick failed to process dmg_background@2x.png" + } + + # Combine into TIFF + & tiffutil ` + -cathidpicheck ` + "$Bg1xOut" ` + "$Bg2xOut" ` + -out "$BgTiff" | Write-Host + + if ($LASTEXITCODE -ne 0) { + throw "tiffutil failed to create TIFF" + } + + # ----------------------------- + # Step 2: Build DMG + # ----------------------------- + $DmgName = "$InstallerFileBase.dmg" + $DmgPath = Join-Path (Get-Location) $DmgName + + if (Test-Path $DmgPath) { + Remove-Item $DmgPath -Force + } + + if (Test-Path $AppBundlePath) { + Remove-Item $AppBundlePath -Recurse -Force + } + + Move-Item -Path $AppPath -Destination $AppBundlePath + + & codesign --deep --force --sign - $AppBundlePath | Write-Host + + <# TODO: create-dmg currently places hidden .background file to the right of the visible area, so we have to leave some space for the horizontal scroll bar #> + & create-dmg ` + --volname "$ApplicationDisplayName" ` + --background "$BgTiff" ` + --window-size 600 448 ` + --icon-size 128 ` + --icon "$(Split-Path $AppBundlePath -Leaf)" 132 280 ` + --app-drop-link 468 280 ` + "$DmgPath" ` + "$AppBundlePath" | Write-Host + + if ($LASTEXITCODE -ne 0) { + throw "create-dmg failed" + } + + Write-Output $DmgPath +} +finally { + # Cleanup temp files + if (Test-Path $TempDir) { + Remove-Item $TempDir -Recurse -Force + } +} diff --git a/scripts/vcpkg b/scripts/vcpkg index 3561516f..6fb07583 160000 --- a/scripts/vcpkg +++ b/scripts/vcpkg @@ -1 +1 @@ -Subproject commit 3561516f372ae595301c5bf373ca6930766e540d +Subproject commit 6fb07583f1e76206975508f33a21bfddce799bc0 diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 4264e630..d95d1d65 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -29,6 +29,7 @@ endif() ck_configure_application( ${_ico_args} ${_icns_args} + INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/info.plist.in ) qm_configure_target(${PROJECT_NAME} @@ -70,6 +71,12 @@ ck_add_shared_files( # SRC conf/${CK_PLATFORM_LOWER}/qtmediate.json DEST ${CK_BUILD_QT_CONF_DIR} # qtmediate.json ) +if(EXISTS ${CMAKE_CURRENT_BINARY_DIR}/icons/dspx.icns) + ck_add_shared_files( + SRC ${CMAKE_CURRENT_BINARY_DIR}/icons/dspx.icns DEST ${CK_BUILD_DATA_DIR} + ) +endif() + file(GLOB _icons icons/*) list(FILTER _icons EXCLUDE REGEX CMakeLists.txt) diff --git a/src/app/info.plist.in b/src/app/info.plist.in new file mode 100644 index 00000000..c10a6e46 --- /dev/null +++ b/src/app/info.plist.in @@ -0,0 +1,92 @@ + + + + + + CFBundleDevelopmentRegion + English + + CFBundleExecutable + @APPLICATION_NAME@ + + CFBundleIconFile + app.icns + + CFBundleIdentifier + @APPLICATION_NAME@ + + CFBundleInfoDictionaryVersion + 6.0 + + CFBundleName + @APPLICATION_DISPLAY_NAME@ + + CFBundlePackageType + APPL + + CFBundleShortVersionString + @APPLICATION_SEMVER@ + + CFBundleSignature + ???? + + CFBundleVersion + @APPLICATION_SEMVER@ + + CSResourcesFileMapped + + + NSHumanReadableCopyright + @RC_COPYRIGHT@ + + UTExportedTypeDeclarations + + + UTTypeIdentifier + org.diffscope.dspx + + UTTypeDescription + DiffScope Project Exchange Format + + UTTypeConformsTo + + public.json + + + UTTypeTagSpecification + + public.filename-extension + + dspx + + + public.mime-type + application/vnd.openvpi.dspx+json + + + + + CFBundleDocumentTypes + + + CFBundleTypeName + DiffScope Project File + + CFBundleTypeRole + Editor + + LSItemContentTypes + + org.diffscope.dspx + + + CFBundleTypeIconFile + dspx.icns + + LSHandlerRank + Default + + + + + diff --git a/src/app/main.cpp b/src/app/main.cpp index b1b4c6f8..041d94f7 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -39,6 +39,8 @@ static QSettings::Format getJsonSettingsFormat() { static QQmlEngine *engine{}; +static constexpr char kNoWarningLastInitialization[] = "--no-warning-last-initialization"; + class MyLoaderSpec : public Loader::LoaderSpec { public: MyLoaderSpec() { @@ -55,6 +57,11 @@ class MyLoaderSpec : public Loader::LoaderSpec { QStringLiteral("/config.json"); pluginPaths << ApplicationInfo::applicationLocation(ApplicationInfo::BuiltinPlugins); coreName = QStringLiteral("org.diffscope.core"); + extraArguments << Argument{ + {kNoWarningLastInitialization}, + {}, + QStringLiteral("Suppress warning about 'Last initialization was aborted abnormally'") + }; } QSettings *createExtensionSystemSettings(QSettings::Scope scope) override { @@ -102,11 +109,18 @@ class MyLoaderSpec : public Loader::LoaderSpec { locale.setNumberOptions(QLocale::OmitGroupSeparator); RuntimeInterface::setTranslationManager(new TranslationManager(RuntimeInterface::instance())); RuntimeInterface::translationManager()->setLocale(locale); - RuntimeInterface::translationManager()->addTranslationPath(ApplicationInfo::systemLocation(ApplicationInfo::Resources) + QStringLiteral("/ChorusKit/translations")); - RuntimeInterface::translationManager()->addTranslationPath(ApplicationInfo::systemLocation(ApplicationInfo::Resources) + QStringLiteral("/svscraft/translations")); - RuntimeInterface::translationManager()->addTranslationPath(ApplicationInfo::systemLocation(ApplicationInfo::Resources) + QStringLiteral("/uishell/translations")); + auto translationBaseDir = +#ifdef Q_OS_MAC + ApplicationInfo::systemLocation(ApplicationInfo::Resources) + QStringLiteral("/share"); +#else + ApplicationInfo::systemLocation(ApplicationInfo::Resources); +#endif + RuntimeInterface::translationManager()->addTranslationPath(translationBaseDir + QStringLiteral("/ChorusKit/translations")); + RuntimeInterface::translationManager()->addTranslationPath(translationBaseDir + QStringLiteral("/svscraft/translations")); + RuntimeInterface::translationManager()->addTranslationPath(translationBaseDir + QStringLiteral("/uishell/translations")); - if (settings->value("lastInitializationAbortedFlag").toBool()) { + bool lastInitializationWarningSuppressed = QApplication::arguments().contains(kNoWarningLastInitialization); + if (settings->value("lastInitializationAbortedFlag").toBool() && !lastInitializationWarningSuppressed) { qInfo() << "Last initialization was aborted abnormally"; QQmlComponent component(engine, "DiffScope.UIShell", "InitializationFailureWarningDialog"); std::unique_ptr dialog(component.isError() ? nullptr : component.createWithInitialProperties({ diff --git a/src/app/share/dmg/dmg_background.png b/src/app/share/dmg/dmg_background.png new file mode 100644 index 00000000..56a7dd80 Binary files /dev/null and b/src/app/share/dmg/dmg_background.png differ diff --git a/src/app/share/dmg/dmg_background@2x.png b/src/app/share/dmg/dmg_background@2x.png new file mode 100644 index 00000000..0f3a01fc Binary files /dev/null and b/src/app/share/dmg/dmg_background@2x.png differ diff --git a/src/libs/3rdparty/choruskit b/src/libs/3rdparty/choruskit index 2aa4fed3..e40b3316 160000 --- a/src/libs/3rdparty/choruskit +++ b/src/libs/3rdparty/choruskit @@ -1 +1 @@ -Subproject commit 2aa4fed3506d79bfc0f3d1dde6acc5b4e86deb2a +Subproject commit e40b331652f9a81eea9be505305dbb198abd8dae diff --git a/src/libs/3rdparty/opendspx b/src/libs/3rdparty/opendspx index 6fda26bb..45fbaf09 160000 --- a/src/libs/3rdparty/opendspx +++ b/src/libs/3rdparty/opendspx @@ -1 +1 @@ -Subproject commit 6fda26bb78001cf955bfd190a3bddff9c6498bde +Subproject commit 45fbaf0987083c57bf5a06c0adcbd88956d0334a diff --git a/src/libs/3rdparty/qactionkit b/src/libs/3rdparty/qactionkit index 5f075d0a..573def48 160000 --- a/src/libs/3rdparty/qactionkit +++ b/src/libs/3rdparty/qactionkit @@ -1 +1 @@ -Subproject commit 5f075d0ac57c8316ed719f5ba077b4fa36eb626e +Subproject commit 573def486e3f8c0f62768bcdcbc490b24cbd59af diff --git a/src/libs/3rdparty/scopicflow b/src/libs/3rdparty/scopicflow index 9ef06154..d32af0d9 160000 --- a/src/libs/3rdparty/scopicflow +++ b/src/libs/3rdparty/scopicflow @@ -1 +1 @@ -Subproject commit 9ef0615400f9835b11179c5fdc6f593f048c102a +Subproject commit d32af0d99f7e6fd1023412c2686fde559a5da7db diff --git a/src/libs/3rdparty/svscraft b/src/libs/3rdparty/svscraft index c9c7d6c2..1289b602 160000 --- a/src/libs/3rdparty/svscraft +++ b/src/libs/3rdparty/svscraft @@ -1 +1 @@ -Subproject commit c9c7d6c28a2564c4a0ee79f485a6a15b7b81dc69 +Subproject commit 1289b602146a559e1c9d69f3347043e8cc935920 diff --git a/src/libs/application/dspxmodel/src/AnchorNode.cpp b/src/libs/application/dspxmodel/src/AnchorNode.cpp index def64001..47aae0d2 100644 --- a/src/libs/application/dspxmodel/src/AnchorNode.cpp +++ b/src/libs/application/dspxmodel/src/AnchorNode.cpp @@ -1,29 +1,17 @@ #include "AnchorNode.h" +#include "AnchorNode_p.h" #include #include #include +#include #include #include namespace dspx { - class AnchorNodePrivate { - Q_DECLARE_PUBLIC(AnchorNode) - public: - AnchorNode *q_ptr; - AnchorNode::InterpolationMode interp; - int x; - int y; - - void setInterpUnchecked(AnchorNode::InterpolationMode interp_); - void setInterp(AnchorNode::InterpolationMode interp_); - void setXUnchecked(int x_); - void setX(int x_); - }; - void AnchorNodePrivate::setInterpUnchecked(AnchorNode::InterpolationMode interp_) { Q_Q(AnchorNode); q->model()->strategy()->setEntityProperty(q->handle(), ModelStrategy::P_Type, QVariant::fromValue(interp_)); @@ -31,8 +19,9 @@ namespace dspx { void AnchorNodePrivate::setInterp(AnchorNode::InterpolationMode interp_) { Q_Q(AnchorNode); - if (auto engine = qjsEngine(q); engine && (interp_ != AnchorNode::None && interp_ != AnchorNode::Linear && interp_ != AnchorNode::Hermite)) { - engine->throwError(QJSValue::RangeError, QStringLiteral("Interpolation mode must be one of None, Linear, or Hermite")); + if ((interp_ != AnchorNode::None && interp_ != AnchorNode::Linear && interp_ != AnchorNode::Hermite)) { + if (auto engine = qjsEngine(q)) + engine->throwError(QJSValue::RangeError, QStringLiteral("Interpolation mode must be one of None, Linear, or Hermite")); return; } setInterpUnchecked(interp_); @@ -45,13 +34,22 @@ namespace dspx { void AnchorNodePrivate::setX(int x_) { Q_Q(AnchorNode); - if (auto engine = qjsEngine(q); engine && x_ < 0) { - engine->throwError(QJSValue::RangeError, QStringLiteral("Position must be greater or equal to 0")); + if (x_ < 0) { + if (auto engine = qjsEngine(q)) + engine->throwError(QJSValue::RangeError, QStringLiteral("Position must be greater or equal to 0")); return; } setXUnchecked(x_); } + void AnchorNodePrivate::setAnchorNodeSequence(AnchorNode *item, AnchorNodeSequence *anchorNodeSequence) { + auto d = item->d_func(); + if (d->anchorNodeSequence != anchorNodeSequence) { + d->anchorNodeSequence = anchorNodeSequence; + Q_EMIT item->anchorNodeSequenceChanged(); + } + } + AnchorNode::AnchorNode(Handle handle, Model *model) : EntityObject(handle, model), d_ptr(new AnchorNodePrivate) { Q_D(AnchorNode); @@ -112,6 +110,11 @@ namespace dspx { setY(node.y); } + AnchorNodeSequence *AnchorNode::anchorNodeSequence() const { + Q_D(const AnchorNode); + return d->anchorNodeSequence; + } + void AnchorNode::handleSetEntityProperty(int property, const QVariant &value) { Q_D(AnchorNode); switch (property) { diff --git a/src/libs/application/dspxmodel/src/AnchorNode.h b/src/libs/application/dspxmodel/src/AnchorNode.h index 7f7c7826..4eaef0e8 100644 --- a/src/libs/application/dspxmodel/src/AnchorNode.h +++ b/src/libs/application/dspxmodel/src/AnchorNode.h @@ -11,6 +11,10 @@ namespace QDspx { namespace dspx { + class AnchorNodeSequence; + + class AnchorNodeSequencePrivate; + class AnchorNodePrivate; class DSPX_MODEL_EXPORT AnchorNode : public EntityObject { @@ -21,6 +25,7 @@ namespace dspx { Q_PRIVATE_PROPERTY(d_func(), InterpolationMode interp MEMBER interp WRITE setInterp NOTIFY interpChanged) Q_PRIVATE_PROPERTY(d_func(), int x MEMBER x WRITE setX NOTIFY xChanged) Q_PROPERTY(int y READ y WRITE setY NOTIFY yChanged) + Q_PROPERTY(AnchorNodeSequence *anchorNodeSequence READ anchorNodeSequence NOTIFY anchorNodeSequenceChanged) public: enum InterpolationMode { @@ -44,10 +49,13 @@ namespace dspx { QDspx::AnchorNode toQDspx() const; void fromQDspx(const QDspx::AnchorNode &node); + AnchorNodeSequence *anchorNodeSequence() const; + Q_SIGNALS: void interpChanged(InterpolationMode interp); void xChanged(int x); void yChanged(int y); + void anchorNodeSequenceChanged(); protected: void handleSetEntityProperty(int property, const QVariant &value) override; diff --git a/src/libs/application/dspxmodel/src/AnchorNodeSequence.cpp b/src/libs/application/dspxmodel/src/AnchorNodeSequence.cpp index 2544e95e..2d6e9d87 100644 --- a/src/libs/application/dspxmodel/src/AnchorNodeSequence.cpp +++ b/src/libs/application/dspxmodel/src/AnchorNodeSequence.cpp @@ -1,4 +1,5 @@ #include "AnchorNodeSequence.h" +#include "AnchorNodeSequence_p.h" #include #include @@ -7,21 +8,27 @@ #include #include +#include #include -#include #include namespace dspx { - class AnchorNodeSequencePrivate : public PointSequenceData { - Q_DECLARE_PUBLIC(AnchorNodeSequence) - }; - - AnchorNodeSequence::AnchorNodeSequence(Handle handle, Model *model) : EntityObject(handle, model), d_ptr(new AnchorNodeSequencePrivate) { + AnchorNodeSequence::AnchorNodeSequence(ParamCurveAnchor *paramCurveAnchor, Handle handle, Model *model) : EntityObject(handle, model), d_ptr(new AnchorNodeSequencePrivate) { Q_D(AnchorNodeSequence); Q_ASSERT(model->strategy()->getEntityType(handle) == ModelStrategy::ES_ParamCurveAnchorNodes); d->q_ptr = this; d->pModel = ModelPrivate::get(model); + d->paramCurveAnchor = paramCurveAnchor; + + d->init(model->strategy()->getEntitiesFromSequenceContainer(handle)); + + connect(this, &AnchorNodeSequence::itemInserted, this, [=](AnchorNode *item) { + AnchorNodePrivate::setAnchorNodeSequence(item, this); + }); + connect(this, &AnchorNodeSequence::itemRemoved, this, [=](AnchorNode *item) { + AnchorNodePrivate::setAnchorNodeSequence(item, nullptr); + }); } AnchorNodeSequence::~AnchorNodeSequence() = default; @@ -102,6 +109,13 @@ namespace dspx { d->handleTakeFromSequenceContainer(takenEntity, entity); } + ParamCurveAnchor *AnchorNodeSequence::paramCurveAnchor() const { + Q_D(const AnchorNodeSequence); + return d->paramCurveAnchor; + } + + + } #include "moc_AnchorNodeSequence.cpp" diff --git a/src/libs/application/dspxmodel/src/AnchorNodeSequence.h b/src/libs/application/dspxmodel/src/AnchorNodeSequence.h index adcf4727..a965ba5f 100644 --- a/src/libs/application/dspxmodel/src/AnchorNodeSequence.h +++ b/src/libs/application/dspxmodel/src/AnchorNodeSequence.h @@ -4,6 +4,7 @@ #include #include +#include namespace QDspx { struct AnchorNode; @@ -12,6 +13,7 @@ namespace QDspx { namespace dspx { class AnchorNode; + class ParamCurveAnchor; class AnchorNodeSequencePrivate; @@ -23,6 +25,7 @@ namespace dspx { Q_PROPERTY(int size READ size NOTIFY sizeChanged) Q_PROPERTY(AnchorNode *firstItem READ firstItem NOTIFY firstItemChanged) Q_PROPERTY(AnchorNode *lastItem READ lastItem NOTIFY lastItemChanged) + Q_PROPERTY(ParamCurveAnchor *paramCurveAnchor READ paramCurveAnchor CONSTANT) Q_PRIVATE_PROPERTY(d_func(), QJSValue iterable READ iterable CONSTANT) public: ~AnchorNodeSequence() override; @@ -41,6 +44,12 @@ namespace dspx { QList toQDspx() const; void fromQDspx(const QList &nodes); + ParamCurveAnchor *paramCurveAnchor() const; + + auto asRange() const { + return impl::SequenceRange(this); + } + Q_SIGNALS: void itemAboutToInsert(AnchorNode *item); void itemInserted(AnchorNode *item); @@ -56,7 +65,7 @@ namespace dspx { private: friend class ModelPrivate; - explicit AnchorNodeSequence(Handle handle, Model *model); + explicit AnchorNodeSequence(ParamCurveAnchor *paramCurveAnchor, Handle handle, Model *model); QScopedPointer d_ptr; }; diff --git a/src/libs/application/dspxmodel/src/AnchorNodeSequence_p.h b/src/libs/application/dspxmodel/src/AnchorNodeSequence_p.h new file mode 100644 index 00000000..8b27bf67 --- /dev/null +++ b/src/libs/application/dspxmodel/src/AnchorNodeSequence_p.h @@ -0,0 +1,19 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_ANCHORNODESEQUENCE_P_H +#define DIFFSCOPE_DSPX_MODEL_ANCHORNODESEQUENCE_P_H + +#include + +#include +#include + +namespace dspx { + + class AnchorNodeSequencePrivate : public PointSequenceData { + Q_DECLARE_PUBLIC(AnchorNodeSequence) + public: + ParamCurveAnchor *paramCurveAnchor{}; + }; + +} + +#endif //DIFFSCOPE_DSPX_MODEL_ANCHORNODESEQUENCE_P_H \ No newline at end of file diff --git a/src/libs/application/dspxmodel/src/AnchorNode_p.h b/src/libs/application/dspxmodel/src/AnchorNode_p.h new file mode 100644 index 00000000..3b2704b6 --- /dev/null +++ b/src/libs/application/dspxmodel/src/AnchorNode_p.h @@ -0,0 +1,27 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_ANCHORNODE_P_H +#define DIFFSCOPE_DSPX_MODEL_ANCHORNODE_P_H + +#include + +namespace dspx { + + class AnchorNodePrivate { + Q_DECLARE_PUBLIC(AnchorNode) + public: + AnchorNode *q_ptr; + AnchorNode::InterpolationMode interp; + int x; + int y; + AnchorNodeSequence *anchorNodeSequence{}; + + void setInterpUnchecked(AnchorNode::InterpolationMode interp_); + void setInterp(AnchorNode::InterpolationMode interp_); + void setXUnchecked(int x_); + void setX(int x_); + + static void setAnchorNodeSequence(AnchorNode *item, AnchorNodeSequence *anchorNodeSequence); + }; + +} + +#endif //DIFFSCOPE_DSPX_MODEL_ANCHORNODE_P_H \ No newline at end of file diff --git a/src/libs/application/dspxmodel/src/BasicModelStrategy.cpp b/src/libs/application/dspxmodel/src/BasicModelStrategy.cpp index 6773a52d..0e6e8a67 100644 --- a/src/libs/application/dspxmodel/src/BasicModelStrategy.cpp +++ b/src/libs/application/dspxmodel/src/BasicModelStrategy.cpp @@ -2,129 +2,18 @@ #include -#include -#include - #include +#include namespace dspx { - class BasicModelStrategyEntity : public QObject { - Q_OBJECT - public: - using QObject::QObject; - - ModelStrategy::Entity type{}; - }; - - class BasicModelStrategyItemEntity : public BasicModelStrategyEntity { - Q_OBJECT - public: - using BasicModelStrategyEntity::BasicModelStrategyEntity; - - QHash properties; - QHash associatedSubEntities; - }; - - class BasicModelStrategySequenceContainerEntity : public BasicModelStrategyEntity { - Q_OBJECT - public: - using BasicModelStrategyEntity::BasicModelStrategyEntity; - - QSet sequence; - }; - - class BasicModelStrategyListContainerEntity : public BasicModelStrategyEntity { - Q_OBJECT - public: - using BasicModelStrategyEntity::BasicModelStrategyEntity; - - QList list; - }; - - class BasicModelStrategyMapContainerEntity : public BasicModelStrategyEntity { - Q_OBJECT - public: - using BasicModelStrategyEntity::BasicModelStrategyEntity; - - QHash map; - }; - - class BasicModelStrategyDataArrayEntity : public BasicModelStrategyEntity { - Q_OBJECT - public: - using BasicModelStrategyEntity::BasicModelStrategyEntity; - - QVariantList data; - }; - - static BasicModelStrategyEntity *createByType(ModelStrategy::Entity type, QObject *parent) { - BasicModelStrategyEntity *obj; - switch (type) { - case ModelStrategy::EI_AudioClip: - case ModelStrategy::EI_Global: - case ModelStrategy::EI_Label: - case ModelStrategy::EI_Note: - case ModelStrategy::EI_Param: - case ModelStrategy::EI_ParamCurveAnchor: - case ModelStrategy::EI_ParamCurveFree: - case ModelStrategy::EI_ParamCurveAnchorNode: - case ModelStrategy::EI_Phoneme: - case ModelStrategy::EI_SingingClip: - case ModelStrategy::EI_Source: - case ModelStrategy::EI_Tempo: - case ModelStrategy::EI_TimeSignature: - case ModelStrategy::EI_Track: - case ModelStrategy::EI_WorkspaceInfo: - obj = new BasicModelStrategyItemEntity(parent); - obj->type = type; - break; - case ModelStrategy::ES_Clips: - case ModelStrategy::ES_Labels: - case ModelStrategy::ES_Notes: - case ModelStrategy::ES_ParamCurveAnchorNodes: - case ModelStrategy::ES_ParamCurves: - case ModelStrategy::ES_Tempos: - case ModelStrategy::ES_TimeSignatures: - obj = new BasicModelStrategySequenceContainerEntity(parent); - obj->type = type; - break; - case ModelStrategy::EL_Phonemes: - case ModelStrategy::EL_Tracks: - obj = new BasicModelStrategyListContainerEntity(parent); - obj->type = type; - break; - case ModelStrategy::ED_ParamCurveFreeValues: - case ModelStrategy::ED_VibratoPoints: - obj = new BasicModelStrategyDataArrayEntity(parent); - obj->type = type; - break; - case ModelStrategy::EM_Params: - case ModelStrategy::EM_Sources: - case ModelStrategy::EM_Workspace: - obj = new BasicModelStrategyMapContainerEntity(parent); - obj->type = type; - break; - default: - Q_UNREACHABLE(); - } - return obj; - } - - template - T *handleCast(Handle entity) { - auto obj = qobject_cast(reinterpret_cast(entity.d)); - Q_ASSERT(obj); - return obj; - } - BasicModelStrategy::BasicModelStrategy(QObject *parent) : ModelStrategy(parent) { } BasicModelStrategy::~BasicModelStrategy() = default; Handle BasicModelStrategy::createEntity(Entity entityType) { - auto object = createByType(entityType, this); + auto object = BasicModelStrategyEntity::createByType(entityType, this); Handle entity{reinterpret_cast(object)}; Q_EMIT createEntityNotified(entity, entityType); return entity; @@ -140,6 +29,30 @@ namespace dspx { return reinterpret_cast(entity.d)->type; } + QList BasicModelStrategy::getEntitiesFromSequenceContainer(Handle sequenceContainerEntity) { + QList a; + std::ranges::transform(handleCast(sequenceContainerEntity)->sequence, std::back_inserter(a), [](auto *obj) { + return Handle{reinterpret_cast(obj)}; + }); + return a; + } + + QList BasicModelStrategy::getEntitiesFromListContainer(Handle listContainerEntity) { + QList a; + std::ranges::transform(handleCast(listContainerEntity)->list, std::back_inserter(a), [](auto *obj) { + return Handle{reinterpret_cast(obj)}; + }); + return a; + } + + QList> BasicModelStrategy::getEntitiesFromMapContainer(Handle mapContainerEntity) { + QList> a; + for (auto [key, value] : handleCast(mapContainerEntity)->map.asKeyValueRange()) { + a.append({key, Handle{reinterpret_cast(value)}}); + } + return a; + } + bool BasicModelStrategy::insertIntoSequenceContainer(Handle sequenceContainerEntity, Handle entity) { auto sequenceContainerObject = handleCast(sequenceContainerEntity); auto object = reinterpret_cast(entity.d); @@ -231,12 +144,14 @@ namespace dspx { void BasicModelStrategy::setEntityProperty(Handle entity, Property property, const QVariant &value) { auto object = handleCast(entity); + Q_ASSERT(isEntityTypeAndPropertyTypeCompatible(object->type, property)); object->properties.insert(property, value); Q_EMIT setEntityPropertyNotified(entity, property, value); } QVariant BasicModelStrategy::getEntityProperty(Handle entity, Property property) { auto object = handleCast(entity); + Q_ASSERT(isEntityTypeAndPropertyTypeCompatible(object->type, property)); return object->properties.value(property); } bool BasicModelStrategy::spliceDataArray(Handle dataArrayEntity, int index, int length, const QVariantList &values) { @@ -273,64 +188,8 @@ namespace dspx { auto object = handleCast(entity); auto subObject = object->associatedSubEntities.value(relationship); if (!subObject) { - Entity subObjectType; - switch (relationship) { - case R_Children: - switch (object->type) { - case EI_Global: - subObjectType = EL_Tracks; - break; - case EI_Track: - subObjectType = ES_Clips; - break; - case EI_SingingClip: - subObjectType = ES_Notes; - break; - case EI_ParamCurveAnchor: - subObjectType = ES_ParamCurveAnchorNodes; - break; - case EI_ParamCurveFree: - subObjectType = ED_ParamCurveFreeValues; - break; - default: - Q_UNREACHABLE(); - } - break; - case R_Labels: - subObjectType = ES_Labels; - break; - case R_ParamCurvesEdited: - case R_ParamCurvesOriginal: - case R_ParamCurvesTransform: - subObjectType = ES_ParamCurves; - break; - case R_Params: - subObjectType = EM_Params; - break; - case R_PhonemesEdited: - case R_PhonemesOriginal: - subObjectType = EL_Phonemes; - break; - case R_Sources: - subObjectType = EM_Sources; - break; - case R_Tempos: - subObjectType = ES_Tempos; - break; - case R_TimeSignatures: - subObjectType = ES_TimeSignatures; - break; - case R_VibratoPointsAmplitude: - case R_VibratoPointsFrequency: - subObjectType = ED_VibratoPoints; - break; - case R_Workspace: - subObjectType = EM_Workspace; - break; - default: - Q_UNREACHABLE(); - } - subObject = createByType(subObjectType, object); + Entity subObjectType = getAssociatedSubEntityTypeFromEntityTypeAndRelationship(object->type, relationship); + subObject = BasicModelStrategyEntity::createByType(subObjectType, object); object->associatedSubEntities.insert(relationship, subObject); } Handle subEntity{reinterpret_cast(subObject)}; @@ -338,5 +197,3 @@ namespace dspx { } } - -#include "BasicModelStrategy.moc" diff --git a/src/libs/application/dspxmodel/src/BasicModelStrategy.h b/src/libs/application/dspxmodel/src/BasicModelStrategy.h index 222f6b67..9f638397 100644 --- a/src/libs/application/dspxmodel/src/BasicModelStrategy.h +++ b/src/libs/application/dspxmodel/src/BasicModelStrategy.h @@ -14,6 +14,9 @@ namespace dspx { Handle createEntity(Entity entityType) override; void destroyEntity(Handle entity) override; Entity getEntityType(Handle entity) override; + QList getEntitiesFromSequenceContainer(Handle sequenceContainerEntity) override; + QList getEntitiesFromListContainer(Handle listContainerEntity) override; + QList> getEntitiesFromMapContainer(Handle mapContainerEntity) override; bool insertIntoSequenceContainer(Handle sequenceContainerEntity, Handle entity) override; bool insertIntoListContainer(Handle listContainerEntity, Handle entity, int index) override; bool insertIntoMapContainer(Handle mapContainerEntity, Handle entity, const QString &key) override; diff --git a/src/libs/application/dspxmodel/src/BasicModelStrategyEntity_p.h b/src/libs/application/dspxmodel/src/BasicModelStrategyEntity_p.h new file mode 100644 index 00000000..8b111d48 --- /dev/null +++ b/src/libs/application/dspxmodel/src/BasicModelStrategyEntity_p.h @@ -0,0 +1,123 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_BASICMODELSTRATEGYENTITY_P_H +#define DIFFSCOPE_DSPX_MODEL_BASICMODELSTRATEGYENTITY_P_H + +#include +#include +#include + +#include + +namespace dspx { + class BasicModelStrategyEntity : public QObject { + Q_OBJECT + public: + using QObject::QObject; + + ModelStrategy::Entity type{}; + + static inline BasicModelStrategyEntity *createByType(ModelStrategy::Entity type, QObject *parent); + }; + + class BasicModelStrategyItemEntity : public BasicModelStrategyEntity { + Q_OBJECT + public: + using BasicModelStrategyEntity::BasicModelStrategyEntity; + + QHash properties; + QHash associatedSubEntities; + }; + + class BasicModelStrategySequenceContainerEntity : public BasicModelStrategyEntity { + Q_OBJECT + public: + using BasicModelStrategyEntity::BasicModelStrategyEntity; + + QSet sequence; + }; + + class BasicModelStrategyListContainerEntity : public BasicModelStrategyEntity { + Q_OBJECT + public: + using BasicModelStrategyEntity::BasicModelStrategyEntity; + + QList list; + }; + + class BasicModelStrategyMapContainerEntity : public BasicModelStrategyEntity { + Q_OBJECT + public: + using BasicModelStrategyEntity::BasicModelStrategyEntity; + + QHash map; + }; + + class BasicModelStrategyDataArrayEntity : public BasicModelStrategyEntity { + Q_OBJECT + public: + using BasicModelStrategyEntity::BasicModelStrategyEntity; + + QVariantList data; + }; + + BasicModelStrategyEntity *BasicModelStrategyEntity::createByType(ModelStrategy::Entity type, QObject *parent) { + BasicModelStrategyEntity *obj; + switch (type) { + case ModelStrategy::EI_AudioClip: + case ModelStrategy::EI_Global: + case ModelStrategy::EI_Label: + case ModelStrategy::EI_Note: + case ModelStrategy::EI_Param: + case ModelStrategy::EI_ParamCurveAnchor: + case ModelStrategy::EI_ParamCurveFree: + case ModelStrategy::EI_ParamCurveAnchorNode: + case ModelStrategy::EI_Phoneme: + case ModelStrategy::EI_SingingClip: + case ModelStrategy::EI_Source: + case ModelStrategy::EI_Tempo: + case ModelStrategy::EI_TimeSignature: + case ModelStrategy::EI_Track: + case ModelStrategy::EI_WorkspaceInfo: + obj = new BasicModelStrategyItemEntity(parent); + obj->type = type; + break; + case ModelStrategy::ES_Clips: + case ModelStrategy::ES_Labels: + case ModelStrategy::ES_Notes: + case ModelStrategy::ES_ParamCurveAnchorNodes: + case ModelStrategy::ES_ParamCurves: + case ModelStrategy::ES_Tempos: + case ModelStrategy::ES_TimeSignatures: + obj = new BasicModelStrategySequenceContainerEntity(parent); + obj->type = type; + break; + case ModelStrategy::EL_Phonemes: + case ModelStrategy::EL_Tracks: + obj = new BasicModelStrategyListContainerEntity(parent); + obj->type = type; + break; + case ModelStrategy::ED_ParamCurveFreeValues: + case ModelStrategy::ED_VibratoPoints: + obj = new BasicModelStrategyDataArrayEntity(parent); + obj->type = type; + break; + case ModelStrategy::EM_Params: + case ModelStrategy::EM_Sources: + case ModelStrategy::EM_Workspace: + obj = new BasicModelStrategyMapContainerEntity(parent); + obj->type = type; + break; + default: + Q_UNREACHABLE(); + } + return obj; + } + + template + T *handleCast(Handle entity) { + auto obj = qobject_cast(reinterpret_cast(entity.d)); + Q_ASSERT(obj); + return obj; + } +} + +#endif //DIFFSCOPE_DSPX_MODEL_BASICMODELSTRATEGYENTITY_P_H diff --git a/src/libs/application/dspxmodel/src/Clip.cpp b/src/libs/application/dspxmodel/src/Clip.cpp index 5014cf63..4caf4492 100644 --- a/src/libs/application/dspxmodel/src/Clip.cpp +++ b/src/libs/application/dspxmodel/src/Clip.cpp @@ -1,4 +1,5 @@ #include "Clip.h" +#include "Clip_p.h" #include #include @@ -9,25 +10,27 @@ #include #include #include -#include +#include #include #include namespace dspx { - class ClipPrivate { - Q_DECLARE_PUBLIC(Clip) - public: - Clip *q_ptr; - ModelPrivate *pModel; - QString name; - BusControl *control; - ClipTime *time; - Clip::ClipType type; - Track *track{}; - Workspace *workspace; - bool overlapped{}; - }; + void ClipPrivate::setOverlapped(Clip *item, bool overlapped) { + auto d = item->d_func(); + if (d->overlapped != overlapped) { + d->overlapped = overlapped; + Q_EMIT item->overlappedChanged(overlapped); + } + } + + void ClipPrivate::setClipSequence(Clip *item, ClipSequence *clipSequence) { + auto d = item->d_func(); + if (d->clipSequence != clipSequence) { + d->clipSequence = clipSequence; + Q_EMIT item->clipSequenceChanged(); + } + } Clip::Clip(ClipType type, Handle handle, Model *model) : EntityObject(handle, model), d_ptr(new ClipPrivate) { Q_D(Clip); @@ -107,9 +110,9 @@ namespace dspx { } } - Track *Clip::track() const { + ClipSequence *Clip::clipSequence() const { Q_D(const Clip); - return d->track; + return d->clipSequence; } int Clip::position() const { @@ -127,22 +130,6 @@ namespace dspx { return d->overlapped; } - void Clip::setOverlapped(bool overlapped) { - Q_D(Clip); - if (d->overlapped != overlapped) { - d->overlapped = overlapped; - Q_EMIT overlappedChanged(overlapped); - } - } - - void Clip::setTrack(Track *track) { - Q_D(Clip); - if (d->track != track) { - d->track = track; - Q_EMIT trackChanged(); - } - } - void Clip::handleSetEntityProperty(int property, const QVariant &value) { Q_D(Clip); switch (property) { diff --git a/src/libs/application/dspxmodel/src/Clip.h b/src/libs/application/dspxmodel/src/Clip.h index 5dbbcb69..c35024e9 100644 --- a/src/libs/application/dspxmodel/src/Clip.h +++ b/src/libs/application/dspxmodel/src/Clip.h @@ -15,9 +15,7 @@ namespace dspx { class BusControl; class ClipTime; class Workspace; - class Track; - - class ClipSequencePrivate; + class ClipSequence; class ClipPrivate; @@ -31,7 +29,7 @@ namespace dspx { Q_PROPERTY(ClipTime *time READ time CONSTANT) Q_PROPERTY(ClipType type READ type CONSTANT) Q_PROPERTY(Workspace *workspace READ workspace CONSTANT) - Q_PROPERTY(Track *track READ track NOTIFY trackChanged) + Q_PROPERTY(ClipSequence *clipSequence READ clipSequence NOTIFY clipSequenceChanged) Q_PROPERTY(bool overlapped READ isOverlapped NOTIFY overlappedChanged) public: enum ClipType { @@ -56,7 +54,7 @@ namespace dspx { QDspx::ClipRef toQDspx() const; void fromQDspx(const QDspx::ClipRef &clip); - Track *track() const; + ClipSequence *clipSequence() const; int position() const; @@ -66,7 +64,7 @@ namespace dspx { Q_SIGNALS: void nameChanged(const QString &name); - void trackChanged(); + void clipSequenceChanged(); void positionChanged(int position); void lengthChanged(int length); void overlappedChanged(bool overlapped); @@ -76,10 +74,7 @@ namespace dspx { void handleSetEntityProperty(int property, const QVariant &value) override; private: - friend class ClipSequencePrivate; QScopedPointer d_ptr; - void setTrack(Track *track); - void setOverlapped(bool overlapped); }; } // dspx diff --git a/src/libs/application/dspxmodel/src/ClipSequence.cpp b/src/libs/application/dspxmodel/src/ClipSequence.cpp index ddc52650..93f6a778 100644 --- a/src/libs/application/dspxmodel/src/ClipSequence.cpp +++ b/src/libs/application/dspxmodel/src/ClipSequence.cpp @@ -1,4 +1,5 @@ #include "ClipSequence.h" +#include "ClipSequence_p.h" #include #include @@ -11,42 +12,24 @@ #include #include #include -#include -#include #include -#include -#include namespace dspx { - static void setClipOverlapped(Clip *item, bool overlapped); - - class ClipSequencePrivate : public RangeSequenceData { - Q_DECLARE_PUBLIC(ClipSequence) - public: - Track *track{}; - static void setOverlapped(Clip *item, bool overlapped) { - item->setOverlapped(overlapped); - } - static void setTrack(Clip *item, Track *track) { - item->setTrack(track); - } - }; - - void setClipOverlapped(Clip *item, bool overlapped) { - ClipSequencePrivate::setOverlapped(item, overlapped); - } - - ClipSequence::ClipSequence(Handle handle, Model *model) : EntityObject(handle, model), d_ptr(new ClipSequencePrivate) { + ClipSequence::ClipSequence(Track *track, Handle handle, Model *model) : EntityObject(handle, model), d_ptr(new ClipSequencePrivate) { Q_D(ClipSequence); Q_ASSERT(model->strategy()->getEntityType(handle) == ModelStrategy::ES_Clips); d->q_ptr = this; d->pModel = ModelPrivate::get(model); + d->track = track; + + d->init(model->strategy()->getEntitiesFromSequenceContainer(handle)); + connect(this, &ClipSequence::itemInserted, this, [=](Clip *item) { - ClipSequencePrivate::setTrack(item, d->track); + ClipPrivate::setClipSequence(item, this); }); connect(this, &ClipSequence::itemRemoved, this, [=](Clip *item) { - ClipSequencePrivate::setTrack(item, nullptr); + ClipPrivate::setClipSequence(item, nullptr); }); } @@ -133,11 +116,6 @@ namespace dspx { } } - void ClipSequence::setTrack(Track *track) { - Q_D(ClipSequence); - d->track = track; - } - void ClipSequence::handleInsertIntoSequenceContainer(Handle entity) { Q_D(ClipSequence); d->handleInsertIntoSequenceContainer(entity); diff --git a/src/libs/application/dspxmodel/src/ClipSequence.h b/src/libs/application/dspxmodel/src/ClipSequence.h index bbd14f69..26ae777c 100644 --- a/src/libs/application/dspxmodel/src/ClipSequence.h +++ b/src/libs/application/dspxmodel/src/ClipSequence.h @@ -4,6 +4,7 @@ #include #include +#include namespace QDspx { struct Clip; @@ -46,6 +47,10 @@ namespace dspx { Track *track() const; + auto asRange() const { + return impl::SequenceRange(this); + } + Q_SIGNALS: void itemAboutToInsert(Clip *item); void itemInserted(Clip *item); @@ -61,9 +66,7 @@ namespace dspx { private: friend class ModelPrivate; - friend class Track; - explicit ClipSequence(Handle handle, Model *model); - void setTrack(Track *track); + explicit ClipSequence(Track *track, Handle handle, Model *model); QScopedPointer d_ptr; }; diff --git a/src/libs/application/dspxmodel/src/ClipSequence_p.h b/src/libs/application/dspxmodel/src/ClipSequence_p.h new file mode 100644 index 00000000..b6c27acb --- /dev/null +++ b/src/libs/application/dspxmodel/src/ClipSequence_p.h @@ -0,0 +1,19 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_CLIPSEQUENCE_P_H +#define DIFFSCOPE_DSPX_MODEL_CLIPSEQUENCE_P_H + +#include + +#include +#include + +namespace dspx { + + class ClipSequencePrivate : public RangeSequenceData { + Q_DECLARE_PUBLIC(ClipSequence) + public: + Track *track{}; + }; + +} + +#endif //DIFFSCOPE_DSPX_MODEL_CLIPSEQUENCE_P_H diff --git a/src/libs/application/dspxmodel/src/ClipTime.cpp b/src/libs/application/dspxmodel/src/ClipTime.cpp index 6e80b886..478bc8d6 100644 --- a/src/libs/application/dspxmodel/src/ClipTime.cpp +++ b/src/libs/application/dspxmodel/src/ClipTime.cpp @@ -37,8 +37,9 @@ namespace dspx { void ClipTimePrivate::setLength(int length_) { Q_Q(ClipTime); - if (auto engine = qjsEngine(q); engine && length_ < 0) { - engine->throwError(QJSValue::RangeError, QStringLiteral("Length must be greater than or equal to 0")); + if (length_ < 0) { + if (auto engine = qjsEngine(q)) + engine->throwError(QJSValue::RangeError, QStringLiteral("Length must be greater than or equal to 0")); return; } setLengthUnchecked(length_); @@ -51,8 +52,9 @@ namespace dspx { void ClipTimePrivate::setClipStart(int clipStart_) { Q_Q(ClipTime); - if (auto engine = qjsEngine(q); engine && clipStart_ < 0) { - engine->throwError(QJSValue::RangeError, QStringLiteral("ClipStart must be greater than or equal to 0")); + if (clipStart_ < 0) { + if (auto engine = qjsEngine(q)) + engine->throwError(QJSValue::RangeError, QStringLiteral("ClipStart must be greater than or equal to 0")); return; } setClipStartUnchecked(clipStart_); @@ -65,8 +67,9 @@ namespace dspx { void ClipTimePrivate::setClipLen(int clipLen_) { Q_Q(ClipTime); - if (auto engine = qjsEngine(q); engine && clipLen_ < 0) { - engine->throwError(QJSValue::RangeError, QStringLiteral("ClipLen must be greater than or equal to 0")); + if (clipLen_ < 0) { + if (auto engine = qjsEngine(q)) + engine->throwError(QJSValue::RangeError, QStringLiteral("ClipLen must be greater than or equal to 0")); return; } setClipLenUnchecked(clipLen_); diff --git a/src/libs/application/dspxmodel/src/Clip_p.h b/src/libs/application/dspxmodel/src/Clip_p.h new file mode 100644 index 00000000..e3a3c02c --- /dev/null +++ b/src/libs/application/dspxmodel/src/Clip_p.h @@ -0,0 +1,27 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_CLIP_P_H +#define DIFFSCOPE_DSPX_MODEL_CLIP_P_H + +#include + +namespace dspx { + + class ClipPrivate { + Q_DECLARE_PUBLIC(Clip) + public: + Clip *q_ptr; + ModelPrivate *pModel; + QString name; + BusControl *control; + ClipTime *time; + Clip::ClipType type; + ClipSequence *clipSequence{}; + Workspace *workspace; + bool overlapped{}; + + static void setOverlapped(Clip *item, bool overlapped); + static void setClipSequence(Clip *item, ClipSequence *clipSequence); + }; + +} + +#endif //DIFFSCOPE_DSPX_MODEL_CLIP_P_H diff --git a/src/libs/application/dspxmodel/src/Control.cpp b/src/libs/application/dspxmodel/src/Control.cpp index d0c823ca..62d68586 100644 --- a/src/libs/application/dspxmodel/src/Control.cpp +++ b/src/libs/application/dspxmodel/src/Control.cpp @@ -33,8 +33,9 @@ namespace dspx { void ControlPrivate::setGain(double gain_) { Q_Q(Control); - if (auto engine = qjsEngine(q); engine && (gain_ < 0)) { - engine->throwError(QJSValue::RangeError, QStringLiteral("Gain must be greater or equal to 0")); + if ((gain_ < 0)) { + if (auto engine = qjsEngine(q)) + engine->throwError(QJSValue::RangeError, QStringLiteral("Gain must be greater or equal to 0")); return; } setGainUnchecked(gain_); @@ -47,8 +48,9 @@ namespace dspx { void ControlPrivate::setPan(double pan_) { Q_Q(Control); - if (auto engine = qjsEngine(q); engine && (pan_ < -1 || pan_ > 1)) { - engine->throwError(QJSValue::RangeError, QStringLiteral("Pan must be in range [-1.0, 1.0]")); + if ((pan_ < -1 || pan_ > 1)) { + if (auto engine = qjsEngine(q)) + engine->throwError(QJSValue::RangeError, QStringLiteral("Pan must be in range [-1.0, 1.0]")); return; } setPanUnchecked(pan_); diff --git a/src/libs/application/dspxmodel/src/Control.h b/src/libs/application/dspxmodel/src/Control.h index 20135658..777294e7 100644 --- a/src/libs/application/dspxmodel/src/Control.h +++ b/src/libs/application/dspxmodel/src/Control.h @@ -14,7 +14,7 @@ namespace dspx { class ControlPrivate; - class Control : public QObject { + class DSPX_MODEL_EXPORT Control : public QObject { Q_OBJECT QML_ELEMENT QML_UNCREATABLE("") diff --git a/src/libs/application/dspxmodel/src/DataArrayData_p.h b/src/libs/application/dspxmodel/src/DataArrayData_p.h index 92770f3d..0d7c261e 100644 --- a/src/libs/application/dspxmodel/src/DataArrayData_p.h +++ b/src/libs/application/dspxmodel/src/DataArrayData_p.h @@ -24,6 +24,12 @@ namespace dspx { QList data; QJSValue iterable_; + void init(const QVariantList &values) { + for (auto v : values) { + data.append(v.value()); + } + } + int size() const { return data.size(); } diff --git a/src/libs/application/dspxmodel/src/EntityObject.cpp b/src/libs/application/dspxmodel/src/EntityObject.cpp index 4c89bd07..b95fc8a6 100644 --- a/src/libs/application/dspxmodel/src/EntityObject.cpp +++ b/src/libs/application/dspxmodel/src/EntityObject.cpp @@ -22,7 +22,7 @@ namespace dspx { EntityObject::~EntityObject() { Q_D(EntityObject); if (d->model && d->handle) { - d->model->strategy()->destroyEntity(d->handle); + Q_ASSERT(false && "EntityObject::~EntityObject: handle is not null. You should call Model::destroyItem() to delete EntityObject."); } } diff --git a/src/libs/application/dspxmodel/src/FreeValueDataArray.cpp b/src/libs/application/dspxmodel/src/FreeValueDataArray.cpp index 8c65fcb8..6f085afa 100644 --- a/src/libs/application/dspxmodel/src/FreeValueDataArray.cpp +++ b/src/libs/application/dspxmodel/src/FreeValueDataArray.cpp @@ -1,19 +1,25 @@ #include "FreeValueDataArray.h" #include +#include #include namespace dspx { class FreeValueDataArrayPrivate : public DataArrayData { Q_DECLARE_PUBLIC(FreeValueDataArray) + public: + ParamCurveFree *paramCurveFree{}; }; - FreeValueDataArray::FreeValueDataArray(Handle handle, Model *model) : EntityObject(handle, model), d_ptr(new FreeValueDataArrayPrivate) { + FreeValueDataArray::FreeValueDataArray(ParamCurveFree *paramCurveFree, Handle handle, Model *model) : EntityObject(handle, model), d_ptr(new FreeValueDataArrayPrivate) { Q_D(FreeValueDataArray); Q_ASSERT(model->strategy()->getEntityType(handle) == ModelStrategy::ED_ParamCurveFreeValues); d->q_ptr = this; d->pModel = ModelPrivate::get(model); + d->paramCurveFree = paramCurveFree; + + d->init(model->strategy()->sliceDataArray(handle, 0, model->strategy()->getSizeOfDataArray(handle))); } FreeValueDataArray::~FreeValueDataArray() = default; @@ -65,6 +71,13 @@ namespace dspx { d->handleRotateDataArray(leftIndex, middleIndex, rightIndex); } + ParamCurveFree *FreeValueDataArray::paramCurveFree() const { + Q_D(const FreeValueDataArray); + return d->paramCurveFree; + } + + + } #include "moc_FreeValueDataArray.cpp" diff --git a/src/libs/application/dspxmodel/src/FreeValueDataArray.h b/src/libs/application/dspxmodel/src/FreeValueDataArray.h index f3dd0284..2c9a263a 100644 --- a/src/libs/application/dspxmodel/src/FreeValueDataArray.h +++ b/src/libs/application/dspxmodel/src/FreeValueDataArray.h @@ -7,6 +7,8 @@ namespace dspx { + class ParamCurveFree; + class FreeValueDataArrayPrivate; class DSPX_MODEL_EXPORT FreeValueDataArray : public EntityObject { @@ -15,6 +17,7 @@ namespace dspx { QML_UNCREATABLE("") Q_DECLARE_PRIVATE(FreeValueDataArray) Q_PROPERTY(int size READ size NOTIFY sizeChanged) + Q_PROPERTY(ParamCurveFree *paramCurveFree READ paramCurveFree CONSTANT) Q_PRIVATE_PROPERTY(d_func(), QJSValue iterable READ iterable CONSTANT) public: @@ -28,6 +31,8 @@ namespace dspx { QList toQDspx() const; void fromQDspx(const QList &values); + ParamCurveFree *paramCurveFree() const; + Q_SIGNALS: void sizeChanged(int size); void aboutToSplice(int index, int length, const QList &values); @@ -41,7 +46,7 @@ namespace dspx { private: friend class ModelPrivate; - explicit FreeValueDataArray(Handle handle, Model *model); + explicit FreeValueDataArray(ParamCurveFree *paramCurveFree, Handle handle, Model *model); QScopedPointer d_ptr; }; diff --git a/src/libs/application/dspxmodel/src/Global.cpp b/src/libs/application/dspxmodel/src/Global.cpp index e687463f..3747dcaa 100644 --- a/src/libs/application/dspxmodel/src/Global.cpp +++ b/src/libs/application/dspxmodel/src/Global.cpp @@ -34,8 +34,9 @@ namespace dspx { void GlobalPrivate::setCentShift(int centShift) { Q_Q(Global); - if (auto engine = qjsEngine(q); engine && (centShift < -50 || centShift > 50)) { - engine->throwError(QJSValue::RangeError, QStringLiteral("Cent shift must be in range [-50, 50]")); + if ((centShift < -50 || centShift > 50)) { + if (auto engine = qjsEngine(q)) + engine->throwError(QJSValue::RangeError, QStringLiteral("Cent shift must be in range [-50, 50]")); return; } setCentShiftUnchecked(centShift); diff --git a/src/libs/application/dspxmodel/src/Global.h b/src/libs/application/dspxmodel/src/Global.h index 651f2129..8eeaa2ca 100644 --- a/src/libs/application/dspxmodel/src/Global.h +++ b/src/libs/application/dspxmodel/src/Global.h @@ -5,6 +5,8 @@ #include +#include + namespace QDspx { struct Global; } @@ -17,7 +19,7 @@ namespace dspx { class GlobalPrivate; - class Global : public QObject { + class DSPX_MODEL_EXPORT Global : public QObject { Q_OBJECT QML_ELEMENT QML_UNCREATABLE("") diff --git a/src/libs/application/dspxmodel/src/Label.cpp b/src/libs/application/dspxmodel/src/Label.cpp index c4b86312..2b508749 100644 --- a/src/libs/application/dspxmodel/src/Label.cpp +++ b/src/libs/application/dspxmodel/src/Label.cpp @@ -1,26 +1,17 @@ #include "Label.h" +#include "Label_p.h" #include #include #include +#include #include #include namespace dspx { - class LabelPrivate { - Q_DECLARE_PUBLIC(Label) - public: - Label *q_ptr; - int pos; - QString text; - - void setPosUnchecked(int pos_); - void setPos(int pos_); - }; - void LabelPrivate::setPosUnchecked(int pos_) { Q_Q(Label); q->model()->strategy()->setEntityProperty(q->handle(), ModelStrategy::P_Position, pos_); @@ -28,13 +19,22 @@ namespace dspx { void LabelPrivate::setPos(int pos_) { Q_Q(Label); - if (auto engine = qjsEngine(q); engine && pos_ < -0) { - engine->throwError(QJSValue::RangeError, QStringLiteral("Pos must be greater or equal to 0")); + if (pos_ < -0) { + if (auto engine = qjsEngine(q)) + engine->throwError(QJSValue::RangeError, QStringLiteral("Pos must be greater or equal to 0")); return; } setPosUnchecked(pos_); } + void LabelPrivate::setLabelSequence(Label *item, LabelSequence *labelSequence) { + auto d = item->d_func(); + if (d->labelSequence != labelSequence) { + d->labelSequence = labelSequence; + Q_EMIT item->labelSequenceChanged(); + } + } + Label::Label(Handle handle, Model *model) : EntityObject(handle, model), d_ptr(new LabelPrivate) { Q_D(Label); Q_ASSERT(model->strategy()->getEntityType(handle) == ModelStrategy::EI_Label); @@ -66,6 +66,11 @@ namespace dspx { model()->strategy()->setEntityProperty(handle(), ModelStrategy::P_Text, text); } + LabelSequence *Label::labelSequence() const { + Q_D(const Label); + return d->labelSequence; + } + QDspx::Label Label::toQDspx() const { return { .pos = pos(), diff --git a/src/libs/application/dspxmodel/src/Label.h b/src/libs/application/dspxmodel/src/Label.h index a5b5cf8b..dd21eb3f 100644 --- a/src/libs/application/dspxmodel/src/Label.h +++ b/src/libs/application/dspxmodel/src/Label.h @@ -11,6 +11,7 @@ namespace QDspx { namespace dspx { + class LabelSequence; class LabelPrivate; class DSPX_MODEL_EXPORT Label : public EntityObject { @@ -20,6 +21,7 @@ namespace dspx { Q_DECLARE_PRIVATE(Label); Q_PRIVATE_PROPERTY(d_func(), int pos MEMBER pos WRITE setPos NOTIFY posChanged) Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged) + Q_PROPERTY(LabelSequence *labelSequence READ labelSequence NOTIFY labelSequenceChanged) public: ~Label() override; @@ -29,12 +31,15 @@ namespace dspx { QString text() const; void setText(const QString &text); + LabelSequence *labelSequence() const; + QDspx::Label toQDspx() const; void fromQDspx(const QDspx::Label &label); Q_SIGNALS: void posChanged(int pos); void textChanged(const QString &text); + void labelSequenceChanged(); protected: void handleSetEntityProperty(int property, const QVariant &value) override; diff --git a/src/libs/application/dspxmodel/src/LabelSequence.cpp b/src/libs/application/dspxmodel/src/LabelSequence.cpp index 27e6e19c..2eb86c2b 100644 --- a/src/libs/application/dspxmodel/src/LabelSequence.cpp +++ b/src/libs/application/dspxmodel/src/LabelSequence.cpp @@ -1,4 +1,5 @@ #include "LabelSequence.h" +#include "LabelSequence_p.h" #include #include @@ -8,20 +9,24 @@ #include #include #include -#include #include namespace dspx { - class LabelSequencePrivate : public PointSequenceData { - Q_DECLARE_PUBLIC(LabelSequence) - }; - LabelSequence::LabelSequence(Handle handle, Model *model) : EntityObject(handle, model), d_ptr(new LabelSequencePrivate) { Q_D(LabelSequence); Q_ASSERT(model->strategy()->getEntityType(handle) == ModelStrategy::ES_Labels); d->q_ptr = this; d->pModel = ModelPrivate::get(model); + + d->init(model->strategy()->getEntitiesFromSequenceContainer(handle)); + + connect(this, &LabelSequence::itemInserted, this, [=](Label *item) { + LabelPrivate::setLabelSequence(item, this); + }); + connect(this, &LabelSequence::itemRemoved, this, [=](Label *item) { + LabelPrivate::setLabelSequence(item, nullptr); + }); } LabelSequence::~LabelSequence() = default; diff --git a/src/libs/application/dspxmodel/src/LabelSequence.h b/src/libs/application/dspxmodel/src/LabelSequence.h index bb03d155..cca09003 100644 --- a/src/libs/application/dspxmodel/src/LabelSequence.h +++ b/src/libs/application/dspxmodel/src/LabelSequence.h @@ -4,6 +4,7 @@ #include #include +#include namespace QDspx { struct Label; @@ -41,6 +42,10 @@ namespace dspx { QList toQDspx() const; void fromQDspx(const QList &labels); + auto asRange() const { + return impl::SequenceRange(this); + } + Q_SIGNALS: void itemAboutToInsert(Label *item); void itemInserted(Label *item); diff --git a/src/libs/application/dspxmodel/src/LabelSequence_p.h b/src/libs/application/dspxmodel/src/LabelSequence_p.h new file mode 100644 index 00000000..fe03ee4a --- /dev/null +++ b/src/libs/application/dspxmodel/src/LabelSequence_p.h @@ -0,0 +1,17 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_LABELSEQUENCE_P_H +#define DIFFSCOPE_DSPX_MODEL_LABELSEQUENCE_P_H + +#include + +#include +#include + +namespace dspx { + + class LabelSequencePrivate : public PointSequenceData { + Q_DECLARE_PUBLIC(LabelSequence) + }; + +} + +#endif //DIFFSCOPE_DSPX_MODEL_LABELSEQUENCE_P_H \ No newline at end of file diff --git a/src/libs/application/dspxmodel/src/Label_p.h b/src/libs/application/dspxmodel/src/Label_p.h new file mode 100644 index 00000000..0ef4b5b3 --- /dev/null +++ b/src/libs/application/dspxmodel/src/Label_p.h @@ -0,0 +1,24 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_LABEL_P_H +#define DIFFSCOPE_DSPX_MODEL_LABEL_P_H + +#include + +namespace dspx { + + class LabelPrivate { + Q_DECLARE_PUBLIC(Label) + public: + Label *q_ptr; + int pos; + QString text; + LabelSequence *labelSequence{}; + + void setPosUnchecked(int pos_); + void setPos(int pos_); + + static void setLabelSequence(Label *item, LabelSequence *labelSequence); + }; + +} + +#endif //DIFFSCOPE_DSPX_MODEL_LABEL_P_H \ No newline at end of file diff --git a/src/libs/application/dspxmodel/src/ListData_p.h b/src/libs/application/dspxmodel/src/ListData_p.h index fd16c589..dff09987 100644 --- a/src/libs/application/dspxmodel/src/ListData_p.h +++ b/src/libs/application/dspxmodel/src/ListData_p.h @@ -35,6 +35,12 @@ namespace dspx { Q_UNREACHABLE(); } + void init(const QList &handles) { + for (auto handle : handles) { + itemList.append(getItem(handle, true)); + } + } + void insertItem(int index, ItemType *item) { auto q = q_ptr; Q_EMIT q->itemAboutToInsert(index, item); diff --git a/src/libs/application/dspxmodel/src/MapData_p.h b/src/libs/application/dspxmodel/src/MapData_p.h index 1c20349d..a177940b 100644 --- a/src/libs/application/dspxmodel/src/MapData_p.h +++ b/src/libs/application/dspxmodel/src/MapData_p.h @@ -34,6 +34,12 @@ namespace dspx { Q_UNREACHABLE(); } + void init(const QList> &handles) { + for (auto &[key, handle] : handles) { + itemMap.insert(key, getItem(handle, true)); + } + } + void insertItem(const QString &key, ItemType *item) { auto q = q_ptr; Q_EMIT q->itemAboutToInsert(key, item); diff --git a/src/libs/application/dspxmodel/src/Master.cpp b/src/libs/application/dspxmodel/src/Master.cpp index 95dd4e05..7f971fc6 100644 --- a/src/libs/application/dspxmodel/src/Master.cpp +++ b/src/libs/application/dspxmodel/src/Master.cpp @@ -16,18 +16,36 @@ namespace dspx { public: Master *q_ptr; ModelPrivate *pModel; + Handle handle; BusControl *control; + bool multiChannelOutput{}; }; Master::Master(Model *model) : QObject(model), d_ptr(new MasterPrivate) { Q_D(Master); d->q_ptr = this; d->pModel = ModelPrivate::get(model); + d->handle = model->handle(); d->control = d->pModel->createObject(model->handle()); } void Master::handleProxySetEntityProperty(int property, const QVariant &value) { Q_D(Master); - ModelPrivate::proxySetEntityPropertyNotify(d->control, property, value); + switch (property) { + case ModelStrategy::P_ControlGain: + case ModelStrategy::P_ControlPan: + case ModelStrategy::P_ControlMute: { + ModelPrivate::proxySetEntityPropertyNotify(d->control, property, value); + break; + } + case ModelStrategy::P_MultiChannelOutput: { + d->multiChannelOutput = value.toBool(); + Q_EMIT multiChannelOutputChanged(d->multiChannelOutput); + break; + } + default: + Q_UNREACHABLE(); + } + } Master::~Master() = default; @@ -35,6 +53,14 @@ namespace dspx { Q_D(const Master); return d->control; } + bool Master::multiChannelOutput() const { + Q_D(const Master); + return d->multiChannelOutput; + } + void Master::setMultiChannelOutput(bool multiChannelOutput) { + Q_D(Master); + d->pModel->strategy->setEntityProperty(d->handle, ModelStrategy::P_MultiChannelOutput, multiChannelOutput); + } QDspx::Master Master::toQDspx() const { return { diff --git a/src/libs/application/dspxmodel/src/Master.h b/src/libs/application/dspxmodel/src/Master.h index 507ae783..d0ac456d 100644 --- a/src/libs/application/dspxmodel/src/Master.h +++ b/src/libs/application/dspxmodel/src/Master.h @@ -1,9 +1,10 @@ #ifndef DIFFSCOPE_DSPX_MODEL_MASTER_H #define DIFFSCOPE_DSPX_MODEL_MASTER_H +#include #include -#include +#include namespace QDspx { struct Master; @@ -19,21 +20,27 @@ namespace dspx { class MasterPrivate; - class Master : public QObject { + class DSPX_MODEL_EXPORT Master : public QObject { Q_OBJECT QML_ELEMENT QML_UNCREATABLE("") Q_DECLARE_PRIVATE(Master) Q_PROPERTY(BusControl *control READ control CONSTANT) - + Q_PROPERTY(bool multiChannelOutput READ multiChannelOutput WRITE setMultiChannelOutput NOTIFY multiChannelOutputChanged) public: ~Master() override; BusControl *control() const; + bool multiChannelOutput() const; + void setMultiChannelOutput(bool multiChannelOutput); + QDspx::Master toQDspx() const; void fromQDspx(const QDspx::Master &master); + Q_SIGNALS: + void multiChannelOutputChanged(bool multiChannelOutput); + private: friend class ModelPrivate; explicit Master(Model *model); diff --git a/src/libs/application/dspxmodel/src/Model.cpp b/src/libs/application/dspxmodel/src/Model.cpp index f02d945c..cce0485a 100644 --- a/src/libs/application/dspxmodel/src/Model.cpp +++ b/src/libs/application/dspxmodel/src/Model.cpp @@ -63,12 +63,15 @@ namespace dspx { labels = new LabelSequence(strategy->getAssociatedSubEntity(handle, ModelStrategy::R_Labels), q); tempos = new TempoSequence(strategy->getAssociatedSubEntity(handle, ModelStrategy::R_Tempos), q); timeSignatures = new TimeSignatureSequence(strategy->getAssociatedSubEntity(handle, ModelStrategy::R_TimeSignatures), q); - trackList = new TrackList(strategy->getAssociatedSubEntity(handle, ModelStrategy::R_Children), q); + tracks = new TrackList(strategy->getAssociatedSubEntity(handle, ModelStrategy::R_Children), q); workspace = new Workspace(strategy->getAssociatedSubEntity(handle, ModelStrategy::R_Workspace), q); } void ModelPrivate::handleNotifications() { Q_Q(Model); + QObject::connect(strategy, &ModelStrategy::destroyEntityNotified, q, [=, this](Handle handle) { + handleEntityDestroyed(handle); + }); QObject::connect(strategy, &ModelStrategy::insertIntoSequenceContainerNotified, q, [=, this](Handle sequenceContainerEntity, Handle entity) { if (auto sequenceContainerObject = mapToObject(sequenceContainerEntity)) { sequenceContainerObject->handleInsertIntoSequenceContainer(entity); @@ -196,9 +199,9 @@ namespace dspx { return d->timeline; } - TrackList *Model::trackList() const { + TrackList *Model::tracks() const { Q_D(const Model); - return d->trackList; + return d->tracks; } Workspace *Model::workspace() const { @@ -207,16 +210,27 @@ namespace dspx { } QDspx::Model Model::toQDspx() const { - return { + QDspx::Model model = { .version = QDspx::Model::V1, .content = { .global = global()->toQDspx(), .master = master()->toQDspx(), .timeline = timeline()->toQDspx(), - .tracks = trackList()->toQDspx(), + .tracks = tracks()->toQDspx(), .workspace = workspace()->toQDspx(), } }; + model.content.workspace["diffscope"] = QJsonObject{ + {"loop", QJsonObject{ + {"enabled", timeline()->isLoopEnabled()}, + {"start", timeline()->loopStart()}, + {"length", timeline()->loopLength()}, + }}, + {"master", QJsonObject{ + {"multiChannelOutput", master()->multiChannelOutput()} + }} + }; + return model; } void Model::fromQDspx(const QDspx::Model &model) { @@ -224,8 +238,27 @@ namespace dspx { d->global->fromQDspx(model.content.global); d->master->fromQDspx(model.content.master); d->timeline->fromQDspx(model.content.timeline); - d->trackList->fromQDspx(model.content.tracks); + d->tracks->fromQDspx(model.content.tracks); d->workspace->fromQDspx(model.content.workspace); + { + auto loop = model.content.workspace.value("diffscope").value("loop").toObject(); + auto enabled = loop.value("enabled").toBool(); + auto start = loop.value("start").toInt(); + auto length = loop.value("length").toInt(); + if (start < 0 || length <= 0) { + d->timeline->setLoopEnabled(false); + d->timeline->setLoopStart(0); + d->timeline->setLoopLength(1920); + } else { + d->timeline->setLoopEnabled(enabled); + d->timeline->setLoopStart(start); + d->timeline->setLoopLength(length); + } + } + { + auto master = model.content.workspace.value("diffscope").value("master").toObject(); + d->master->setMultiChannelOutput(master.value("multiChannelOutput").toBool()); + } } Label *Model::createLabel() { @@ -312,6 +345,14 @@ namespace dspx { return d->createObject(handle); } + void Model::destroyItem(EntityObject *object) { + Q_D(Model); + auto handle = object->handle(); + object->d_func()->handle = {}; + d->strategy->destroyEntity(handle); + object->deleteLater(); + } + void Model::handleSetEntityProperty(int property, const QVariant &value) { Q_D(Model); switch (property) { @@ -342,10 +383,17 @@ namespace dspx { } case ModelStrategy::P_ControlGain: case ModelStrategy::P_ControlPan: - case ModelStrategy::P_ControlMute: { + case ModelStrategy::P_ControlMute: + case ModelStrategy::P_MultiChannelOutput: { ModelPrivate::proxySetEntityPropertyNotify(d->master, property, value); break; } + case ModelStrategy::P_LoopEnabled: + case ModelStrategy::P_LoopLength: + case ModelStrategy::P_LoopStart: { + ModelPrivate::proxySetEntityPropertyNotify(d->timeline, property, value); + break; + } default: Q_UNREACHABLE(); } diff --git a/src/libs/application/dspxmodel/src/Model.h b/src/libs/application/dspxmodel/src/Model.h index b94df10f..02d762f2 100644 --- a/src/libs/application/dspxmodel/src/Model.h +++ b/src/libs/application/dspxmodel/src/Model.h @@ -44,7 +44,7 @@ namespace dspx { Q_PROPERTY(Global *global READ global CONSTANT) Q_PROPERTY(Master *master READ master CONSTANT) Q_PROPERTY(Timeline *timeline READ timeline CONSTANT) - Q_PROPERTY(TrackList *trackList READ trackList CONSTANT) + Q_PROPERTY(TrackList *tracks READ tracks CONSTANT) Q_PROPERTY(Workspace *workspace READ workspace CONSTANT) public: @@ -56,7 +56,7 @@ namespace dspx { Global *global() const; Master *master() const; Timeline *timeline() const; - TrackList *trackList() const; + TrackList *tracks() const; Workspace *workspace() const; QDspx::Model toQDspx() const; @@ -77,6 +77,8 @@ namespace dspx { Q_INVOKABLE Param *createParam(); Q_INVOKABLE Source *createSource(); + Q_INVOKABLE void destroyItem(EntityObject *object); + protected: void handleSetEntityProperty(int property, const QVariant &value) override; diff --git a/src/libs/application/dspxmodel/src/ModelStrategy.h b/src/libs/application/dspxmodel/src/ModelStrategy.h index e500a589..d36200c6 100644 --- a/src/libs/application/dspxmodel/src/ModelStrategy.h +++ b/src/libs/application/dspxmodel/src/ModelStrategy.h @@ -1,6 +1,8 @@ #ifndef DIFFSCOPE_DSPX_MODEL_MODELSTRATEGY_H #define DIFFSCOPE_DSPX_MODEL_MODELSTRATEGY_H +#include + #include #include @@ -55,18 +57,25 @@ namespace dspx { P_CentShift, P_ClipStart, P_ClipLength, + P_ColorId, P_ControlGain, P_ControlMute, P_ControlPan, + P_ControlRecord, P_ControlSolo, P_Denominator, P_EditorId, P_EditorName, + P_Height, P_JsonObject, P_KeyNumber, P_Language, P_Length, + P_LoopEnabled, + P_LoopLength, + P_LoopStart, P_Measure, + P_MultiChannelOutput, P_Name, P_Numerator, P_Onset, @@ -102,10 +111,29 @@ namespace dspx { R_Workspace, }; + struct PropertySpec { + Property propertyType; + QMetaType::Type metaType; + bool (*validate)(const QVariant &value); + + PropertySpec(Property propertyType, QMetaType::Type metaType, bool (*validate)(const QVariant &value) = [](const QVariant &) { return true; }) + : propertyType(propertyType), metaType(metaType), validate(validate) { + } + }; + + static inline Entity getAssociatedSubEntityTypeFromEntityTypeAndRelationship(Entity entityType, Relationship relationship); + static inline std::vector getEntityPropertySpecsFromEntityType(Entity entityType); + static inline PropertySpec getPropertySpecFromEntityTypeAndPropertyType(Entity entityType, Property propertyType); + static inline bool isEntityTypeAndPropertyTypeCompatible(Entity entityType, Property propertyType); + virtual Handle createEntity(Entity entityType) = 0; virtual void destroyEntity(Handle entity) = 0; virtual Entity getEntityType(Handle entity) = 0; + virtual QList getEntitiesFromSequenceContainer(Handle sequenceContainerEntity) = 0; + virtual QList getEntitiesFromListContainer(Handle listContainerEntity) = 0; + virtual QList> getEntitiesFromMapContainer(Handle mapContainerEntity) = 0; + virtual bool insertIntoSequenceContainer(Handle sequenceContainerEntity, Handle entity) = 0; virtual bool insertIntoListContainer(Handle listContainerEntity, Handle entity, int index) = 0; virtual bool insertIntoMapContainer(Handle mapContainerEntity, Handle entity, const QString &key) = 0; @@ -144,6 +172,222 @@ namespace dspx { void rotateDataArrayNotified(Handle dataContainerEntity, int leftIndex, int middleIndex, int rightIndex); }; + ModelStrategy::Entity ModelStrategy::getAssociatedSubEntityTypeFromEntityTypeAndRelationship(Entity entityType, Relationship relationship) { + if (relationship == R_Children) { + if (entityType == EI_Global) { + return EL_Tracks; + } + if (entityType == EI_Track) { + return ES_Clips; + } + if (entityType == EI_SingingClip) { + return ES_Notes; + } + if (entityType == EI_ParamCurveAnchor) { + return ES_ParamCurveAnchorNodes; + } + if (entityType == EI_ParamCurveFree) { + return ED_ParamCurveFreeValues; + } + } else if (relationship == R_Labels) { + if (entityType == EI_Global) { + return ES_Labels; + } + } else if (relationship == R_ParamCurvesEdited || relationship == R_ParamCurvesOriginal || relationship == R_ParamCurvesTransform) { + if (entityType == EI_Param) { + return ES_ParamCurves; + } + } else if (relationship == R_Params) { + if (entityType == EI_SingingClip) { + return EM_Params; + } + } else if (relationship == R_PhonemesEdited || relationship == R_PhonemesOriginal) { + if (entityType == EI_Note) { + return EL_Phonemes; + } + } else if (relationship == R_Sources) { + if (entityType == EI_SingingClip) { + return EM_Sources; + } + } else if (relationship == R_Tempos) { + if (entityType == EI_Global) { + return ES_Tempos; + } + } else if (relationship == R_TimeSignatures) { + if (entityType == EI_Global) { + return ES_TimeSignatures; + } + } else if (relationship == R_VibratoPointsAmplitude || relationship == R_VibratoPointsFrequency) { + if (entityType == EI_Note) { + return ED_VibratoPoints; + } + } else if (relationship == R_Workspace) { + if (entityType == EI_Global || entityType == EI_Track || entityType == EI_AudioClip || entityType == EI_SingingClip || entityType == EI_Note) { + return EM_Workspace; + } + } + Q_UNREACHABLE(); + } + + std::vector ModelStrategy::getEntityPropertySpecsFromEntityType(Entity entityType) { + static auto validateCentShift = [](const QVariant &value) { + auto v = value.toInt(); + return v >= -50 && v <= 50; + }; + static auto validatePan = [](const QVariant &value) { + auto v = value.toDouble(); + return v >= -1 && v <= 1; + }; + static auto validateIntGreaterOrEqualZero = [](const QVariant &value) { + auto v = value.toInt(); + return v >= 0; + }; + static auto validateIntGreaterZero = [](const QVariant &value) { + auto v = value.toInt(); + return v > 0; + }; + static auto validateDoubleGreaterOrEqualZero = [](const QVariant &value) { + auto v = value.toDouble(); + return v >= 0; + }; + static auto validateDoubleBetweenZeroAndOne = [](const QVariant &value) { + auto v = value.toDouble(); + return v >= 0 && v <= 1; + }; + static auto validateKeyNumber = [](const QVariant &value) { + auto v = value.toInt(); + return v >= 0 && v <= 127; + }; + static auto validateTempo = [](const QVariant &value) { + auto v = value.toDouble(); + return v >= 10 && v <= 1000; + }; + static auto validateTimeSignatureDenominator = [](const QVariant &value) { + auto v = value.toInt(); + return v == 1 || v == 2 || v == 4 || v == 8 || v == 16 || v == 32 || v == 64 || v == 128; + }; + static auto validateTimeSignatureNumerator = [](const QVariant &value) { + auto v = value.toInt(); + return v >= 1; + }; + switch (entityType) { + case EI_AudioClip: return { + {P_Name, QMetaType::QString}, + {P_ControlGain, QMetaType::Double}, + {P_ControlPan, QMetaType::Double, validatePan}, + {P_ControlMute, QMetaType::Bool}, + {P_Position, QMetaType::Int}, + {P_Length, QMetaType::Int, validateIntGreaterOrEqualZero}, + {P_ClipStart, QMetaType::Int, validateIntGreaterOrEqualZero}, + {P_ClipLength, QMetaType::Int, validateIntGreaterOrEqualZero}, + {P_Path, QMetaType::QString} + }; + case EI_Global: return { + {P_Name, QMetaType::QString}, + {P_Author, QMetaType::QString}, + {P_CentShift, QMetaType::Int, validateCentShift}, + {P_EditorId, QMetaType::QString}, + {P_EditorName, QMetaType::QString}, + {P_ControlGain, QMetaType::Double}, + {P_ControlPan, QMetaType::Double, validatePan}, + {P_ControlMute, QMetaType::Bool}, + {P_MultiChannelOutput, QMetaType::Bool}, + {P_LoopEnabled, QMetaType::Bool}, + {P_LoopStart, QMetaType::Int, validateIntGreaterOrEqualZero}, + {P_LoopLength, QMetaType::Int, validateIntGreaterZero}, + }; + case EI_Label: return { + {P_Position, QMetaType::Int, validateIntGreaterOrEqualZero}, + {P_Text, QMetaType::QString} + }; + case EI_Note: return { + {P_CentShift, QMetaType::Int, validateCentShift}, + {P_KeyNumber, QMetaType::Int, validateKeyNumber}, + {P_Language, QMetaType::QString}, + {P_Length, QMetaType::Int, validateIntGreaterOrEqualZero}, + {P_Text, QMetaType::QString}, + {P_Position, QMetaType::Int, validateIntGreaterOrEqualZero}, + {P_PronunciationOriginal, QMetaType::QString}, + {P_PronunciationEdited, QMetaType::QString}, + {P_VibratoAmplitude, QMetaType::Int, validateIntGreaterOrEqualZero}, + {P_VibratoEnd, QMetaType::Double, validateDoubleBetweenZeroAndOne}, + {P_VibratoFrequency, QMetaType::Double, validateDoubleGreaterOrEqualZero}, + {P_VibratoOffset, QMetaType::Int}, + {P_VibratoPhase, QMetaType::Double, validateDoubleBetweenZeroAndOne}, + {P_VibratoStart, QMetaType::Double, validateDoubleBetweenZeroAndOne} + }; + case EI_Param: return {}; + case EI_ParamCurveAnchor: return { + {P_Position, QMetaType::Int} + }; + case EI_ParamCurveFree: return { + {P_Position, QMetaType::Int} + }; + case EI_ParamCurveAnchorNode: return { + {P_Type, QMetaType::Int}, + {P_Position, QMetaType::Int, validateIntGreaterOrEqualZero}, + {P_Value, QMetaType::Int} + }; + case EI_Phoneme: return { + {P_Language, QMetaType::QString}, + {P_Position, QMetaType::Int}, + {P_Text, QMetaType::QString}, + {P_Onset, QMetaType::Bool} + }; + case EI_SingingClip: return { + {P_Name, QMetaType::QString}, + {P_ControlGain, QMetaType::Double}, + {P_ControlPan, QMetaType::Double, validatePan}, + {P_ControlMute, QMetaType::Bool}, + {P_Position, QMetaType::Int}, + {P_Length, QMetaType::Int, validateIntGreaterOrEqualZero}, + {P_ClipStart, QMetaType::Int, validateIntGreaterOrEqualZero}, + {P_ClipLength, QMetaType::Int, validateIntGreaterOrEqualZero} + }; + case EI_Source: return { + {P_JsonObject, QMetaType::QJsonObject} + }; + case EI_Tempo: return { + {P_Position, QMetaType::Int, validateIntGreaterOrEqualZero}, + {P_Value, QMetaType::Double, validateTempo} + }; + case EI_TimeSignature: return { + {P_Measure, QMetaType::Int, validateIntGreaterOrEqualZero}, + {P_Numerator, QMetaType::Int, validateTimeSignatureNumerator}, + {P_Denominator, QMetaType::Int, validateTimeSignatureDenominator} + }; + case EI_Track: return { + {P_Name, QMetaType::QString}, + {P_ColorId, QMetaType::Int}, + {P_ControlGain, QMetaType::Double}, + {P_ControlPan, QMetaType::Double, validatePan}, + {P_ControlMute, QMetaType::Bool}, + {P_ControlRecord, QMetaType::Bool}, + {P_ControlSolo, QMetaType::Bool}, + {P_Height, QMetaType::Double, validateDoubleGreaterOrEqualZero} + }; + case EI_WorkspaceInfo: return { + {P_JsonObject, QMetaType::QJsonObject} + }; + default: Q_UNREACHABLE(); + } + } + + inline ModelStrategy::PropertySpec ModelStrategy::getPropertySpecFromEntityTypeAndPropertyType(Entity entityType, Property propertyType) { + auto v = getEntityPropertySpecsFromEntityType(entityType); + auto it = std::ranges::find_if(v, [propertyType](const auto &spec) { return spec.propertyType == propertyType; }); + if (it == v.end()) { + Q_UNREACHABLE(); + } + return *it; + } + + inline bool ModelStrategy::isEntityTypeAndPropertyTypeCompatible(Entity entityType, Property propertyType) { + return std::ranges::any_of(getEntityPropertySpecsFromEntityType(entityType), [propertyType](const auto &spec) { return spec.propertyType == propertyType; }); + } + + + } #endif //DIFFSCOPE_DSPX_MODEL_MODELSTRATEGY_H diff --git a/src/libs/application/dspxmodel/src/Model_p.h b/src/libs/application/dspxmodel/src/Model_p.h index 56b8448e..4f65328a 100644 --- a/src/libs/application/dspxmodel/src/Model_p.h +++ b/src/libs/application/dspxmodel/src/Model_p.h @@ -26,7 +26,7 @@ namespace dspx { LabelSequence *labels; TempoSequence *tempos; TimeSignatureSequence *timeSignatures; - TrackList *trackList; + TrackList *tracks; Workspace *workspace; QHash objectMap; @@ -49,6 +49,12 @@ namespace dspx { EntityObject *mapToObject(Handle handle) const; Handle mapToHandle(EntityObject *object) const; + template + T *createObject(S *superItem, Handle handle) { + Q_Q(Model); + return new T(superItem, handle, q); + } + template T *createObject(Handle handle) { Q_Q(Model); diff --git a/src/libs/application/dspxmodel/src/Note.cpp b/src/libs/application/dspxmodel/src/Note.cpp index 9f127f29..3760f649 100644 --- a/src/libs/application/dspxmodel/src/Note.cpp +++ b/src/libs/application/dspxmodel/src/Note.cpp @@ -1,4 +1,5 @@ #include "Note.h" +#include "Note_p.h" #include #include @@ -8,42 +9,13 @@ #include #include #include -#include +#include #include #include #include namespace dspx { - class NotePrivate { - Q_DECLARE_PUBLIC(Note) - public: - Note *q_ptr; - ModelPrivate *pModel; - - int centShift; - int keyNum; - QString language; - int length; - QString lyric; - PhonemeInfo *phonemes; - int pos; - Pronunciation *pronunciation; - Vibrato *vibrato; - Workspace *workspace; - SingingClip *singingClip{}; - bool overlapped{}; - - void setCentShiftUnchecked(int centShift_); - void setCentShift(int centShift_); - void setKeyNumUnchecked(int keyNum_); - void setKeyNum(int keyNum_); - void setLengthUnchecked(int length_); - void setLength(int length_); - void setPosUnchecked(int pos_); - void setPos(int pos_); - }; - void NotePrivate::setCentShiftUnchecked(int centShift_) { Q_Q(Note); pModel->strategy->setEntityProperty(q->handle(), ModelStrategy::P_CentShift, centShift_); @@ -51,8 +23,9 @@ namespace dspx { void NotePrivate::setCentShift(int centShift_) { Q_Q(Note); - if (auto engine = qjsEngine(q); engine && (centShift_ < -50 || centShift_ > 50)) { - engine->throwError(QJSValue::RangeError, QStringLiteral("CentShift must be in range [-50, 50]")); + if ((centShift_ < -50 || centShift_ > 50)) { + if (auto engine = qjsEngine(q)) + engine->throwError(QJSValue::RangeError, QStringLiteral("CentShift must be in range [-50, 50]")); return; } setCentShiftUnchecked(centShift_); @@ -65,8 +38,9 @@ namespace dspx { void NotePrivate::setKeyNum(int keyNum_) { Q_Q(Note); - if (auto engine = qjsEngine(q); engine && (keyNum_ < 0 || keyNum_ > 127)) { - engine->throwError(QJSValue::RangeError, QStringLiteral("KeyNum must be in range [0, 127]")); + if ((keyNum_ < 0 || keyNum_ > 127)) { + if (auto engine = qjsEngine(q)) + engine->throwError(QJSValue::RangeError, QStringLiteral("KeyNum must be in range [0, 127]")); return; } setKeyNumUnchecked(keyNum_); @@ -79,8 +53,9 @@ namespace dspx { void NotePrivate::setLength(int length_) { Q_Q(Note); - if (auto engine = qjsEngine(q); engine && length_ < 0) { - engine->throwError(QJSValue::RangeError, QStringLiteral("Length must be greater than or equal to 0")); + if (length_ < 0) { + if (auto engine = qjsEngine(q)) + engine->throwError(QJSValue::RangeError, QStringLiteral("Length must be greater than or equal to 0")); return; } setLengthUnchecked(length_); @@ -93,13 +68,30 @@ namespace dspx { void NotePrivate::setPos(int pos_) { Q_Q(Note); - if (auto engine = qjsEngine(q); engine && pos_ < 0) { - engine->throwError(QJSValue::RangeError, QStringLiteral("Pos must be greater than or equal to 0")); + if (pos_ < 0) { + if (auto engine = qjsEngine(q)) + engine->throwError(QJSValue::RangeError, QStringLiteral("Pos must be greater than or equal to 0")); return; } setPosUnchecked(pos_); } + void NotePrivate::setOverlapped(Note *item, bool overlapped) { + auto d = item->d_func(); + if (d->overlapped != overlapped) { + d->overlapped = overlapped; + Q_EMIT item->overlappedChanged(overlapped); + } + } + + void NotePrivate::setNoteSequence(Note *item, NoteSequence *noteSequence) { + auto d = item->d_func(); + if (d->noteSequence != noteSequence) { + d->noteSequence = noteSequence; + Q_EMIT item->noteSequenceChanged(); + } + } + Note::Note(Handle handle, Model *model) : EntityObject(handle, model), d_ptr(new NotePrivate) { Q_D(Note); Q_ASSERT(model->strategy()->getEntityType(handle) == ModelStrategy::EI_Note); @@ -111,7 +103,7 @@ namespace dspx { d->length = d->pModel->strategy->getEntityProperty(handle, ModelStrategy::P_Length).toInt(); d->lyric = d->pModel->strategy->getEntityProperty(handle, ModelStrategy::P_Text).toString(); d->pos = d->pModel->strategy->getEntityProperty(handle, ModelStrategy::P_Position).toInt(); - d->phonemes = d->pModel->createObject(handle); + d->phonemes = d->pModel->createObject(this, handle); d->pronunciation = d->pModel->createObject(handle); d->vibrato = d->pModel->createObject(handle); d->workspace = d->pModel->createObject(d->pModel->strategy->getAssociatedSubEntity(handle, ModelStrategy::R_Workspace)); @@ -203,9 +195,9 @@ namespace dspx { return d->workspace; } - SingingClip *Note::singingClip() const { + NoteSequence *Note::noteSequence() const { Q_D(const Note); - return d->singingClip; + return d->noteSequence; } bool Note::isOverlapped() const { @@ -293,18 +285,6 @@ namespace dspx { } } - void Note::setSingingClip(SingingClip *singingClip) { - Q_D(Note); - d->singingClip = singingClip; - Q_EMIT singingClipChanged(); - } - - void Note::setOverlapped(bool overlapped) { - Q_D(Note); - d->overlapped = overlapped; - Q_EMIT overlappedChanged(overlapped); - } - } #include "moc_Note.cpp" diff --git a/src/libs/application/dspxmodel/src/Note.h b/src/libs/application/dspxmodel/src/Note.h index 71fb896f..e96311d5 100644 --- a/src/libs/application/dspxmodel/src/Note.h +++ b/src/libs/application/dspxmodel/src/Note.h @@ -15,7 +15,7 @@ namespace dspx { class Pronunciation; class Vibrato; class Workspace; - class SingingClip; + class NoteSequence; class NoteSequencePrivate; @@ -36,7 +36,7 @@ namespace dspx { Q_PROPERTY(Pronunciation *pronunciation READ pronunciation CONSTANT) Q_PROPERTY(Vibrato *vibrato READ vibrato CONSTANT) Q_PROPERTY(Workspace *workspace READ workspace CONSTANT) - Q_PROPERTY(SingingClip *singingClip READ singingClip NOTIFY singingClipChanged) + Q_PROPERTY(NoteSequence *noteSequence READ noteSequence NOTIFY noteSequenceChanged) Q_PROPERTY(bool overlapped READ isOverlapped NOTIFY overlappedChanged) public: @@ -68,7 +68,7 @@ namespace dspx { Workspace *workspace() const; - SingingClip *singingClip() const; + NoteSequence *noteSequence() const; bool isOverlapped() const; @@ -82,7 +82,7 @@ namespace dspx { void lengthChanged(int length); void lyricChanged(const QString &lyric); void posChanged(int pos); - void singingClipChanged(); + void noteSequenceChanged(); void overlappedChanged(bool overlapped); protected: @@ -90,11 +90,8 @@ namespace dspx { private: friend class ModelPrivate; - friend class NoteSequencePrivate; explicit Note(Handle handle, Model *model); QScopedPointer d_ptr; - void setSingingClip(SingingClip *singingClip); - void setOverlapped(bool overlapped); }; } diff --git a/src/libs/application/dspxmodel/src/NoteSequence.cpp b/src/libs/application/dspxmodel/src/NoteSequence.cpp index 8aed6974..dd73d9c4 100644 --- a/src/libs/application/dspxmodel/src/NoteSequence.cpp +++ b/src/libs/application/dspxmodel/src/NoteSequence.cpp @@ -1,4 +1,5 @@ #include "NoteSequence.h" +#include "NoteSequence_p.h" #include #include @@ -9,41 +10,24 @@ #include #include #include -#include #include -#include -#include namespace dspx { - static void setNoteOverlapped(Note *item, bool overlapped); - - class NoteSequencePrivate : public RangeSequenceData { - Q_DECLARE_PUBLIC(NoteSequence) - public: - SingingClip *singingClip{}; - static void setOverlapped(Note *item, bool overlapped) { - item->setOverlapped(overlapped); - } - static void setSingingClip(Note *item, SingingClip *singingClip) { - item->setSingingClip(singingClip); - } - }; - - void setNoteOverlapped(Note *item, bool overlapped) { - NoteSequencePrivate::setOverlapped(item, overlapped); - } - - NoteSequence::NoteSequence(Handle handle, Model *model) : EntityObject(handle, model), d_ptr(new NoteSequencePrivate) { + NoteSequence::NoteSequence(SingingClip *singingClip, Handle handle, Model *model) : EntityObject(handle, model), d_ptr(new NoteSequencePrivate) { Q_D(NoteSequence); Q_ASSERT(model->strategy()->getEntityType(handle) == ModelStrategy::ES_Notes); d->q_ptr = this; d->pModel = ModelPrivate::get(model); + d->singingClip = singingClip; + + d->init(model->strategy()->getEntitiesFromSequenceContainer(handle)); + connect(this, &NoteSequence::itemInserted, this, [=](Note *item) { - NoteSequencePrivate::setSingingClip(item, d->singingClip); + NotePrivate::setNoteSequence(item, this); }); connect(this, &NoteSequence::itemRemoved, this, [=](Note *item) { - NoteSequencePrivate::setSingingClip(item, nullptr); + NotePrivate::setNoteSequence(item, nullptr); }); } @@ -119,11 +103,6 @@ namespace dspx { } } - void NoteSequence::setSingingClip(SingingClip *singingClip) { - Q_D(NoteSequence); - d->singingClip = singingClip; - } - void NoteSequence::handleInsertIntoSequenceContainer(Handle entity) { Q_D(NoteSequence); d->handleInsertIntoSequenceContainer(entity); diff --git a/src/libs/application/dspxmodel/src/NoteSequence.h b/src/libs/application/dspxmodel/src/NoteSequence.h index 65cb4948..211c09ee 100644 --- a/src/libs/application/dspxmodel/src/NoteSequence.h +++ b/src/libs/application/dspxmodel/src/NoteSequence.h @@ -4,6 +4,7 @@ #include #include +#include namespace QDspx { struct Note; @@ -46,6 +47,10 @@ namespace dspx { SingingClip *singingClip() const; + auto asRange() const { + return impl::SequenceRange(this); + } + Q_SIGNALS: void itemAboutToInsert(Note *item); void itemInserted(Note *item); @@ -61,9 +66,7 @@ namespace dspx { private: friend class ModelPrivate; - friend class SingingClip; - explicit NoteSequence(Handle handle, Model *model); - void setSingingClip(SingingClip *singingClip); + explicit NoteSequence(SingingClip *singingClip, Handle handle, Model *model); QScopedPointer d_ptr; }; diff --git a/src/libs/application/dspxmodel/src/NoteSequence_p.h b/src/libs/application/dspxmodel/src/NoteSequence_p.h new file mode 100644 index 00000000..950ff19b --- /dev/null +++ b/src/libs/application/dspxmodel/src/NoteSequence_p.h @@ -0,0 +1,19 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_NOTESEQUENCE_P_H +#define DIFFSCOPE_DSPX_MODEL_NOTESEQUENCE_P_H + +#include + +#include +#include + +namespace dspx { + + class NoteSequencePrivate : public RangeSequenceData { + Q_DECLARE_PUBLIC(NoteSequence) + public: + SingingClip *singingClip{}; + }; + +} + +#endif //DIFFSCOPE_DSPX_MODEL_NOTESEQUENCE_P_H \ No newline at end of file diff --git a/src/libs/application/dspxmodel/src/Note_p.h b/src/libs/application/dspxmodel/src/Note_p.h new file mode 100644 index 00000000..c6a70912 --- /dev/null +++ b/src/libs/application/dspxmodel/src/Note_p.h @@ -0,0 +1,42 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_NOTE_P_H +#define DIFFSCOPE_DSPX_MODEL_NOTE_P_H + +#include + +namespace dspx { + + class NotePrivate { + Q_DECLARE_PUBLIC(Note) + public: + Note *q_ptr; + ModelPrivate *pModel; + + int centShift; + int keyNum; + QString language; + int length; + QString lyric; + PhonemeInfo *phonemes; + int pos; + Pronunciation *pronunciation; + Vibrato *vibrato; + Workspace *workspace; + NoteSequence *noteSequence{}; + bool overlapped{}; + + void setCentShiftUnchecked(int centShift_); + void setCentShift(int centShift_); + void setKeyNumUnchecked(int keyNum_); + void setKeyNum(int keyNum_); + void setLengthUnchecked(int length_); + void setLength(int length_); + void setPosUnchecked(int pos_); + void setPos(int pos_); + + static void setOverlapped(Note *item, bool overlapped); + static void setNoteSequence(Note *item, NoteSequence *noteSequence); + }; + +} + +#endif //DIFFSCOPE_DSPX_MODEL_NOTE_P_H \ No newline at end of file diff --git a/src/libs/application/dspxmodel/src/Param.cpp b/src/libs/application/dspxmodel/src/Param.cpp index 7c46e1ac..b81c7f12 100644 --- a/src/libs/application/dspxmodel/src/Param.cpp +++ b/src/libs/application/dspxmodel/src/Param.cpp @@ -1,32 +1,33 @@ #include "Param.h" +#include "Param_p.h" #include #include #include +#include #include namespace dspx { - class ParamPrivate { - Q_DECLARE_PUBLIC(Param) - public: - Param *q_ptr; - ModelPrivate *pModel; - ParamCurveSequence *original; - ParamCurveSequence *transform; - ParamCurveSequence *edited; - }; + void ParamPrivate::setParamMap(Param *item, ParamMap *paramMap) { + auto d = item->d_func(); + if (d->paramMap != paramMap) { + d->paramMap = paramMap; + Q_EMIT item->paramMapChanged(); + } + } Param::Param(Handle handle, Model *model) : EntityObject(handle, model), d_ptr(new ParamPrivate) { Q_D(Param); Q_ASSERT(model->strategy()->getEntityType(handle) == ModelStrategy::EI_Param); d->q_ptr = this; d->pModel = ModelPrivate::get(model); + d->paramMap = nullptr; - d->original = d->pModel->createObject(d->pModel->strategy->getAssociatedSubEntity(handle, ModelStrategy::R_ParamCurvesOriginal)); - d->transform = d->pModel->createObject(d->pModel->strategy->getAssociatedSubEntity(handle, ModelStrategy::R_ParamCurvesTransform)); - d->edited = d->pModel->createObject(d->pModel->strategy->getAssociatedSubEntity(handle, ModelStrategy::R_ParamCurvesEdited)); + d->original = d->pModel->createObject(this, d->pModel->strategy->getAssociatedSubEntity(handle, ModelStrategy::R_ParamCurvesOriginal)); + d->transform = d->pModel->createObject(this, d->pModel->strategy->getAssociatedSubEntity(handle, ModelStrategy::R_ParamCurvesTransform)); + d->edited = d->pModel->createObject(this, d->pModel->strategy->getAssociatedSubEntity(handle, ModelStrategy::R_ParamCurvesEdited)); } Param::~Param() = default; @@ -62,6 +63,11 @@ namespace dspx { d->edited->fromQDspx(param.edited); } + ParamMap *Param::paramMap() const { + Q_D(const Param); + return d->paramMap; + } + } #include "moc_Param.cpp" diff --git a/src/libs/application/dspxmodel/src/Param.h b/src/libs/application/dspxmodel/src/Param.h index 46d26c4a..dbb04407 100644 --- a/src/libs/application/dspxmodel/src/Param.h +++ b/src/libs/application/dspxmodel/src/Param.h @@ -12,6 +12,7 @@ namespace QDspx { namespace dspx { class ParamCurveSequence; + class ParamMap; class ParamPrivate; class DSPX_MODEL_EXPORT Param : public EntityObject { @@ -22,6 +23,7 @@ namespace dspx { Q_PROPERTY(ParamCurveSequence *original READ original CONSTANT) Q_PROPERTY(ParamCurveSequence *transform READ transform CONSTANT) Q_PROPERTY(ParamCurveSequence *edited READ edited CONSTANT) + Q_PROPERTY(ParamMap *paramMap READ paramMap CONSTANT) public: ~Param() override; @@ -30,9 +32,14 @@ namespace dspx { ParamCurveSequence *transform() const; ParamCurveSequence *edited() const; + ParamMap *paramMap() const; + QDspx::Param toQDspx() const; void fromQDspx(const QDspx::Param ¶m); + Q_SIGNALS: + void paramMapChanged(); + private: friend class ModelPrivate; explicit Param(Handle handle, Model *model); diff --git a/src/libs/application/dspxmodel/src/ParamCurve.cpp b/src/libs/application/dspxmodel/src/ParamCurve.cpp index d1336bfc..1da88780 100644 --- a/src/libs/application/dspxmodel/src/ParamCurve.cpp +++ b/src/libs/application/dspxmodel/src/ParamCurve.cpp @@ -1,4 +1,5 @@ #include "ParamCurve.h" +#include "ParamCurve_p.h" #include #include @@ -7,18 +8,19 @@ #include #include #include +#include +#include #include namespace dspx { - class ParamCurvePrivate { - Q_DECLARE_PUBLIC(ParamCurve) - public: - ParamCurve *q_ptr; - ModelPrivate *pModel; - ParamCurve::CurveType type; - int start{}; - }; + void ParamCurvePrivate::setParamCurveSequence(ParamCurve *paramCurve, ParamCurveSequence *paramCurveSequence) { + auto d = paramCurve->d_func(); + if (d->paramCurveSequence == paramCurveSequence) + return; + d->paramCurveSequence = paramCurveSequence; + Q_EMIT paramCurve->paramCurveSequenceChanged(); + } ParamCurve::ParamCurve(CurveType type, Handle handle, Model *model) : EntityObject(handle, model), d_ptr(new ParamCurvePrivate) { @@ -26,6 +28,7 @@ namespace dspx { d->q_ptr = this; d->pModel = ModelPrivate::get(model); d->type = type; + d->paramCurveSequence = nullptr; } ParamCurve::~ParamCurve() = default; @@ -45,6 +48,11 @@ namespace dspx { return d->type; } + ParamCurveSequence *ParamCurve::paramCurveSequence() const { + Q_D(const ParamCurve); + return d->paramCurveSequence; + } + QDspx::ParamCurveRef ParamCurve::toQDspx() const { Q_D(const ParamCurve); switch (d->type) { diff --git a/src/libs/application/dspxmodel/src/ParamCurve.h b/src/libs/application/dspxmodel/src/ParamCurve.h index d7b55439..7cff8619 100644 --- a/src/libs/application/dspxmodel/src/ParamCurve.h +++ b/src/libs/application/dspxmodel/src/ParamCurve.h @@ -12,6 +12,7 @@ namespace QDspx { namespace dspx { + class ParamCurveSequence; class ParamCurvePrivate; class DSPX_MODEL_EXPORT ParamCurve : public EntityObject { @@ -21,6 +22,7 @@ namespace dspx { Q_DECLARE_PRIVATE(ParamCurve) Q_PROPERTY(int start READ start WRITE setStart NOTIFY startChanged) Q_PROPERTY(CurveType type READ type CONSTANT) + Q_PROPERTY(ParamCurveSequence *paramCurveSequence READ paramCurveSequence NOTIFY paramCurveSequenceChanged) public: enum CurveType { @@ -36,11 +38,14 @@ namespace dspx { CurveType type() const; + ParamCurveSequence *paramCurveSequence() const; + QDspx::ParamCurveRef toQDspx() const; void fromQDspx(const QDspx::ParamCurveRef &curve); Q_SIGNALS: void startChanged(int start); + void paramCurveSequenceChanged(); protected: explicit ParamCurve(CurveType type, Handle handle, Model *model); diff --git a/src/libs/application/dspxmodel/src/ParamCurveAnchor.cpp b/src/libs/application/dspxmodel/src/ParamCurveAnchor.cpp index 348ffaa9..7e2bd226 100644 --- a/src/libs/application/dspxmodel/src/ParamCurveAnchor.cpp +++ b/src/libs/application/dspxmodel/src/ParamCurveAnchor.cpp @@ -23,7 +23,7 @@ namespace dspx { d->q_ptr = this; d->pModel = ModelPrivate::get(model); - d->nodes = d->pModel->createObject(d->pModel->strategy->getAssociatedSubEntity(handle, ModelStrategy::R_Children)); + d->nodes = d->pModel->createObject(this, d->pModel->strategy->getAssociatedSubEntity(handle, ModelStrategy::R_Children)); } ParamCurveAnchor::~ParamCurveAnchor() = default; diff --git a/src/libs/application/dspxmodel/src/ParamCurveFree.cpp b/src/libs/application/dspxmodel/src/ParamCurveFree.cpp index 5c82eaa5..ed94fdb1 100644 --- a/src/libs/application/dspxmodel/src/ParamCurveFree.cpp +++ b/src/libs/application/dspxmodel/src/ParamCurveFree.cpp @@ -23,7 +23,7 @@ namespace dspx { d->q_ptr = this; d->pModel = ModelPrivate::get(model); - d->values = d->pModel->createObject(d->pModel->strategy->getAssociatedSubEntity(handle, ModelStrategy::R_Children)); + d->values = d->pModel->createObject(this, d->pModel->strategy->getAssociatedSubEntity(handle, ModelStrategy::R_Children)); } ParamCurveFree::~ParamCurveFree() = default; diff --git a/src/libs/application/dspxmodel/src/ParamCurveSequence.cpp b/src/libs/application/dspxmodel/src/ParamCurveSequence.cpp index 65026885..2ad19e7f 100644 --- a/src/libs/application/dspxmodel/src/ParamCurveSequence.cpp +++ b/src/libs/application/dspxmodel/src/ParamCurveSequence.cpp @@ -1,4 +1,5 @@ #include "ParamCurveSequence.h" +#include "ParamCurveSequence_p.h" #include #include @@ -9,6 +10,7 @@ #include #include +#include #include #include #include @@ -17,15 +19,21 @@ namespace dspx { - class ParamCurveSequencePrivate : public PointSequenceData { - Q_DECLARE_PUBLIC(ParamCurveSequence) - }; - - ParamCurveSequence::ParamCurveSequence(Handle handle, Model *model) : EntityObject(handle, model), d_ptr(new ParamCurveSequencePrivate) { + ParamCurveSequence::ParamCurveSequence(Param *param, Handle handle, Model *model) : EntityObject(handle, model), d_ptr(new ParamCurveSequencePrivate) { Q_D(ParamCurveSequence); Q_ASSERT(model->strategy()->getEntityType(handle) == ModelStrategy::ES_ParamCurves); d->q_ptr = this; d->pModel = ModelPrivate::get(model); + d->param = param; + + d->init(model->strategy()->getEntitiesFromSequenceContainer(handle)); + + connect(this, &ParamCurveSequence::itemInserted, this, [this](ParamCurve *paramCurve) { + ParamCurvePrivate::setParamCurveSequence(paramCurve, this); + }); + connect(this, &ParamCurveSequence::itemRemoved, this, [this](ParamCurve *paramCurve) { + ParamCurvePrivate::setParamCurveSequence(paramCurve, nullptr); + }); } ParamCurveSequence::~ParamCurveSequence() = default; @@ -116,6 +124,11 @@ namespace dspx { d->handleTakeFromSequenceContainer(takenEntity, entity); } + Param *ParamCurveSequence::param() const { + Q_D(const ParamCurveSequence); + return d->param; + } + } #include "moc_ParamCurveSequence.cpp" diff --git a/src/libs/application/dspxmodel/src/ParamCurveSequence.h b/src/libs/application/dspxmodel/src/ParamCurveSequence.h index e2cb1779..b19c69f8 100644 --- a/src/libs/application/dspxmodel/src/ParamCurveSequence.h +++ b/src/libs/application/dspxmodel/src/ParamCurveSequence.h @@ -4,6 +4,7 @@ #include #include +#include namespace QDspx { struct ParamCurve; @@ -13,6 +14,7 @@ namespace QDspx { namespace dspx { class ParamCurve; + class Param; class ParamCurveSequencePrivate; @@ -24,6 +26,7 @@ namespace dspx { Q_PROPERTY(int size READ size NOTIFY sizeChanged) Q_PROPERTY(ParamCurve *firstItem READ firstItem NOTIFY firstItemChanged) Q_PROPERTY(ParamCurve *lastItem READ lastItem NOTIFY lastItemChanged) + Q_PROPERTY(Param *param READ param CONSTANT) Q_PRIVATE_PROPERTY(d_func(), QJSValue iterable READ iterable CONSTANT) public: ~ParamCurveSequence() override; @@ -42,6 +45,12 @@ namespace dspx { QList toQDspx() const; void fromQDspx(const QList &curves); + Param *param() const; + + auto asRange() const { + return impl::SequenceRange(this); + } + Q_SIGNALS: void itemAboutToInsert(ParamCurve *item); void itemInserted(ParamCurve *item); @@ -57,7 +66,7 @@ namespace dspx { private: friend class ModelPrivate; - explicit ParamCurveSequence(Handle handle, Model *model); + explicit ParamCurveSequence(Param *param, Handle handle, Model *model); QScopedPointer d_ptr; }; diff --git a/src/libs/application/dspxmodel/src/ParamCurveSequence_p.h b/src/libs/application/dspxmodel/src/ParamCurveSequence_p.h new file mode 100644 index 00000000..8ffb2c96 --- /dev/null +++ b/src/libs/application/dspxmodel/src/ParamCurveSequence_p.h @@ -0,0 +1,19 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_PARAMCURVESEQUENCE_P_H +#define DIFFSCOPE_DSPX_MODEL_PARAMCURVESEQUENCE_P_H + +#include + +#include +#include + +namespace dspx { + + class ParamCurveSequencePrivate : public PointSequenceData { + Q_DECLARE_PUBLIC(ParamCurveSequence) + public: + Param *param{}; + }; + +} + +#endif //DIFFSCOPE_DSPX_MODEL_PARAMCURVESEQUENCE_P_H \ No newline at end of file diff --git a/src/libs/application/dspxmodel/src/ParamCurve_p.h b/src/libs/application/dspxmodel/src/ParamCurve_p.h new file mode 100644 index 00000000..f8478651 --- /dev/null +++ b/src/libs/application/dspxmodel/src/ParamCurve_p.h @@ -0,0 +1,25 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_PARAMCURVE_P_H +#define DIFFSCOPE_DSPX_MODEL_PARAMCURVE_P_H + +#include + +namespace dspx { + + class ParamCurveSequence; + class ModelPrivate; + + class ParamCurvePrivate { + Q_DECLARE_PUBLIC(ParamCurve) + public: + ParamCurve *q_ptr; + ModelPrivate *pModel; + ParamCurve::CurveType type; + int start{}; + ParamCurveSequence *paramCurveSequence{}; + + static void setParamCurveSequence(ParamCurve *item, ParamCurveSequence *paramCurveSequence); + }; + +} + +#endif //DIFFSCOPE_DSPX_MODEL_PARAMCURVE_P_H \ No newline at end of file diff --git a/src/libs/application/dspxmodel/src/ParamMap.cpp b/src/libs/application/dspxmodel/src/ParamMap.cpp index 89df5c92..e9537f23 100644 --- a/src/libs/application/dspxmodel/src/ParamMap.cpp +++ b/src/libs/application/dspxmodel/src/ParamMap.cpp @@ -4,20 +4,34 @@ #include #include +#include #include #include +#include namespace dspx { class ParamMapPrivate : public MapData { Q_DECLARE_PUBLIC(ParamMap) + public: + SingingClip *singingClip; }; - ParamMap::ParamMap(Handle handle, Model *model) : EntityObject(handle, model), d_ptr(new ParamMapPrivate) { + ParamMap::ParamMap(SingingClip *singingClip, Handle handle, Model *model) : EntityObject(handle, model), d_ptr(new ParamMapPrivate) { Q_D(ParamMap); Q_ASSERT(model->strategy()->getEntityType(handle) == ModelStrategy::EM_Params); d->q_ptr = this; d->pModel = ModelPrivate::get(model); + d->singingClip = singingClip; + + d->init(model->strategy()->getEntitiesFromMapContainer(handle)); + + connect(this, &ParamMap::itemInserted, this, [this](const QString &, Param *item) { + ParamPrivate::setParamMap(item, this); + }); + connect(this, &ParamMap::itemRemoved, this, [this](const QString &, Param *item) { + ParamPrivate::setParamMap(item, nullptr); + }); } ParamMap::~ParamMap() = default; @@ -78,6 +92,11 @@ namespace dspx { } } + SingingClip *ParamMap::singingClip() const { + Q_D(const ParamMap); + return d->singingClip; + } + void ParamMap::handleInsertIntoMapContainer(Handle entity, const QString &key) { Q_D(ParamMap); d->handleInsertIntoMapContainer(entity, key); diff --git a/src/libs/application/dspxmodel/src/ParamMap.h b/src/libs/application/dspxmodel/src/ParamMap.h index e52f2f78..0338d524 100644 --- a/src/libs/application/dspxmodel/src/ParamMap.h +++ b/src/libs/application/dspxmodel/src/ParamMap.h @@ -12,6 +12,7 @@ namespace QDspx { namespace dspx { + class SingingClip; class Param; class ParamMapPrivate; @@ -23,6 +24,7 @@ namespace dspx { Q_PROPERTY(int size READ size NOTIFY sizeChanged) Q_PROPERTY(QStringList keys READ keys NOTIFY keysChanged) Q_PROPERTY(QList items READ items NOTIFY itemsChanged) + Q_PROPERTY(SingingClip *singingClip READ singingClip CONSTANT) Q_PRIVATE_PROPERTY(d_func(), QJSValue iterable READ iterable CONSTANT) public: @@ -39,6 +41,8 @@ namespace dspx { QDspx::Params toQDspx() const; void fromQDspx(const QDspx::Params ¶mMap); + SingingClip *singingClip() const; + Q_SIGNALS: void itemAboutToInsert(const QString &key, Param *item); void itemInserted(const QString &key, Param *item); @@ -54,7 +58,7 @@ namespace dspx { private: friend class ModelPrivate; - explicit ParamMap(Handle handle, Model *model); + explicit ParamMap(SingingClip *singingClip, Handle handle, Model *model); QScopedPointer d_ptr; }; diff --git a/src/libs/application/dspxmodel/src/Param_p.h b/src/libs/application/dspxmodel/src/Param_p.h new file mode 100644 index 00000000..a179fe4f --- /dev/null +++ b/src/libs/application/dspxmodel/src/Param_p.h @@ -0,0 +1,26 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_PARAM_P_H +#define DIFFSCOPE_DSPX_MODEL_PARAM_P_H + +#include + +namespace dspx { + + class ParamMap; + class ModelPrivate; + + class ParamPrivate { + Q_DECLARE_PUBLIC(Param) + public: + Param *q_ptr; + ModelPrivate *pModel; + ParamCurveSequence *original; + ParamCurveSequence *transform; + ParamCurveSequence *edited; + ParamMap *paramMap; + + static void setParamMap(Param *item, ParamMap *paramMap); + }; + +} + +#endif //DIFFSCOPE_DSPX_MODEL_PARAM_P_H \ No newline at end of file diff --git a/src/libs/application/dspxmodel/src/Phoneme.cpp b/src/libs/application/dspxmodel/src/Phoneme.cpp index a025c6a5..32f6e792 100644 --- a/src/libs/application/dspxmodel/src/Phoneme.cpp +++ b/src/libs/application/dspxmodel/src/Phoneme.cpp @@ -1,4 +1,5 @@ #include "Phoneme.h" +#include "Phoneme_p.h" #include #include @@ -7,23 +8,25 @@ #include #include +#include +#include namespace dspx { - class PhonemePrivate { - Q_DECLARE_PUBLIC(Phoneme) - public: - Phoneme *q_ptr; - QString language; - int start; - QString token; - bool onset; - }; + void PhonemePrivate::setPhonemeList(Phoneme *item, PhonemeList *phonemeList) { + auto d = item->d_func(); + if (d->phonemeList != phonemeList) { + d->phonemeList = phonemeList; + Q_EMIT item->phonemeListChanged(); + } + } Phoneme::Phoneme(Handle handle, Model *model) : EntityObject(handle, model), d_ptr(new PhonemePrivate) { Q_D(Phoneme); Q_ASSERT(model->strategy()->getEntityType(handle) == ModelStrategy::EI_Phoneme); d->q_ptr = this; + d->pModel = ModelPrivate::get(model); + d->phonemeList = nullptr; d->language = model->strategy()->getEntityProperty(handle, ModelStrategy::P_Language).toString(); d->start = model->strategy()->getEntityProperty(handle, ModelStrategy::P_Position).toInt(); d->token = model->strategy()->getEntityProperty(handle, ModelStrategy::P_Text).toString(); @@ -116,6 +119,11 @@ namespace dspx { } } + PhonemeList *Phoneme::phonemeList() const { + Q_D(const Phoneme); + return d->phonemeList; + } + } #include "moc_Phoneme.cpp" diff --git a/src/libs/application/dspxmodel/src/Phoneme.h b/src/libs/application/dspxmodel/src/Phoneme.h index 527262f7..1698944c 100644 --- a/src/libs/application/dspxmodel/src/Phoneme.h +++ b/src/libs/application/dspxmodel/src/Phoneme.h @@ -11,6 +11,7 @@ namespace QDspx { namespace dspx { + class PhonemeList; class PhonemePrivate; class DSPX_MODEL_EXPORT Phoneme : public EntityObject { @@ -22,6 +23,7 @@ namespace dspx { Q_PROPERTY(int start READ start WRITE setStart NOTIFY startChanged) Q_PROPERTY(QString token READ token WRITE setToken NOTIFY tokenChanged) Q_PROPERTY(bool onset READ onset WRITE setOnset NOTIFY onsetChanged) + Q_PROPERTY(PhonemeList *phonemeList READ phonemeList NOTIFY phonemeListChanged) public: ~Phoneme() override; @@ -38,6 +40,8 @@ namespace dspx { bool onset() const; void setOnset(bool onset); + PhonemeList *phonemeList() const; + QDspx::Phoneme toQDspx() const; void fromQDspx(const QDspx::Phoneme &phoneme); @@ -46,6 +50,7 @@ namespace dspx { void startChanged(int start); void tokenChanged(const QString &token); void onsetChanged(bool onset); + void phonemeListChanged(); protected: void handleSetEntityProperty(int property, const QVariant &value) override; diff --git a/src/libs/application/dspxmodel/src/PhonemeInfo.cpp b/src/libs/application/dspxmodel/src/PhonemeInfo.cpp index 48bca837..40008544 100644 --- a/src/libs/application/dspxmodel/src/PhonemeInfo.cpp +++ b/src/libs/application/dspxmodel/src/PhonemeInfo.cpp @@ -1,4 +1,5 @@ #include "PhonemeInfo.h" +#include "PhonemeInfo_p.h" #include #include @@ -7,25 +8,26 @@ #include #include +#include #include namespace dspx { - class PhonemeInfoPrivate { - Q_DECLARE_PUBLIC(PhonemeInfo) - public: - PhonemeInfo *q_ptr; - ModelPrivate *pModel; - PhonemeList *edited; - PhonemeList *original; - }; + void PhonemeInfoPrivate::setNote(PhonemeInfo *item, Note *note) { + auto d = item->d_func(); + if (d->note != note) { + d->note = note; + Q_EMIT item->noteChanged(); + } + } - PhonemeInfo::PhonemeInfo(Handle handle, Model *model) : QObject(model), d_ptr(new PhonemeInfoPrivate) { + PhonemeInfo::PhonemeInfo(Note *note, Handle handle, Model *model) : QObject(model), d_ptr(new PhonemeInfoPrivate) { Q_D(PhonemeInfo); d->q_ptr = this; d->pModel = ModelPrivate::get(model); - d->edited = d->pModel->createObject(d->pModel->strategy->getAssociatedSubEntity(handle, ModelStrategy::R_PhonemesEdited)); - d->original = d->pModel->createObject(d->pModel->strategy->getAssociatedSubEntity(handle, ModelStrategy::R_PhonemesOriginal)); + d->note = note; + d->edited = d->pModel->createObject(this, d->pModel->strategy->getAssociatedSubEntity(handle, ModelStrategy::R_PhonemesEdited)); + d->original = d->pModel->createObject(this, d->pModel->strategy->getAssociatedSubEntity(handle, ModelStrategy::R_PhonemesOriginal)); } PhonemeInfo::~PhonemeInfo() = default; @@ -53,6 +55,11 @@ namespace dspx { edited()->fromQDspx(phonemeInfo.edited); } + Note *PhonemeInfo::note() const { + Q_D(const PhonemeInfo); + return d->note; + } + } #include "moc_PhonemeInfo.cpp" diff --git a/src/libs/application/dspxmodel/src/PhonemeInfo.h b/src/libs/application/dspxmodel/src/PhonemeInfo.h index b3305cd7..6ed2d4cd 100644 --- a/src/libs/application/dspxmodel/src/PhonemeInfo.h +++ b/src/libs/application/dspxmodel/src/PhonemeInfo.h @@ -16,6 +16,7 @@ namespace dspx { class Model; class ModelPrivate; class PhonemeList; + class Note; class PhonemeInfoPrivate; @@ -26,6 +27,7 @@ namespace dspx { Q_DECLARE_PRIVATE(PhonemeInfo) Q_PROPERTY(PhonemeList *edited READ edited CONSTANT) Q_PROPERTY(PhonemeList *original READ original CONSTANT) + Q_PROPERTY(Note *note READ note CONSTANT) public: ~PhonemeInfo() override; @@ -33,12 +35,17 @@ namespace dspx { PhonemeList *edited() const; PhonemeList *original() const; + Note *note() const; + QDspx::Phonemes toQDspx() const; void fromQDspx(const QDspx::Phonemes &phonemeInfo); + Q_SIGNALS: + void noteChanged(); + private: friend class ModelPrivate; - explicit PhonemeInfo(Handle handle, Model *model); + explicit PhonemeInfo(Note *note, Handle handle, Model *model); QScopedPointer d_ptr; }; diff --git a/src/libs/application/dspxmodel/src/PhonemeInfo_p.h b/src/libs/application/dspxmodel/src/PhonemeInfo_p.h new file mode 100644 index 00000000..987e3c7d --- /dev/null +++ b/src/libs/application/dspxmodel/src/PhonemeInfo_p.h @@ -0,0 +1,25 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_PHONEMEINFO_P_H +#define DIFFSCOPE_DSPX_MODEL_PHONEMEINFO_P_H + +#include + +namespace dspx { + + class Note; + class ModelPrivate; + + class PhonemeInfoPrivate { + Q_DECLARE_PUBLIC(PhonemeInfo) + public: + PhonemeInfo *q_ptr; + ModelPrivate *pModel; + PhonemeList *edited{}; + PhonemeList *original{}; + Note *note{}; + + static void setNote(PhonemeInfo *item, Note *note); + }; + +} + +#endif //DIFFSCOPE_DSPX_MODEL_PHONEMEINFO_P_H \ No newline at end of file diff --git a/src/libs/application/dspxmodel/src/PhonemeList.cpp b/src/libs/application/dspxmodel/src/PhonemeList.cpp index 159d49b4..6b2c76de 100644 --- a/src/libs/application/dspxmodel/src/PhonemeList.cpp +++ b/src/libs/application/dspxmodel/src/PhonemeList.cpp @@ -4,20 +4,29 @@ #include #include +#include #include #include +#include +#include namespace dspx { - class PhonemeListPrivate : public ListData { - Q_DECLARE_PUBLIC(PhonemeList) - }; - - PhonemeList::PhonemeList(Handle handle, Model *model) : EntityObject(handle, model), d_ptr(new PhonemeListPrivate) { + PhonemeList::PhonemeList(PhonemeInfo *phonemeInfo, Handle handle, Model *model) : EntityObject(handle, model), d_ptr(new PhonemeListPrivate) { Q_D(PhonemeList); Q_ASSERT(model->strategy()->getEntityType(handle) == ModelStrategy::EL_Phonemes); d->q_ptr = this; d->pModel = ModelPrivate::get(model); + d->phonemeInfo = phonemeInfo; + + d->init(model->strategy()->getEntitiesFromListContainer(handle)); + + connect(this, &PhonemeList::itemInserted, this, [this](int, Phoneme *item) { + PhonemePrivate::setPhonemeList(item, this); + }); + connect(this, &PhonemeList::itemRemoved, this, [this](int, Phoneme *item) { + PhonemePrivate::setPhonemeList(item, nullptr); + }); } PhonemeList::~PhonemeList() = default; @@ -88,6 +97,11 @@ namespace dspx { d->handleRotateListContainer(leftIndex, middleIndex, rightIndex); } + PhonemeInfo *PhonemeList::phonemeInfo() const { + Q_D(const PhonemeList); + return d->phonemeInfo; + } + } #include "moc_PhonemeList.cpp" diff --git a/src/libs/application/dspxmodel/src/PhonemeList.h b/src/libs/application/dspxmodel/src/PhonemeList.h index dfa9d019..504d4066 100644 --- a/src/libs/application/dspxmodel/src/PhonemeList.h +++ b/src/libs/application/dspxmodel/src/PhonemeList.h @@ -12,6 +12,7 @@ namespace QDspx { namespace dspx { class Phoneme; + class PhonemeInfo; class PhonemeListPrivate; @@ -22,6 +23,7 @@ namespace dspx { Q_DECLARE_PRIVATE(PhonemeList) Q_PROPERTY(int size READ size NOTIFY sizeChanged) Q_PROPERTY(QList items READ items NOTIFY itemsChanged) + Q_PROPERTY(PhonemeInfo *phonemeInfo READ phonemeInfo CONSTANT) Q_PRIVATE_PROPERTY(d_func(), QJSValue iterable READ iterable CONSTANT) public: @@ -37,6 +39,8 @@ namespace dspx { QList toQDspx() const; void fromQDspx(const QList &phonemeList); + PhonemeInfo *phonemeInfo() const; + Q_SIGNALS: void itemAboutToInsert(int index, Phoneme *item); void itemInserted(int index, Phoneme *item); @@ -54,7 +58,7 @@ namespace dspx { private: friend class ModelPrivate; - explicit PhonemeList(Handle handle, Model *model); + explicit PhonemeList(PhonemeInfo *phonemeInfo, Handle handle, Model *model); QScopedPointer d_ptr; }; diff --git a/src/libs/application/dspxmodel/src/PhonemeList_p.h b/src/libs/application/dspxmodel/src/PhonemeList_p.h new file mode 100644 index 00000000..5cb257c2 --- /dev/null +++ b/src/libs/application/dspxmodel/src/PhonemeList_p.h @@ -0,0 +1,19 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_PHONEMELIST_P_H +#define DIFFSCOPE_DSPX_MODEL_PHONEMELIST_P_H + +#include +#include + +namespace dspx { + + class PhonemeInfo; + + class PhonemeListPrivate : public ListData { + Q_DECLARE_PUBLIC(PhonemeList) + public: + PhonemeInfo *phonemeInfo{}; + }; + +} + +#endif //DIFFSCOPE_DSPX_MODEL_PHONEMELIST_P_H \ No newline at end of file diff --git a/src/libs/application/dspxmodel/src/Phoneme_p.h b/src/libs/application/dspxmodel/src/Phoneme_p.h new file mode 100644 index 00000000..05010ae0 --- /dev/null +++ b/src/libs/application/dspxmodel/src/Phoneme_p.h @@ -0,0 +1,32 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_PHONEME_P_H +#define DIFFSCOPE_DSPX_MODEL_PHONEME_P_H + +#include + +namespace dspx { + + class PhonemeList; + class ModelPrivate; + + class PhonemePrivate { + Q_DECLARE_PUBLIC(Phoneme) + public: + Phoneme *q_ptr; + ModelPrivate *pModel; + QString language; + int start{}; + QString token; + bool onset{}; + PhonemeList *phonemeList{}; + + static void setPhonemeList(Phoneme *item, PhonemeList *phonemeList); + + void setLanguageUnchecked(const QString &language); + void setStartUnchecked(int start); + void setTokenUnchecked(const QString &token); + void setOnsetUnchecked(bool onset); + }; + +} + +#endif //DIFFSCOPE_DSPX_MODEL_PHONEME_P_H \ No newline at end of file diff --git a/src/libs/application/dspxmodel/src/PointSequenceContainer_p.h b/src/libs/application/dspxmodel/src/PointSequenceContainer_p.h index 606addeb..4068dc1b 100644 --- a/src/libs/application/dspxmodel/src/PointSequenceContainer_p.h +++ b/src/libs/application/dspxmodel/src/PointSequenceContainer_p.h @@ -15,10 +15,16 @@ namespace dspx { QHash m_positions; T *firstItem() const { + if (m_items.empty()) { + return nullptr; + } return m_items.cbegin()->second; } T *lastItem() const { + if (m_items.empty()) { + return nullptr; + } return m_items.crbegin()->second; } @@ -70,10 +76,7 @@ namespace dspx { return {}; } QList ret; - auto it = m_items.lower_bound(position + length - 1); - while (it != m_items.end() && it != m_items.begin() && it->first >= position + length - 1) { - --it; - } + auto it = m_items.lower_bound(position + length); std::transform(m_items.lower_bound(position), it, std::back_inserter(ret), [](auto it) { return it.second; }); diff --git a/src/libs/application/dspxmodel/src/PointSequenceData_p.h b/src/libs/application/dspxmodel/src/PointSequenceData_p.h index aa08842e..f05334d8 100644 --- a/src/libs/application/dspxmodel/src/PointSequenceData_p.h +++ b/src/libs/application/dspxmodel/src/PointSequenceData_p.h @@ -35,6 +35,14 @@ namespace dspx { Q_UNREACHABLE(); } + void init(const QList &handles) { + for (auto handle : handles) { + auto item = getItem(handle, true); + container.insertItem(item, (item->*positionGetter)()); + } + updateFirstAndLastItem(); + } + void insertItem(ItemType *item, int position) { auto q = q_ptr; bool containsItem = container.contains(item); diff --git a/src/libs/application/dspxmodel/src/RangeSequenceData_p.h b/src/libs/application/dspxmodel/src/RangeSequenceData_p.h index e1b44015..e5f188a2 100644 --- a/src/libs/application/dspxmodel/src/RangeSequenceData_p.h +++ b/src/libs/application/dspxmodel/src/RangeSequenceData_p.h @@ -34,6 +34,19 @@ namespace dspx { Q_UNREACHABLE(); } + void init(const QList &handles) { + for (auto handle : handles) { + auto item = getItem(handle, true); + pointContainer.insertItem(item, (item->*positionGetter)()); + auto affectedItems = rangeContainer.insertItem(item, (item->*positionGetter)(), (item->*lengthGetter)()); + for (auto affectedItem : affectedItems) { + bool isOverlapped = rangeContainer.isOverlapped(affectedItem); + setOverlapped(affectedItem, isOverlapped); + } + } + updateFirstAndLastItem(); + } + void insertItem(ItemType *item, int position, int length) { auto q = q_ptr; bool containsItem = pointContainer.contains(item); diff --git a/src/libs/application/dspxmodel/src/SingingClip.cpp b/src/libs/application/dspxmodel/src/SingingClip.cpp index 2115440f..17fb30c6 100644 --- a/src/libs/application/dspxmodel/src/SingingClip.cpp +++ b/src/libs/application/dspxmodel/src/SingingClip.cpp @@ -28,9 +28,8 @@ namespace dspx { Q_ASSERT(model->strategy()->getEntityType(handle) == ModelStrategy::EI_SingingClip); d->q_ptr = this; d->pModel = ModelPrivate::get(model); - d->notes = d->pModel->createObject(d->pModel->strategy->getAssociatedSubEntity(handle, ModelStrategy::R_Children)); - d->notes->setSingingClip(this); - d->params = d->pModel->createObject(d->pModel->strategy->getAssociatedSubEntity(handle, ModelStrategy::R_Params)); + d->notes = d->pModel->createObject(this, d->pModel->strategy->getAssociatedSubEntity(handle, ModelStrategy::R_Children)); + d->params = d->pModel->createObject(this, d->pModel->strategy->getAssociatedSubEntity(handle, ModelStrategy::R_Params)); d->sources = d->pModel->createObject(d->pModel->strategy->getAssociatedSubEntity(handle, ModelStrategy::R_Sources)); } diff --git a/src/libs/application/dspxmodel/src/SourceMap.cpp b/src/libs/application/dspxmodel/src/SourceMap.cpp index b523ac84..76477524 100644 --- a/src/libs/application/dspxmodel/src/SourceMap.cpp +++ b/src/libs/application/dspxmodel/src/SourceMap.cpp @@ -18,6 +18,8 @@ namespace dspx { Q_ASSERT(model->strategy()->getEntityType(handle) == ModelStrategy::EM_Sources); d->q_ptr = this; d->pModel = ModelPrivate::get(model); + + d->init(model->strategy()->getEntitiesFromMapContainer(handle)); } SourceMap::~SourceMap() = default; diff --git a/src/libs/application/dspxmodel/src/Tempo.cpp b/src/libs/application/dspxmodel/src/Tempo.cpp index 178cc9ac..da07fa53 100644 --- a/src/libs/application/dspxmodel/src/Tempo.cpp +++ b/src/libs/application/dspxmodel/src/Tempo.cpp @@ -1,4 +1,5 @@ #include "Tempo.h" +#include "Tempo_p.h" #include #include @@ -7,23 +8,10 @@ #include #include +#include namespace dspx { - class TempoPrivate { - Q_DECLARE_PUBLIC(Tempo) - public: - Tempo *q_ptr; - int pos; - double value; - - void setPosUnchecked(int pos_); - void setPos(int pos_); - - void setValueUnchecked(double value_); - void setValue(double value_); - }; - void TempoPrivate::setPosUnchecked(int pos_) { Q_Q(Tempo); q->model()->strategy()->setEntityProperty(q->handle(), ModelStrategy::P_Position, pos_); @@ -31,8 +19,9 @@ namespace dspx { void TempoPrivate::setPos(int pos_) { Q_Q(Tempo); - if (auto engine = qjsEngine(q); engine && pos_ < 0) { - engine->throwError(QJSValue::RangeError, QStringLiteral("Pos must be greater or equal to 0")); + if (pos_ < 0) { + if (auto engine = qjsEngine(q)) + engine->throwError(QJSValue::RangeError, QStringLiteral("Pos must be greater or equal to 0")); return; } setPosUnchecked(pos_); @@ -45,13 +34,22 @@ namespace dspx { void TempoPrivate::setValue(double value_) { Q_Q(Tempo); - if (auto engine = qjsEngine(q); engine && (value_ < 10.0 || value_ > 1000.0)) { - engine->throwError(QJSValue::RangeError, QStringLiteral("Value must be in range [10.0, 1000.0]")); + if ((value_ < 10.0 || value_ > 1000.0)) { + if (auto engine = qjsEngine(q)) + engine->throwError(QJSValue::RangeError, QStringLiteral("Value must be in range [10.0, 1000.0]")); return; } setValueUnchecked(value_); } + void TempoPrivate::setTempoSequence(Tempo *item, TempoSequence *tempoSequence) { + auto d = item->d_func(); + if (d->tempoSequence != tempoSequence) { + d->tempoSequence = tempoSequence; + Q_EMIT item->tempoSequenceChanged(); + } + } + Tempo::Tempo(Handle handle, Model *model) : EntityObject(handle, model), d_ptr(new TempoPrivate) { Q_D(Tempo); Q_ASSERT(model->strategy()->getEntityType(handle) == ModelStrategy::EI_Tempo); @@ -84,6 +82,11 @@ namespace dspx { d->setValueUnchecked(value); } + TempoSequence *Tempo::tempoSequence() const { + Q_D(const Tempo); + return d->tempoSequence; + } + QDspx::Tempo Tempo::toQDspx() const { return { .pos = pos(), diff --git a/src/libs/application/dspxmodel/src/Tempo.h b/src/libs/application/dspxmodel/src/Tempo.h index 4dd8254e..53b71fa7 100644 --- a/src/libs/application/dspxmodel/src/Tempo.h +++ b/src/libs/application/dspxmodel/src/Tempo.h @@ -11,6 +11,7 @@ namespace QDspx { namespace dspx { + class TempoSequence; class TempoPrivate; class DSPX_MODEL_EXPORT Tempo : public EntityObject { @@ -20,6 +21,7 @@ namespace dspx { Q_DECLARE_PRIVATE(Tempo); Q_PRIVATE_PROPERTY(d_func(), int pos MEMBER pos WRITE setPos NOTIFY posChanged) Q_PRIVATE_PROPERTY(d_func(), double value MEMBER value WRITE setValue NOTIFY valueChanged) + Q_PROPERTY(TempoSequence *tempoSequence READ tempoSequence NOTIFY tempoSequenceChanged) public: ~Tempo() override; @@ -29,12 +31,15 @@ namespace dspx { double value() const; void setValue(double value); + TempoSequence *tempoSequence() const; + QDspx::Tempo toQDspx() const; void fromQDspx(const QDspx::Tempo &tempo); Q_SIGNALS: void posChanged(int pos); void valueChanged(double value); + void tempoSequenceChanged(); protected: void handleSetEntityProperty(int property, const QVariant &value) override; diff --git a/src/libs/application/dspxmodel/src/TempoSequence.cpp b/src/libs/application/dspxmodel/src/TempoSequence.cpp index a6851599..3372afa4 100644 --- a/src/libs/application/dspxmodel/src/TempoSequence.cpp +++ b/src/libs/application/dspxmodel/src/TempoSequence.cpp @@ -1,4 +1,5 @@ #include "TempoSequence.h" +#include "TempoSequence_p.h" #include #include @@ -8,20 +9,24 @@ #include #include #include -#include #include namespace dspx { - class TempoSequencePrivate : public PointSequenceData { - Q_DECLARE_PUBLIC(TempoSequence) - }; - TempoSequence::TempoSequence(Handle handle, Model *model) : EntityObject(handle, model), d_ptr(new TempoSequencePrivate) { Q_D(TempoSequence); Q_ASSERT(model->strategy()->getEntityType(handle) == ModelStrategy::ES_Tempos); d->q_ptr = this; d->pModel = ModelPrivate::get(model); + + d->init(model->strategy()->getEntitiesFromSequenceContainer(handle)); + + connect(this, &TempoSequence::itemInserted, this, [=](Tempo *item) { + TempoPrivate::setTempoSequence(item, this); + }); + connect(this, &TempoSequence::itemRemoved, this, [=](Tempo *item) { + TempoPrivate::setTempoSequence(item, nullptr); + }); } TempoSequence::~TempoSequence() = default; diff --git a/src/libs/application/dspxmodel/src/TempoSequence.h b/src/libs/application/dspxmodel/src/TempoSequence.h index ef71cf74..623a9cef 100644 --- a/src/libs/application/dspxmodel/src/TempoSequence.h +++ b/src/libs/application/dspxmodel/src/TempoSequence.h @@ -4,6 +4,7 @@ #include #include +#include namespace QDspx { struct Tempo; @@ -41,6 +42,10 @@ namespace dspx { QList toQDspx() const; void fromQDspx(const QList &tempos); + auto asRange() const { + return impl::SequenceRange(this); + } + Q_SIGNALS: void itemAboutToInsert(Tempo *item); void itemInserted(Tempo *item); diff --git a/src/libs/application/dspxmodel/src/TempoSequence_p.h b/src/libs/application/dspxmodel/src/TempoSequence_p.h new file mode 100644 index 00000000..44435be5 --- /dev/null +++ b/src/libs/application/dspxmodel/src/TempoSequence_p.h @@ -0,0 +1,17 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_TEMPOSEQUENCE_P_H +#define DIFFSCOPE_DSPX_MODEL_TEMPOSEQUENCE_P_H + +#include + +#include +#include + +namespace dspx { + + class TempoSequencePrivate : public PointSequenceData { + Q_DECLARE_PUBLIC(TempoSequence) + }; + +} + +#endif //DIFFSCOPE_DSPX_MODEL_TEMPOSEQUENCE_P_H \ No newline at end of file diff --git a/src/libs/application/dspxmodel/src/Tempo_p.h b/src/libs/application/dspxmodel/src/Tempo_p.h new file mode 100644 index 00000000..8bb93871 --- /dev/null +++ b/src/libs/application/dspxmodel/src/Tempo_p.h @@ -0,0 +1,26 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_TEMPO_P_H +#define DIFFSCOPE_DSPX_MODEL_TEMPO_P_H + +#include + +namespace dspx { + + class TempoPrivate { + Q_DECLARE_PUBLIC(Tempo) + public: + Tempo *q_ptr; + int pos; + double value; + TempoSequence *tempoSequence{}; + + void setPosUnchecked(int pos_); + void setPos(int pos_); + void setValueUnchecked(double value_); + void setValue(double value_); + + static void setTempoSequence(Tempo *item, TempoSequence *tempoSequence); + }; + +} + +#endif //DIFFSCOPE_DSPX_MODEL_TEMPO_P_H \ No newline at end of file diff --git a/src/libs/application/dspxmodel/src/TimeSignature.cpp b/src/libs/application/dspxmodel/src/TimeSignature.cpp index 99fc0cb9..8f0fa068 100644 --- a/src/libs/application/dspxmodel/src/TimeSignature.cpp +++ b/src/libs/application/dspxmodel/src/TimeSignature.cpp @@ -1,4 +1,5 @@ #include "TimeSignature.h" +#include "TimeSignature_p.h" #include #include @@ -7,24 +8,13 @@ #include #include +#include namespace dspx { - class TimeSignaturePrivate { - Q_DECLARE_PUBLIC(TimeSignature) - public: - TimeSignature *q_ptr; - int index; - int numerator; - int denominator; - - void setIndexUnchecked(int index_); - void setIndex(int index_); - void setNumeratorUnchecked(int numerator_); - void setNumerator(int numerator_); - void setDenominatorUnchecked(int denominator_); - void setDenominator(int denominator_); - }; + static constexpr bool validateDenominator(int d) { + return d == 1 || d == 2 || d == 4 || d == 8 || d == 16 || d == 32 || d == 64 || d == 128 || d == 256; + } void TimeSignaturePrivate::setIndexUnchecked(int index_) { Q_Q(TimeSignature); @@ -33,8 +23,9 @@ namespace dspx { void TimeSignaturePrivate::setIndex(int index_) { Q_Q(TimeSignature); - if (auto engine = qjsEngine(q); engine && index_ < 0) { - engine->throwError(QJSValue::RangeError, QStringLiteral("Index must be greater or equal to 0")); + if (index_ < 0) { + if (auto engine = qjsEngine(q)) + engine->throwError(QJSValue::RangeError, QStringLiteral("Index must be greater or equal to 0")); return; } setIndexUnchecked(index_); @@ -47,8 +38,9 @@ namespace dspx { void TimeSignaturePrivate::setNumerator(int numerator_) { Q_Q(TimeSignature); - if (auto engine = qjsEngine(q); engine && numerator_ < 1) { - engine->throwError(QJSValue::RangeError, QStringLiteral("Numerator must be greater or equal to 1")); + if (numerator_ < 1) { + if (auto engine = qjsEngine(q)) + engine->throwError(QJSValue::RangeError, QStringLiteral("Numerator must be greater or equal to 1")); return; } setNumeratorUnchecked(numerator_); @@ -59,10 +51,6 @@ namespace dspx { q->model()->strategy()->setEntityProperty(q->handle(), ModelStrategy::P_Denominator, denominator_); } - static constexpr bool validateDenominator(int d) { - return d == 1 || d == 2 || d == 4 || d == 8 || d == 16 || d == 32 || d == 64 || d == 128 || d == 256; - } - void TimeSignaturePrivate::setDenominator(int denominator_) { Q_Q(TimeSignature); if (auto engine = qjsEngine(q); engine) { @@ -74,6 +62,14 @@ namespace dspx { setDenominatorUnchecked(denominator_); } + void TimeSignaturePrivate::setTimeSignatureSequence(TimeSignature *item, TimeSignatureSequence *timeSignatureSequence) { + auto d = item->d_func(); + if (d->timeSignatureSequence != timeSignatureSequence) { + d->timeSignatureSequence = timeSignatureSequence; + Q_EMIT item->timeSignatureSequenceChanged(); + } + } + TimeSignature::TimeSignature(Handle handle, Model *model) : EntityObject(handle, model), d_ptr(new TimeSignaturePrivate) { Q_D(TimeSignature); Q_ASSERT(model->strategy()->getEntityType(handle) == ModelStrategy::EI_TimeSignature); @@ -118,6 +114,11 @@ namespace dspx { d->setDenominatorUnchecked(denominator); } + TimeSignatureSequence *TimeSignature::timeSignatureSequence() const { + Q_D(const TimeSignature); + return d->timeSignatureSequence; + } + QDspx::TimeSignature TimeSignature::toQDspx() const { return { .index = index(), diff --git a/src/libs/application/dspxmodel/src/TimeSignature.h b/src/libs/application/dspxmodel/src/TimeSignature.h index 25918a5a..99f23148 100644 --- a/src/libs/application/dspxmodel/src/TimeSignature.h +++ b/src/libs/application/dspxmodel/src/TimeSignature.h @@ -11,6 +11,7 @@ namespace QDspx { namespace dspx { + class TimeSignatureSequence; class TimeSignaturePrivate; class DSPX_MODEL_EXPORT TimeSignature : public EntityObject { @@ -21,6 +22,7 @@ namespace dspx { Q_PRIVATE_PROPERTY(d_func(), int index MEMBER index WRITE setIndex NOTIFY indexChanged) Q_PRIVATE_PROPERTY(d_func(), int numerator MEMBER numerator WRITE setNumerator NOTIFY numeratorChanged) Q_PRIVATE_PROPERTY(d_func(), int denominator MEMBER denominator WRITE setDenominator NOTIFY denominatorChanged) + Q_PROPERTY(TimeSignatureSequence *timeSignatureSequence READ timeSignatureSequence NOTIFY timeSignatureSequenceChanged) public: ~TimeSignature() override; @@ -33,6 +35,8 @@ namespace dspx { int denominator() const; void setDenominator(int denominator); + TimeSignatureSequence *timeSignatureSequence() const; + QDspx::TimeSignature toQDspx() const; void fromQDspx(const QDspx::TimeSignature &timeSignature); @@ -40,6 +44,7 @@ namespace dspx { void indexChanged(int index); void numeratorChanged(int numerator); void denominatorChanged(int denominator); + void timeSignatureSequenceChanged(); protected: void handleSetEntityProperty(int property, const QVariant &value) override; diff --git a/src/libs/application/dspxmodel/src/TimeSignatureSequence.cpp b/src/libs/application/dspxmodel/src/TimeSignatureSequence.cpp index ad817520..441019f0 100644 --- a/src/libs/application/dspxmodel/src/TimeSignatureSequence.cpp +++ b/src/libs/application/dspxmodel/src/TimeSignatureSequence.cpp @@ -1,4 +1,5 @@ #include "TimeSignatureSequence.h" +#include "TimeSignatureSequence_p.h" #include #include @@ -8,20 +9,24 @@ #include #include #include -#include #include namespace dspx { - class TimeSignatureSequencePrivate : public PointSequenceData { - Q_DECLARE_PUBLIC(TimeSignatureSequence) - }; - TimeSignatureSequence::TimeSignatureSequence(Handle handle, Model *model) : EntityObject(handle, model), d_ptr(new TimeSignatureSequencePrivate) { Q_D(TimeSignatureSequence); Q_ASSERT(model->strategy()->getEntityType(handle) == ModelStrategy::ES_TimeSignatures); d->q_ptr = this; d->pModel = ModelPrivate::get(model); + + d->init(model->strategy()->getEntitiesFromSequenceContainer(handle)); + + connect(this, &TimeSignatureSequence::itemInserted, this, [=](TimeSignature *item) { + TimeSignaturePrivate::setTimeSignatureSequence(item, this); + }); + connect(this, &TimeSignatureSequence::itemRemoved, this, [=](TimeSignature *item) { + TimeSignaturePrivate::setTimeSignatureSequence(item, nullptr); + }); } TimeSignatureSequence::~TimeSignatureSequence() = default; diff --git a/src/libs/application/dspxmodel/src/TimeSignatureSequence.h b/src/libs/application/dspxmodel/src/TimeSignatureSequence.h index 4779dd39..8e00f951 100644 --- a/src/libs/application/dspxmodel/src/TimeSignatureSequence.h +++ b/src/libs/application/dspxmodel/src/TimeSignatureSequence.h @@ -4,6 +4,7 @@ #include #include +#include namespace QDspx { struct TimeSignature; @@ -41,6 +42,10 @@ namespace dspx { QList toQDspx() const; void fromQDspx(const QList &timeSignatures); + auto asRange() const { + return impl::SequenceRange(this); + } + Q_SIGNALS: void itemAboutToInsert(TimeSignature *item); void itemInserted(TimeSignature *item); diff --git a/src/libs/application/dspxmodel/src/TimeSignatureSequence_p.h b/src/libs/application/dspxmodel/src/TimeSignatureSequence_p.h new file mode 100644 index 00000000..3cf31cbe --- /dev/null +++ b/src/libs/application/dspxmodel/src/TimeSignatureSequence_p.h @@ -0,0 +1,17 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_TIMESIGNATURESEQUENCE_P_H +#define DIFFSCOPE_DSPX_MODEL_TIMESIGNATURESEQUENCE_P_H + +#include + +#include +#include + +namespace dspx { + + class TimeSignatureSequencePrivate : public PointSequenceData { + Q_DECLARE_PUBLIC(TimeSignatureSequence) + }; + +} + +#endif //DIFFSCOPE_DSPX_MODEL_TIMESIGNATURESEQUENCE_P_H \ No newline at end of file diff --git a/src/libs/application/dspxmodel/src/TimeSignature_p.h b/src/libs/application/dspxmodel/src/TimeSignature_p.h new file mode 100644 index 00000000..7a787946 --- /dev/null +++ b/src/libs/application/dspxmodel/src/TimeSignature_p.h @@ -0,0 +1,29 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_TIMESIGNATURE_P_H +#define DIFFSCOPE_DSPX_MODEL_TIMESIGNATURE_P_H + +#include + +namespace dspx { + + class TimeSignaturePrivate { + Q_DECLARE_PUBLIC(TimeSignature) + public: + TimeSignature *q_ptr; + int index; + int numerator; + int denominator; + TimeSignatureSequence *timeSignatureSequence{}; + + void setIndexUnchecked(int index_); + void setIndex(int index_); + void setNumeratorUnchecked(int numerator_); + void setNumerator(int numerator_); + void setDenominatorUnchecked(int denominator_); + void setDenominator(int denominator_); + + static void setTimeSignatureSequence(TimeSignature *item, TimeSignatureSequence *timeSignatureSequence); + }; + +} + +#endif //DIFFSCOPE_DSPX_MODEL_TIMESIGNATURE_P_H \ No newline at end of file diff --git a/src/libs/application/dspxmodel/src/Timeline.cpp b/src/libs/application/dspxmodel/src/Timeline.cpp index 7119ef2c..b73df2ac 100644 --- a/src/libs/application/dspxmodel/src/Timeline.cpp +++ b/src/libs/application/dspxmodel/src/Timeline.cpp @@ -1,7 +1,10 @@ #include "Timeline.h" +#include + #include +#include #include #include #include @@ -14,12 +17,69 @@ namespace dspx { public: Timeline *q_ptr; ModelPrivate *pModel; + Handle handle; + + bool loopEnabled{false}; + int loopStart{0}; + int loopLength{1920}; + + void setLoopStartUnchecked(int loopStart_) { + Q_Q(Timeline); + pModel->strategy->setEntityProperty(handle, ModelStrategy::P_LoopStart, loopStart_); + } + + void setLoopStart(int loopStart_) { + Q_Q(Timeline); + if (loopStart_ < 0) { + if (auto engine = qjsEngine(q)) + engine->throwError(QJSValue::RangeError, QStringLiteral("Loop start must be greater or equal to 0")); + return; + } + setLoopStartUnchecked(loopStart_); + } + + void setLoopLengthUnchecked(int loopLength_) { + Q_Q(Timeline); + pModel->strategy->setEntityProperty(handle, ModelStrategy::P_LoopLength, loopLength_); + } + + void setLoopLength(int loopLength_) { + Q_Q(Timeline); + if (loopLength_ <= 0) { + if (auto engine = qjsEngine(q)) + engine->throwError(QJSValue::RangeError, QStringLiteral("Loop length must be greater than 0")); + return; + } + setLoopLengthUnchecked(loopLength_); + } }; - Timeline::Timeline(Model *model) : QObject(model), d_ptr(new TimelinePrivate) { + Timeline::Timeline(Model *model) + : QObject(model), d_ptr(new TimelinePrivate) { Q_D(Timeline); d->q_ptr = this; d->pModel = ModelPrivate::get(model); + d->handle = model->handle(); + } + + void Timeline::handleProxySetEntityProperty(int property, const QVariant &value) { + Q_D(Timeline); + switch (property) { + case ModelStrategy::P_LoopEnabled: + d->loopEnabled = value.toBool(); + Q_EMIT loopEnabledChanged(d->loopEnabled); + break; + case ModelStrategy::P_LoopStart: + d->loopStart = value.toInt(); + Q_EMIT loopStartChanged(d->loopStart); + break; + case ModelStrategy::P_LoopLength: + d->loopLength = value.toInt(); + Q_EMIT loopLengthChanged(d->loopLength); + break; + default: + Q_UNREACHABLE(); + } } Timeline::~Timeline() = default; @@ -39,6 +99,38 @@ namespace dspx { return d->pModel->timeSignatures; } + bool Timeline::isLoopEnabled() const { + Q_D(const Timeline); + return d->loopEnabled; + } + + void Timeline::setLoopEnabled(bool enabled) { + Q_D(Timeline); + d->pModel->strategy->setEntityProperty(d->handle, ModelStrategy::P_LoopEnabled, enabled); + } + + int Timeline::loopStart() const { + Q_D(const Timeline); + return d->loopStart; + } + + void Timeline::setLoopStart(int loopStart) { + Q_D(Timeline); + Q_ASSERT(loopStart >= 0); + d->setLoopStartUnchecked(loopStart); + } + + int Timeline::loopLength() const { + Q_D(const Timeline); + return d->loopLength; + } + + void Timeline::setLoopLength(int loopLength) { + Q_D(Timeline); + Q_ASSERT(loopLength > 0); + d->setLoopLengthUnchecked(loopLength); + } + QDspx::Timeline Timeline::toQDspx() const { return { labels()->toQDspx(), diff --git a/src/libs/application/dspxmodel/src/Timeline.h b/src/libs/application/dspxmodel/src/Timeline.h index 8ce439e2..2352052f 100644 --- a/src/libs/application/dspxmodel/src/Timeline.h +++ b/src/libs/application/dspxmodel/src/Timeline.h @@ -29,6 +29,9 @@ namespace dspx { Q_PROPERTY(LabelSequence *labels READ labels CONSTANT) Q_PROPERTY(TempoSequence *tempos READ tempos CONSTANT) Q_PROPERTY(TimeSignatureSequence *timeSignatures READ timeSignatures CONSTANT) + Q_PROPERTY(bool loopEnabled READ isLoopEnabled WRITE setLoopEnabled NOTIFY loopEnabledChanged) + Q_PRIVATE_PROPERTY(d_func(), int loopStart MEMBER loopStart WRITE setLoopStart NOTIFY loopStartChanged) + Q_PRIVATE_PROPERTY(d_func(), int loopLength MEMBER loopLength WRITE setLoopLength NOTIFY loopLengthChanged) public: ~Timeline() override; @@ -36,12 +39,27 @@ namespace dspx { TempoSequence *tempos() const; TimeSignatureSequence *timeSignatures() const; + bool isLoopEnabled() const; + void setLoopEnabled(bool enabled); + + int loopStart() const; + void setLoopStart(int loopStart); + + int loopLength() const; + void setLoopLength(int loopLength); + QDspx::Timeline toQDspx() const; void fromQDspx(const QDspx::Timeline &timeline); + Q_SIGNALS: + void loopEnabledChanged(bool enabled); + void loopStartChanged(int loopStart); + void loopLengthChanged(int loopLength); + private: friend class ModelPrivate; explicit Timeline(Model *model); + void handleProxySetEntityProperty(int property, const QVariant &value); QScopedPointer d_ptr; }; diff --git a/src/libs/application/dspxmodel/src/Track.cpp b/src/libs/application/dspxmodel/src/Track.cpp index 013b541d..a0e7140d 100644 --- a/src/libs/application/dspxmodel/src/Track.cpp +++ b/src/libs/application/dspxmodel/src/Track.cpp @@ -1,4 +1,5 @@ #include "Track.h" +#include "Track_p.h" #include #include @@ -9,27 +10,18 @@ #include #include #include +#include #include +#include namespace dspx { - class TrackPrivate { - Q_DECLARE_PUBLIC(Track) - public: - Track *q_ptr; - ModelPrivate *pModel; - ClipSequence *clips; - QString name; - TrackControl *control; - Workspace *workspace; - }; - - static QString colorToHex(const QColor &color) { - return color.isValid() ? color.name(QColor::HexRgb) : QString(); - } - - static QColor hexToColor(const QString &hex) { - return QColor::fromString(hex); + void TrackPrivate::setTrackList(Track *item, TrackList *trackList) { + auto d = item->d_func(); + if (d->trackList != trackList) { + d->trackList = trackList; + Q_EMIT item->trackListChanged(); + } } Track::Track(Handle handle, Model *model) : EntityObject(handle, model), d_ptr(new TrackPrivate) { @@ -38,10 +30,11 @@ namespace dspx { d->q_ptr = this; d->pModel = ModelPrivate::get(model); d->name = d->pModel->strategy->getEntityProperty(handle, ModelStrategy::P_Name).toString(); + d->colorId = d->pModel->strategy->getEntityProperty(handle, ModelStrategy::P_ColorId).toInt(); + d->height = d->pModel->strategy->getEntityProperty(handle, ModelStrategy::P_Height).toDouble(); d->control = d->pModel->createObject(handle); d->workspace = d->pModel->createObject(d->pModel->strategy->getAssociatedSubEntity(handle, ModelStrategy::R_Workspace)); - d->clips = d->pModel->createObject(d->pModel->strategy->getAssociatedSubEntity(handle, ModelStrategy::R_Children)); - d->clips->setTrack(this); + d->clips = d->pModel->createObject(this, d->pModel->strategy->getAssociatedSubEntity(handle, ModelStrategy::R_Children)); } Track::~Track() = default; @@ -51,6 +44,26 @@ namespace dspx { return d->clips; } + int Track::colorId() const { + Q_D(const Track); + return d->colorId; + } + + void Track::setColorId(int colorId) { + Q_D(Track); + d->pModel->strategy->setEntityProperty(handle(), ModelStrategy::P_ColorId, colorId); + } + + double Track::height() const { + Q_D(const Track); + return d->height; + } + + void Track::setHeight(double height) { + Q_D(Track); + d->pModel->strategy->setEntityProperty(handle(), ModelStrategy::P_Height, height); + } + TrackControl *Track::control() const { Q_D(const Track); return d->control; @@ -72,12 +85,18 @@ namespace dspx { } QDspx::Track Track::toQDspx() const { - return { + QDspx::Track track { .name = name(), .control = control()->toQDspx(), .clips = clips()->toQDspx(), .workspace = workspace()->toQDspx(), }; + track.workspace["diffscope"] = QJsonObject{ + {"colorId", colorId()}, + {"height", height()}, + {"record", control()->record()}, + }; + return track; } void Track::fromQDspx(const QDspx::Track &track) { @@ -85,6 +104,14 @@ namespace dspx { control()->fromQDspx(track.control); clips()->fromQDspx(track.clips); workspace()->fromQDspx(track.workspace); + setColorId(track.workspace["diffscope"]["colorId"].toInt()); + setHeight(track.workspace["diffscope"]["height"].toDouble(80)); + control()->setRecord(track.workspace["diffscope"]["record"].toBool()); + } + + TrackList *Track::trackList() const { + Q_D(const Track); + return d->trackList; } void Track::handleSetEntityProperty(int property, const QVariant &value) { @@ -95,9 +122,20 @@ namespace dspx { Q_EMIT nameChanged(d->name); break; } + case ModelStrategy::P_ColorId: { + d->colorId = value.toInt(); + Q_EMIT colorIdChanged(d->colorId); + break; + } + case ModelStrategy::P_Height: { + d->height = value.toDouble(); + Q_EMIT heightChanged(d->height); + break; + } case ModelStrategy::P_ControlGain: case ModelStrategy::P_ControlPan: case ModelStrategy::P_ControlMute: + case ModelStrategy::P_ControlRecord: case ModelStrategy::P_ControlSolo: { ModelPrivate::proxySetEntityPropertyNotify(d->control, property, value); break; diff --git a/src/libs/application/dspxmodel/src/Track.h b/src/libs/application/dspxmodel/src/Track.h index 4f25a1d0..c49fd48c 100644 --- a/src/libs/application/dspxmodel/src/Track.h +++ b/src/libs/application/dspxmodel/src/Track.h @@ -15,6 +15,8 @@ namespace dspx { class TrackControl; class Workspace; + class TrackList; + class TrackPrivate; class DSPX_MODEL_EXPORT Track : public EntityObject { @@ -23,15 +25,24 @@ namespace dspx { QML_UNCREATABLE("") Q_DECLARE_PRIVATE(Track) Q_PROPERTY(ClipSequence *clips READ clips CONSTANT) + Q_PROPERTY(int colorId READ colorId WRITE setColorId NOTIFY colorIdChanged) + Q_PROPERTY(double height READ height WRITE setHeight NOTIFY heightChanged) Q_PROPERTY(TrackControl *control READ control CONSTANT) Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) Q_PROPERTY(Workspace *workspace READ workspace CONSTANT) + Q_PROPERTY(TrackList *trackList READ trackList CONSTANT) public: ~Track() override; ClipSequence *clips() const; + int colorId() const; + void setColorId(int colorId); + + double height() const; + void setHeight(double height); + TrackControl *control() const; QString name() const; @@ -42,8 +53,13 @@ namespace dspx { QDspx::Track toQDspx() const; void fromQDspx(const QDspx::Track &track); + TrackList *trackList() const; + Q_SIGNALS: void nameChanged(const QString &name); + void colorIdChanged(int colorId); + void heightChanged(double height); + void trackListChanged(); protected: void handleSetEntityProperty(int property, const QVariant &value) override; diff --git a/src/libs/application/dspxmodel/src/TrackControl.cpp b/src/libs/application/dspxmodel/src/TrackControl.cpp index 1e543252..f3142173 100644 --- a/src/libs/application/dspxmodel/src/TrackControl.cpp +++ b/src/libs/application/dspxmodel/src/TrackControl.cpp @@ -13,6 +13,7 @@ namespace dspx { TrackControl *q_ptr; ModelPrivate *pModel; bool solo; + bool record; }; TrackControl::TrackControl(Handle handle, Model *model) : Control(handle, model), d_ptr(new TrackControlPrivate) { @@ -20,6 +21,7 @@ namespace dspx { d->q_ptr = this; d->pModel = ModelPrivate::get(model); d->solo = d->pModel->strategy->getEntityProperty(handle, ModelStrategy::P_ControlSolo).toBool(); + d->record = d->pModel->strategy->getEntityProperty(handle, ModelStrategy::P_ControlRecord).toBool(); } TrackControl::~TrackControl() = default; @@ -34,6 +36,16 @@ namespace dspx { d->pModel->strategy->setEntityProperty(handle(), ModelStrategy::P_ControlSolo, solo); } + bool TrackControl::record() const { + Q_D(const TrackControl); + return d->record; + } + + void TrackControl::setRecord(bool record) { + Q_D(TrackControl); + d->pModel->strategy->setEntityProperty(handle(), ModelStrategy::P_ControlRecord, record); + } + QDspx::TrackControl TrackControl::toQDspx() const { return { .gain = gain(), @@ -57,6 +69,11 @@ namespace dspx { Q_EMIT soloChanged(d->solo); break; } + case ModelStrategy::P_ControlRecord: { + d->record = value.toBool(); + Q_EMIT recordChanged(d->record); + break; + } default: { Control::handleProxySetEntityProperty(property, value); } diff --git a/src/libs/application/dspxmodel/src/TrackControl.h b/src/libs/application/dspxmodel/src/TrackControl.h index d7018840..5d077a01 100644 --- a/src/libs/application/dspxmodel/src/TrackControl.h +++ b/src/libs/application/dspxmodel/src/TrackControl.h @@ -11,12 +11,13 @@ namespace dspx { class TrackControlPrivate; - class TrackControl : public Control { + class DSPX_MODEL_EXPORT TrackControl : public Control { Q_OBJECT QML_ELEMENT QML_UNCREATABLE("") Q_DECLARE_PRIVATE(TrackControl) Q_PROPERTY(bool solo READ solo WRITE setSolo NOTIFY soloChanged) + Q_PROPERTY(bool record READ record WRITE setRecord NOTIFY recordChanged) public: ~TrackControl() override; @@ -24,11 +25,15 @@ namespace dspx { bool solo() const; void setSolo(bool solo); + bool record() const; + void setRecord(bool record); + QDspx::TrackControl toQDspx() const; void fromQDspx(const QDspx::TrackControl &trackControl); Q_SIGNALS: void soloChanged(bool solo); + void recordChanged(bool record); private: friend class ModelPrivate; diff --git a/src/libs/application/dspxmodel/src/TrackList.cpp b/src/libs/application/dspxmodel/src/TrackList.cpp index 49011c19..65b3ce9b 100644 --- a/src/libs/application/dspxmodel/src/TrackList.cpp +++ b/src/libs/application/dspxmodel/src/TrackList.cpp @@ -6,6 +6,7 @@ #include #include #include +#include namespace dspx { @@ -18,6 +19,15 @@ namespace dspx { Q_ASSERT(model->strategy()->getEntityType(handle) == ModelStrategy::EL_Tracks); d->q_ptr = this; d->pModel = ModelPrivate::get(model); + + d->init(model->strategy()->getEntitiesFromListContainer(handle)); + + connect(this, &TrackList::itemInserted, this, [this](int, Track *item) { + TrackPrivate::setTrackList(item, this); + }); + connect(this, &TrackList::itemRemoved, this, [this](int, Track *item) { + TrackPrivate::setTrackList(item, nullptr); + }); } TrackList::~TrackList() = default; diff --git a/src/libs/application/dspxmodel/src/Track_p.h b/src/libs/application/dspxmodel/src/Track_p.h new file mode 100644 index 00000000..4a4caeba --- /dev/null +++ b/src/libs/application/dspxmodel/src/Track_p.h @@ -0,0 +1,26 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_TRACK_P_H +#define DIFFSCOPE_DSPX_MODEL_TRACK_P_H + +#include + +namespace dspx { + + class TrackPrivate { + Q_DECLARE_PUBLIC(Track) + public: + Track *q_ptr; + ModelPrivate *pModel; + ClipSequence *clips; + QString name; + int colorId; + double height; + TrackControl *control; + Workspace *workspace; + TrackList *trackList; + + static void setTrackList(Track *item, TrackList *trackList); + }; + +} + +#endif //DIFFSCOPE_DSPX_MODEL_TRACK_P_H diff --git a/src/libs/application/dspxmodel/src/UndoableModelStrategy.cpp b/src/libs/application/dspxmodel/src/UndoableModelStrategy.cpp new file mode 100644 index 00000000..bfd2ad73 --- /dev/null +++ b/src/libs/application/dspxmodel/src/UndoableModelStrategy.cpp @@ -0,0 +1,538 @@ +#include "UndoableModelStrategy.h" +#include "UndoableModelStrategy_p.h" + +#include + +#include + +namespace dspx { + + //================================================================ + // Create/Destroy Commands + //================================================================ + + CreateEntityCommand::CreateEntityCommand(UndoableModelStrategy *strategy, BasicModelStrategy::Entity entityType, + QUndoCommand *parent) + : QUndoCommand(parent), m_strategy(strategy), m_entityType(entityType), m_object(nullptr) { + } + + CreateEntityCommand::~CreateEntityCommand() { + // Delete object if command is in undone state + if (m_object && m_undone) { + delete m_object.data(); + } + } + + void CreateEntityCommand::undo() { + // Detach object from strategy but keep it alive + m_object->setParent(nullptr); + m_undone = true; + Q_EMIT m_strategy->destroyEntityNotified(entity()); + } + + void CreateEntityCommand::redo() { + if (!m_object) { + // First time: create the object + m_object = BasicModelStrategyEntity::createByType(m_entityType, m_strategy); + } else { + // Subsequent times: re-parent the existing object + m_object->setParent(m_strategy); + } + m_undone = false; + Q_EMIT m_strategy->createEntityNotified(entity(), m_entityType); + } + + Handle CreateEntityCommand::entity() const { + return {reinterpret_cast(m_object.data())}; + } + + DestroyEntityCommand::DestroyEntityCommand(UndoableModelStrategy *strategy, Handle entity, + QUndoCommand *parent) + : QUndoCommand(parent), m_strategy(strategy), m_object(handle_cast(entity)) { + } + + DestroyEntityCommand::~DestroyEntityCommand() { + // Delete object if command is NOT in undone state (i.e., destroy was executed) + if (m_object && !m_undone) { + delete m_object.data(); + } + } + + void DestroyEntityCommand::recursivelyCreate(BasicModelStrategyEntity *object) { + object->setParent(m_strategy); + Q_EMIT m_strategy->createEntityNotified({reinterpret_cast(object)}, object->type); + + if (auto seq = qobject_cast(object)) { + for (auto child : std::as_const(seq->sequence)) { + recursivelyCreate(child); + } + } else if (auto list = qobject_cast(object)) { + for (auto child : std::as_const(list->list)) { + recursivelyCreate(child); + } + } else if (auto map = qobject_cast(object)) { + for (auto child : std::as_const(map->map)) { + recursivelyCreate(child); + } + } + } + + void DestroyEntityCommand::recursivelyDestroy(BasicModelStrategyEntity *object) { + if (auto seq = qobject_cast(object)) { + for (auto child : std::as_const(seq->sequence)) { + recursivelyDestroy(child); + } + } else if (auto list = qobject_cast(object)) { + for (auto child : std::as_const(list->list)) { + recursivelyDestroy(child); + } + } else if (auto map = qobject_cast(object)) { + for (auto child : std::as_const(map->map)) { + recursivelyDestroy(child); + } + } + + object->setParent(nullptr); + Q_EMIT m_strategy->destroyEntityNotified({reinterpret_cast(object)}); + } + + void DestroyEntityCommand::undo() { + recursivelyCreate(m_object); + m_undone = true; + } + + void DestroyEntityCommand::redo() { + recursivelyDestroy(m_object); + m_undone = false; + } + + //================================================================ + // Container Commands + //================================================================ + + InsertIntoSequenceContainerCommand::InsertIntoSequenceContainerCommand( + UndoableModelStrategy *strategy, Handle sequenceContainerEntity, + Handle entity, QUndoCommand *parent) + : QUndoCommand(parent), m_strategy(strategy), m_container(sequenceContainerEntity), m_entity(entity) { + } + + void InsertIntoSequenceContainerCommand::undo() { + auto containerObj = handle_cast(m_container); + auto entityObj = handle_cast(m_entity); + containerObj->sequence.remove(entityObj); + entityObj->setParent(m_strategy); + Q_EMIT m_strategy->takeFromContainerNotified(m_entity, m_container, m_entity); + } + + void InsertIntoSequenceContainerCommand::redo() { + auto containerObj = handle_cast(m_container); + auto entityObj = handle_cast(m_entity); + containerObj->sequence.insert(entityObj); + entityObj->setParent(containerObj); + Q_EMIT m_strategy->insertIntoSequenceContainerNotified(m_container, m_entity); + } + + TakeFromSequenceContainerCommand::TakeFromSequenceContainerCommand( + UndoableModelStrategy *strategy, Handle sequenceContainerEntity, + Handle entity, QUndoCommand *parent) + : QUndoCommand(parent), m_strategy(strategy), m_container(sequenceContainerEntity), + m_object(handle_cast(entity)) { + } + + TakeFromSequenceContainerCommand::~TakeFromSequenceContainerCommand() { + // Delete object if command is NOT in undone state (i.e., take was executed) + if (m_object && !m_undone) { + delete m_object.data(); + } + } + + void TakeFromSequenceContainerCommand::undo() { + auto containerObj = handle_cast(m_container); + containerObj->sequence.insert(m_object); + m_object->setParent(containerObj); + m_undone = true; + Q_EMIT m_strategy->insertIntoSequenceContainerNotified(m_container, {reinterpret_cast(m_object.data())}); + } + + void TakeFromSequenceContainerCommand::redo() { + auto containerObj = handle_cast(m_container); + containerObj->sequence.remove(m_object); + m_object->setParent(m_strategy); + m_undone = false; + Q_EMIT m_strategy->takeFromContainerNotified({reinterpret_cast(m_object.data())}, m_container, + {reinterpret_cast(m_object.data())}); + } + + InsertIntoListContainerCommand::InsertIntoListContainerCommand( + UndoableModelStrategy *strategy, Handle listContainerEntity, + Handle entity, int index, QUndoCommand *parent) + : QUndoCommand(parent), m_strategy(strategy), m_container(listContainerEntity), m_entity(entity), + m_index(index) { + } + + void InsertIntoListContainerCommand::undo() { + auto containerObj = handle_cast(m_container); + auto entityObj = handle_cast(m_entity); + containerObj->list.removeAt(m_index); + entityObj->setParent(m_strategy); + Q_EMIT m_strategy->takeFromListContainerNotified(m_entity, m_container, m_index); + } + + void InsertIntoListContainerCommand::redo() { + auto containerObj = handle_cast(m_container); + auto entityObj = handle_cast(m_entity); + containerObj->list.insert(m_index, entityObj); + entityObj->setParent(containerObj); + Q_EMIT m_strategy->insertIntoListContainerNotified(m_container, m_entity, m_index); + } + + TakeFromListContainerCommand::TakeFromListContainerCommand(UndoableModelStrategy *strategy, + Handle listContainerEntity, + int index, QUndoCommand *parent) + : QUndoCommand(parent), m_strategy(strategy), m_container(listContainerEntity), m_object(nullptr), + m_index(index) { + } + + TakeFromListContainerCommand::~TakeFromListContainerCommand() { + // Delete object if command is NOT in undone state (i.e., take was executed) + if (m_object && !m_undone) { + delete m_object.data(); + } + } + + void TakeFromListContainerCommand::undo() { + auto containerObj = handle_cast(m_container); + containerObj->list.insert(m_index, m_object); + m_object->setParent(containerObj); + m_undone = true; + Q_EMIT m_strategy->insertIntoListContainerNotified(m_container, entity(), m_index); + } + + void TakeFromListContainerCommand::redo() { + if (!m_object) { + auto containerObj = handle_cast(m_container); + m_object = containerObj->list.at(m_index); + } + auto containerObj = handle_cast(m_container); + containerObj->list.removeAt(m_index); + m_object->setParent(m_strategy); + m_undone = false; + Q_EMIT m_strategy->takeFromListContainerNotified(entity(), m_container, m_index); + } + + Handle TakeFromListContainerCommand::entity() const { + return {reinterpret_cast(m_object.data())}; + } + + InsertIntoMapContainerCommand::InsertIntoMapContainerCommand(UndoableModelStrategy *strategy, + Handle mapContainerEntity, + Handle entity, const QString &key, + QUndoCommand *parent) + : QUndoCommand(parent), m_strategy(strategy), m_container(mapContainerEntity), m_entity(entity), m_key(key), + m_oldObject(nullptr) { + } + + InsertIntoMapContainerCommand::~InsertIntoMapContainerCommand() { + if (m_oldObject) { + delete m_oldObject.data(); + } + } + + void InsertIntoMapContainerCommand::undo() { + auto containerObj = handle_cast(m_container); + auto entityObj = handle_cast(m_entity); + + containerObj->map.remove(m_key); + entityObj->setParent(m_strategy); + Q_EMIT m_strategy->takeFromMapContainerNotified(m_entity, m_container, m_key); + + if (m_oldObject) { + containerObj->map.insert(m_key, m_oldObject); + m_oldObject->setParent(containerObj); + Q_EMIT m_strategy->insertIntoMapContainerNotified(m_container, + {reinterpret_cast(m_oldObject.data())}, m_key); + m_oldObject = nullptr; // Transfer ownership back to container + } + } + + void InsertIntoMapContainerCommand::redo() { + auto containerObj = handle_cast(m_container); + auto entityObj = handle_cast(m_entity); + + if (containerObj->map.contains(m_key)) { + m_oldObject = containerObj->map.take(m_key); + m_oldObject->setParent(nullptr); // Take ownership + Q_EMIT m_strategy->takeFromMapContainerNotified({reinterpret_cast(m_oldObject.get())}, m_container, + m_key); + } + + containerObj->map.insert(m_key, entityObj); + entityObj->setParent(containerObj); + Q_EMIT m_strategy->insertIntoMapContainerNotified(m_container, m_entity, m_key); + } + + TakeFromMapContainerCommand::TakeFromMapContainerCommand(UndoableModelStrategy *strategy, + Handle mapContainerEntity, + const QString &key, QUndoCommand *parent) + : QUndoCommand(parent), m_strategy(strategy), m_container(mapContainerEntity), m_object(nullptr), m_key(key) { + } + + TakeFromMapContainerCommand::~TakeFromMapContainerCommand() { + // Delete object if command is NOT in undone state (i.e., take was executed) + if (m_object && !m_undone) { + delete m_object.data(); + } + } + + void TakeFromMapContainerCommand::undo() { + auto containerObj = handle_cast(m_container); + containerObj->map.insert(m_key, m_object); + m_object->setParent(containerObj); + m_undone = true; + Q_EMIT m_strategy->insertIntoMapContainerNotified(m_container, entity(), m_key); + } + + void TakeFromMapContainerCommand::redo() { + if (!m_object) { + auto containerObj = handle_cast(m_container); + m_object = containerObj->map.value(m_key); + } + auto containerObj = handle_cast(m_container); + containerObj->map.remove(m_key); + m_object->setParent(m_strategy); + m_undone = false; + Q_EMIT m_strategy->takeFromMapContainerNotified(entity(), m_container, m_key); + } + + Handle TakeFromMapContainerCommand::entity() const { + return {reinterpret_cast(m_object.data())}; + } + + RotateListContainerCommand::RotateListContainerCommand(UndoableModelStrategy *strategy, + Handle listContainerEntity, int left, + int middle, int right, QUndoCommand *parent) + : QUndoCommand(parent), m_strategy(strategy), m_container(listContainerEntity), m_left(left), + m_middle(middle), m_right(right) { + } + + void RotateListContainerCommand::undo() { + auto &list = handle_cast(m_container)->list; + std::rotate(list.begin() + m_left, list.begin() + m_right - (m_middle - m_left), list.begin() + m_right); + Q_EMIT m_strategy->rotateListContainerNotified(m_container, m_left, m_right - (m_middle - m_left), m_right); + } + + void RotateListContainerCommand::redo() { + auto &list = handle_cast(m_container)->list; + std::rotate(list.begin() + m_left, list.begin() + m_middle, list.begin() + m_right); + Q_EMIT m_strategy->rotateListContainerNotified(m_container, m_left, m_middle, m_right); + } + + //================================================================ + // Property & Data Commands + //================================================================ + + SetEntityPropertyCommand::SetEntityPropertyCommand(UndoableModelStrategy *strategy, + Handle entity, + BasicModelStrategy::Property property, const QVariant &value, + QUndoCommand *parent) + : QUndoCommand(parent), m_strategy(strategy), m_entity(entity), m_property(property), m_newValue(value) { + m_oldValue = m_strategy->getEntityProperty(m_entity, m_property); + } + + void SetEntityPropertyCommand::undo() { + auto object = handle_cast(m_entity); + object->properties.insert(m_property, m_oldValue); + Q_EMIT m_strategy->setEntityPropertyNotified(m_entity, m_property, m_oldValue); + } + + void SetEntityPropertyCommand::redo() { + auto object = handle_cast(m_entity); + object->properties.insert(m_property, m_newValue); + Q_EMIT m_strategy->setEntityPropertyNotified(m_entity, m_property, m_newValue); + } + + bool SetEntityPropertyCommand::mergeWith(const QUndoCommand *command) { + const auto other = static_cast(command); + if (other->m_entity != m_entity || other->m_property != m_property) { + return false; + } + m_newValue = other->m_newValue; + return true; + } + + int SetEntityPropertyCommand::id() const { + // Return a constant to force mergeWith to be called for comparison + return -1; + } + + SpliceDataArrayCommand::SpliceDataArrayCommand(UndoableModelStrategy *strategy, + Handle dataContainerEntity, int index, + int length, const QVariantList &values, QUndoCommand *parent) + : QUndoCommand(parent), m_strategy(strategy), m_container(dataContainerEntity), m_index(index), + m_length(length), m_values(values) { + } + + void SpliceDataArrayCommand::undo() { + auto &data = handle_cast(m_container)->data; + SpliceHelper::splice(data, data.begin() + m_index, data.begin() + m_index + m_values.size(), + m_oldValues.begin(), m_oldValues.end()); + Q_EMIT m_strategy->spliceDataArrayNotified(m_container, m_index, m_values.size(), m_oldValues); + } + + void SpliceDataArrayCommand::redo() { + auto &data = handle_cast(m_container)->data; + if (m_oldValues.isEmpty() && m_length > 0) { + m_oldValues = data.mid(m_index, m_length); + } + SpliceHelper::splice(data, data.begin() + m_index, data.begin() + m_index + m_length, m_values.begin(), + m_values.end()); + Q_EMIT m_strategy->spliceDataArrayNotified(m_container, m_index, m_length, m_values); + } + + RotateDataArrayCommand::RotateDataArrayCommand(UndoableModelStrategy *strategy, + Handle dataContainerEntity, int left, + int middle, int right, QUndoCommand *parent) + : QUndoCommand(parent), m_strategy(strategy), m_container(dataContainerEntity), m_left(left), + m_middle(middle), m_right(right) { + } + + void RotateDataArrayCommand::undo() { + auto &data = handle_cast(m_container)->data; + std::rotate(data.begin() + m_left, data.begin() + m_right - (m_middle - m_left), data.begin() + m_right); + Q_EMIT m_strategy->rotateDataArrayNotified(m_container, m_left, m_right - (m_middle - m_left), m_right); + } + + void RotateDataArrayCommand::redo() { + auto &data = handle_cast(m_container)->data; + std::rotate(data.begin() + m_left, data.begin() + m_middle, data.begin() + m_right); + Q_EMIT m_strategy->rotateDataArrayNotified(m_container, m_left, m_middle, m_right); + } + + //================================================================ + // UndoableModelStrategy + //================================================================ + + UndoableModelStrategy::UndoableModelStrategy(QObject *parent) + : BasicModelStrategy(parent), m_undoStack(new QUndoStack(this)) { + } + + UndoableModelStrategy::~UndoableModelStrategy() = default; + + QUndoStack *UndoableModelStrategy::undoStack() const { + return m_undoStack; + } + + Handle UndoableModelStrategy::createEntity(Entity entityType) { + auto cmd = new CreateEntityCommand(this, entityType); + m_undoStack->push(cmd); + return cmd->entity(); + } + + void UndoableModelStrategy::destroyEntity(Handle entity) { + m_undoStack->push(new DestroyEntityCommand(this, entity)); + } + + bool UndoableModelStrategy::insertIntoSequenceContainer(Handle sequenceContainerEntity, Handle entity) { + auto entityObj = handle_cast(entity); + if (entityObj->parent() != this) { + return false; + } + m_undoStack->push(new InsertIntoSequenceContainerCommand(this, sequenceContainerEntity, entity)); + return true; + } + + bool UndoableModelStrategy::insertIntoListContainer(Handle listContainerEntity, Handle entity, int index) { + auto listContainerObject = handle_cast(listContainerEntity); + if (index < 0 || index > listContainerObject->list.size()) { + return false; + } + auto entityObj = handle_cast(entity); + if (entityObj->parent() != this) { + return false; + } + m_undoStack->push(new InsertIntoListContainerCommand(this, listContainerEntity, entity, index)); + return true; + } + + bool UndoableModelStrategy::insertIntoMapContainer(Handle mapContainerEntity, Handle entity, const QString &key) { + auto entityObj = handle_cast(entity); + if (entityObj->parent() != this) { + return false; + } + m_undoStack->push(new InsertIntoMapContainerCommand(this, mapContainerEntity, entity, key)); + return true; + } + + Handle UndoableModelStrategy::takeFromSequenceContainer(Handle sequenceContainerEntity, + Handle entity) { + auto sequenceContainerObject = handle_cast(sequenceContainerEntity); + auto object = reinterpret_cast(entity.d); + if (!sequenceContainerObject->sequence.contains(object)) { + return {}; + } + auto cmd = new TakeFromSequenceContainerCommand(this, sequenceContainerEntity, entity); + m_undoStack->push(cmd); + return entity; + } + + Handle UndoableModelStrategy::takeFromListContainer(Handle listContainerEntity, int index) { + auto listContainerObject = handle_cast(listContainerEntity); + if (index < 0 || index >= listContainerObject->list.size()) { + return {}; + } + auto cmd = new TakeFromListContainerCommand(this, listContainerEntity, index); + m_undoStack->push(cmd); + return cmd->entity(); + } + + Handle UndoableModelStrategy::takeFromMapContainer(Handle mapContainerEntity, + const QString &key) { + auto mapContainerObject = handle_cast(mapContainerEntity); + if (!mapContainerObject->map.contains(key)) { + return {}; + } + auto cmd = new TakeFromMapContainerCommand(this, mapContainerEntity, key); + m_undoStack->push(cmd); + return cmd->entity(); + } + + bool UndoableModelStrategy::rotateListContainer(Handle listContainerEntity, int leftIndex, int middleIndex, + int rightIndex) { + auto listContainerObject = handle_cast(listContainerEntity); + if (leftIndex < 0 || leftIndex > listContainerObject->list.size() || middleIndex < leftIndex || + middleIndex > listContainerObject->list.size() || rightIndex < middleIndex || + rightIndex > listContainerObject->list.size()) { + return false; + } + m_undoStack->push(new RotateListContainerCommand(this, listContainerEntity, leftIndex, middleIndex, rightIndex)); + return true; + } + + void UndoableModelStrategy::setEntityProperty(Handle entity, Property property, const QVariant &value) { + auto object = handle_cast(entity); + Q_ASSERT(isEntityTypeAndPropertyTypeCompatible(object->type, property)); + m_undoStack->push(new SetEntityPropertyCommand(this, entity, property, value)); + } + + bool UndoableModelStrategy::spliceDataArray(Handle dataArrayEntity, int index, int length, + const QVariantList &values) { + auto &data = handle_cast(dataArrayEntity)->data; + if (index < 0 || index > data.size() || length < 0 || index + length > data.size()) { + return false; + } + m_undoStack->push(new SpliceDataArrayCommand(this, dataArrayEntity, index, length, values)); + return true; + } + + bool UndoableModelStrategy::rotateDataArray(Handle dataArrayEntity, int leftIndex, int middleIndex, + int rightIndex) { + auto &data = handle_cast(dataArrayEntity)->data; + if (leftIndex < 0 || leftIndex > data.size() || middleIndex < leftIndex || middleIndex > data.size() || + rightIndex < middleIndex || rightIndex > data.size()) { + return false; + } + m_undoStack->push(new RotateDataArrayCommand(this, dataArrayEntity, leftIndex, middleIndex, rightIndex)); + return true; + } + +} diff --git a/src/libs/application/dspxmodel/src/UndoableModelStrategy.h b/src/libs/application/dspxmodel/src/UndoableModelStrategy.h new file mode 100644 index 00000000..dfc39fa7 --- /dev/null +++ b/src/libs/application/dspxmodel/src/UndoableModelStrategy.h @@ -0,0 +1,37 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_UNDOABLEMODELSTRATEGY_H +#define DIFFSCOPE_DSPX_MODEL_UNDOABLEMODELSTRATEGY_H + +#include + +class QUndoStack; + +namespace dspx { + + class DSPX_MODEL_EXPORT UndoableModelStrategy : public BasicModelStrategy { + Q_OBJECT + public: + explicit UndoableModelStrategy(QObject *parent = nullptr); + ~UndoableModelStrategy() override; + + QUndoStack *undoStack() const; + + Handle createEntity(Entity entityType) override; + void destroyEntity(Handle entity) override; + bool insertIntoSequenceContainer(Handle sequenceContainerEntity, Handle entity) override; + bool insertIntoListContainer(Handle listContainerEntity, Handle entity, int index) override; + bool insertIntoMapContainer(Handle mapContainerEntity, Handle entity, const QString &key) override; + Handle takeFromSequenceContainer(Handle sequenceContainerEntity, Handle entity) override; + Handle takeFromListContainer(Handle listContainerEntity, int index) override; + Handle takeFromMapContainer(Handle mapContainerEntity, const QString &key) override; + bool rotateListContainer(Handle listContainerEntity, int leftIndex, int middleIndex, int rightIndex) override; + void setEntityProperty(Handle entity, Property property, const QVariant &value) override; + bool spliceDataArray(Handle dataContainerEntity, int index, int length, const QVariantList &values) override; + bool rotateDataArray(Handle dataContainerEntity, int leftIndex, int middleIndex, int rightIndex) override; + + private: + QUndoStack *m_undoStack; + }; + +} + +#endif // DIFFSCOPE_DSPX_MODEL_UNDOABLEMODELSTRATEGY_H diff --git a/src/libs/application/dspxmodel/src/UndoableModelStrategy_p.h b/src/libs/application/dspxmodel/src/UndoableModelStrategy_p.h new file mode 100644 index 00000000..dbe90368 --- /dev/null +++ b/src/libs/application/dspxmodel/src/UndoableModelStrategy_p.h @@ -0,0 +1,225 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_UNDOABLEMODELSTRATEGY_P_H +#define DIFFSCOPE_DSPX_MODEL_UNDOABLEMODELSTRATEGY_P_H + +#include +#include + +#include +#include + +namespace dspx { + + // Helper to cast handle to internal object + template + inline T *handle_cast(Handle entity) { + return reinterpret_cast(entity.d); + } + + //================================================================ + // Create/Destroy Commands + //================================================================ + + class CreateEntityCommand : public QUndoCommand { + public: + CreateEntityCommand(UndoableModelStrategy *strategy, BasicModelStrategy::Entity entityType, + QUndoCommand *parent = nullptr); + ~CreateEntityCommand() override; + void undo() override; + void redo() override; + Handle entity() const; + + private: + UndoableModelStrategy *m_strategy; + BasicModelStrategy::Entity m_entityType; + QPointer m_object; + bool m_undone = false; + }; + + class DestroyEntityCommand : public QUndoCommand { + public: + DestroyEntityCommand(UndoableModelStrategy *strategy, Handle entity, + QUndoCommand *parent = nullptr); + ~DestroyEntityCommand() override; + void undo() override; + void redo() override; + + private: + UndoableModelStrategy *m_strategy; + QPointer m_object; + bool m_undone = false; + void recursivelyDestroy(BasicModelStrategyEntity *object); + void recursivelyCreate(BasicModelStrategyEntity *object); + }; + + //================================================================ + // Container Commands + //================================================================ + + class InsertIntoSequenceContainerCommand : public QUndoCommand { + public: + InsertIntoSequenceContainerCommand(UndoableModelStrategy *strategy, + Handle sequenceContainerEntity, + Handle entity, QUndoCommand *parent = nullptr); + ~InsertIntoSequenceContainerCommand() override = default; + void undo() override; + void redo() override; + + private: + UndoableModelStrategy *m_strategy; + Handle m_container; + Handle m_entity; + }; + + class TakeFromSequenceContainerCommand : public QUndoCommand { + public: + TakeFromSequenceContainerCommand(UndoableModelStrategy *strategy, + Handle sequenceContainerEntity, + Handle entity, QUndoCommand *parent = nullptr); + ~TakeFromSequenceContainerCommand() override; + void undo() override; + void redo() override; + + private: + UndoableModelStrategy *m_strategy; + Handle m_container; + QPointer m_object; + bool m_undone = false; + }; + + class InsertIntoListContainerCommand : public QUndoCommand { + public: + InsertIntoListContainerCommand(UndoableModelStrategy *strategy, Handle listContainerEntity, + Handle entity, int index, QUndoCommand *parent = nullptr); + ~InsertIntoListContainerCommand() override = default; + void undo() override; + void redo() override; + + private: + UndoableModelStrategy *m_strategy; + Handle m_container; + Handle m_entity; + int m_index; + }; + + class TakeFromListContainerCommand : public QUndoCommand { + public: + TakeFromListContainerCommand(UndoableModelStrategy *strategy, Handle listContainerEntity, + int index, QUndoCommand *parent = nullptr); + ~TakeFromListContainerCommand() override; + void undo() override; + void redo() override; + Handle entity() const; + + private: + UndoableModelStrategy *m_strategy; + Handle m_container; + QPointer m_object; + int m_index; + bool m_undone = false; + }; + + class InsertIntoMapContainerCommand : public QUndoCommand { + public: + InsertIntoMapContainerCommand(UndoableModelStrategy *strategy, Handle mapContainerEntity, + Handle entity, const QString &key, + QUndoCommand *parent = nullptr); + ~InsertIntoMapContainerCommand() override; + void undo() override; + void redo() override; + + private: + UndoableModelStrategy *m_strategy; + Handle m_container; + Handle m_entity; + QString m_key; + QPointer m_oldObject; + }; + + class TakeFromMapContainerCommand : public QUndoCommand { + public: + TakeFromMapContainerCommand(UndoableModelStrategy *strategy, Handle mapContainerEntity, + const QString &key, QUndoCommand *parent = nullptr); + ~TakeFromMapContainerCommand() override; + void undo() override; + void redo() override; + Handle entity() const; + + private: + UndoableModelStrategy *m_strategy; + Handle m_container; + QPointer m_object; + QString m_key; + bool m_undone = false; + }; + + class RotateListContainerCommand : public QUndoCommand { + public: + RotateListContainerCommand(UndoableModelStrategy *strategy, Handle listContainerEntity, + int left, int middle, int right, QUndoCommand *parent = nullptr); + ~RotateListContainerCommand() override = default; + void undo() override; + void redo() override; + + private: + UndoableModelStrategy *m_strategy; + Handle m_container; + int m_left, m_middle, m_right; + }; + + //================================================================ + // Property & Data Commands + //================================================================ + + class SetEntityPropertyCommand : public QUndoCommand { + public: + SetEntityPropertyCommand(UndoableModelStrategy *strategy, Handle entity, + BasicModelStrategy::Property property, const QVariant &value, + QUndoCommand *parent = nullptr); + ~SetEntityPropertyCommand() override = default; + void undo() override; + void redo() override; + bool mergeWith(const QUndoCommand *command) override; + int id() const override; + + private: + UndoableModelStrategy *m_strategy; + Handle m_entity; + BasicModelStrategy::Property m_property; + QVariant m_newValue; + QVariant m_oldValue; + }; + + class SpliceDataArrayCommand : public QUndoCommand { + public: + SpliceDataArrayCommand(UndoableModelStrategy *strategy, Handle dataContainerEntity, + int index, int length, const QVariantList &values, QUndoCommand *parent = nullptr); + ~SpliceDataArrayCommand() override = default; + void undo() override; + void redo() override; + + private: + UndoableModelStrategy *m_strategy; + Handle m_container; + int m_index; + int m_length; + QVariantList m_values; + QVariantList m_oldValues; + }; + + class RotateDataArrayCommand : public QUndoCommand { + public: + RotateDataArrayCommand(UndoableModelStrategy *strategy, Handle dataContainerEntity, + int left, int middle, int right, QUndoCommand *parent = nullptr); + ~RotateDataArrayCommand() override = default; + void undo() override; + void redo() override; + + private: + UndoableModelStrategy *m_strategy; + Handle m_container; + int m_left, m_middle, m_right; + }; + +} + +#endif // DIFFSCOPE_DSPX_MODEL_UNDOABLEMODELSTRATEGY_P_H diff --git a/src/libs/application/dspxmodel/src/Vibrato.cpp b/src/libs/application/dspxmodel/src/Vibrato.cpp index 73c84e31..be5c2d3a 100644 --- a/src/libs/application/dspxmodel/src/Vibrato.cpp +++ b/src/libs/application/dspxmodel/src/Vibrato.cpp @@ -44,8 +44,9 @@ namespace dspx { void VibratoPrivate::setEnd(double end_) { Q_Q(Vibrato); - if (auto engine = qjsEngine(q); engine && (end_ < 0.0 || end_ > 1.0)) { - engine->throwError(QJSValue::RangeError, QStringLiteral("End must be in range [0, 1]")); + if ((end_ < 0.0 || end_ > 1.0)) { + if (auto engine = qjsEngine(q)) + engine->throwError(QJSValue::RangeError, QStringLiteral("End must be in range [0, 1]")); return; } setEndUnchecked(end_); @@ -58,8 +59,9 @@ namespace dspx { void VibratoPrivate::setFreq(double freq_) { Q_Q(Vibrato); - if (auto engine = qjsEngine(q); engine && freq_ < 0.0) { - engine->throwError(QJSValue::RangeError, QStringLiteral("Freq must be greater than or equal to 0")); + if (freq_ < 0.0) { + if (auto engine = qjsEngine(q)) + engine->throwError(QJSValue::RangeError, QStringLiteral("Freq must be greater than or equal to 0")); return; } setFreqUnchecked(freq_); @@ -72,8 +74,9 @@ namespace dspx { void VibratoPrivate::setPhase(double phase_) { Q_Q(Vibrato); - if (auto engine = qjsEngine(q); engine && (phase_ < 0.0 || phase_ > 1.0)) { - engine->throwError(QJSValue::RangeError, QStringLiteral("Phase must be in range [0, 1]")); + if ((phase_ < 0.0 || phase_ > 1.0)) { + if (auto engine = qjsEngine(q)) + engine->throwError(QJSValue::RangeError, QStringLiteral("Phase must be in range [0, 1]")); return; } setPhaseUnchecked(phase_); @@ -86,8 +89,9 @@ namespace dspx { void VibratoPrivate::setStart(double start_) { Q_Q(Vibrato); - if (auto engine = qjsEngine(q); engine && (start_ < 0.0 || start_ > 1.0)) { - engine->throwError(QJSValue::RangeError, QStringLiteral("Start must be in range [0, 1]")); + if ((start_ < 0.0 || start_ > 1.0)) { + if (auto engine = qjsEngine(q)) + engine->throwError(QJSValue::RangeError, QStringLiteral("Start must be in range [0, 1]")); return; } setStartUnchecked(start_); diff --git a/src/libs/application/dspxmodel/src/VibratoPointDataArray.cpp b/src/libs/application/dspxmodel/src/VibratoPointDataArray.cpp index 9ee69222..45af4508 100644 --- a/src/libs/application/dspxmodel/src/VibratoPointDataArray.cpp +++ b/src/libs/application/dspxmodel/src/VibratoPointDataArray.cpp @@ -18,6 +18,8 @@ namespace dspx { Q_ASSERT(model->strategy()->getEntityType(handle) == ModelStrategy::ED_VibratoPoints); d->q_ptr = this; d->pModel = ModelPrivate::get(model); + + d->init(model->strategy()->sliceDataArray(handle, 0, model->strategy()->getSizeOfDataArray(handle))); } VibratoPointDataArray::~VibratoPointDataArray() = default; diff --git a/src/libs/application/dspxmodel/src/Workspace.cpp b/src/libs/application/dspxmodel/src/Workspace.cpp index e9e97934..576897c8 100644 --- a/src/libs/application/dspxmodel/src/Workspace.cpp +++ b/src/libs/application/dspxmodel/src/Workspace.cpp @@ -18,6 +18,8 @@ namespace dspx { Q_ASSERT(model->strategy()->getEntityType(handle) == ModelStrategy::EM_Workspace); d->q_ptr = this; d->pModel = ModelPrivate::get(model); + + d->init(model->strategy()->getEntitiesFromMapContainer(handle)); } Workspace::~Workspace() = default; diff --git a/src/libs/application/dspxmodel/src/rangehelpers.h b/src/libs/application/dspxmodel/src/rangehelpers.h new file mode 100644 index 00000000..458160b5 --- /dev/null +++ b/src/libs/application/dspxmodel/src/rangehelpers.h @@ -0,0 +1,114 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_RANGEHELPERS_H +#define DIFFSCOPE_DSPX_MODEL_RANGEHELPERS_H + +#include + +namespace dspx::impl { + + template + class SequenceRange { + public: + SequenceRange(const SequenceType *sequence) : m_sequence(sequence) { + } + + using ItemType = std::remove_pointer_t().firstItem())>; + + class iterator { + public: + using iterator_category = std::bidirectional_iterator_tag; + using value_type = ItemType *; + using difference_type = std::ptrdiff_t; + using pointer = ItemType **; + using reference = ItemType *&; + + iterator(const SequenceType *sequence, ItemType *item) : m_sequence(sequence), m_item(item) { + } + + value_type operator*() const { + return m_item; + } + + iterator &operator++() { + if (m_item) { + m_item = m_sequence->nextItem(m_item); + } + return *this; + } + + iterator operator++(int) { + iterator temp = *this; + ++(*this); + return temp; + } + + iterator &operator--() { + if (m_item) { + m_item = m_sequence->previousItem(m_item); + } else { + m_item = m_sequence->lastItem(); + } + return *this; + } + + iterator operator--(int) { + iterator temp = *this; + --(*this); + return temp; + } + + bool operator==(const iterator &other) const { + return m_item == other.m_item; + } + + bool operator!=(const iterator &other) const { + return m_item != other.m_item; + } + + private: + const SequenceType *m_sequence; + ItemType *m_item; + }; + + using const_iterator = iterator; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + iterator begin() const { + return iterator(m_sequence, m_sequence->firstItem()); + } + + iterator end() const { + return iterator(m_sequence, nullptr); + } + + const_iterator cbegin() const { + return begin(); + } + + const_iterator cend() const { + return end(); + } + + reverse_iterator rbegin() const { + return reverse_iterator(end()); + } + + reverse_iterator rend() const { + return reverse_iterator(begin()); + } + + const_reverse_iterator crbegin() const { + return rbegin(); + } + + const_reverse_iterator crend() const { + return rend(); + } + + private: + const SequenceType *m_sequence; + }; + +} + +#endif //DIFFSCOPE_DSPX_MODEL_RANGEHELPERS_H diff --git a/src/libs/application/dspxmodel/src/selectionmodel/AnchorNodeSelectionModel.cpp b/src/libs/application/dspxmodel/src/selectionmodel/AnchorNodeSelectionModel.cpp new file mode 100644 index 00000000..7d6fd145 --- /dev/null +++ b/src/libs/application/dspxmodel/src/selectionmodel/AnchorNodeSelectionModel.cpp @@ -0,0 +1,44 @@ +#include "AnchorNodeSelectionModel.h" +#include "AnchorNodeSelectionModel_p.h" + +#include +#include +#include + +namespace dspx { + + AnchorNodeSelectionModel::AnchorNodeSelectionModel(QObject *parent) : QObject(parent), d_ptr(new AnchorNodeSelectionModelPrivate) { + Q_D(AnchorNodeSelectionModel); + d->q_ptr = this; + } + + AnchorNodeSelectionModel::~AnchorNodeSelectionModel() = default; + + AnchorNode *AnchorNodeSelectionModel::currentItem() const { + Q_D(const AnchorNodeSelectionModel); + return d->currentItem; + } + + QList AnchorNodeSelectionModel::selectedItems() const { + Q_D(const AnchorNodeSelectionModel); + return d->selectedItems; + } + + int AnchorNodeSelectionModel::selectedCount() const { + Q_D(const AnchorNodeSelectionModel); + return d->selectedItems.size(); + } + + QList AnchorNodeSelectionModel::paramCurvesAnchorWithSelectedItems() const { + Q_D(const AnchorNodeSelectionModel); + return d->paramCurvesAnchorWithSelectedItems; + } + + ParamCurveSequence *AnchorNodeSelectionModel::paramCurveSequenceWithSelectedItems() const { + Q_D(const AnchorNodeSelectionModel); + return d->paramCurveSequenceWithSelectedItems; + } + +} + +#include "moc_AnchorNodeSelectionModel.cpp" diff --git a/src/libs/application/dspxmodel/src/selectionmodel/AnchorNodeSelectionModel.h b/src/libs/application/dspxmodel/src/selectionmodel/AnchorNodeSelectionModel.h new file mode 100644 index 00000000..0ad011b5 --- /dev/null +++ b/src/libs/application/dspxmodel/src/selectionmodel/AnchorNodeSelectionModel.h @@ -0,0 +1,53 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_ANCHORNODESELECTIONMODEL_H +#define DIFFSCOPE_DSPX_MODEL_ANCHORNODESELECTIONMODEL_H + +#include +#include +#include + +#include + +namespace dspx { + + class AnchorNode; + class ParamCurveAnchor; + class ParamCurveSequence; + class AnchorNodeSelectionModelPrivate; + class SelectionModel; + + class DSPX_MODEL_EXPORT AnchorNodeSelectionModel : public QObject { + Q_OBJECT + QML_ELEMENT + Q_DECLARE_PRIVATE(AnchorNodeSelectionModel) + + Q_PROPERTY(AnchorNode *currentItem READ currentItem NOTIFY currentItemChanged) + Q_PROPERTY(QList selectedItems READ selectedItems NOTIFY selectedItemsChanged) + Q_PROPERTY(int selectedCount READ selectedCount NOTIFY selectedCountChanged) + Q_PROPERTY(QList paramCurvesAnchorWithSelectedItems READ paramCurvesAnchorWithSelectedItems NOTIFY paramCurvesAnchorWithSelectedItemsChanged) + Q_PROPERTY(ParamCurveSequence *paramCurveSequenceWithSelectedItems READ paramCurveSequenceWithSelectedItems NOTIFY paramCurveSequenceWithSelectedItemsChanged) + + public: + ~AnchorNodeSelectionModel() override; + + AnchorNode *currentItem() const; + QList selectedItems() const; + int selectedCount() const; + QList paramCurvesAnchorWithSelectedItems() const; + ParamCurveSequence *paramCurveSequenceWithSelectedItems() const; + + Q_SIGNALS: + void currentItemChanged(); + void selectedItemsChanged(); + void selectedCountChanged(); + void paramCurvesAnchorWithSelectedItemsChanged(); + void paramCurveSequenceWithSelectedItemsChanged(); + + private: + friend class SelectionModel; + explicit AnchorNodeSelectionModel(QObject *parent = nullptr); + QScopedPointer d_ptr; + }; + +} + +#endif //DIFFSCOPE_DSPX_MODEL_ANCHORNODESELECTIONMODEL_H \ No newline at end of file diff --git a/src/libs/application/dspxmodel/src/selectionmodel/AnchorNodeSelectionModel_p.h b/src/libs/application/dspxmodel/src/selectionmodel/AnchorNodeSelectionModel_p.h new file mode 100644 index 00000000..3023358e --- /dev/null +++ b/src/libs/application/dspxmodel/src/selectionmodel/AnchorNodeSelectionModel_p.h @@ -0,0 +1,20 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_ANCHORNODESELECTIONMODEL_P_H +#define DIFFSCOPE_DSPX_MODEL_ANCHORNODESELECTIONMODEL_P_H + +#include + +namespace dspx { + + class AnchorNodeSelectionModelPrivate { + Q_DECLARE_PUBLIC(AnchorNodeSelectionModel) + public: + AnchorNodeSelectionModel *q_ptr; + AnchorNode *currentItem = nullptr; + QList selectedItems; + QList paramCurvesAnchorWithSelectedItems; + ParamCurveSequence *paramCurveSequenceWithSelectedItems = nullptr; + }; + +} + +#endif //DIFFSCOPE_DSPX_MODEL_ANCHORNODESELECTIONMODEL_P_H \ No newline at end of file diff --git a/src/libs/application/dspxmodel/src/selectionmodel/ClipSelectionModel.cpp b/src/libs/application/dspxmodel/src/selectionmodel/ClipSelectionModel.cpp new file mode 100644 index 00000000..c0bfa978 --- /dev/null +++ b/src/libs/application/dspxmodel/src/selectionmodel/ClipSelectionModel.cpp @@ -0,0 +1,115 @@ +#include "ClipSelectionModel.h" +#include "ClipSelectionModel_p.h" +#include "Model.h" + +#include +#include +#include + +namespace dspx { + + bool ClipSelectionModelPrivate::isAddedToModel(Clip *item) const { + return item->clipSequence() && item->clipSequence()->track()->trackList() == selectionModel->model()->tracks(); + } + + void ClipSelectionModelPrivate::updateAssociation(Clip *item) { + Q_Q(ClipSelectionModel); + auto previousClipSequence = clipClipSequence.value(item); + auto currentClipSequence = item->clipSequence(); + if (previousClipSequence == currentClipSequence) { + return; + } + bool clipSequencesWithSelectedItemsUpdatedFlag = false; + + if (previousClipSequence) { + clipSequencesWithSelectedItems[previousClipSequence].remove(item); + if (clipSequencesWithSelectedItems[previousClipSequence].isEmpty()) { + clipSequencesWithSelectedItemsUpdatedFlag = true; + QObject::disconnect(previousClipSequence, nullptr, q, nullptr); + clipSequencesWithSelectedItems.remove(previousClipSequence); + } + } + + Q_ASSERT(currentClipSequence); + clipClipSequence.insert(item, currentClipSequence); + if (!clipSequencesWithSelectedItems.contains(currentClipSequence)) { + clipSequencesWithSelectedItemsUpdatedFlag = true; + QObject::connect(currentClipSequence->track(), &Track::trackListChanged, q, [currentClipSequence, this] { + if (currentClipSequence->track()->trackList() == selectionModel->model()->tracks()) + return; + for (auto item : clipSequencesWithSelectedItems[currentClipSequence]) { + updateOnItemRemoved(item); + } + }); + } + clipSequencesWithSelectedItems[currentClipSequence].insert(item); + + if (clipSequencesWithSelectedItemsUpdatedFlag) { + Q_EMIT q->clipSequencesWithSelectedItemsChanged(); + } + } + + void ClipSelectionModelPrivate::removeAssociation(Clip *item) { + Q_Q(ClipSelectionModel); + auto clipSequence = clipClipSequence.value(item); + bool clipSequencesWithSelectedItemsUpdatedFlag = false; + + Q_ASSERT(clipSequence); + clipSequencesWithSelectedItems[clipSequence].remove(item); + if (clipSequencesWithSelectedItems[clipSequence].isEmpty()) { + clipSequencesWithSelectedItemsUpdatedFlag = true; + QObject::disconnect(clipSequence, nullptr, q, nullptr); + clipSequencesWithSelectedItems.remove(clipSequence); + } + clipClipSequence.remove(item); + + if (clipSequencesWithSelectedItemsUpdatedFlag) { + Q_EMIT q->clipSequencesWithSelectedItemsChanged(); + } + } + + void ClipSelectionModelPrivate::clearAssociation() { + Q_Q(ClipSelectionModel); + if (clipClipSequence.isEmpty()) + return; + clipClipSequence.clear(); + clipSequencesWithSelectedItems.clear(); + Q_EMIT q->clipSequencesWithSelectedItemsChanged(); + } + + ClipSelectionModel::ClipSelectionModel(SelectionModel *parent) : QObject(parent), d_ptr(new ClipSelectionModelPrivate) { + Q_D(ClipSelectionModel); + d->q_ptr = this; + d->selectionModel = parent; + } + + ClipSelectionModel::~ClipSelectionModel() = default; + + Clip *ClipSelectionModel::currentItem() const { + Q_D(const ClipSelectionModel); + return d->currentItem; + } + + QList ClipSelectionModel::selectedItems() const { + Q_D(const ClipSelectionModel); + return d->selectedItems.values(); + } + + int ClipSelectionModel::selectedCount() const { + Q_D(const ClipSelectionModel); + return d->selectedItems.size(); + } + + QList ClipSelectionModel::clipSequencesWithSelectedItems() const { + Q_D(const ClipSelectionModel); + return d->clipSequencesWithSelectedItems.keys(); + } + + bool ClipSelectionModel::isItemSelected(Clip *item) const { + Q_D(const ClipSelectionModel); + return d->selectedItems.contains(item); + } + +} + +#include "moc_ClipSelectionModel.cpp" diff --git a/src/libs/application/dspxmodel/src/selectionmodel/ClipSelectionModel.h b/src/libs/application/dspxmodel/src/selectionmodel/ClipSelectionModel.h new file mode 100644 index 00000000..bc4b8218 --- /dev/null +++ b/src/libs/application/dspxmodel/src/selectionmodel/ClipSelectionModel.h @@ -0,0 +1,52 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_CLIPSELECTIONMODEL_H +#define DIFFSCOPE_DSPX_MODEL_CLIPSELECTIONMODEL_H + +#include +#include +#include + +#include + +namespace dspx { + + class SelectionModel; + class Clip; + class ClipSequence; + class ClipSelectionModelPrivate; + + class DSPX_MODEL_EXPORT ClipSelectionModel : public QObject { + Q_OBJECT + QML_ELEMENT + Q_DECLARE_PRIVATE(ClipSelectionModel) + + Q_PROPERTY(Clip *currentItem READ currentItem NOTIFY currentItemChanged) + Q_PROPERTY(QList selectedItems READ selectedItems NOTIFY selectedItemsChanged) + Q_PROPERTY(int selectedCount READ selectedCount NOTIFY selectedCountChanged) + Q_PROPERTY(QList clipSequencesWithSelectedItems READ clipSequencesWithSelectedItems NOTIFY clipSequencesWithSelectedItemsChanged) + + public: + ~ClipSelectionModel() override; + + Clip *currentItem() const; + QList selectedItems() const; + int selectedCount() const; + QList clipSequencesWithSelectedItems() const; + + Q_INVOKABLE bool isItemSelected(Clip *item) const; + + Q_SIGNALS: + void currentItemChanged(); + void selectedItemsChanged(); + void selectedCountChanged(); + void clipSequencesWithSelectedItemsChanged(); + void itemSelected(Clip *item, bool selected); + + private: + friend class SelectionModel; + explicit ClipSelectionModel(SelectionModel *parent = nullptr); + QScopedPointer d_ptr; + }; + +} + +#endif //DIFFSCOPE_DSPX_MODEL_CLIPSELECTIONMODEL_H \ No newline at end of file diff --git a/src/libs/application/dspxmodel/src/selectionmodel/ClipSelectionModel_p.h b/src/libs/application/dspxmodel/src/selectionmodel/ClipSelectionModel_p.h new file mode 100644 index 00000000..a5e27278 --- /dev/null +++ b/src/libs/application/dspxmodel/src/selectionmodel/ClipSelectionModel_p.h @@ -0,0 +1,32 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_CLIPSELECTIONMODEL_P_H +#define DIFFSCOPE_DSPX_MODEL_CLIPSELECTIONMODEL_P_H + +#include +#include +#include + +#include + +namespace dspx { + + class ClipSelectionModelPrivate : public GenericGlobalItemSelectionModelData< + ClipSelectionModel, + ClipSelectionModelPrivate, + Clip, + &Clip::clipSequenceChanged + > { + Q_DECLARE_PUBLIC(ClipSelectionModel) + public: + QHash clipClipSequence; + QHash> clipSequencesWithSelectedItems; + + bool isAddedToModel(Clip *item) const; + void updateAssociation(Clip *item); + void removeAssociation(Clip *item); + void clearAssociation(); + + }; + +} + +#endif //DIFFSCOPE_DSPX_MODEL_CLIPSELECTIONMODEL_P_H \ No newline at end of file diff --git a/src/libs/application/dspxmodel/src/selectionmodel/GenericGlobalItemSelectionModelData_p.h b/src/libs/application/dspxmodel/src/selectionmodel/GenericGlobalItemSelectionModelData_p.h new file mode 100644 index 00000000..ab14e939 --- /dev/null +++ b/src/libs/application/dspxmodel/src/selectionmodel/GenericGlobalItemSelectionModelData_p.h @@ -0,0 +1,197 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_GENERICGLOBALITEMSELECTIONMODELDATA_P_H +#define DIFFSCOPE_DSPX_MODEL_GENERICGLOBALITEMSELECTIONMODELDATA_P_H + +#include + +#include + +namespace dspx { + + template < + class PublicClass, + class PrivateClass, + class Item, + void (Item::*superItemChangedSignal)(), + class SuperItem = void + > + class GenericGlobalItemSelectionModelData { + public: + PublicClass *q_ptr; + SelectionModel *selectionModel; + Item *currentItem = nullptr; + QSet selectedItems; + SuperItem *superItem = nullptr; + + void select(Item *item, SelectionModel::SelectionCommand command) { + auto q = q_ptr; + bool selectionUpdatedFlag = false; + bool currentItemUpdatedFlag = false; + + if (item && !static_cast(this)->isAddedToModel(item)) { + return; + } + + if (auto currentSuperItem = static_cast(this)->getSuperItem(item); superItem && currentSuperItem && superItem != currentSuperItem) { + selectionUpdatedFlag = true; + currentItemUpdatedFlag = true; + clearSelection(); + setCurrentItem(nullptr); + static_cast(this)->clearSuperItem(); + } + + if ((command & SelectionModel::ClearPreviousSelection)) { + if (!selectedItems.isEmpty()) { + selectionUpdatedFlag = true; + } + clearSelection(); + } + + if (command & SelectionModel::SetCurrentItem) { + if (currentItem != item) { + currentItemUpdatedFlag = true; + setCurrentItem(item); + } + } + + // Update selection + if (item) { + if ((command & SelectionModel::Select) && (command & SelectionModel::Deselect)) { + // Toggle + selectionUpdatedFlag = true; + if (selectedItems.contains(item)) { + removeFromSelection(item); + } else { + addToSelection(item); + } + } else if (command & SelectionModel::Select) { + // Select only + if (!selectedItems.contains(item)) { + selectionUpdatedFlag = true; + addToSelection(item); + } + } else if (command & SelectionModel::Deselect) { + // Deselect only + if (selectedItems.contains(item)) { + selectionUpdatedFlag = true; + removeFromSelection(item); + } + } + } + + // Emit signals + if (selectionUpdatedFlag) { + Q_EMIT q->selectedItemsChanged(); + Q_EMIT q->selectedCountChanged(); + } + if (currentItemUpdatedFlag) { + Q_EMIT q->currentItemChanged(); + } + } + + void setCurrentItem(Item *item) { + auto q = q_ptr; + if (currentItem) { + if (!selectedItems.contains(currentItem)) { + QObject::disconnect(currentItem, nullptr, q, nullptr); + } + } + currentItem = item; + static_cast(this)->updateSuperItem(getSuperItem(item)); + if (item) { + if (!selectedItems.contains(item)) { + QObject::connect(item, superItemChangedSignal, q, [item, this] { + if (static_cast(this)->isAddedToModel(item)) { + static_cast(this)->updateAssociation(item); + } else { + updateOnItemRemoved(item); + } + }); + QObject::connect(item, &QObject::destroyed, q, [item, this] { + updateOnItemRemoved(item); + }); + } + } + } + + void addToSelection(Item *item) { + auto q = q_ptr; + if (currentItem != item) { + QObject::connect(item, superItemChangedSignal, q, [item, this] { + if (static_cast(this)->isAddedToModel(item)) { + static_cast(this)->updateAssociation(item); + } else { + updateOnItemRemoved(item); + } + }); + QObject::connect(item, &QObject::destroyed, q, [item, this] { + updateOnItemRemoved(item); + }); + } + selectedItems.insert(item); + static_cast(this)->updateSuperItem(getSuperItem(item)); + static_cast(this)->updateAssociation(item); + Q_EMIT q->itemSelected(item, true); + } + void removeFromSelection(Item *item) { + auto q = q_ptr; + if (currentItem != item) { + QObject::disconnect(item, nullptr, q, nullptr); + } + selectedItems.remove(item); + static_cast(this)->removeAssociation(item); + Q_EMIT q->itemSelected(item, false); + } + void clearSelection() { + auto q = q_ptr; + for (auto item : selectedItems) { + if (currentItem != item) { + QObject::disconnect(item, nullptr, q, nullptr); + } + Q_EMIT q->itemSelected(item, false); + } + selectedItems.clear(); + static_cast(this)->clearAssociation(); + } + void updateOnItemRemoved(Item *item) { + auto q = q_ptr; + bool selectionUpdatedFlag = false; + bool currentItemUpdatedFlag = false; + if (currentItem == item) { + currentItemUpdatedFlag = true; + setCurrentItem(nullptr); + } + if (selectedItems.contains(item)) { + selectionUpdatedFlag = true; + removeFromSelection(item); + } + if (selectionUpdatedFlag) { + Q_EMIT q->selectedItemsChanged(); + Q_EMIT q->selectedCountChanged(); + } + if (currentItemUpdatedFlag) { + Q_EMIT q->currentItemChanged(); + } + } + + void clearAll() { + auto q = q_ptr; + clearSelection(); + setCurrentItem(nullptr); + static_cast(this)->clearSuperItem(); + Q_EMIT q->selectedItemsChanged(); + Q_EMIT q->selectedCountChanged(); + Q_EMIT q->currentItemChanged(); + } + + static void updateAssociation(Item *) {} + static void removeAssociation(Item *) {} + static void clearAssociation() {} + static SuperItem *getSuperItem(Item *) { return nullptr; } + void clearSuperItem() { superItem = nullptr; } + void updateSuperItem(SuperItem *superItem_) { superItem = superItem_; } + + }; + +} + +#endif //DIFFSCOPE_DSPX_MODEL_GENERICGLOBALITEMSELECTIONMODELDATA_P_H diff --git a/src/libs/application/dspxmodel/src/selectionmodel/LabelSelectionModel.cpp b/src/libs/application/dspxmodel/src/selectionmodel/LabelSelectionModel.cpp new file mode 100644 index 00000000..eb832235 --- /dev/null +++ b/src/libs/application/dspxmodel/src/selectionmodel/LabelSelectionModel.cpp @@ -0,0 +1,45 @@ +#include "LabelSelectionModel.h" +#include "LabelSelectionModel_p.h" + +#include +#include +#include +#include + +namespace dspx { + + bool LabelSelectionModelPrivate::isAddedToModel(Label *item) const { + return item->labelSequence() == selectionModel->model()->timeline()->labels(); + } + + LabelSelectionModel::LabelSelectionModel(SelectionModel *parent) : QObject(parent), d_ptr(new LabelSelectionModelPrivate) { + Q_D(LabelSelectionModel); + d->q_ptr = this; + d->selectionModel = parent; + } + + LabelSelectionModel::~LabelSelectionModel() = default; + + Label *LabelSelectionModel::currentItem() const { + Q_D(const LabelSelectionModel); + return d->currentItem; + } + + QList