diff --git a/.github/actions/initialize-build-environment/action.yml b/.github/actions/initialize-build-environment/action.yml index 18aed6d3..12e10df1 100644 --- a/.github/actions/initialize-build-environment/action.yml +++ b/.github/actions/initialize-build-environment/action.yml @@ -39,7 +39,7 @@ 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 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 index f46f57d3..c4dd5544 100644 --- a/scripts/ci/Create-DMG.ps1 +++ b/scripts/ci/Create-DMG.ps1 @@ -32,6 +32,8 @@ 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" @@ -87,16 +89,24 @@ try { 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 $AppPath -Leaf)" 132 280 ` + --icon "$(Split-Path $AppBundlePath -Leaf)" 132 280 ` --app-drop-link 468 280 ` "$DmgPath" ` - "$AppPath" | Write-Host + "$AppBundlePath" | Write-Host if ($LASTEXITCODE -ne 0) { throw "create-dmg failed" diff --git a/scripts/vcpkg b/scripts/vcpkg index 5e0443fe..cf97d45c 160000 --- a/scripts/vcpkg +++ b/scripts/vcpkg @@ -1 +1 @@ -Subproject commit 5e0443fea12ce12f165fb16d7546b20a64894615 +Subproject commit cf97d45c7731dbe4a739a29fe7f8b9434c0e4d46 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..ee7fe48a 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -17,6 +18,9 @@ #include #include +#include +#include + #include #include @@ -26,6 +30,8 @@ #include #include +#include + #ifdef APPLICATION_ENABLE_BREAKPAD # include #endif @@ -39,6 +45,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 +63,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 +115,25 @@ 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")); + + for (auto pluginSpec : ExtensionSystem::PluginManager::plugins()) { + static QRegularExpression rx("^([a-z_][a-z0-9_]*)(\\.[a-z_][a-z0-9_]*)+$"); + if (!rx.match(pluginSpec->name()).hasMatch()) { + qFatal() << "Refused to load due to an invalid plugin name:" << pluginSpec->name() << "Plugin name should match" << rx.pattern(); + } + } - 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({ @@ -182,5 +209,13 @@ int main(int argc, char *argv[]) { QQuickStyle::setFallbackStyle("Basic"); QApplication::setStyle(QStyleFactory::create("Fusion")); + // Check QML import + { + QQmlComponent component(engine, "DiffScope.UIShell", "ProjectWindow"); + if (component.isError()) { + qFatal().nospace() << "QML Import Check Failed: " << component.errorString() << "\n\n" << "Note for developers: If you encounter this error when running after building DiffScope, please check:\n- Whether all targets have been built\n- Whether the correct QML_IMPORT_PATH environment variable was specified at runtime (it may need to be set to `../qml`)"; + } + } + return MyLoaderSpec().run(); } diff --git a/src/libs/3rdparty/choruskit b/src/libs/3rdparty/choruskit index ead08258..e40b3316 160000 --- a/src/libs/3rdparty/choruskit +++ b/src/libs/3rdparty/choruskit @@ -1 +1 @@ -Subproject commit ead082581b7116763a27e29f733fa13e4edb963b +Subproject commit e40b331652f9a81eea9be505305dbb198abd8dae diff --git a/src/libs/3rdparty/opendspx b/src/libs/3rdparty/opendspx index 0a4bb52e..333b2b8f 160000 --- a/src/libs/3rdparty/opendspx +++ b/src/libs/3rdparty/opendspx @@ -1 +1 @@ -Subproject commit 0a4bb52e0f199d74d288eda2c9c41bfe2d34d80d +Subproject commit 333b2b8f422688c707dfa1f2d48f71cadede3ad4 diff --git a/src/libs/3rdparty/qactionkit b/src/libs/3rdparty/qactionkit index 42b9cd90..573def48 160000 --- a/src/libs/3rdparty/qactionkit +++ b/src/libs/3rdparty/qactionkit @@ -1 +1 @@ -Subproject commit 42b9cd9053d76aeb8d66728a650827d97f40f11a +Subproject commit 573def486e3f8c0f62768bcdcbc490b24cbd59af diff --git a/src/libs/3rdparty/scopicflow b/src/libs/3rdparty/scopicflow index 06238363..23e75d14 160000 --- a/src/libs/3rdparty/scopicflow +++ b/src/libs/3rdparty/scopicflow @@ -1 +1 @@ -Subproject commit 062383639e43b2baa9c3ae20674c8dd153d3a73e +Subproject commit 23e75d14a6ad6006de3efcfbbc2dfc2119c20cad diff --git a/src/libs/3rdparty/svscraft b/src/libs/3rdparty/svscraft index 70fab799..161777b6 160000 --- a/src/libs/3rdparty/svscraft +++ b/src/libs/3rdparty/svscraft @@ -1 +1 @@ -Subproject commit 70fab7997428056a557a82a8e3dce37941c15795 +Subproject commit 161777b66cee8b6cb3213f3e77c564125c3301bf diff --git a/src/libs/application/dspxmodel/src/AnchorNode.cpp b/src/libs/application/dspxmodel/src/AnchorNode.cpp index 22eaba9f..bacf4382 100644 --- a/src/libs/application/dspxmodel/src/AnchorNode.cpp +++ b/src/libs/application/dspxmodel/src/AnchorNode.cpp @@ -1,7 +1,6 @@ #include "AnchorNode.h" #include "AnchorNode_p.h" -#include #include #include @@ -12,39 +11,27 @@ namespace dspx { - void AnchorNodePrivate::setInterpUnchecked(AnchorNode::InterpolationMode interp_) { - Q_Q(AnchorNode); - q->model()->strategy()->setEntityProperty(q->handle(), ModelStrategy::P_Type, QVariant::fromValue(interp_)); - } - - 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")); - return; + void AnchorNodePrivate::setAnchorNodeSequence(AnchorNode *item, AnchorNodeSequence *anchorNodeSequence) { + auto d = item->d_func(); + if (d->anchorNodeSequence != anchorNodeSequence) { + d->anchorNodeSequence = anchorNodeSequence; + Q_EMIT item->anchorNodeSequenceChanged(); } - setInterpUnchecked(interp_); } - void AnchorNodePrivate::setXUnchecked(int x_) { - Q_Q(AnchorNode); - q->model()->strategy()->setEntityProperty(q->handle(), ModelStrategy::P_Position, x_); - } - - 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")); - return; + void AnchorNodePrivate::setPreviousItem(AnchorNode *item, AnchorNode *previousItem) { + auto d = item->d_func(); + if (d->previousItem != previousItem) { + d->previousItem = previousItem; + Q_EMIT item->previousItemChanged(); } - setXUnchecked(x_); } - void AnchorNodePrivate::setAnchorNodeSequence(AnchorNode *item, AnchorNodeSequence *anchorNodeSequence) { + void AnchorNodePrivate::setNextItem(AnchorNode *item, AnchorNode *nextItem) { auto d = item->d_func(); - if (d->anchorNodeSequence != anchorNodeSequence) { - d->anchorNodeSequence = anchorNodeSequence; - Q_EMIT item->anchorNodeSequenceChanged(); + if (d->nextItem != nextItem) { + d->nextItem = nextItem; + Q_EMIT item->nextItemChanged(); } } @@ -68,7 +55,7 @@ namespace dspx { void AnchorNode::setInterp(InterpolationMode interp) { Q_D(AnchorNode); Q_ASSERT(interp == None || interp == Linear || interp == Hermite); - d->setInterpUnchecked(interp); + model()->strategy()->setEntityProperty(handle(), ModelStrategy::P_Type, QVariant::fromValue(interp)); } int AnchorNode::x() const { @@ -79,7 +66,7 @@ namespace dspx { void AnchorNode::setX(int x) { Q_D(AnchorNode); Q_ASSERT(x >= 0); - d->setXUnchecked(x); + model()->strategy()->setEntityProperty(handle(), ModelStrategy::P_Position, x); } int AnchorNode::y() const { @@ -113,6 +100,16 @@ namespace dspx { return d->anchorNodeSequence; } + AnchorNode *AnchorNode::previousItem() const { + Q_D(const AnchorNode); + return d->previousItem; + } + + AnchorNode *AnchorNode::nextItem() const { + Q_D(const AnchorNode); + return d->nextItem; + } + 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 4eaef0e8..226043f2 100644 --- a/src/libs/application/dspxmodel/src/AnchorNode.h +++ b/src/libs/application/dspxmodel/src/AnchorNode.h @@ -22,10 +22,12 @@ namespace dspx { QML_ELEMENT QML_UNCREATABLE("") Q_DECLARE_PRIVATE(AnchorNode) - 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(InterpolationMode interp READ interp WRITE setInterp NOTIFY interpChanged) + Q_PROPERTY(int x READ x WRITE setX NOTIFY xChanged) Q_PROPERTY(int y READ y WRITE setY NOTIFY yChanged) Q_PROPERTY(AnchorNodeSequence *anchorNodeSequence READ anchorNodeSequence NOTIFY anchorNodeSequenceChanged) + Q_PROPERTY(AnchorNode *previousItem READ previousItem NOTIFY previousItemChanged) + Q_PROPERTY(AnchorNode *nextItem READ nextItem NOTIFY nextItemChanged) public: enum InterpolationMode { @@ -50,12 +52,16 @@ namespace dspx { void fromQDspx(const QDspx::AnchorNode &node); AnchorNodeSequence *anchorNodeSequence() const; + AnchorNode *previousItem() const; + AnchorNode *nextItem() const; Q_SIGNALS: void interpChanged(InterpolationMode interp); void xChanged(int x); void yChanged(int y); void anchorNodeSequenceChanged(); + void previousItemChanged(); + void nextItemChanged(); 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 2d6e9d87..15f8f19d 100644 --- a/src/libs/application/dspxmodel/src/AnchorNodeSequence.cpp +++ b/src/libs/application/dspxmodel/src/AnchorNodeSequence.cpp @@ -22,13 +22,6 @@ namespace dspx { 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; diff --git a/src/libs/application/dspxmodel/src/AnchorNodeSequence_p.h b/src/libs/application/dspxmodel/src/AnchorNodeSequence_p.h index 8b27bf67..24cb0f23 100644 --- a/src/libs/application/dspxmodel/src/AnchorNodeSequence_p.h +++ b/src/libs/application/dspxmodel/src/AnchorNodeSequence_p.h @@ -8,7 +8,14 @@ namespace dspx { - class AnchorNodeSequencePrivate : public PointSequenceData { + class AnchorNodeSequencePrivate : public PointSequenceData< + AnchorNodeSequence, + AnchorNode, + &AnchorNode::x, + &AnchorNode::xChanged, + &AnchorNodePrivate::setAnchorNodeSequence, + &AnchorNodePrivate::setPreviousItem, + &AnchorNodePrivate::setNextItem> { Q_DECLARE_PUBLIC(AnchorNodeSequence) public: ParamCurveAnchor *paramCurveAnchor{}; diff --git a/src/libs/application/dspxmodel/src/AnchorNode_p.h b/src/libs/application/dspxmodel/src/AnchorNode_p.h index 3b2704b6..ec8af70a 100644 --- a/src/libs/application/dspxmodel/src/AnchorNode_p.h +++ b/src/libs/application/dspxmodel/src/AnchorNode_p.h @@ -13,13 +13,12 @@ namespace dspx { int x; int y; AnchorNodeSequence *anchorNodeSequence{}; - - void setInterpUnchecked(AnchorNode::InterpolationMode interp_); - void setInterp(AnchorNode::InterpolationMode interp_); - void setXUnchecked(int x_); - void setX(int x_); + AnchorNode *previousItem{}; + AnchorNode *nextItem{}; static void setAnchorNodeSequence(AnchorNode *item, AnchorNodeSequence *anchorNodeSequence); + static void setPreviousItem(AnchorNode *item, AnchorNode *previousItem); + static void setNextItem(AnchorNode *item, AnchorNode *nextItem); }; } diff --git a/src/libs/application/dspxmodel/src/BasicModelStrategy.cpp b/src/libs/application/dspxmodel/src/BasicModelStrategy.cpp index 0e6e8a67..a88d80df 100644 --- a/src/libs/application/dspxmodel/src/BasicModelStrategy.cpp +++ b/src/libs/application/dspxmodel/src/BasicModelStrategy.cpp @@ -96,6 +96,20 @@ namespace dspx { return true; } + bool BasicModelStrategy::moveToAnotherSequenceContainer(Handle sequenceContainerEntity, Handle entity, Handle otherSequenceContainerEntity) { + auto sequenceContainerObject = handleCast(sequenceContainerEntity); + auto otherSequenceContainerObject = handleCast(otherSequenceContainerEntity); + auto object = reinterpret_cast(entity.d); + if (!sequenceContainerObject->sequence.contains(object)) { + return false; + } + sequenceContainerObject->sequence.remove(object); + otherSequenceContainerObject->sequence.insert(object); + object->setParent(otherSequenceContainerObject); + Q_EMIT moveToAnotherSequenceContainerNotified(sequenceContainerEntity, entity, otherSequenceContainerEntity); + return true; + } + Handle BasicModelStrategy::takeFromSequenceContainer(Handle sequenceContainerEntity, Handle entity) { auto sequenceContainerObject = handleCast(sequenceContainerEntity); auto object = reinterpret_cast(entity.d); diff --git a/src/libs/application/dspxmodel/src/BasicModelStrategy.h b/src/libs/application/dspxmodel/src/BasicModelStrategy.h index 9f638397..0cf44804 100644 --- a/src/libs/application/dspxmodel/src/BasicModelStrategy.h +++ b/src/libs/application/dspxmodel/src/BasicModelStrategy.h @@ -20,6 +20,7 @@ namespace dspx { 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; + bool moveToAnotherSequenceContainer(Handle sequenceContainerEntity, Handle entity, Handle otherSequenceContainerEntity) override; Handle takeFromSequenceContainer(Handle sequenceContainerEntity, Handle entity) override; Handle takeFromListContainer(Handle listContainerEntity, int index) override; Handle takeFromMapContainer(Handle mapContainerEntity, const QString &key) override; diff --git a/src/libs/application/dspxmodel/src/Clip.cpp b/src/libs/application/dspxmodel/src/Clip.cpp index 4caf4492..a8ec4f8a 100644 --- a/src/libs/application/dspxmodel/src/Clip.cpp +++ b/src/libs/application/dspxmodel/src/Clip.cpp @@ -32,6 +32,22 @@ namespace dspx { } } + void ClipPrivate::setPreviousItem(Clip *item, Clip *previousItem) { + auto d = item->d_func(); + if (d->previousItem != previousItem) { + d->previousItem = previousItem; + Q_EMIT item->previousItemChanged(); + } + } + + void ClipPrivate::setNextItem(Clip *item, Clip *nextItem) { + auto d = item->d_func(); + if (d->nextItem != nextItem) { + d->nextItem = nextItem; + Q_EMIT item->nextItemChanged(); + } + } + Clip::Clip(ClipType type, Handle handle, Model *model) : EntityObject(handle, model), d_ptr(new ClipPrivate) { Q_D(Clip); d->q_ptr = this; @@ -115,6 +131,16 @@ namespace dspx { return d->clipSequence; } + Clip *Clip::previousItem() const { + Q_D(const Clip); + return d->previousItem; + } + + Clip *Clip::nextItem() const { + Q_D(const Clip); + return d->nextItem; + } + int Clip::position() const { Q_D(const Clip); return d->time->start() + d->time->clipStart(); diff --git a/src/libs/application/dspxmodel/src/Clip.h b/src/libs/application/dspxmodel/src/Clip.h index c35024e9..4d304d3f 100644 --- a/src/libs/application/dspxmodel/src/Clip.h +++ b/src/libs/application/dspxmodel/src/Clip.h @@ -30,6 +30,8 @@ namespace dspx { Q_PROPERTY(ClipType type READ type CONSTANT) Q_PROPERTY(Workspace *workspace READ workspace CONSTANT) Q_PROPERTY(ClipSequence *clipSequence READ clipSequence NOTIFY clipSequenceChanged) + Q_PROPERTY(Clip *previousItem READ previousItem NOTIFY previousItemChanged) + Q_PROPERTY(Clip *nextItem READ nextItem NOTIFY nextItemChanged) Q_PROPERTY(bool overlapped READ isOverlapped NOTIFY overlappedChanged) public: enum ClipType { @@ -56,6 +58,9 @@ namespace dspx { ClipSequence *clipSequence() const; + Clip *previousItem() const; + Clip *nextItem() const; + int position() const; int length() const; @@ -65,6 +70,8 @@ namespace dspx { Q_SIGNALS: void nameChanged(const QString &name); void clipSequenceChanged(); + void previousItemChanged(); + void nextItemChanged(); void positionChanged(int position); void lengthChanged(int length); void overlappedChanged(bool overlapped); diff --git a/src/libs/application/dspxmodel/src/ClipSequence.cpp b/src/libs/application/dspxmodel/src/ClipSequence.cpp index 93f6a778..16c2fb08 100644 --- a/src/libs/application/dspxmodel/src/ClipSequence.cpp +++ b/src/libs/application/dspxmodel/src/ClipSequence.cpp @@ -16,6 +16,67 @@ namespace dspx { + void ClipSequencePrivate::handleMoveFromAnotherSequenceContainer(Handle entity, Handle otherSequenceContainerEntity) { + auto q = q_ptr; + auto item = getItem(entity, true); + if (!pointContainer.contains(item)) { + QObject::connect(item, &Clip::positionChanged, q, [=](int pos) { + int length = item->length(); + insertItem(item, pos, length); + }); + QObject::connect(item, &Clip::lengthChanged, q, [=](int len) { + int position = item->position(); + insertItem(item, position, len); + }); + QObject::connect(item, &QObject::destroyed, q, [=] { + removeItem(item); + }); + } + auto otherClipSequence = qobject_cast(pModel->mapToObject(otherSequenceContainerEntity)); + bool containsItem = pointContainer.contains(item); + if (!containsItem) { + Q_EMIT q->itemAboutToInsert(item, otherClipSequence); + } + + pointContainer.insertItem(item, item->position()); + ClipPrivate::setClipSequence(item, q); + auto affectedItems = rangeContainer.insertItem(item, item->position(), item->length()); + + for (auto affectedItem : affectedItems) { + bool isOverlapped = rangeContainer.isOverlapped(affectedItem); + ClipPrivate::setOverlapped(affectedItem, isOverlapped); + } + + if (!containsItem) { + updateFirstAndLastItem(); + Q_EMIT q->itemInserted(item, otherClipSequence); + Q_EMIT q->sizeChanged(pointContainer.size()); + } + } + + void ClipSequencePrivate::handleMoveToAnotherSequenceContainer(Handle entity, Handle otherSequenceContainerEntity) { + auto q = q_ptr; + auto item = getItem(entity, false); + if (item) { + QObject::disconnect(item, nullptr, q, nullptr); + auto otherClipSequence = qobject_cast(pModel->mapToObject(otherSequenceContainerEntity)); + Q_EMIT q->itemAboutToRemove(item, otherClipSequence); + + pointContainer.removeItem(item); + ClipPrivate::setClipSequence(item, otherClipSequence); + auto affectedItems = rangeContainer.removeItem(item); + + for (auto affectedItem : affectedItems) { + bool isOverlapped = rangeContainer.isOverlapped(affectedItem); + ClipPrivate::setOverlapped(affectedItem, isOverlapped); + } + + updateFirstAndLastItem(); + Q_EMIT q->itemRemoved(item, otherClipSequence); + Q_EMIT q->sizeChanged(pointContainer.size()); + } + } + 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); @@ -24,13 +85,6 @@ namespace dspx { d->track = track; d->init(model->strategy()->getEntitiesFromSequenceContainer(handle)); - - connect(this, &ClipSequence::itemInserted, this, [=](Clip *item) { - ClipPrivate::setClipSequence(item, this); - }); - connect(this, &ClipSequence::itemRemoved, this, [=](Clip *item) { - ClipPrivate::setClipSequence(item, nullptr); - }); } ClipSequence::~ClipSequence() = default; @@ -85,6 +139,11 @@ namespace dspx { return d->pModel->strategy->takeFromSequenceContainer(handle(), item->handle()); } + bool ClipSequence::moveToAnotherClipSequence(Clip *item, ClipSequence *sequence) { + Q_D(ClipSequence); + return d->pModel->strategy->moveToAnotherSequenceContainer(handle(), item->handle(), sequence->handle()); + } + QList ClipSequence::toQDspx() const { Q_D(const ClipSequence); QList ret; @@ -125,6 +184,14 @@ namespace dspx { Q_D(ClipSequence); d->handleTakeFromSequenceContainer(takenEntity, entity); } + void ClipSequence::handleMoveFromAnotherSequenceContainer(Handle entity, Handle otherSequenceContainerEntity) { + Q_D(ClipSequence); + d->handleMoveFromAnotherSequenceContainer(entity, otherSequenceContainerEntity); + } + void ClipSequence::handleMoveToAnotherSequenceContainer(Handle entity, Handle otherSequenceContainerEntity) { + Q_D(ClipSequence); + d->handleMoveToAnotherSequenceContainer(entity, otherSequenceContainerEntity); + } } diff --git a/src/libs/application/dspxmodel/src/ClipSequence.h b/src/libs/application/dspxmodel/src/ClipSequence.h index 26ae777c..6a9123f8 100644 --- a/src/libs/application/dspxmodel/src/ClipSequence.h +++ b/src/libs/application/dspxmodel/src/ClipSequence.h @@ -41,6 +41,7 @@ namespace dspx { Q_INVOKABLE bool insertItem(Clip *item); Q_INVOKABLE bool removeItem(Clip *item); + Q_INVOKABLE bool moveToAnotherClipSequence(Clip *item, ClipSequence *sequence); QList toQDspx() const; void fromQDspx(const QList &clips); @@ -52,10 +53,10 @@ namespace dspx { } Q_SIGNALS: - void itemAboutToInsert(Clip *item); - void itemInserted(Clip *item); - void itemAboutToRemove(Clip *item); - void itemRemoved(Clip *item); + void itemAboutToInsert(Clip *item, ClipSequence *clipSequenceFromWhichMoved = nullptr); + void itemInserted(Clip *item, ClipSequence *clipSequenceFromWhichMoved = nullptr); + void itemAboutToRemove(Clip *item, ClipSequence *clipSequenceToWhichMoved = nullptr); + void itemRemoved(Clip *item, ClipSequence *clipSequenceToWhichMoved = nullptr); void sizeChanged(int size); void firstItemChanged(Clip *item); void lastItemChanged(Clip *item); @@ -63,6 +64,8 @@ namespace dspx { protected: void handleInsertIntoSequenceContainer(Handle entity) override; void handleTakeFromSequenceContainer(Handle takenEntity, Handle entity) override; + void handleMoveFromAnotherSequenceContainer(Handle entity, Handle otherSequenceContainerEntity) override; + void handleMoveToAnotherSequenceContainer(Handle entity, Handle otherSequenceContainerEntity) override; private: friend class ModelPrivate; diff --git a/src/libs/application/dspxmodel/src/ClipSequence_p.h b/src/libs/application/dspxmodel/src/ClipSequence_p.h index b6c27acb..78f7d8cd 100644 --- a/src/libs/application/dspxmodel/src/ClipSequence_p.h +++ b/src/libs/application/dspxmodel/src/ClipSequence_p.h @@ -8,10 +8,13 @@ namespace dspx { - class ClipSequencePrivate : public RangeSequenceData { + class ClipSequencePrivate : public RangeSequenceData { Q_DECLARE_PUBLIC(ClipSequence) public: Track *track{}; + + void handleMoveFromAnotherSequenceContainer(Handle entity, Handle otherSequenceContainerEntity); + void handleMoveToAnotherSequenceContainer(Handle entity, Handle otherSequenceContainerEntity); }; } diff --git a/src/libs/application/dspxmodel/src/ClipTime.cpp b/src/libs/application/dspxmodel/src/ClipTime.cpp index 6e80b886..50df3a7a 100644 --- a/src/libs/application/dspxmodel/src/ClipTime.cpp +++ b/src/libs/application/dspxmodel/src/ClipTime.cpp @@ -1,6 +1,4 @@ #include "ClipTime.h" - -#include #include #include @@ -21,56 +19,9 @@ namespace dspx { int length; int clipStart; int clipLen; - - void setLengthUnchecked(int length_); - void setLength(int length_); - void setClipStartUnchecked(int clipStart_); - void setClipStart(int clipStart_); - void setClipLenUnchecked(int clipLen_); - void setClipLen(int clipLen_); }; - void ClipTimePrivate::setLengthUnchecked(int length_) { - Q_Q(ClipTime); - pModel->strategy->setEntityProperty(handle, ModelStrategy::P_Length, length_); - } - - 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")); - return; - } - setLengthUnchecked(length_); - } - - void ClipTimePrivate::setClipStartUnchecked(int clipStart_) { - Q_Q(ClipTime); - pModel->strategy->setEntityProperty(handle, ModelStrategy::P_ClipStart, clipStart_); - } - 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")); - return; - } - setClipStartUnchecked(clipStart_); - } - - void ClipTimePrivate::setClipLenUnchecked(int clipLen_) { - Q_Q(ClipTime); - pModel->strategy->setEntityProperty(handle, ModelStrategy::P_ClipLength, clipLen_); - } - - 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")); - return; - } - setClipLenUnchecked(clipLen_); - } ClipTime::ClipTime(Handle handle, Model *model) : QObject(model), d_ptr(new ClipTimePrivate) { Q_D(ClipTime); @@ -103,7 +54,7 @@ namespace dspx { void ClipTime::setLength(int length) { Q_D(ClipTime); Q_ASSERT(length >= 0); - d->setLengthUnchecked(length); + d->pModel->strategy->setEntityProperty(d->handle, ModelStrategy::P_Length, length); } int ClipTime::clipStart() const { @@ -114,7 +65,7 @@ namespace dspx { void ClipTime::setClipStart(int clipStart) { Q_D(ClipTime); Q_ASSERT(clipStart >= 0); - d->setClipStartUnchecked(clipStart); + d->pModel->strategy->setEntityProperty(d->handle, ModelStrategy::P_ClipStart, clipStart); } int ClipTime::clipLen() const { @@ -125,7 +76,7 @@ namespace dspx { void ClipTime::setClipLen(int clipLen) { Q_D(ClipTime); Q_ASSERT(clipLen >= 0); - d->setClipLenUnchecked(clipLen); + d->pModel->strategy->setEntityProperty(d->handle, ModelStrategy::P_ClipLength, clipLen); } QDspx::ClipTime ClipTime::toQDspx() const { diff --git a/src/libs/application/dspxmodel/src/ClipTime.h b/src/libs/application/dspxmodel/src/ClipTime.h index a4621347..20375cdb 100644 --- a/src/libs/application/dspxmodel/src/ClipTime.h +++ b/src/libs/application/dspxmodel/src/ClipTime.h @@ -18,15 +18,15 @@ namespace dspx { class ClipTimePrivate; - class ClipTime : public QObject { + class DSPX_MODEL_EXPORT ClipTime : public QObject { Q_OBJECT QML_ELEMENT QML_UNCREATABLE("") Q_DECLARE_PRIVATE(ClipTime) Q_PROPERTY(int start READ start WRITE setStart NOTIFY startChanged) - Q_PRIVATE_PROPERTY(d_func(), int length MEMBER length WRITE setLength NOTIFY lengthChanged) - Q_PRIVATE_PROPERTY(d_func(), int clipStart MEMBER clipStart WRITE setClipStart NOTIFY clipStartChanged) - Q_PRIVATE_PROPERTY(d_func(), int clipLen MEMBER clipLen WRITE setClipLen NOTIFY clipLenChanged) + Q_PROPERTY(int length READ length WRITE setLength NOTIFY lengthChanged) + Q_PROPERTY(int clipStart READ clipStart WRITE setClipStart NOTIFY clipStartChanged) + Q_PROPERTY(int clipLen READ clipLen WRITE setClipLen NOTIFY clipLenChanged) public: ~ClipTime() override; diff --git a/src/libs/application/dspxmodel/src/Clip_p.h b/src/libs/application/dspxmodel/src/Clip_p.h index e3a3c02c..a2e8cba5 100644 --- a/src/libs/application/dspxmodel/src/Clip_p.h +++ b/src/libs/application/dspxmodel/src/Clip_p.h @@ -15,11 +15,15 @@ namespace dspx { ClipTime *time; Clip::ClipType type; ClipSequence *clipSequence{}; + Clip *previousItem{}; + Clip *nextItem{}; Workspace *workspace; bool overlapped{}; static void setOverlapped(Clip *item, bool overlapped); static void setClipSequence(Clip *item, ClipSequence *clipSequence); + static void setPreviousItem(Clip *item, Clip *previousItem); + static void setNextItem(Clip *item, Clip *nextItem); }; } diff --git a/src/libs/application/dspxmodel/src/Control.cpp b/src/libs/application/dspxmodel/src/Control.cpp index d0c823ca..4d8beb45 100644 --- a/src/libs/application/dspxmodel/src/Control.cpp +++ b/src/libs/application/dspxmodel/src/Control.cpp @@ -1,6 +1,4 @@ #include "Control.h" - -#include #include #include @@ -19,40 +17,10 @@ namespace dspx { double pan; bool mute; - void setGainUnchecked(double gain_); - void setGain(double gain_); - void setPanUnchecked(double pan_); - void setPan(double pan_); }; - void ControlPrivate::setGainUnchecked(double gain_) { - Q_Q(Control); - pModel->strategy->setEntityProperty(handle, ModelStrategy::P_ControlGain, gain_); - } - - 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")); - return; - } - setGainUnchecked(gain_); - } - void ControlPrivate::setPanUnchecked(double pan_) { - Q_Q(Control); - pModel->strategy->setEntityProperty(handle, ModelStrategy::P_ControlPan, pan_); - } - - 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]")); - return; - } - setPanUnchecked(pan_); - } Control::Control(Handle handle, Model *model) : QObject(model), d_ptr(new ControlPrivate) { Q_D(Control); @@ -74,7 +42,7 @@ namespace dspx { void Control::setGain(double gain) { Q_D(Control); Q_ASSERT(gain >= 0.0); - d->setGainUnchecked(gain); + d->pModel->strategy->setEntityProperty(d->handle, ModelStrategy::P_ControlGain, gain); } double Control::pan() const { @@ -85,7 +53,7 @@ namespace dspx { void Control::setPan(double pan) { Q_D(Control); Q_ASSERT(pan >= -1.0 && pan <= 1.0); - d->setPanUnchecked(pan); + d->pModel->strategy->setEntityProperty(d->handle, ModelStrategy::P_ControlPan, pan); } bool Control::mute() const { diff --git a/src/libs/application/dspxmodel/src/Control.h b/src/libs/application/dspxmodel/src/Control.h index 20135658..d0330ec7 100644 --- a/src/libs/application/dspxmodel/src/Control.h +++ b/src/libs/application/dspxmodel/src/Control.h @@ -14,13 +14,13 @@ namespace dspx { class ControlPrivate; - class Control : public QObject { + class DSPX_MODEL_EXPORT Control : public QObject { Q_OBJECT QML_ELEMENT QML_UNCREATABLE("") Q_DECLARE_PRIVATE(Control) - Q_PRIVATE_PROPERTY(d_func(), double gain MEMBER gain WRITE setGain NOTIFY gainChanged) - Q_PRIVATE_PROPERTY(d_func(), double pan MEMBER pan WRITE setPan NOTIFY panChanged) + Q_PROPERTY(double gain READ gain WRITE setGain NOTIFY gainChanged) + Q_PROPERTY(double pan READ pan WRITE setPan NOTIFY panChanged) Q_PROPERTY(bool mute READ mute WRITE setMute NOTIFY muteChanged) public: diff --git a/src/libs/application/dspxmodel/src/EntityObject.cpp b/src/libs/application/dspxmodel/src/EntityObject.cpp index a42f104a..75019821 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) { - + Q_ASSERT(false && "EntityObject::~EntityObject: handle is not null. You should call Model::destroyItem() to delete EntityObject."); } } @@ -45,6 +45,12 @@ namespace dspx { void EntityObject::handleInsertIntoMapContainer(Handle entity, const QString &key) { Q_UNREACHABLE(); } + void EntityObject::handleMoveToAnotherSequenceContainer(Handle entity, Handle otherSequenceContainerEntity) { + Q_UNREACHABLE(); + } + void EntityObject::handleMoveFromAnotherSequenceContainer(Handle entity, Handle otherSequenceContainerEntity) { + Q_UNREACHABLE(); + } void EntityObject::handleTakeFromSequenceContainer(Handle takenEntity, Handle entity) { Q_UNREACHABLE(); } diff --git a/src/libs/application/dspxmodel/src/EntityObject.h b/src/libs/application/dspxmodel/src/EntityObject.h index 1d9e89c4..c4cb85a8 100644 --- a/src/libs/application/dspxmodel/src/EntityObject.h +++ b/src/libs/application/dspxmodel/src/EntityObject.h @@ -29,6 +29,9 @@ namespace dspx { virtual void handleInsertIntoListContainer(Handle entities, int index); virtual void handleInsertIntoMapContainer(Handle entity, const QString &key); + virtual void handleMoveToAnotherSequenceContainer(Handle entity, Handle otherSequenceContainerEntity); + virtual void handleMoveFromAnotherSequenceContainer(Handle entity, Handle otherSequenceContainerEntity); + virtual void handleTakeFromSequenceContainer(Handle takenEntity, Handle entity); virtual void handleTakeFromListContainer(Handle takenEntities, int index); virtual void handleTakeFromMapContainer(Handle takenEntity, const QString &key); diff --git a/src/libs/application/dspxmodel/src/Global.cpp b/src/libs/application/dspxmodel/src/Global.cpp index e687463f..fd35f5e5 100644 --- a/src/libs/application/dspxmodel/src/Global.cpp +++ b/src/libs/application/dspxmodel/src/Global.cpp @@ -1,6 +1,4 @@ #include "Global.h" - -#include #include #include @@ -17,30 +15,8 @@ namespace dspx { Global *q_ptr; ModelPrivate *pModel; Handle handle; - - int centShift() const; - void setCentShiftUnchecked(int centShift); - void setCentShift(int centShift); }; - int GlobalPrivate::centShift() const { - return pModel->centShift; - } - - void GlobalPrivate::setCentShiftUnchecked(int centShift) { - Q_Q(Global); - pModel->strategy->setEntityProperty(handle, ModelStrategy::P_CentShift, centShift); - } - - 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]")); - return; - } - setCentShiftUnchecked(centShift); - } - Global::Global(Model *model) : QObject(model), d_ptr(new GlobalPrivate) { Q_D(Global); d->q_ptr = this; @@ -67,12 +43,21 @@ namespace dspx { } int Global::centShift() const { Q_D(const Global); - return d->centShift(); + return d->pModel->centShift; } void Global::setCentShift(int centShift) { Q_D(Global); Q_ASSERT(centShift >= -50 && centShift <= 50); - d->setCentShiftUnchecked(centShift); + d->pModel->strategy->setEntityProperty(d->handle, ModelStrategy::P_CentShift, centShift); + } + Global::AccidentalType Global::accidentalType() const { + Q_D(const Global); + return static_cast(d->pModel->accidentalType); + } + void Global::setAccidentalType(AccidentalType accidentalType) { + Q_D(Global); + Q_ASSERT(accidentalType == Flat || accidentalType == Sharp); + d->pModel->strategy->setEntityProperty(d->handle, ModelStrategy::P_AccidentalType, accidentalType); } QString Global::editorId() const { Q_D(const Global); diff --git a/src/libs/application/dspxmodel/src/Global.h b/src/libs/application/dspxmodel/src/Global.h index 651f2129..e30b1760 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,14 +19,15 @@ namespace dspx { class GlobalPrivate; - class Global : public QObject { + class DSPX_MODEL_EXPORT Global : public QObject { Q_OBJECT QML_ELEMENT QML_UNCREATABLE("") Q_DECLARE_PRIVATE(Global) Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) Q_PROPERTY(QString author READ author WRITE setAuthor NOTIFY authorChanged) - Q_PRIVATE_PROPERTY(d_func(), int centShift READ centShift WRITE setCentShift NOTIFY centShiftChanged) + Q_PROPERTY(int centShift READ centShift WRITE setCentShift NOTIFY centShiftChanged) + Q_PROPERTY(AccidentalType accidentalType READ accidentalType WRITE setAccidentalType NOTIFY accidentalTypeChanged) Q_PROPERTY(QString editorId READ editorId WRITE setEditorId NOTIFY editorIdChanged) Q_PROPERTY(QString editorName READ editorName WRITE setEditorName NOTIFY editorNameChanged) @@ -40,6 +43,14 @@ namespace dspx { int centShift() const; void setCentShift(int centShift); + enum AccidentalType { + Flat, + Sharp, + }; + Q_ENUM(AccidentalType) + AccidentalType accidentalType() const; + void setAccidentalType(AccidentalType accidentalType); + QString editorId() const; void setEditorId(const QString &editorId); @@ -53,6 +64,7 @@ namespace dspx { void nameChanged(const QString &name); void authorChanged(const QString &author); void centShiftChanged(int centShift); + void accidentalTypeChanged(AccidentalType accidentalType); void editorIdChanged(const QString &editorId); void editorNameChanged(const QString &editorName); diff --git a/src/libs/application/dspxmodel/src/Label.cpp b/src/libs/application/dspxmodel/src/Label.cpp index 7fdfecfe..75c6fb82 100644 --- a/src/libs/application/dspxmodel/src/Label.cpp +++ b/src/libs/application/dspxmodel/src/Label.cpp @@ -1,7 +1,5 @@ #include "Label.h" #include "Label_p.h" - -#include #include #include @@ -12,19 +10,7 @@ namespace dspx { - void LabelPrivate::setPosUnchecked(int pos_) { - Q_Q(Label); - q->model()->strategy()->setEntityProperty(q->handle(), ModelStrategy::P_Position, pos_); - } - 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")); - return; - } - setPosUnchecked(pos_); - } void LabelPrivate::setLabelSequence(Label *item, LabelSequence *labelSequence) { auto d = item->d_func(); @@ -34,6 +20,22 @@ namespace dspx { } } + void LabelPrivate::setPreviousItem(Label *item, Label *previousItem) { + auto d = item->d_func(); + if (d->previousItem != previousItem) { + d->previousItem = previousItem; + Q_EMIT item->previousItemChanged(); + } + } + + void LabelPrivate::setNextItem(Label *item, Label *nextItem) { + auto d = item->d_func(); + if (d->nextItem != nextItem) { + d->nextItem = nextItem; + Q_EMIT item->nextItemChanged(); + } + } + 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); @@ -52,7 +54,7 @@ namespace dspx { void Label::setPos(int pos) { Q_D(Label); Q_ASSERT(pos >= 0); - d->setPosUnchecked(pos); + model()->strategy()->setEntityProperty(handle(), ModelStrategy::P_Position, pos); } QString Label::text() const { @@ -70,6 +72,16 @@ namespace dspx { return d->labelSequence; } + Label *Label::previousItem() const { + Q_D(const Label); + return d->previousItem; + } + + Label *Label::nextItem() const { + Q_D(const Label); + return d->nextItem; + } + 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 dd21eb3f..882a1d2b 100644 --- a/src/libs/application/dspxmodel/src/Label.h +++ b/src/libs/application/dspxmodel/src/Label.h @@ -19,9 +19,11 @@ namespace dspx { QML_ELEMENT QML_UNCREATABLE("") Q_DECLARE_PRIVATE(Label); - Q_PRIVATE_PROPERTY(d_func(), int pos MEMBER pos WRITE setPos NOTIFY posChanged) + Q_PROPERTY(int pos READ pos WRITE setPos NOTIFY posChanged) Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged) Q_PROPERTY(LabelSequence *labelSequence READ labelSequence NOTIFY labelSequenceChanged) + Q_PROPERTY(Label *previousItem READ previousItem NOTIFY previousItemChanged) + Q_PROPERTY(Label *nextItem READ nextItem NOTIFY nextItemChanged) public: ~Label() override; @@ -33,6 +35,9 @@ namespace dspx { LabelSequence *labelSequence() const; + Label *previousItem() const; + Label *nextItem() const; + QDspx::Label toQDspx() const; void fromQDspx(const QDspx::Label &label); @@ -40,6 +45,8 @@ namespace dspx { void posChanged(int pos); void textChanged(const QString &text); void labelSequenceChanged(); + void previousItemChanged(); + void nextItemChanged(); 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 2eb86c2b..45ff44ef 100644 --- a/src/libs/application/dspxmodel/src/LabelSequence.cpp +++ b/src/libs/application/dspxmodel/src/LabelSequence.cpp @@ -20,13 +20,6 @@ namespace dspx { 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_p.h b/src/libs/application/dspxmodel/src/LabelSequence_p.h index fe03ee4a..2bcb8c4a 100644 --- a/src/libs/application/dspxmodel/src/LabelSequence_p.h +++ b/src/libs/application/dspxmodel/src/LabelSequence_p.h @@ -8,7 +8,7 @@ namespace dspx { - class LabelSequencePrivate : public PointSequenceData { + class LabelSequencePrivate : public PointSequenceData { Q_DECLARE_PUBLIC(LabelSequence) }; diff --git a/src/libs/application/dspxmodel/src/Label_p.h b/src/libs/application/dspxmodel/src/Label_p.h index 0ef4b5b3..70e56681 100644 --- a/src/libs/application/dspxmodel/src/Label_p.h +++ b/src/libs/application/dspxmodel/src/Label_p.h @@ -12,11 +12,12 @@ namespace dspx { int pos; QString text; LabelSequence *labelSequence{}; - - void setPosUnchecked(int pos_); - void setPos(int pos_); + Label *previousItem{}; + Label *nextItem{}; static void setLabelSequence(Label *item, LabelSequence *labelSequence); + static void setPreviousItem(Label *item, Label *previousItem); + static void setNextItem(Label *item, Label *nextItem); }; } diff --git a/src/libs/application/dspxmodel/src/ListData_p.h b/src/libs/application/dspxmodel/src/ListData_p.h index dff09987..358b707e 100644 --- a/src/libs/application/dspxmodel/src/ListData_p.h +++ b/src/libs/application/dspxmodel/src/ListData_p.h @@ -15,7 +15,7 @@ namespace dspx { static QJSValue create(QObject *o); }; - template + template class ListData { public: ListType *q_ptr; @@ -36,8 +36,11 @@ namespace dspx { } void init(const QList &handles) { + auto q = q_ptr; for (auto handle : handles) { - itemList.append(getItem(handle, true)); + auto item = getItem(handle, true); + itemList.append(item); + setList(item, q); } } @@ -45,6 +48,7 @@ namespace dspx { auto q = q_ptr; Q_EMIT q->itemAboutToInsert(index, item); itemList.insert(index, item); + setList(item, q); Q_EMIT q->itemInserted(index, item); Q_EMIT q->sizeChanged(itemList.size()); Q_EMIT q->itemsChanged(); @@ -55,6 +59,7 @@ namespace dspx { auto item = itemList.at(index); Q_EMIT q->itemAboutToRemove(index, item); itemList.removeAt(index); + setList(item, nullptr); Q_EMIT q->itemRemoved(index, item); Q_EMIT q->sizeChanged(itemList.size()); Q_EMIT q->itemsChanged(); 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 a91105e0..b17401c5 100644 --- a/src/libs/application/dspxmodel/src/Model.cpp +++ b/src/libs/application/dspxmodel/src/Model.cpp @@ -90,6 +90,15 @@ namespace dspx { } }); + QObject::connect(strategy, &ModelStrategy::moveToAnotherSequenceContainerNotified, q, [=, this](Handle sequenceContainerEntity, Handle entity, Handle otherSequenceContainerEntity) { + auto sequenceContainerObject = mapToObject(sequenceContainerEntity); + auto otherSequenceContainerObject = mapToObject(otherSequenceContainerEntity); + if (sequenceContainerObject && otherSequenceContainerObject) { + sequenceContainerObject->handleMoveToAnotherSequenceContainer(entity, otherSequenceContainerEntity); + otherSequenceContainerObject->handleMoveFromAnotherSequenceContainer(entity, sequenceContainerEntity); + } + }); + QObject::connect(strategy, &ModelStrategy::takeFromContainerNotified, q, [=, this](Handle takenEntity, Handle sequenceContainerEntity, Handle entity) { if (auto sequenceContainerObject = mapToObject(sequenceContainerEntity)) { sequenceContainerObject->handleTakeFromSequenceContainer(takenEntity, entity); @@ -210,7 +219,7 @@ namespace dspx { } QDspx::Model Model::toQDspx() const { - return { + QDspx::Model model = { .version = QDspx::Model::V1, .content = { .global = global()->toQDspx(), @@ -220,6 +229,18 @@ namespace dspx { .workspace = workspace()->toQDspx(), } }; + model.content.workspace["diffscope"] = QJsonObject{ + {"accidentalType", static_cast(global()->accidentalType())}, + {"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) { @@ -229,6 +250,29 @@ namespace dspx { d->timeline->fromQDspx(model.content.timeline); d->tracks->fromQDspx(model.content.tracks); d->workspace->fromQDspx(model.content.workspace); + { + auto accidentalType = model.content.workspace.value("diffscope").value("accidentalType").toInt(); + d->global->setAccidentalType(static_cast(accidentalType)); + } + { + 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() { @@ -341,6 +385,11 @@ namespace dspx { Q_EMIT d->global->centShiftChanged(d->centShift); break; } + case ModelStrategy::P_AccidentalType: { + d->accidentalType = static_cast(value.toInt()); + Q_EMIT d->global->accidentalTypeChanged(static_cast(d->accidentalType)); + break; + } case ModelStrategy::P_EditorId: { d->editorId = value.toString(); Q_EMIT d->global->editorIdChanged(d->editorId); @@ -353,10 +402,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/ModelStrategy.h b/src/libs/application/dspxmodel/src/ModelStrategy.h index 0a56fbfa..dc70161d 100644 --- a/src/libs/application/dspxmodel/src/ModelStrategy.h +++ b/src/libs/application/dspxmodel/src/ModelStrategy.h @@ -53,6 +53,7 @@ namespace dspx { }; enum Property { + P_AccidentalType, P_Author, P_CentShift, P_ClipStart, @@ -61,15 +62,21 @@ namespace dspx { 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, @@ -132,6 +139,8 @@ namespace dspx { virtual bool insertIntoListContainer(Handle listContainerEntity, Handle entity, int index) = 0; virtual bool insertIntoMapContainer(Handle mapContainerEntity, Handle entity, const QString &key) = 0; + virtual bool moveToAnotherSequenceContainer(Handle sequenceContainerEntity, Handle entity, Handle otherSequenceContainerEntity) = 0; + virtual Handle takeFromSequenceContainer(Handle sequenceContainerEntity, Handle entity) = 0; virtual Handle takeFromListContainer(Handle listContainerEntity, int index) = 0; virtual Handle takeFromMapContainer(Handle mapContainerEntity, const QString &key) = 0; @@ -155,6 +164,8 @@ namespace dspx { void insertIntoListContainerNotified(Handle listContainerEntity, Handle entity, int index); void insertIntoMapContainerNotified(Handle mapContainerEntity, Handle entity, const QString &key); + void moveToAnotherSequenceContainerNotified(Handle sequenceContainerEntity, Handle entity, Handle otherSequenceContainerEntity); + void takeFromContainerNotified(Handle takenEntity, Handle sequenceContainerEntity, Handle entity); void takeFromListContainerNotified(Handle takenEntities, Handle listContainerEntity, int index); void takeFromMapContainerNotified(Handle takenEntity, Handle mapContainerEntity, const QString &key); @@ -228,6 +239,10 @@ namespace dspx { auto v = value.toInt(); return v >= -50 && v <= 50; }; + static auto validateAccidentalType = [](const QVariant &value) { + auto v = value.toInt(); + return v == 0 || v == 1; + }; static auto validatePan = [](const QVariant &value) { auto v = value.toDouble(); return v >= -1 && v <= 1; @@ -236,6 +251,10 @@ namespace dspx { 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; @@ -276,11 +295,16 @@ namespace dspx { {P_Name, QMetaType::QString}, {P_Author, QMetaType::QString}, {P_CentShift, QMetaType::Int, validateCentShift}, + {P_AccidentalType, QMetaType::Int, validateAccidentalType}, {P_EditorId, QMetaType::QString}, {P_EditorName, QMetaType::QString}, {P_ControlGain, QMetaType::Double}, {P_ControlPan, QMetaType::Double, validatePan}, - {P_ControlMute, QMetaType::Bool} + {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}, @@ -348,7 +372,9 @@ namespace dspx { {P_ControlGain, QMetaType::Double}, {P_ControlPan, QMetaType::Double, validatePan}, {P_ControlMute, QMetaType::Bool}, - {P_ControlSolo, QMetaType::Bool} + {P_ControlRecord, QMetaType::Bool}, + {P_ControlSolo, QMetaType::Bool}, + {P_Height, QMetaType::Double, validateDoubleGreaterOrEqualZero} }; case EI_WorkspaceInfo: return { {P_JsonObject, QMetaType::QJsonObject} diff --git a/src/libs/application/dspxmodel/src/Model_p.h b/src/libs/application/dspxmodel/src/Model_p.h index 4f65328a..b2fa0210 100644 --- a/src/libs/application/dspxmodel/src/Model_p.h +++ b/src/libs/application/dspxmodel/src/Model_p.h @@ -35,6 +35,7 @@ namespace dspx { QString name; QString author; int centShift; + int accidentalType; QString editorId; QString editorName; diff --git a/src/libs/application/dspxmodel/src/Note.cpp b/src/libs/application/dspxmodel/src/Note.cpp index 9888c90c..6cf5281d 100644 --- a/src/libs/application/dspxmodel/src/Note.cpp +++ b/src/libs/application/dspxmodel/src/Note.cpp @@ -1,7 +1,5 @@ #include "Note.h" #include "Note_p.h" - -#include #include #include @@ -16,61 +14,7 @@ namespace dspx { - void NotePrivate::setCentShiftUnchecked(int centShift_) { - Q_Q(Note); - pModel->strategy->setEntityProperty(q->handle(), ModelStrategy::P_CentShift, centShift_); - } - - 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]")); - return; - } - setCentShiftUnchecked(centShift_); - } - void NotePrivate::setKeyNumUnchecked(int keyNum_) { - Q_Q(Note); - pModel->strategy->setEntityProperty(q->handle(), ModelStrategy::P_KeyNumber, keyNum_); - } - - 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]")); - return; - } - setKeyNumUnchecked(keyNum_); - } - - void NotePrivate::setLengthUnchecked(int length_) { - Q_Q(Note); - pModel->strategy->setEntityProperty(q->handle(), ModelStrategy::P_Length, length_); - } - - 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")); - return; - } - setLengthUnchecked(length_); - } - - void NotePrivate::setPosUnchecked(int pos_) { - Q_Q(Note); - pModel->strategy->setEntityProperty(q->handle(), ModelStrategy::P_Position, pos_); - } - - 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")); - return; - } - setPosUnchecked(pos_); - } void NotePrivate::setOverlapped(Note *item, bool overlapped) { auto d = item->d_func(); @@ -88,6 +32,22 @@ namespace dspx { } } + void NotePrivate::setPreviousItem(Note *item, Note *previousItem) { + auto d = item->d_func(); + if (d->previousItem != previousItem) { + d->previousItem = previousItem; + Q_EMIT item->previousItemChanged(); + } + } + + void NotePrivate::setNextItem(Note *item, Note *nextItem) { + auto d = item->d_func(); + if (d->nextItem != nextItem) { + d->nextItem = nextItem; + Q_EMIT item->nextItemChanged(); + } + } + 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); @@ -115,7 +75,7 @@ namespace dspx { void Note::setCentShift(int centShift) { Q_D(Note); Q_ASSERT(centShift >= -50 && centShift <= 50); - d->setCentShiftUnchecked(centShift); + d->pModel->strategy->setEntityProperty(handle(), ModelStrategy::P_CentShift, centShift); } int Note::keyNum() const { @@ -126,7 +86,7 @@ namespace dspx { void Note::setKeyNum(int keyNum) { Q_D(Note); Q_ASSERT(keyNum >= 0 && keyNum <= 127); - d->setKeyNumUnchecked(keyNum); + d->pModel->strategy->setEntityProperty(handle(), ModelStrategy::P_KeyNumber, keyNum); } QString Note::language() const { @@ -147,7 +107,7 @@ namespace dspx { void Note::setLength(int length) { Q_D(Note); Q_ASSERT(length >= 0); - d->setLengthUnchecked(length); + d->pModel->strategy->setEntityProperty(handle(), ModelStrategy::P_Length, length); } QString Note::lyric() const { @@ -173,7 +133,7 @@ namespace dspx { void Note::setPos(int pos) { Q_D(Note); Q_ASSERT(pos >= 0); - d->setPosUnchecked(pos); + d->pModel->strategy->setEntityProperty(handle(), ModelStrategy::P_Position, pos); } Pronunciation *Note::pronunciation() const { @@ -196,6 +156,16 @@ namespace dspx { return d->noteSequence; } + Note *Note::previousItem() const { + Q_D(const Note); + return d->previousItem; + } + + Note *Note::nextItem() const { + Q_D(const Note); + return d->nextItem; + } + bool Note::isOverlapped() const { Q_D(const Note); return d->overlapped; diff --git a/src/libs/application/dspxmodel/src/Note.h b/src/libs/application/dspxmodel/src/Note.h index e96311d5..df79af38 100644 --- a/src/libs/application/dspxmodel/src/Note.h +++ b/src/libs/application/dspxmodel/src/Note.h @@ -26,17 +26,19 @@ namespace dspx { QML_ELEMENT QML_UNCREATABLE("") Q_DECLARE_PRIVATE(Note) - Q_PRIVATE_PROPERTY(d_func(), int centShift MEMBER centShift WRITE setCentShift NOTIFY centShiftChanged) - Q_PRIVATE_PROPERTY(d_func(), int keyNum MEMBER keyNum WRITE setKeyNum NOTIFY keyNumChanged) + Q_PROPERTY(int centShift READ centShift WRITE setCentShift NOTIFY centShiftChanged) + Q_PROPERTY(int keyNum READ keyNum WRITE setKeyNum NOTIFY keyNumChanged) Q_PROPERTY(QString language READ language WRITE setLanguage NOTIFY languageChanged) - Q_PRIVATE_PROPERTY(d_func(), int length MEMBER length WRITE setLength NOTIFY lengthChanged) + Q_PROPERTY(int length READ length WRITE setLength NOTIFY lengthChanged) Q_PROPERTY(QString lyric READ lyric WRITE setLyric NOTIFY lyricChanged) Q_PROPERTY(PhonemeInfo *phonemes READ phonemes CONSTANT) - Q_PRIVATE_PROPERTY(d_func(), int pos MEMBER pos WRITE setPos NOTIFY posChanged) + Q_PROPERTY(int pos READ pos WRITE setPos NOTIFY posChanged) Q_PROPERTY(Pronunciation *pronunciation READ pronunciation CONSTANT) Q_PROPERTY(Vibrato *vibrato READ vibrato CONSTANT) Q_PROPERTY(Workspace *workspace READ workspace CONSTANT) Q_PROPERTY(NoteSequence *noteSequence READ noteSequence NOTIFY noteSequenceChanged) + Q_PROPERTY(Note *previousItem READ previousItem NOTIFY previousItemChanged) + Q_PROPERTY(Note *nextItem READ nextItem NOTIFY nextItemChanged) Q_PROPERTY(bool overlapped READ isOverlapped NOTIFY overlappedChanged) public: @@ -70,6 +72,9 @@ namespace dspx { NoteSequence *noteSequence() const; + Note *previousItem() const; + Note *nextItem() const; + bool isOverlapped() const; QDspx::Note toQDspx() const; @@ -83,6 +88,8 @@ namespace dspx { void lyricChanged(const QString &lyric); void posChanged(int pos); void noteSequenceChanged(); + void previousItemChanged(); + void nextItemChanged(); void overlappedChanged(bool overlapped); protected: diff --git a/src/libs/application/dspxmodel/src/NoteSequence.cpp b/src/libs/application/dspxmodel/src/NoteSequence.cpp index dd73d9c4..416cabb0 100644 --- a/src/libs/application/dspxmodel/src/NoteSequence.cpp +++ b/src/libs/application/dspxmodel/src/NoteSequence.cpp @@ -22,13 +22,6 @@ namespace dspx { d->singingClip = singingClip; d->init(model->strategy()->getEntitiesFromSequenceContainer(handle)); - - connect(this, &NoteSequence::itemInserted, this, [=](Note *item) { - NotePrivate::setNoteSequence(item, this); - }); - connect(this, &NoteSequence::itemRemoved, this, [=](Note *item) { - NotePrivate::setNoteSequence(item, nullptr); - }); } NoteSequence::~NoteSequence() = default; diff --git a/src/libs/application/dspxmodel/src/NoteSequence_p.h b/src/libs/application/dspxmodel/src/NoteSequence_p.h index 950ff19b..1200cb50 100644 --- a/src/libs/application/dspxmodel/src/NoteSequence_p.h +++ b/src/libs/application/dspxmodel/src/NoteSequence_p.h @@ -8,7 +8,7 @@ namespace dspx { - class NoteSequencePrivate : public RangeSequenceData { + class NoteSequencePrivate : public RangeSequenceData { Q_DECLARE_PUBLIC(NoteSequence) public: SingingClip *singingClip{}; diff --git a/src/libs/application/dspxmodel/src/Note_p.h b/src/libs/application/dspxmodel/src/Note_p.h index c6a70912..72d6865a 100644 --- a/src/libs/application/dspxmodel/src/Note_p.h +++ b/src/libs/application/dspxmodel/src/Note_p.h @@ -22,19 +22,14 @@ namespace dspx { Vibrato *vibrato; Workspace *workspace; NoteSequence *noteSequence{}; + Note *previousItem{}; + Note *nextItem{}; 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); + static void setPreviousItem(Note *item, Note *previousItem); + static void setNextItem(Note *item, Note *nextItem); }; } diff --git a/src/libs/application/dspxmodel/src/ParamCurve.cpp b/src/libs/application/dspxmodel/src/ParamCurve.cpp index 1da88780..6820d9f4 100644 --- a/src/libs/application/dspxmodel/src/ParamCurve.cpp +++ b/src/libs/application/dspxmodel/src/ParamCurve.cpp @@ -22,6 +22,22 @@ namespace dspx { Q_EMIT paramCurve->paramCurveSequenceChanged(); } + void ParamCurvePrivate::setPreviousItem(ParamCurve *paramCurve, ParamCurve *previousItem) { + auto d = paramCurve->d_func(); + if (d->previousItem != previousItem) { + d->previousItem = previousItem; + Q_EMIT paramCurve->previousItemChanged(); + } + } + + void ParamCurvePrivate::setNextItem(ParamCurve *paramCurve, ParamCurve *nextItem) { + auto d = paramCurve->d_func(); + if (d->nextItem != nextItem) { + d->nextItem = nextItem; + Q_EMIT paramCurve->nextItemChanged(); + } + } + ParamCurve::ParamCurve(CurveType type, Handle handle, Model *model) : EntityObject(handle, model), d_ptr(new ParamCurvePrivate) { Q_D(ParamCurve); @@ -53,6 +69,16 @@ namespace dspx { return d->paramCurveSequence; } + ParamCurve *ParamCurve::previousItem() const { + Q_D(const ParamCurve); + return d->previousItem; + } + + ParamCurve *ParamCurve::nextItem() const { + Q_D(const ParamCurve); + return d->nextItem; + } + 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 7cff8619..787ea0d4 100644 --- a/src/libs/application/dspxmodel/src/ParamCurve.h +++ b/src/libs/application/dspxmodel/src/ParamCurve.h @@ -23,6 +23,8 @@ namespace dspx { 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) + Q_PROPERTY(ParamCurve *previousItem READ previousItem NOTIFY previousItemChanged) + Q_PROPERTY(ParamCurve *nextItem READ nextItem NOTIFY nextItemChanged) public: enum CurveType { @@ -40,12 +42,17 @@ namespace dspx { ParamCurveSequence *paramCurveSequence() const; + ParamCurve *previousItem() const; + ParamCurve *nextItem() const; + QDspx::ParamCurveRef toQDspx() const; void fromQDspx(const QDspx::ParamCurveRef &curve); Q_SIGNALS: void startChanged(int start); void paramCurveSequenceChanged(); + void previousItemChanged(); + void nextItemChanged(); protected: explicit ParamCurve(CurveType type, Handle handle, Model *model); diff --git a/src/libs/application/dspxmodel/src/ParamCurveSequence.cpp b/src/libs/application/dspxmodel/src/ParamCurveSequence.cpp index 2ad19e7f..cc34ff57 100644 --- a/src/libs/application/dspxmodel/src/ParamCurveSequence.cpp +++ b/src/libs/application/dspxmodel/src/ParamCurveSequence.cpp @@ -27,13 +27,6 @@ namespace dspx { 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; diff --git a/src/libs/application/dspxmodel/src/ParamCurveSequence_p.h b/src/libs/application/dspxmodel/src/ParamCurveSequence_p.h index 8ffb2c96..e7ee2b0f 100644 --- a/src/libs/application/dspxmodel/src/ParamCurveSequence_p.h +++ b/src/libs/application/dspxmodel/src/ParamCurveSequence_p.h @@ -8,7 +8,7 @@ namespace dspx { - class ParamCurveSequencePrivate : public PointSequenceData { + class ParamCurveSequencePrivate : public PointSequenceData { Q_DECLARE_PUBLIC(ParamCurveSequence) public: Param *param{}; diff --git a/src/libs/application/dspxmodel/src/ParamCurve_p.h b/src/libs/application/dspxmodel/src/ParamCurve_p.h index f8478651..d8ceb1ee 100644 --- a/src/libs/application/dspxmodel/src/ParamCurve_p.h +++ b/src/libs/application/dspxmodel/src/ParamCurve_p.h @@ -16,8 +16,12 @@ namespace dspx { ParamCurve::CurveType type; int start{}; ParamCurveSequence *paramCurveSequence{}; + ParamCurve *previousItem{}; + ParamCurve *nextItem{}; static void setParamCurveSequence(ParamCurve *item, ParamCurveSequence *paramCurveSequence); + static void setPreviousItem(ParamCurve *item, ParamCurve *previousItem); + static void setNextItem(ParamCurve *item, ParamCurve *nextItem); }; } diff --git a/src/libs/application/dspxmodel/src/PhonemeList.cpp b/src/libs/application/dspxmodel/src/PhonemeList.cpp index 6b2c76de..66b87c37 100644 --- a/src/libs/application/dspxmodel/src/PhonemeList.cpp +++ b/src/libs/application/dspxmodel/src/PhonemeList.cpp @@ -20,13 +20,6 @@ namespace dspx { 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; diff --git a/src/libs/application/dspxmodel/src/PhonemeList_p.h b/src/libs/application/dspxmodel/src/PhonemeList_p.h index 5cb257c2..336cd0a1 100644 --- a/src/libs/application/dspxmodel/src/PhonemeList_p.h +++ b/src/libs/application/dspxmodel/src/PhonemeList_p.h @@ -3,12 +3,13 @@ #include #include +#include namespace dspx { class PhonemeInfo; - class PhonemeListPrivate : public ListData { + class PhonemeListPrivate : public ListData { Q_DECLARE_PUBLIC(PhonemeList) public: PhonemeInfo *phonemeInfo{}; diff --git a/src/libs/application/dspxmodel/src/PointSequenceData_p.h b/src/libs/application/dspxmodel/src/PointSequenceData_p.h index f05334d8..c7a2ca44 100644 --- a/src/libs/application/dspxmodel/src/PointSequenceData_p.h +++ b/src/libs/application/dspxmodel/src/PointSequenceData_p.h @@ -13,7 +13,14 @@ namespace dspx { static QJSValue create(QObject *o); }; - template + template < + class SequenceType, + class ItemType, + int (ItemType::*positionGetter)() const, + void (ItemType::*positionChangedSignal)(int), + void (*setSequence)(ItemType *item, SequenceType *sequence), + void (*setPreviousItem)(ItemType *item, ItemType *previousItem), + void (*setNextItem)(ItemType *item, ItemType *nextItem)> class PointSequenceData { public: SequenceType *q_ptr; @@ -36,9 +43,11 @@ namespace dspx { } void init(const QList &handles) { + auto q = q_ptr; for (auto handle : handles) { auto item = getItem(handle, true); container.insertItem(item, (item->*positionGetter)()); + setSequence(item, q); } updateFirstAndLastItem(); } @@ -46,12 +55,31 @@ namespace dspx { void insertItem(ItemType *item, int position) { auto q = q_ptr; bool containsItem = container.contains(item); + auto oldPreviousItem = container.previousItem(item); + auto oldNextItem = container.nextItem(item); if (!containsItem) { Q_EMIT q->itemAboutToInsert(item); } container.insertItem(item, position); + setSequence(item, q); + updateFirstAndLastItem(); + auto newPreviousItem = container.previousItem(item); + auto newNextItem = container.nextItem(item); + if (oldPreviousItem) { + setNextItem(oldPreviousItem, oldNextItem); + } + if (oldNextItem) { + setPreviousItem(oldNextItem, oldPreviousItem); + } + if (newPreviousItem) { + setNextItem(newPreviousItem, item); + } + if (newNextItem) { + setPreviousItem(newNextItem, item); + } + setPreviousItem(item, newPreviousItem); + setNextItem(item, newNextItem); if (!containsItem) { - updateFirstAndLastItem(); Q_EMIT q->itemInserted(item); Q_EMIT q->sizeChanged(container.size()); } @@ -60,8 +88,19 @@ namespace dspx { void removeItem(ItemType *item) { auto q = q_ptr; Q_EMIT q->itemAboutToRemove(item); + auto oldPreviousItem = container.previousItem(item); + auto oldNextItem = container.nextItem(item); container.removeItem(item); + setSequence(item, nullptr); updateFirstAndLastItem(); + if (oldPreviousItem) { + setNextItem(oldPreviousItem, oldNextItem); + } + if (oldNextItem) { + setPreviousItem(oldNextItem, oldPreviousItem); + } + setPreviousItem(item, nullptr); + setNextItem(item, nullptr); Q_EMIT q->itemRemoved(item); Q_EMIT q->sizeChanged(container.size()); } diff --git a/src/libs/application/dspxmodel/src/RangeSequenceData_p.h b/src/libs/application/dspxmodel/src/RangeSequenceData_p.h index e5f188a2..f97c46f0 100644 --- a/src/libs/application/dspxmodel/src/RangeSequenceData_p.h +++ b/src/libs/application/dspxmodel/src/RangeSequenceData_p.h @@ -11,7 +11,17 @@ namespace dspx { class PointSequenceJSIterable; - template + template < + class SequenceType, + class ItemType, + int (ItemType::*positionGetter)() const, + void (ItemType::*positionChangedSignal)(int), + int (ItemType::*lengthGetter)() const, + void (ItemType::*lengthChangedSignal)(int), + void (*setOverlapped)(ItemType *item, bool overlapped), + void (*setSequence)(ItemType *item, SequenceType *sequence), + void (*setPreviousItem)(ItemType *item, ItemType *previousItem), + void (*setNextItem)(ItemType *item, ItemType *nextItem)> class RangeSequenceData { public: SequenceType *q_ptr; @@ -35,9 +45,11 @@ namespace dspx { } void init(const QList &handles) { + auto q = q_ptr; for (auto handle : handles) { auto item = getItem(handle, true); pointContainer.insertItem(item, (item->*positionGetter)()); + setSequence(item, q); auto affectedItems = rangeContainer.insertItem(item, (item->*positionGetter)(), (item->*lengthGetter)()); for (auto affectedItem : affectedItems) { bool isOverlapped = rangeContainer.isOverlapped(affectedItem); @@ -53,9 +65,11 @@ namespace dspx { if (!containsItem) { Q_EMIT q->itemAboutToInsert(item); } - + auto oldPreviousItem = pointContainer.previousItem(item); + auto oldNextItem = pointContainer.nextItem(item); // Insert into both containers pointContainer.insertItem(item, position); + setSequence(item, q); auto affectedItems = rangeContainer.insertItem(item, position, length); // Update overlapped status for all affected items @@ -63,9 +77,24 @@ namespace dspx { bool isOverlapped = rangeContainer.isOverlapped(affectedItem); setOverlapped(affectedItem, isOverlapped); } - + updateFirstAndLastItem(); + auto newPreviousItem = pointContainer.previousItem(item); + auto newNextItem = pointContainer.nextItem(item); + if (oldPreviousItem) { + setNextItem(oldPreviousItem, oldNextItem); + } + if (oldNextItem) { + setPreviousItem(oldNextItem, oldPreviousItem); + } + if (newPreviousItem) { + setNextItem(newPreviousItem, item); + } + if (newNextItem) { + setPreviousItem(newNextItem, item); + } + setPreviousItem(item, newPreviousItem); + setNextItem(item, newNextItem); if (!containsItem) { - updateFirstAndLastItem(); Q_EMIT q->itemInserted(item); Q_EMIT q->sizeChanged(pointContainer.size()); } @@ -74,9 +103,11 @@ namespace dspx { void removeItem(ItemType *item) { auto q = q_ptr; Q_EMIT q->itemAboutToRemove(item); - + auto oldPreviousItem = pointContainer.previousItem(item); + auto oldNextItem = pointContainer.nextItem(item); // Remove from both containers pointContainer.removeItem(item); + setSequence(item, nullptr); auto affectedItems = rangeContainer.removeItem(item); // Update overlapped status for all affected items @@ -86,6 +117,14 @@ namespace dspx { } updateFirstAndLastItem(); + if (oldPreviousItem) { + setNextItem(oldPreviousItem, oldNextItem); + } + if (oldNextItem) { + setPreviousItem(oldNextItem, oldPreviousItem); + } + setPreviousItem(item, nullptr); + setNextItem(item, nullptr); Q_EMIT q->itemRemoved(item); Q_EMIT q->sizeChanged(pointContainer.size()); } diff --git a/src/libs/application/dspxmodel/src/Tempo.cpp b/src/libs/application/dspxmodel/src/Tempo.cpp index 0da9844a..814345dc 100644 --- a/src/libs/application/dspxmodel/src/Tempo.cpp +++ b/src/libs/application/dspxmodel/src/Tempo.cpp @@ -1,7 +1,5 @@ #include "Tempo.h" #include "Tempo_p.h" - -#include #include #include @@ -12,39 +10,27 @@ namespace dspx { - void TempoPrivate::setPosUnchecked(int pos_) { - Q_Q(Tempo); - q->model()->strategy()->setEntityProperty(q->handle(), ModelStrategy::P_Position, pos_); - } - - 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")); - return; + void TempoPrivate::setTempoSequence(Tempo *item, TempoSequence *tempoSequence) { + auto d = item->d_func(); + if (d->tempoSequence != tempoSequence) { + d->tempoSequence = tempoSequence; + Q_EMIT item->tempoSequenceChanged(); } - setPosUnchecked(pos_); } - void TempoPrivate::setValueUnchecked(double value_) { - Q_Q(Tempo); - q->model()->strategy()->setEntityProperty(q->handle(), ModelStrategy::P_Value, value_); - } - - 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]")); - return; + void TempoPrivate::setPreviousItem(Tempo *item, Tempo *previousItem) { + auto d = item->d_func(); + if (d->previousItem != previousItem) { + d->previousItem = previousItem; + Q_EMIT item->previousItemChanged(); } - setValueUnchecked(value_); } - void TempoPrivate::setTempoSequence(Tempo *item, TempoSequence *tempoSequence) { + void TempoPrivate::setNextItem(Tempo *item, Tempo *nextItem) { auto d = item->d_func(); - if (d->tempoSequence != tempoSequence) { - d->tempoSequence = tempoSequence; - Q_EMIT item->tempoSequenceChanged(); + if (d->nextItem != nextItem) { + d->nextItem = nextItem; + Q_EMIT item->nextItemChanged(); } } @@ -66,7 +52,7 @@ namespace dspx { void Tempo::setPos(int pos) { Q_D(Tempo); Q_ASSERT(pos >= 0); - d->setPosUnchecked(pos); + model()->strategy()->setEntityProperty(handle(), ModelStrategy::P_Position, pos); } double Tempo::value() const { @@ -77,7 +63,7 @@ namespace dspx { void Tempo::setValue(double value) { Q_D(Tempo); Q_ASSERT(value >= 10.0 && value <= 1000.0); - d->setValueUnchecked(value); + model()->strategy()->setEntityProperty(handle(), ModelStrategy::P_Value, value); } TempoSequence *Tempo::tempoSequence() const { @@ -85,6 +71,16 @@ namespace dspx { return d->tempoSequence; } + Tempo *Tempo::previousItem() const { + Q_D(const Tempo); + return d->previousItem; + } + + Tempo *Tempo::nextItem() const { + Q_D(const Tempo); + return d->nextItem; + } + 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 53b71fa7..2a7c6805 100644 --- a/src/libs/application/dspxmodel/src/Tempo.h +++ b/src/libs/application/dspxmodel/src/Tempo.h @@ -19,9 +19,11 @@ namespace dspx { QML_ELEMENT QML_UNCREATABLE("") 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(int pos READ pos WRITE setPos NOTIFY posChanged) + Q_PROPERTY(double value READ value WRITE setValue NOTIFY valueChanged) Q_PROPERTY(TempoSequence *tempoSequence READ tempoSequence NOTIFY tempoSequenceChanged) + Q_PROPERTY(Tempo *previousItem READ previousItem NOTIFY previousItemChanged) + Q_PROPERTY(Tempo *nextItem READ nextItem NOTIFY nextItemChanged) public: ~Tempo() override; @@ -33,6 +35,9 @@ namespace dspx { TempoSequence *tempoSequence() const; + Tempo *previousItem() const; + Tempo *nextItem() const; + QDspx::Tempo toQDspx() const; void fromQDspx(const QDspx::Tempo &tempo); @@ -40,6 +45,8 @@ namespace dspx { void posChanged(int pos); void valueChanged(double value); void tempoSequenceChanged(); + void previousItemChanged(); + void nextItemChanged(); 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 3372afa4..2f9938ab 100644 --- a/src/libs/application/dspxmodel/src/TempoSequence.cpp +++ b/src/libs/application/dspxmodel/src/TempoSequence.cpp @@ -20,13 +20,6 @@ namespace dspx { 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_p.h b/src/libs/application/dspxmodel/src/TempoSequence_p.h index 44435be5..ec90a19f 100644 --- a/src/libs/application/dspxmodel/src/TempoSequence_p.h +++ b/src/libs/application/dspxmodel/src/TempoSequence_p.h @@ -8,7 +8,7 @@ namespace dspx { - class TempoSequencePrivate : public PointSequenceData { + class TempoSequencePrivate : public PointSequenceData { Q_DECLARE_PUBLIC(TempoSequence) }; diff --git a/src/libs/application/dspxmodel/src/Tempo_p.h b/src/libs/application/dspxmodel/src/Tempo_p.h index 8bb93871..2dee59a1 100644 --- a/src/libs/application/dspxmodel/src/Tempo_p.h +++ b/src/libs/application/dspxmodel/src/Tempo_p.h @@ -12,13 +12,12 @@ namespace dspx { int pos; double value; TempoSequence *tempoSequence{}; - - void setPosUnchecked(int pos_); - void setPos(int pos_); - void setValueUnchecked(double value_); - void setValue(double value_); + Tempo *previousItem{}; + Tempo *nextItem{}; static void setTempoSequence(Tempo *item, TempoSequence *tempoSequence); + static void setPreviousItem(Tempo *item, Tempo *previousItem); + static void setNextItem(Tempo *item, Tempo *nextItem); }; } diff --git a/src/libs/application/dspxmodel/src/TimeSignature.cpp b/src/libs/application/dspxmodel/src/TimeSignature.cpp index 5148bf3d..471d4cce 100644 --- a/src/libs/application/dspxmodel/src/TimeSignature.cpp +++ b/src/libs/application/dspxmodel/src/TimeSignature.cpp @@ -1,7 +1,5 @@ #include "TimeSignature.h" #include "TimeSignature_p.h" - -#include #include #include @@ -13,58 +11,30 @@ namespace dspx { 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); - q->model()->strategy()->setEntityProperty(q->handle(), ModelStrategy::P_Measure, index_); - } - - 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")); - return; - } - setIndexUnchecked(index_); - } - - void TimeSignaturePrivate::setNumeratorUnchecked(int numerator_) { - Q_Q(TimeSignature); - q->model()->strategy()->setEntityProperty(q->handle(), ModelStrategy::P_Numerator, numerator_); + return d == 1 || d == 2 || d == 4 || d == 8 || d == 16 || d == 32 || d == 64 || d == 128; } - 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")); - return; + void TimeSignaturePrivate::setTimeSignatureSequence(TimeSignature *item, TimeSignatureSequence *timeSignatureSequence) { + auto d = item->d_func(); + if (d->timeSignatureSequence != timeSignatureSequence) { + d->timeSignatureSequence = timeSignatureSequence; + Q_EMIT item->timeSignatureSequenceChanged(); } - setNumeratorUnchecked(numerator_); } - void TimeSignaturePrivate::setDenominatorUnchecked(int denominator_) { - Q_Q(TimeSignature); - q->model()->strategy()->setEntityProperty(q->handle(), ModelStrategy::P_Denominator, denominator_); - } - - void TimeSignaturePrivate::setDenominator(int denominator_) { - Q_Q(TimeSignature); - if (auto engine = qjsEngine(q); engine) { - if (!validateDenominator(denominator_)) { - engine->throwError(QJSValue::RangeError, QStringLiteral("Denominator must be one of: 1, 2, 4, 8, 16, 32, 64, 128, 256")); - return; - } + void TimeSignaturePrivate::setPreviousItem(TimeSignature *item, TimeSignature *previousItem) { + auto d = item->d_func(); + if (d->previousItem != previousItem) { + d->previousItem = previousItem; + Q_EMIT item->previousItemChanged(); } - setDenominatorUnchecked(denominator_); } - void TimeSignaturePrivate::setTimeSignatureSequence(TimeSignature *item, TimeSignatureSequence *timeSignatureSequence) { + void TimeSignaturePrivate::setNextItem(TimeSignature *item, TimeSignature *nextItem) { auto d = item->d_func(); - if (d->timeSignatureSequence != timeSignatureSequence) { - d->timeSignatureSequence = timeSignatureSequence; - Q_EMIT item->timeSignatureSequenceChanged(); + if (d->nextItem != nextItem) { + d->nextItem = nextItem; + Q_EMIT item->nextItemChanged(); } } @@ -87,7 +57,7 @@ namespace dspx { void TimeSignature::setIndex(int index) { Q_D(TimeSignature); Q_ASSERT(index >= 0); - d->setIndexUnchecked(index); + model()->strategy()->setEntityProperty(handle(), ModelStrategy::P_Measure, index); } int TimeSignature::numerator() const { @@ -97,8 +67,8 @@ namespace dspx { void TimeSignature::setNumerator(int numerator) { Q_D(TimeSignature); - Q_ASSERT(numerator >= 1); - d->setNumeratorUnchecked(numerator); + Q_ASSERT(numerator > 0); + model()->strategy()->setEntityProperty(handle(), ModelStrategy::P_Numerator, numerator); } int TimeSignature::denominator() const { @@ -109,7 +79,7 @@ namespace dspx { void TimeSignature::setDenominator(int denominator) { Q_D(TimeSignature); Q_ASSERT(validateDenominator(denominator)); - d->setDenominatorUnchecked(denominator); + model()->strategy()->setEntityProperty(handle(), ModelStrategy::P_Denominator, denominator); } TimeSignatureSequence *TimeSignature::timeSignatureSequence() const { @@ -117,6 +87,16 @@ namespace dspx { return d->timeSignatureSequence; } + TimeSignature *TimeSignature::previousItem() const { + Q_D(const TimeSignature); + return d->previousItem; + } + + TimeSignature *TimeSignature::nextItem() const { + Q_D(const TimeSignature); + return d->nextItem; + } + 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 99f23148..b3d17f72 100644 --- a/src/libs/application/dspxmodel/src/TimeSignature.h +++ b/src/libs/application/dspxmodel/src/TimeSignature.h @@ -19,10 +19,12 @@ namespace dspx { QML_ELEMENT QML_UNCREATABLE("") Q_DECLARE_PRIVATE(TimeSignature); - 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(int index READ index WRITE setIndex NOTIFY indexChanged) + Q_PROPERTY(int numerator READ numerator WRITE setNumerator NOTIFY numeratorChanged) + Q_PROPERTY(int denominator READ denominator WRITE setDenominator NOTIFY denominatorChanged) Q_PROPERTY(TimeSignatureSequence *timeSignatureSequence READ timeSignatureSequence NOTIFY timeSignatureSequenceChanged) + Q_PROPERTY(TimeSignature *previousItem READ previousItem NOTIFY previousItemChanged) + Q_PROPERTY(TimeSignature *nextItem READ nextItem NOTIFY nextItemChanged) public: ~TimeSignature() override; @@ -37,6 +39,9 @@ namespace dspx { TimeSignatureSequence *timeSignatureSequence() const; + TimeSignature *previousItem() const; + TimeSignature *nextItem() const; + QDspx::TimeSignature toQDspx() const; void fromQDspx(const QDspx::TimeSignature &timeSignature); @@ -45,6 +50,8 @@ namespace dspx { void numeratorChanged(int numerator); void denominatorChanged(int denominator); void timeSignatureSequenceChanged(); + void previousItemChanged(); + void nextItemChanged(); 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 441019f0..b7486502 100644 --- a/src/libs/application/dspxmodel/src/TimeSignatureSequence.cpp +++ b/src/libs/application/dspxmodel/src/TimeSignatureSequence.cpp @@ -20,13 +20,6 @@ namespace dspx { 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_p.h b/src/libs/application/dspxmodel/src/TimeSignatureSequence_p.h index 3cf31cbe..c8a43f20 100644 --- a/src/libs/application/dspxmodel/src/TimeSignatureSequence_p.h +++ b/src/libs/application/dspxmodel/src/TimeSignatureSequence_p.h @@ -8,7 +8,7 @@ namespace dspx { - class TimeSignatureSequencePrivate : public PointSequenceData { + class TimeSignatureSequencePrivate : public PointSequenceData { Q_DECLARE_PUBLIC(TimeSignatureSequence) }; diff --git a/src/libs/application/dspxmodel/src/TimeSignature_p.h b/src/libs/application/dspxmodel/src/TimeSignature_p.h index 7a787946..32d27f03 100644 --- a/src/libs/application/dspxmodel/src/TimeSignature_p.h +++ b/src/libs/application/dspxmodel/src/TimeSignature_p.h @@ -13,15 +13,12 @@ namespace dspx { 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_); + TimeSignature *previousItem{}; + TimeSignature *nextItem{}; static void setTimeSignatureSequence(TimeSignature *item, TimeSignatureSequence *timeSignatureSequence); + static void setPreviousItem(TimeSignature *item, TimeSignature *previousItem); + static void setNextItem(TimeSignature *item, TimeSignature *nextItem); }; } diff --git a/src/libs/application/dspxmodel/src/Timeline.cpp b/src/libs/application/dspxmodel/src/Timeline.cpp index 7119ef2c..0867647e 100644 --- a/src/libs/application/dspxmodel/src/Timeline.cpp +++ b/src/libs/application/dspxmodel/src/Timeline.cpp @@ -2,6 +2,7 @@ #include +#include #include #include #include @@ -14,12 +15,39 @@ namespace dspx { public: Timeline *q_ptr; ModelPrivate *pModel; + Handle handle; + + bool loopEnabled{false}; + int loopStart{0}; + int loopLength{1920}; }; - 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 +67,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->pModel->strategy->setEntityProperty(d->handle, ModelStrategy::P_LoopStart, 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->pModel->strategy->setEntityProperty(d->handle, ModelStrategy::P_LoopLength, 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..7eb87d8c 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_PROPERTY(int loopStart READ loopStart WRITE setLoopStart NOTIFY loopStartChanged) + Q_PROPERTY(int loopLength READ 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 cb10f594..a0e7140d 100644 --- a/src/libs/application/dspxmodel/src/Track.cpp +++ b/src/libs/application/dspxmodel/src/Track.cpp @@ -31,6 +31,7 @@ namespace dspx { 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(this, d->pModel->strategy->getAssociatedSubEntity(handle, ModelStrategy::R_Children)); @@ -53,6 +54,16 @@ namespace dspx { 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; @@ -80,7 +91,11 @@ namespace dspx { .clips = clips()->toQDspx(), .workspace = workspace()->toQDspx(), }; - track.workspace["diffscope"]["colorId"] = colorId(); + track.workspace["diffscope"] = QJsonObject{ + {"colorId", colorId()}, + {"height", height()}, + {"record", control()->record()}, + }; return track; } @@ -90,6 +105,8 @@ namespace dspx { 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 { @@ -110,9 +127,15 @@ namespace dspx { 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 c686f3d6..c49fd48c 100644 --- a/src/libs/application/dspxmodel/src/Track.h +++ b/src/libs/application/dspxmodel/src/Track.h @@ -26,6 +26,7 @@ namespace dspx { 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) @@ -39,6 +40,9 @@ namespace dspx { int colorId() const; void setColorId(int colorId); + double height() const; + void setHeight(double height); + TrackControl *control() const; QString name() const; @@ -54,6 +58,7 @@ namespace dspx { Q_SIGNALS: void nameChanged(const QString &name); void colorIdChanged(int colorId); + void heightChanged(double height); void trackListChanged(); protected: 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 65b3ce9b..eafbee62 100644 --- a/src/libs/application/dspxmodel/src/TrackList.cpp +++ b/src/libs/application/dspxmodel/src/TrackList.cpp @@ -10,7 +10,7 @@ namespace dspx { - class TrackListPrivate : public ListData { + class TrackListPrivate : public ListData { Q_DECLARE_PUBLIC(TrackList) }; @@ -21,13 +21,6 @@ namespace dspx { 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 index 4f597df9..4a4caeba 100644 --- a/src/libs/application/dspxmodel/src/Track_p.h +++ b/src/libs/application/dspxmodel/src/Track_p.h @@ -13,6 +13,7 @@ namespace dspx { ClipSequence *clips; QString name; int colorId; + double height; TrackControl *control; Workspace *workspace; TrackList *trackList; diff --git a/src/libs/application/dspxmodel/src/UndoableModelStrategy.cpp b/src/libs/application/dspxmodel/src/UndoableModelStrategy.cpp index bfd2ad73..ff1f3c0b 100644 --- a/src/libs/application/dspxmodel/src/UndoableModelStrategy.cpp +++ b/src/libs/application/dspxmodel/src/UndoableModelStrategy.cpp @@ -74,6 +74,10 @@ namespace dspx { for (auto child : std::as_const(map->map)) { recursivelyCreate(child); } + } else if (auto item = qobject_cast(object)) { + for (auto child: std::as_const(item->associatedSubEntities)) { + recursivelyCreate(child); + } } } @@ -90,6 +94,10 @@ namespace dspx { for (auto child : std::as_const(map->map)) { recursivelyDestroy(child); } + } else if (auto item = qobject_cast(object)) { + for (auto child: std::as_const(item->associatedSubEntities)) { + recursivelyDestroy(child); + } } object->setParent(nullptr); @@ -163,6 +171,35 @@ namespace dspx { {reinterpret_cast(m_object.data())}); } + MoveToAnotherSequenceContainerCommand::MoveToAnotherSequenceContainerCommand(UndoableModelStrategy *strategy, + Handle sequenceContainerEntity, + Handle entity, + Handle otherSequenceContainerEntity, + QUndoCommand *parent) + : QUndoCommand(parent), m_strategy(strategy), m_container(sequenceContainerEntity), m_entity(entity), + m_otherContainer(otherSequenceContainerEntity) { + } + + void MoveToAnotherSequenceContainerCommand::undo() { + auto containerObj = handle_cast(m_otherContainer); + auto otherContainerObj = handle_cast(m_container); + auto entityObj = handle_cast(m_entity); + containerObj->sequence.remove(entityObj); + otherContainerObj->sequence.insert(entityObj); + entityObj->setParent(otherContainerObj); + Q_EMIT m_strategy->moveToAnotherSequenceContainerNotified(m_otherContainer, m_entity, m_container); + } + + void MoveToAnotherSequenceContainerCommand::redo() { + auto containerObj = handle_cast(m_container); + auto otherContainerObj = handle_cast(m_otherContainer); + auto entityObj = handle_cast(m_entity); + containerObj->sequence.remove(entityObj); + otherContainerObj->sequence.insert(entityObj); + entityObj->setParent(otherContainerObj); + Q_EMIT m_strategy->moveToAnotherSequenceContainerNotified(m_container, m_entity, m_otherContainer); + } + InsertIntoListContainerCommand::InsertIntoListContainerCommand( UndoableModelStrategy *strategy, Handle listContainerEntity, Handle entity, int index, QUndoCommand *parent) @@ -362,7 +399,7 @@ namespace dspx { int SetEntityPropertyCommand::id() const { // Return a constant to force mergeWith to be called for comparison - return -1; + return 1; } SpliceDataArrayCommand::SpliceDataArrayCommand(UndoableModelStrategy *strategy, @@ -463,6 +500,22 @@ namespace dspx { return true; } + bool UndoableModelStrategy::moveToAnotherSequenceContainer(Handle sequenceContainerEntity, Handle entity, + Handle otherSequenceContainerEntity) { + auto sequenceContainerObject = handle_cast(sequenceContainerEntity); + auto otherSequenceContainerObject = handle_cast(otherSequenceContainerEntity); + auto object = handle_cast(entity); + if (sequenceContainerObject == otherSequenceContainerObject) { + return false; + } + if (!sequenceContainerObject->sequence.contains(object)) { + return false; + } + m_undoStack->push(new MoveToAnotherSequenceContainerCommand(this, sequenceContainerEntity, entity, + otherSequenceContainerEntity)); + return true; + } + Handle UndoableModelStrategy::takeFromSequenceContainer(Handle sequenceContainerEntity, Handle entity) { auto sequenceContainerObject = handle_cast(sequenceContainerEntity); diff --git a/src/libs/application/dspxmodel/src/UndoableModelStrategy.h b/src/libs/application/dspxmodel/src/UndoableModelStrategy.h index dfc39fa7..ec48c5f8 100644 --- a/src/libs/application/dspxmodel/src/UndoableModelStrategy.h +++ b/src/libs/application/dspxmodel/src/UndoableModelStrategy.h @@ -20,6 +20,7 @@ namespace dspx { 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; + bool moveToAnotherSequenceContainer(Handle sequenceContainerEntity, Handle entity, Handle otherSequenceContainerEntity) override; Handle takeFromSequenceContainer(Handle sequenceContainerEntity, Handle entity) override; Handle takeFromListContainer(Handle listContainerEntity, int index) override; Handle takeFromMapContainer(Handle mapContainerEntity, const QString &key) override; diff --git a/src/libs/application/dspxmodel/src/UndoableModelStrategy_p.h b/src/libs/application/dspxmodel/src/UndoableModelStrategy_p.h index dbe90368..dd6737f3 100644 --- a/src/libs/application/dspxmodel/src/UndoableModelStrategy_p.h +++ b/src/libs/application/dspxmodel/src/UndoableModelStrategy_p.h @@ -86,6 +86,24 @@ namespace dspx { bool m_undone = false; }; + class MoveToAnotherSequenceContainerCommand : public QUndoCommand { + public: + MoveToAnotherSequenceContainerCommand(UndoableModelStrategy *strategy, + Handle sequenceContainerEntity, + Handle entity, + Handle otherSequenceContainerEntity, + QUndoCommand *parent = nullptr); + ~MoveToAnotherSequenceContainerCommand() override = default; + void undo() override; + void redo() override; + + private: + UndoableModelStrategy *m_strategy; + Handle m_container; + Handle m_entity; + Handle m_otherContainer; + }; + class InsertIntoListContainerCommand : public QUndoCommand { public: InsertIntoListContainerCommand(UndoableModelStrategy *strategy, Handle listContainerEntity, diff --git a/src/libs/application/dspxmodel/src/Vibrato.cpp b/src/libs/application/dspxmodel/src/Vibrato.cpp index 73c84e31..95c416a3 100644 --- a/src/libs/application/dspxmodel/src/Vibrato.cpp +++ b/src/libs/application/dspxmodel/src/Vibrato.cpp @@ -1,6 +1,4 @@ #include "Vibrato.h" - -#include #include #include @@ -26,73 +24,8 @@ namespace dspx { double phase; VibratoPoints *points; double start; - - void setEndUnchecked(double end_); - void setEnd(double end_); - void setFreqUnchecked(double freq_); - void setFreq(double freq_); - void setPhaseUnchecked(double phase_); - void setPhase(double phase_); - void setStartUnchecked(double start_); - void setStart(double start_); }; - void VibratoPrivate::setEndUnchecked(double end_) { - Q_Q(Vibrato); - pModel->strategy->setEntityProperty(handle, ModelStrategy::P_VibratoEnd, end_); - } - - 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]")); - return; - } - setEndUnchecked(end_); - } - - void VibratoPrivate::setFreqUnchecked(double freq_) { - Q_Q(Vibrato); - pModel->strategy->setEntityProperty(handle, ModelStrategy::P_VibratoFrequency, freq_); - } - - 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")); - return; - } - setFreqUnchecked(freq_); - } - - void VibratoPrivate::setPhaseUnchecked(double phase_) { - Q_Q(Vibrato); - pModel->strategy->setEntityProperty(handle, ModelStrategy::P_VibratoPhase, phase_); - } - - 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]")); - return; - } - setPhaseUnchecked(phase_); - } - - void VibratoPrivate::setStartUnchecked(double start_) { - Q_Q(Vibrato); - pModel->strategy->setEntityProperty(handle, ModelStrategy::P_VibratoStart, start_); - } - - 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]")); - return; - } - setStartUnchecked(start_); - } - Vibrato::Vibrato(Handle handle, Model *model) : QObject(model), d_ptr(new VibratoPrivate) { Q_D(Vibrato); d->q_ptr = this; @@ -126,8 +59,8 @@ namespace dspx { void Vibrato::setEnd(double end) { Q_D(Vibrato); - Q_ASSERT(end >= 0.0 && end <= 1.0); - d->setEndUnchecked(end); + Q_ASSERT(end >= 0 && end <= 1); + d->pModel->strategy->setEntityProperty(d->handle, ModelStrategy::P_VibratoEnd, end); } double Vibrato::freq() const { @@ -137,8 +70,8 @@ namespace dspx { void Vibrato::setFreq(double freq) { Q_D(Vibrato); - Q_ASSERT(freq >= 0.0); - d->setFreqUnchecked(freq); + Q_ASSERT(freq >= 0); + d->pModel->strategy->setEntityProperty(d->handle, ModelStrategy::P_VibratoFrequency, freq); } int Vibrato::offset() const { @@ -158,8 +91,8 @@ namespace dspx { void Vibrato::setPhase(double phase) { Q_D(Vibrato); - Q_ASSERT(phase >= 0.0 && phase <= 1.0); - d->setPhaseUnchecked(phase); + Q_ASSERT(phase >= 0 && phase <= 1); + d->pModel->strategy->setEntityProperty(d->handle, ModelStrategy::P_VibratoPhase, phase); } VibratoPoints *Vibrato::points() const { @@ -174,8 +107,8 @@ namespace dspx { void Vibrato::setStart(double start) { Q_D(Vibrato); - Q_ASSERT(start >= 0.0 && start <= 1.0); - d->setStartUnchecked(start); + Q_ASSERT(start >= 0 && start <= 1); + d->pModel->strategy->setEntityProperty(d->handle, ModelStrategy::P_VibratoStart, start); } QDspx::Vibrato Vibrato::toQDspx() const { diff --git a/src/libs/application/dspxmodel/src/Vibrato.h b/src/libs/application/dspxmodel/src/Vibrato.h index d61009e1..695b492f 100644 --- a/src/libs/application/dspxmodel/src/Vibrato.h +++ b/src/libs/application/dspxmodel/src/Vibrato.h @@ -25,12 +25,12 @@ namespace dspx { QML_UNCREATABLE("") Q_DECLARE_PRIVATE(Vibrato) Q_PROPERTY(int amp READ amp WRITE setAmp NOTIFY ampChanged) - Q_PRIVATE_PROPERTY(d_func(), double end MEMBER end WRITE setEnd NOTIFY endChanged) - Q_PRIVATE_PROPERTY(d_func(), double freq MEMBER freq WRITE setFreq NOTIFY freqChanged) + Q_PROPERTY(double end READ end WRITE setEnd NOTIFY endChanged) + Q_PROPERTY(double freq READ freq WRITE setFreq NOTIFY freqChanged) Q_PROPERTY(int offset READ offset WRITE setOffset NOTIFY offsetChanged) - Q_PRIVATE_PROPERTY(d_func(), double phase MEMBER phase WRITE setPhase NOTIFY phaseChanged) + Q_PROPERTY(double phase READ phase WRITE setPhase NOTIFY phaseChanged) Q_PROPERTY(VibratoPoints *points READ points CONSTANT) - Q_PRIVATE_PROPERTY(d_func(), double start MEMBER start WRITE setStart NOTIFY startChanged) + Q_PROPERTY(double start READ start WRITE setStart NOTIFY startChanged) public: ~Vibrato() override; diff --git a/src/libs/application/dspxmodel/src/rangehelpers.h b/src/libs/application/dspxmodel/src/rangehelpers.h index 458160b5..7843ca3a 100644 --- a/src/libs/application/dspxmodel/src/rangehelpers.h +++ b/src/libs/application/dspxmodel/src/rangehelpers.h @@ -1,6 +1,7 @@ #ifndef DIFFSCOPE_DSPX_MODEL_RANGEHELPERS_H #define DIFFSCOPE_DSPX_MODEL_RANGEHELPERS_H +#include #include namespace dspx::impl { @@ -21,6 +22,9 @@ namespace dspx::impl { using pointer = ItemType **; using reference = ItemType *&; + iterator() : m_sequence(nullptr), m_item(nullptr) { + } + iterator(const SequenceType *sequence, ItemType *item) : m_sequence(sequence), m_item(item) { } @@ -111,4 +115,7 @@ namespace dspx::impl { } +template +constexpr bool std::ranges::enable_borrowed_range> = true; + #endif //DIFFSCOPE_DSPX_MODEL_RANGEHELPERS_H diff --git a/src/libs/application/dspxmodel/src/selectionmodel/AnchorNodeSelectionModel.h b/src/libs/application/dspxmodel/src/selectionmodel/AnchorNodeSelectionModel.h index 0ad011b5..f0d31081 100644 --- a/src/libs/application/dspxmodel/src/selectionmodel/AnchorNodeSelectionModel.h +++ b/src/libs/application/dspxmodel/src/selectionmodel/AnchorNodeSelectionModel.h @@ -18,6 +18,7 @@ namespace dspx { class DSPX_MODEL_EXPORT AnchorNodeSelectionModel : public QObject { Q_OBJECT QML_ELEMENT + QML_UNCREATABLE("") Q_DECLARE_PRIVATE(AnchorNodeSelectionModel) Q_PROPERTY(AnchorNode *currentItem READ currentItem NOTIFY currentItemChanged) diff --git a/src/libs/application/dspxmodel/src/selectionmodel/ClipSelectionModel.cpp b/src/libs/application/dspxmodel/src/selectionmodel/ClipSelectionModel.cpp index c0bfa978..dad78e12 100644 --- a/src/libs/application/dspxmodel/src/selectionmodel/ClipSelectionModel.cpp +++ b/src/libs/application/dspxmodel/src/selectionmodel/ClipSelectionModel.cpp @@ -1,80 +1,211 @@ #include "ClipSelectionModel.h" #include "ClipSelectionModel_p.h" -#include "Model.h" +#include +#include + +#include #include #include #include +#include namespace dspx { - bool ClipSelectionModelPrivate::isAddedToModel(Clip *item) const { - return item->clipSequence() && item->clipSequence()->track()->trackList() == selectionModel->model()->tracks(); + bool ClipSelectionModelPrivate::isValidItem(Clip *item) const { + if (!item) { + return false; + } + auto clipSeq = item->clipSequence(); + if (!clipSeq) { + return false; + } + auto track = clipSeq->track(); + if (!track) { + return false; + } + auto trackList = track->trackList(); + if (!trackList) { + return false; + } + return trackList == selectionModel->model()->tracks(); } - void ClipSelectionModelPrivate::updateAssociation(Clip *item) { - Q_Q(ClipSelectionModel); - auto previousClipSequence = clipClipSequence.value(item); - auto currentClipSequence = item->clipSequence(); - if (previousClipSequence == currentClipSequence) { + void ClipSelectionModelPrivate::connectItem(Clip *item) { + if (connectedItems.contains(item)) { 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); + QObject::connect(item, &QObject::destroyed, q_ptr, [this](QObject *obj) { + dropItem(static_cast(obj)); + }); + QObject::connect(item, &Clip::clipSequenceChanged, q_ptr, [this, item]() { + auto oldClipSequence = clipToClipSequence.value(item, nullptr); + auto newClipSequence = item->clipSequence(); + + if (oldClipSequence != newClipSequence) { + // Update clipToClipSequence + if (newClipSequence) { + clipToClipSequence[item] = newClipSequence; + } else { + clipToClipSequence.remove(item); + } + + // Update clipSequencesWithSelectedItems + if (selectedItems.contains(item)) { + if (oldClipSequence) { + auto &items = clipSequencesWithSelectedItems[oldClipSequence]; + items.remove(item); + if (items.isEmpty()) { + clipSequencesWithSelectedItems.remove(oldClipSequence); + } + } + if (newClipSequence) { + clipSequencesWithSelectedItems[newClipSequence].insert(item); + } + Q_EMIT q_ptr->clipSequencesWithSelectedItemsChanged(); + } + } + + if (!isValidItem(item)) { + dropItem(item); + } + }); + + // Also connect to track's trackListChanged signal + auto clipSeq = item->clipSequence(); + if (clipSeq) { + auto track = clipSeq->track(); + if (track) { + QPointer item_ = item; + QObject::connect(track, &Track::trackListChanged, q_ptr, [this, item_]() { + if (!isValidItem(item_)) { + dropItem(item_); + } + }); } } + + connectedItems.insert(item); + } - 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); - } - }); + void ClipSelectionModelPrivate::disconnectItem(Clip *item) { + QObject::disconnect(item, nullptr, q_ptr, nullptr); + + // Disconnect from track if it exists + auto clipSeq = clipToClipSequence.value(item, nullptr); + if (clipSeq) { + auto track = clipSeq->track(); + if (track) { + QObject::disconnect(track, nullptr, q_ptr, nullptr); + } } - clipSequencesWithSelectedItems[currentClipSequence].insert(item); + + connectedItems.remove(item); + } - if (clipSequencesWithSelectedItemsUpdatedFlag) { - Q_EMIT q->clipSequencesWithSelectedItemsChanged(); + bool ClipSelectionModelPrivate::addToSelection(Clip *item) { + if (!isValidItem(item) || selectedItems.contains(item)) { + return false; } + connectItem(item); + selectedItems.insert(item); + + // Update clipToClipSequence + auto clipSeq = item->clipSequence(); + if (clipSeq) { + clipToClipSequence[item] = clipSeq; + clipSequencesWithSelectedItems[clipSeq].insert(item); + } + + Q_EMIT q_ptr->itemSelected(item, true); + return true; } - void ClipSelectionModelPrivate::removeAssociation(Clip *item) { - Q_Q(ClipSelectionModel); - auto clipSequence = clipClipSequence.value(item); - bool clipSequencesWithSelectedItemsUpdatedFlag = false; + bool ClipSelectionModelPrivate::removeFromSelection(Clip *item) { + if (!item) { + return false; + } + if (!selectedItems.remove(item)) { + return false; + } + + // Update clipSequencesWithSelectedItems + auto clipSeq = clipToClipSequence.value(item); + if (clipSeq) { + auto &items = clipSequencesWithSelectedItems[clipSeq]; + items.remove(item); + if (items.isEmpty()) { + clipSequencesWithSelectedItems.remove(clipSeq); + } + } + clipToClipSequence.remove(item); + + if (item != currentItem) { + disconnectItem(item); + } + Q_EMIT q_ptr->itemSelected(item, false); + return true; + } - Q_ASSERT(clipSequence); - clipSequencesWithSelectedItems[clipSequence].remove(item); - if (clipSequencesWithSelectedItems[clipSequence].isEmpty()) { - clipSequencesWithSelectedItemsUpdatedFlag = true; - QObject::disconnect(clipSequence, nullptr, q, nullptr); - clipSequencesWithSelectedItems.remove(clipSequence); + bool ClipSelectionModelPrivate::clearSelection() { + if (selectedItems.isEmpty()) { + return false; + } + const auto items = selectedItems.values(); + bool selectionChanged = false; + for (auto clip : items) { + selectionChanged |= removeFromSelection(clip); } - clipClipSequence.remove(item); + return selectionChanged; + } - if (clipSequencesWithSelectedItemsUpdatedFlag) { - Q_EMIT q->clipSequencesWithSelectedItemsChanged(); + void ClipSelectionModelPrivate::dropItem(Clip *item) { + if (!item) { + return; + } + const int oldCount = selectedItems.size(); + bool selectionChanged = removeFromSelection(item); + bool countChanged = selectionChanged && oldCount != selectedItems.size(); + bool currentChanged = false; + bool clipSequencesChanged = selectionChanged; + + if (currentItem == item) { + if (!selectedItems.contains(item)) { + disconnectItem(item); + } + currentItem = nullptr; + currentChanged = true; + } + if (selectionChanged) { + Q_EMIT q_ptr->selectedItemsChanged(); + if (countChanged) { + Q_EMIT q_ptr->selectedCountChanged(); + } + } + if (clipSequencesChanged) { + Q_EMIT q_ptr->clipSequencesWithSelectedItemsChanged(); + } + if (currentChanged) { + Q_EMIT q_ptr->currentItemChanged(); } } - void ClipSelectionModelPrivate::clearAssociation() { - Q_Q(ClipSelectionModel); - if (clipClipSequence.isEmpty()) + void ClipSelectionModelPrivate::setCurrentItem(Clip *item) { + if (!isValidItem(item)) { + item = nullptr; + } + if (currentItem == item) { return; - clipClipSequence.clear(); - clipSequencesWithSelectedItems.clear(); - Q_EMIT q->clipSequencesWithSelectedItemsChanged(); + } + auto oldItem = currentItem; + currentItem = item; + if (oldItem && !selectedItems.contains(oldItem)) { + disconnectItem(oldItem); + } + if (currentItem && !selectedItems.contains(currentItem)) { + connectItem(currentItem); + } + Q_EMIT q_ptr->currentItemChanged(); } ClipSelectionModel::ClipSelectionModel(SelectionModel *parent) : QObject(parent), d_ptr(new ClipSelectionModelPrivate) { @@ -110,6 +241,41 @@ namespace dspx { return d->selectedItems.contains(item); } + void ClipSelectionModelPrivate::select(Clip *item, SelectionModel::SelectionCommand command) { + const int oldCount = selectedItems.size(); + bool selectionChanged = false; + + if (command & SelectionModel::ClearPreviousSelection) { + selectionChanged |= clearSelection(); + } + if ((command & SelectionModel::Select) && (command & SelectionModel::Deselect)) { + if (selectedItems.contains(item)) { + selectionChanged |= removeFromSelection(item); + } else { + selectionChanged |= addToSelection(item); + } + } else if (command & SelectionModel::Select) { + selectionChanged |= addToSelection(item); + } else if (command & SelectionModel::Deselect) { + selectionChanged |= removeFromSelection(item); + } + if (command & SelectionModel::SetCurrentItem) { + setCurrentItem(item); + } + + bool clipSequencesChanged = selectionChanged; // TODO + + if (selectionChanged) { + Q_EMIT q_ptr->selectedItemsChanged(); + if (oldCount != selectedItems.size()) { + Q_EMIT q_ptr->selectedCountChanged(); + } + } + if (clipSequencesChanged) { + Q_EMIT q_ptr->clipSequencesWithSelectedItemsChanged(); + } + } + } #include "moc_ClipSelectionModel.cpp" diff --git a/src/libs/application/dspxmodel/src/selectionmodel/ClipSelectionModel.h b/src/libs/application/dspxmodel/src/selectionmodel/ClipSelectionModel.h index bc4b8218..cf89890e 100644 --- a/src/libs/application/dspxmodel/src/selectionmodel/ClipSelectionModel.h +++ b/src/libs/application/dspxmodel/src/selectionmodel/ClipSelectionModel.h @@ -1,11 +1,9 @@ #ifndef DIFFSCOPE_DSPX_MODEL_CLIPSELECTIONMODEL_H #define DIFFSCOPE_DSPX_MODEL_CLIPSELECTIONMODEL_H -#include -#include #include -#include +#include namespace dspx { @@ -17,6 +15,7 @@ namespace dspx { class DSPX_MODEL_EXPORT ClipSelectionModel : public QObject { Q_OBJECT QML_ELEMENT + QML_UNCREATABLE("") Q_DECLARE_PRIVATE(ClipSelectionModel) Q_PROPERTY(Clip *currentItem READ currentItem NOTIFY currentItemChanged) diff --git a/src/libs/application/dspxmodel/src/selectionmodel/ClipSelectionModel_p.h b/src/libs/application/dspxmodel/src/selectionmodel/ClipSelectionModel_p.h index a5e27278..a499ad37 100644 --- a/src/libs/application/dspxmodel/src/selectionmodel/ClipSelectionModel_p.h +++ b/src/libs/application/dspxmodel/src/selectionmodel/ClipSelectionModel_p.h @@ -1,30 +1,38 @@ #ifndef DIFFSCOPE_DSPX_MODEL_CLIPSELECTIONMODEL_P_H #define DIFFSCOPE_DSPX_MODEL_CLIPSELECTIONMODEL_P_H +#include + #include -#include #include -#include - namespace dspx { - class ClipSelectionModelPrivate : public GenericGlobalItemSelectionModelData< - ClipSelectionModel, - ClipSelectionModelPrivate, - Clip, - &Clip::clipSequenceChanged - > { + class SelectionModel; + class ClipSequence; + + class ClipSelectionModelPrivate { Q_DECLARE_PUBLIC(ClipSelectionModel) public: - QHash clipClipSequence; + ClipSelectionModel *q_ptr; + SelectionModel *selectionModel; + QSet selectedItems; + Clip *currentItem = nullptr; + QSet connectedItems; + + QHash clipToClipSequence; QHash> clipSequencesWithSelectedItems; - bool isAddedToModel(Clip *item) const; - void updateAssociation(Clip *item); - void removeAssociation(Clip *item); - void clearAssociation(); + bool isValidItem(Clip *item) const; + void connectItem(Clip *item); + void disconnectItem(Clip *item); + bool addToSelection(Clip *item); + bool removeFromSelection(Clip *item); + bool clearSelection(); + void dropItem(Clip *item); + void setCurrentItem(Clip *item); + void select(Clip *item, SelectionModel::SelectionCommand command); }; } diff --git a/src/libs/application/dspxmodel/src/selectionmodel/GenericGlobalItemSelectionModelData_p.h b/src/libs/application/dspxmodel/src/selectionmodel/GenericGlobalItemSelectionModelData_p.h index ab14e939..57acb1e6 100644 --- a/src/libs/application/dspxmodel/src/selectionmodel/GenericGlobalItemSelectionModelData_p.h +++ b/src/libs/application/dspxmodel/src/selectionmodel/GenericGlobalItemSelectionModelData_p.h @@ -14,7 +14,7 @@ namespace dspx { void (Item::*superItemChangedSignal)(), class SuperItem = void > - class GenericGlobalItemSelectionModelData { + [[deprecated]] class GenericGlobalItemSelectionModelData { public: PublicClass *q_ptr; SelectionModel *selectionModel; diff --git a/src/libs/application/dspxmodel/src/selectionmodel/LabelSelectionModel.cpp b/src/libs/application/dspxmodel/src/selectionmodel/LabelSelectionModel.cpp index eb832235..3f395fc1 100644 --- a/src/libs/application/dspxmodel/src/selectionmodel/LabelSelectionModel.cpp +++ b/src/libs/application/dspxmodel/src/selectionmodel/LabelSelectionModel.cpp @@ -1,15 +1,118 @@ #include "LabelSelectionModel.h" #include "LabelSelectionModel_p.h" +#include + #include #include #include #include +#include namespace dspx { - bool LabelSelectionModelPrivate::isAddedToModel(Label *item) const { - return item->labelSequence() == selectionModel->model()->timeline()->labels(); + bool LabelSelectionModelPrivate::isValidItem(Label *item) const { + return item && item->labelSequence() == selectionModel->model()->timeline()->labels(); + } + + void LabelSelectionModelPrivate::connectItem(Label *item) { + if (connectedItems.contains(item)) { + return; + } + QObject::connect(item, &QObject::destroyed, q_ptr, [this](QObject *obj) { + dropItem(static_cast