diff --git a/.clang-format b/.clang-format index f3affdec..2857fcec 100644 --- a/.clang-format +++ b/.clang-format @@ -1,130 +1,252 @@ -# References: -# https://clang.llvm.org/docs/ClangFormatStyleOptions.html -# https://code.qt.io/cgit/qt/qt5.git/tree/_clang-format - +Language: Cpp BasedOnStyle: LLVM - -Standard: c++20 - -# 指针和引用的对齐方式。 -# 可能的值有: -# PAS_Left (在配置中: Left) 指针左对齐。 -# PAS_Right (在配置中: Right) 指针右对齐。 -# PAS_Middle (在配置中: Middle) 指针中间对齐。 -PointerAlignment: Right - -# public/protected/private 等访问修饰符偏移量 AccessModifierOffset: -4 - -# 缩进长度 -IndentWidth: 4 - -# 连续空行的最大数 -MaxEmptyLinesToKeep: 999 - -# 在OC中的@property后面添加一个空格。例如:使用“@property (readonly)”而不是“@property(readonly)” -ObjCSpaceAfterProperty: true - -# OC块中所拍的字符数 -ObjCBlockIndentWidth: 4 - -# 取决于值, 语句“int f() { return 0; }”可以被放到一个单行。 -# 可能的值有: -# SFS_None (在配置中: None) 从不合并方法或函数到单独的一行。 -# SFS_Empty (在配置中: Empty) 仅合并空的函数。 -# SFS_Inline (在配置中: Inline) 仅合并类中定义的方法或函数. 意味着 “empty”. -# SFS_All (在配置中: All) 合并所有的方法适应单行. -AllowShortFunctionsOnASingleLine: None - -# 如果为真(true), 语句“if (a) return;” 能被放到单行。 -AllowShortIfStatementsOnASingleLine: false - -# 如果为真(true), 对齐注释。 -AlignTrailingComments: true - -# 如果为真,对齐连续的宏定义 -AlignConsecutiveMacros: true - -# 如果为真(true),将会在“[”之后和“]”之前插入空格。 -SpacesInSquareBrackets: false - -# 如果为真(true), 将会在“(”之后和“)”之前插入空格。 -SpacesInParentheses : false - -# 如果为真(true), 校准连续的声明。 -# 这将会校准连续多行的声明的名字。这将会导致像下面这样的格式: -# int aaaa = 12; -# float b = 23; -# std::string ccc = 23; -AlignConsecutiveDeclarations: false - -# 如果为真(true),连续调整多行 -# 这将会调整连续行中的分配操作符。这将会导致像下面这样的格式: -# int aaaa = 12; -# int b = 23; -# int ccc = 23; -AlignConsecutiveAssignments: false - -# 如果为假(false),移除分配操作符(=)前空格。 -SpaceBeforeAssignmentOperators: true - -# 如果为真(true), 将会在字面量容器中插入空格(例如 OC和Javascript的数组和字典字面量)。 -SpacesInContainerLiterals: false - -# 缩进case标签 +AlignAfterOpenBracket: BlockIndent +AlignArrayOfStructures: None +AlignConsecutiveAssignments: + Enabled: false +AlignConsecutiveDeclarations: + Enabled: false +AlignConsecutiveMacros: + Enabled: false +AlignConsecutiveShortCaseStatements: + Enabled: false +AlignEscapedNewlines: Left +AlignOperands: Align +AlignTrailingComments: + Kind: Never +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowBreakBeforeNoexceptSpecifier: Never +AllowShortBlocksOnASingleLine: Empty +AllowShortCaseLabelsOnASingleLine: true +AllowShortEnumsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +AttributeMacros: + - __capability +BinPackArguments: true +BinPackParameters: true +BitFieldColonSpacing: Both +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterExternBlock: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakAfterAttributes: Leave +BreakAfterJavaFieldAnnotations: false +BreakArrays: true +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: Always +BreakBeforeBraces: Attach +BreakBeforeInlineASMColon: OnlyMultiline +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeColon +BreakInheritanceList: BeforeColon +BreakStringLiterals: true +ColumnLimit: 0 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: false +ForEachMacros: [foreach, Q_FOREACH, BOOST_FOREACH, forever, Q_FOREVER, QBENCHMARK, QBENCHMARK_ONCE] +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Regroup +IncludeCategories: + # Local headers + - Regex: '^".*\.h"$' + Priority: -999 + # Qt public + - Regex: '^<(Q[a-zA-Z0-9_]+|qt_[a-zA-Z0-9_]+\.h|qqml_[a-zA-Z0-9_]+\.h)>$' + Priority: 201 + # Qt qpa + - Regex: '^$' + Priority: 202 + # Qt private + - Regex: '^$' + Priority: 203 + # Windows platform-specific + - Regex: '^<[a-zA-Z0-9_]+\.h>$' + Priority: 1 + # C++ standard library + - Regex: '^<[a-z0-9_]+>$' + Priority: 2 + # Boost + - Regex: '^> $env:GITHUB_ENV + New-Item -ItemType Directory -Path ${{ github.workspace }}/../vcpkg_archives -Force + Write-Output VCPKG_DEFAULT_BINARY_CACHE=$(Resolve-Path ${{ github.workspace }}/../vcpkg_archives) >> $env:GITHUB_ENV - name: Restore vcpkg binary cache id: cache_vcpkg uses: actions/cache/restore@v4 with: path: ${{ env.VCPKG_DEFAULT_BINARY_CACHE }} - key: ${{ runner.os }}-vcpkg-${{ inputs.ref }} + key: ${{ runner.os }}-vcpkg-${{ inputs.ref }}-${{ github.sha }} restore-keys: | - ${{ runner.os }}-vcpkg-${{ inputs.ref }} + ${{ runner.os }}-vcpkg-${{ inputs.ref }}- - name: Install vcpkg dependencies env: 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_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)) { New-Item $env:VCPKG_DEFAULT_BINARY_CACHE -ItemType directory } + Set-Location ${{ github.workspace }}/.. git clone https://github.com/microsoft/vcpkg.git Set-Location vcpkg + Write-Output VCPKG_ROOT_DIR=$(Resolve-Path .) >> $env:GITHUB_ENV git checkout ${{ inputs.ref }} ${{ runner.os == 'Windows' && './bootstrap-vcpkg.bat' || 'sh ./bootstrap-vcpkg.sh' }} ./vcpkg install ` --x-manifest-root=${{ github.workspace }}/scripts/vcpkg-manifest ` --x-install-root=./installed - Write-Output VCPKG_ROOT_DIR=$(Resolve-Path .) >> $env:GITHUB_ENV - name: Save vcpkg binary cache uses: actions/cache/save@v4 with: path: ${{ env.VCPKG_DEFAULT_BINARY_CACHE }} - key: ${{ runner.os }}-vcpkg-${{ inputs.ref }} + key: ${{ runner.os }}-vcpkg-${{ inputs.ref }}-${{ github.sha }} 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 71c71284..7dd6dc8c 100644 --- a/.github/workflows/dev-build.yml +++ b/.github/workflows/dev-build.yml @@ -16,12 +16,18 @@ on: - 'LICENSE' - 'crowdin.yml' - '.github/**' + - '**/translations/*.ts' workflow_dispatch: inputs: identifier: description: 'Version identifier' required: false type: string + use_ccache: + description: 'Use CCache' + required: false + type: boolean + default: true defaults: run: @@ -35,13 +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.input.identifier && '.' || '' }}${{ github.event.input.identifier }} + 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 }} @@ -61,24 +70,58 @@ jobs: with: ref: ${{ env.VCPKG_REF }} + - name: Restore CCache cache + uses: actions/cache/restore@v4 + if: ${{ github.event.inputs.use_ccache == 'true' }} + with: + path: ${{ env.CCACHE_DIR }} + key: ${{ runner.os }}-ccache-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-ccache- + - name: Build run: | - $output = & ./scripts/ci/Build.ps1 -BuildType dev -VcpkgRootDir $env:VCPKG_ROOT_DIR -VersionIdentifier $env:VERSION_IDENTIFIER - Write-Output ARTIFACT_NAME=$($output.ApplicationName)_$($output.Semver -replace '[\.\-\+]', '_') >> $env:GITHUB_ENV - Write-Output BUILD_DIR=$($output.BuildDir) >> $env:GITHUB_ENV - Write-Output INSTALLED_DIR=$($output.InstalledDir) >> $env:GITHUB_ENV + $output = & ./scripts/ci/Build.ps1 ` + -BuildType dev ` + -VcpkgRootDir $env:VCPKG_ROOT_DIR ` + -BuildDir $env:BUILD_DIR ` + -InstallDir $env:INSTALL_DIR ` + -VersionIdentifier $env:VERSION_IDENTIFIER ` + ${{ github.event.inputs.use_ccache == 'true' && '-CCache' || '' }} + 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 + uses: actions/cache/save@v4 + if: ${{ github.event.inputs.use_ccache == 'true' }} + with: + path: ${{ env.CCACHE_DIR }} + key: ${{ runner.os }}-ccache-${{ github.sha }} + - name: Collect symbol files run: | - $output = & ./scripts/ci/Collect-Symbol-Files.ps1 -VcpkgRootDir $env:VCPKG_ROOT_DIR -InstalledDir $env:INSTALLED_DIR + $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/.gitmodules b/.gitmodules index 4705759d..848bde1c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -29,6 +29,3 @@ [submodule "opendspx"] path = src/libs/3rdparty/opendspx url = ../../diffscope/opendspx.git -[submodule "src/libs/3rdparty/opendspx"] - path = src/libs/3rdparty/opendspx - url = https://github.com/diffscope/opendspx.git diff --git a/CMakeLists.txt b/CMakeLists.txt index a21b7f91..49c3b32b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,5 @@ cmake_minimum_required(VERSION 3.19) +cmake_policy(SET CMP0141 NEW) project(DiffScope VERSION 0.2.0 LANGUAGES CXX) @@ -31,6 +32,10 @@ endif() if(APPLICATION_INSTALL) include(GNUInstallDirs) include(CMakePackageConfigHelpers) + if(MSVC) + set(CMAKE_INSTALL_SYSTEM_RUNTIME_DESTINATION "") + include(InstallRequiredSystemLibraries) + endif() endif() # ---------------------------------- diff --git a/crowdin.yml b/crowdin.yml index 98144a6e..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/Audio_en_US.ts - translation: /src/plugins/audio/res/translations/Audio_%locale_with_underscore%.ts - - source: /src/plugins/welcomewizard/res/translations/WelcomeWizard_en_US.ts - translation: /src/plugins/welcomewizard/res/translations/WelcomeWizard_%locale_with_underscore%.ts - - source: /src/plugins/achievement/res/translations/Achievement_en_US.ts - translation: /src/plugins/achievement/res/translations/Achievement_%locale_with_underscore%.ts - - source: /src/plugins/maintenance/res/translations/Maintenance_en_US.ts - translation: /src/plugins/maintenance/res/translations/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 366ed748..0afa4394 100644 --- a/scripts/ci/Build.ps1 +++ b/scripts/ci/Build.ps1 @@ -7,16 +7,32 @@ param( [ValidateNotNullOrEmpty()] [string]$VcpkgRootDir, - [string]$VersionIdentifier = "0" + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$BuildDir, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$InstallDir, + + [string]$VersionIdentifier = "0", + + [switch]$CCache = $false ) if (-not (Test-Path $VcpkgRootDir)) { throw "Vcpkg root directory does not exist: $VcpkgRootDir" } +New-Item $BuildDir -ItemType Directory -Force +New-Item $InstallDir -ItemType Directory -Force + Write-Host "Build type: $BuildType" -Write-Host "Commit: $Commit" Write-Host "Vcpkg root directory: $VcpkgRootDir" +Write-Host "Build directory: $(Resolve-Path $BuildDir)" +Write-Host "Install directory: $(Resolve-Path $InstallDir)" +Write-Host "Version identifier: $VersionIdentifier" +Write-Host "Use CCache: $CCache" $cmakeListsPath = "CMakeLists.txt" @@ -62,10 +78,17 @@ Write-Host "Semver: $semver" $installerFileBase = "${applicationName}_$($semver -replace '[\.\-\+]', '_')_installer" -cmake -B build -G Ninja ` +$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)" ` + "-DCMAKE_C_COMPILER_LAUNCHER=$($CCache ? 'ccache' : '')" ` + "-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 ` @@ -74,10 +97,28 @@ cmake -B build -G Ninja ` "-DAPPLICATION_NAME=$applicationName" ` "-DAPPLICATION_DISPLAY_NAME=$applicationDisplayName" ` "-DAPPLICATION_SEMVER=$semver" ` - -DCMAKE_INSTALL_PREFIX=installed | Write-Host + "-DCMAKE_INSTALL_PREFIX=$(Resolve-Path $InstallDir)" | Write-Host +if ($LASTEXITCODE -ne 0) { + throw "Configure failed" +} + +if ($CCache) { + ccache --zero-stats | Write-Host +} + +cmake --build $(Resolve-Path $BuildDir) --target all | Write-Host +if ($LASTEXITCODE -ne 0) { + throw "Build failed" +} + +if ($CCache) { + ccache --show-stats | Write-Host +} -cmake --build build --target all | Write-Host -cmake --build build --target install | Write-Host +cmake --build $(Resolve-Path $BuildDir) --target install | Write-Host +if ($LASTEXITCODE -ne 0) { + throw "Install failed" +} $buildResult = @{ ProjectName = $projectName @@ -85,8 +126,6 @@ $buildResult = @{ ApplicationName = $applicationName ApplicationDisplayName = $applicationDisplayName Semver = $semver - BuildDir = $(Resolve-Path "build") - InstalledDir = $(Resolve-Path "installed") InstallerFileBase = $installerFileBase } diff --git a/scripts/ci/Collect-Symbol-Files.ps1 b/scripts/ci/Collect-Symbol-Files.ps1 index f04d1c27..914b3c52 100644 --- a/scripts/ci/Collect-Symbol-Files.ps1 +++ b/scripts/ci/Collect-Symbol-Files.ps1 @@ -5,7 +5,7 @@ param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] - [string]$InstalledDir + [string]$InstallDir ) $symbolFilesDirectory = [System.IO.Path]::GetTempPath() + "DiffScope-Symbols" @@ -15,14 +15,14 @@ if ($IsWindows) { $PATTERN = "PDB file found at.*'(.*)'" $env:_NT_ALT_SYMBOL_PATH = $(Join-Path $VcpkgRootDir "/installed/x64-windows/bin") - Push-Location $InstalledDir + Push-Location $InstallDir $dllFiles = Get-ChildItem -Path . -Recurse | Where-Object { $_.Extension -eq '.exe' -or $_.Extension -eq '.dll' } foreach ($dllFile in $dllFiles) { dumpbin /PDBPATH:VERBOSE $dllFile.FullName | Write-Host $dumpbinOutput = dumpbin /PDBPATH $dllFile.FullName - $matches = [regex]::Matches($dumpbinOutput, $PATTERN) - if ($matches.Count -gt 0) { - $pdbPath = $matches.Groups[1].Value + $m = [regex]::Matches($dumpbinOutput, $PATTERN) + if ($m.Count -gt 0) { + $pdbPath = $m.Groups[1].Value Write-Host "$dllFile -> $pdbPath" $pdbTargetDirectory = "$symbolFilesDirectory/$(Split-Path $(Resolve-Path $dllFile.FullName -Relative))" if (!(Test-Path $pdbTargetDirectory)) { @@ -35,7 +35,7 @@ if ($IsWindows) { } Pop-Location } elseif ($IsMacOS) { - Push-Location $InstalledDir + Push-Location $InstallDir $dllFiles = Get-ChildItem -Path . -Recurse | Where-Object { (file $_) -match "Mach-O 64-bit" } foreach ($dllFile in $dllFiles) { $dsymutilOutput = dsymutil -s $dllFile.FullName @@ -47,13 +47,14 @@ if ($IsWindows) { } dsymutil $dllFile.FullName -o "$pdbTargetDirectory/$($dllFile.Name).dSYM" strip -S $dllFile.FullName + codesign --force --sign - $dllFile.FullName } else { Write-Host "Skip: $dllFile" } } Pop-Location } else { - Push-Location $InstalledDir + Push-Location $InstallDir $dllFiles = Get-ChildItem -Path . -Recurse | Where-Object { (file $_) -match "ELF 64-bit" } foreach ($dllFile in $dllFiles) { file $dllFile.FullName diff --git a/scripts/ci/Create-DMG.ps1 b/scripts/ci/Create-DMG.ps1 new file mode 100644 index 00000000..c4dd5544 --- /dev/null +++ b/scripts/ci/Create-DMG.ps1 @@ -0,0 +1,122 @@ +param ( + [Parameter(Mandatory)] + [string]$AppPath, + + [Parameter(Mandatory)] + [string]$Semver, + + [Parameter(Mandatory)] + [string]$ApplicationDisplayName, + + [Parameter(Mandatory)] + [string]$InstallerFileBase +) + +$BackgroundSrcDir = "src/app/share/dmg" + +$Bg1x = Join-Path $BackgroundSrcDir "dmg_background.png" +$Bg2x = Join-Path $BackgroundSrcDir "dmg_background@2x.png" + +if (!(Test-Path $Bg1x) -or !(Test-Path $Bg2x)) { + throw "dmg_background.png and dmg_background@2x.png do not exist in $BackgroundSrcDir" +} + +if (!(Test-Path $AppPath)) { + throw "App bundle not exist: $AppPath" +} + +# Temporary directory +$TempDir = Join-Path ([System.IO.Path]::GetTempPath()) ("dmg-build-" + [System.Guid]::NewGuid()) +New-Item -ItemType Directory -Path $TempDir | Out-Null + +$Bg1xOut = Join-Path $TempDir "dmg_background.png" +$Bg2xOut = Join-Path $TempDir "dmg_background@2x.png" +$BgTiff = Join-Path $TempDir "dmg_background.tiff" +$AppBundleName = "$ApplicationDisplayName.app" +$AppBundlePath = Join-Path $TempDir $AppBundleName + +$VersionText = "Version $Semver" + +try { + # ----------------------------- + # Step 1: Preprocess background + # ----------------------------- + + # 1x image + & magick ` + "$Bg1x" ` + -gravity south ` + -pointsize 12 ` + -fill "rgba(37,37,37,0.25)" ` + -annotate +0+8 "$VersionText" ` + "$Bg1xOut" | Write-Host + + if ($LASTEXITCODE -ne 0) { + throw "ImageMagick failed to process dmg_background.png" + } + + # 2x image (scaled) + & magick ` + "$Bg2x" ` + -gravity south ` + -pointsize 24 ` + -fill "rgba(37,37,37,0.25)" ` + -annotate +0+16 "$VersionText" ` + "$Bg2xOut" | Write-Host + + if ($LASTEXITCODE -ne 0) { + throw "ImageMagick failed to process dmg_background@2x.png" + } + + # Combine into TIFF + & tiffutil ` + -cathidpicheck ` + "$Bg1xOut" ` + "$Bg2xOut" ` + -out "$BgTiff" | Write-Host + + if ($LASTEXITCODE -ne 0) { + throw "tiffutil failed to create TIFF" + } + + # ----------------------------- + # Step 2: Build DMG + # ----------------------------- + $DmgName = "$InstallerFileBase.dmg" + $DmgPath = Join-Path (Get-Location) $DmgName + + if (Test-Path $DmgPath) { + Remove-Item $DmgPath -Force + } + + if (Test-Path $AppBundlePath) { + Remove-Item $AppBundlePath -Recurse -Force + } + + Move-Item -Path $AppPath -Destination $AppBundlePath + + & codesign --deep --force --sign - $AppBundlePath | Write-Host + + <# TODO: create-dmg currently places hidden .background file to the right of the visible area, so we have to leave some space for the horizontal scroll bar #> + & create-dmg ` + --volname "$ApplicationDisplayName" ` + --background "$BgTiff" ` + --window-size 600 448 ` + --icon-size 128 ` + --icon "$(Split-Path $AppBundlePath -Leaf)" 132 280 ` + --app-drop-link 468 280 ` + "$DmgPath" ` + "$AppBundlePath" | Write-Host + + if ($LASTEXITCODE -ne 0) { + throw "create-dmg failed" + } + + Write-Output $DmgPath +} +finally { + # Cleanup temp files + if (Test-Path $TempDir) { + Remove-Item $TempDir -Recurse -Force + } +} diff --git a/scripts/vcpkg b/scripts/vcpkg index 4e8a1567..cf97d45c 160000 --- a/scripts/vcpkg +++ b/scripts/vcpkg @@ -1 +1 @@ -Subproject commit 4e8a156710a1a43f2a522e216595f60eaceb8d54 +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 cf55ff35..ee7fe48a 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -17,13 +18,20 @@ #include #include +#include +#include + #include +#include + #include #include #include +#include + #ifdef APPLICATION_ENABLE_BREAKPAD # include #endif @@ -37,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() { @@ -52,6 +62,12 @@ class MyLoaderSpec : public Loader::LoaderSpec { splashConfigPath = ApplicationInfo::applicationLocation(ApplicationInfo::BuiltinResources) + 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 { @@ -84,6 +100,7 @@ class MyLoaderSpec : public Loader::LoaderSpec { void beforeLoadPlugins() override { RuntimeInterface::setQmlEngine(engine); + SVS::FluentSystemIconsImageProvider::addToEngine(engine); auto settings = RuntimeInterface::settings(); QLocale locale; @@ -98,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({ @@ -178,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/app/share/dmg/dmg_background.png b/src/app/share/dmg/dmg_background.png new file mode 100644 index 00000000..56a7dd80 Binary files /dev/null and b/src/app/share/dmg/dmg_background.png differ diff --git a/src/app/share/dmg/dmg_background@2x.png b/src/app/share/dmg/dmg_background@2x.png new file mode 100644 index 00000000..0f3a01fc Binary files /dev/null and b/src/app/share/dmg/dmg_background@2x.png differ diff --git a/src/libs/3rdparty/choruskit b/src/libs/3rdparty/choruskit index 00cbb74c..e40b3316 160000 --- a/src/libs/3rdparty/choruskit +++ b/src/libs/3rdparty/choruskit @@ -1 +1 @@ -Subproject commit 00cbb74c1c39b54bbd7178fbd4bc9e5f6cbdb1d9 +Subproject commit e40b331652f9a81eea9be505305dbb198abd8dae diff --git a/src/libs/3rdparty/opendspx b/src/libs/3rdparty/opendspx index 1fd1b5e3..333b2b8f 160000 --- a/src/libs/3rdparty/opendspx +++ b/src/libs/3rdparty/opendspx @@ -1 +1 @@ -Subproject commit 1fd1b5e3d73d15c99377e8bfcf33bc1b7f86e91f +Subproject commit 333b2b8f422688c707dfa1f2d48f71cadede3ad4 diff --git a/src/libs/3rdparty/qactionkit b/src/libs/3rdparty/qactionkit index 489fdeaa..573def48 160000 --- a/src/libs/3rdparty/qactionkit +++ b/src/libs/3rdparty/qactionkit @@ -1 +1 @@ -Subproject commit 489fdeaa0ab1f8816c873ad383030410681188d8 +Subproject commit 573def486e3f8c0f62768bcdcbc490b24cbd59af diff --git a/src/libs/3rdparty/scopicflow b/src/libs/3rdparty/scopicflow index 5b545596..23e75d14 160000 --- a/src/libs/3rdparty/scopicflow +++ b/src/libs/3rdparty/scopicflow @@ -1 +1 @@ -Subproject commit 5b5455963b11d3549127a33dfe39c05393d8cd7f +Subproject commit 23e75d14a6ad6006de3efcfbbc2dfc2119c20cad diff --git a/src/libs/3rdparty/svscraft b/src/libs/3rdparty/svscraft index db265541..161777b6 160000 --- a/src/libs/3rdparty/svscraft +++ b/src/libs/3rdparty/svscraft @@ -1 +1 @@ -Subproject commit db26554132eebdc78f7f2ecfa684e8920f1e2bfb +Subproject commit 161777b66cee8b6cb3213f3e77c564125c3301bf diff --git a/src/libs/application/CMakeLists.txt b/src/libs/application/CMakeLists.txt index 0ca9e678..21ecdf56 100644 --- a/src/libs/application/CMakeLists.txt +++ b/src/libs/application/CMakeLists.txt @@ -1,3 +1,7 @@ add_subdirectory(loadapi) -add_subdirectory(uishell) \ No newline at end of file +add_subdirectory(uishell) + +add_subdirectory(dspxmodel) + +add_subdirectory(transactional) \ No newline at end of file diff --git a/src/libs/application/dspxmodel/CMakeLists.txt b/src/libs/application/dspxmodel/CMakeLists.txt new file mode 100644 index 00000000..03719520 --- /dev/null +++ b/src/libs/application/dspxmodel/CMakeLists.txt @@ -0,0 +1,5 @@ +add_subdirectory(src) + +if(APPLICATION_BUILD_TESTS) + add_subdirectory(tests) +endif() \ No newline at end of file diff --git a/src/libs/application/dspxmodel/src/AnchorNode.cpp b/src/libs/application/dspxmodel/src/AnchorNode.cpp new file mode 100644 index 00000000..964c52dd --- /dev/null +++ b/src/libs/application/dspxmodel/src/AnchorNode.cpp @@ -0,0 +1,112 @@ +#include "AnchorNode.h" +#include "AnchorNode_p.h" + +#include + +#include + +#include +#include +#include + +namespace dspx { + + void AnchorNodePrivate::setAnchorNodeSequence(AnchorNode *item, AnchorNodeSequence *anchorNodeSequence) { + auto d = item->d_func(); + if (d->anchorNodeSequence != anchorNodeSequence) { + d->anchorNodeSequence = anchorNodeSequence; + Q_EMIT item->anchorNodeSequenceChanged(); + } + } + + AnchorNode::AnchorNode(Handle handle, Model *model) + : EntityObject(handle, model), d_ptr(new AnchorNodePrivate) { + Q_D(AnchorNode); + Q_ASSERT(model->strategy()->getEntityType(handle) == ModelStrategy::EI_ParamCurveAnchorNode); + d->q_ptr = this; + d->interp = model->strategy()->getEntityProperty(handle, ModelStrategy::P_Type).value(); + d->x = model->strategy()->getEntityProperty(handle, ModelStrategy::P_Position).toInt(); + d->y = model->strategy()->getEntityProperty(handle, ModelStrategy::P_Value).toInt(); + } + + AnchorNode::~AnchorNode() = default; + + AnchorNode::InterpolationMode AnchorNode::interp() const { + Q_D(const AnchorNode); + return d->interp; + } + + void AnchorNode::setInterp(InterpolationMode interp) { + Q_D(AnchorNode); + Q_ASSERT(interp == None || interp == Linear || interp == Hermite); + model()->strategy()->setEntityProperty(handle(), ModelStrategy::P_Type, QVariant::fromValue(interp)); + } + + int AnchorNode::x() const { + Q_D(const AnchorNode); + return d->x; + } + + void AnchorNode::setX(int x) { + Q_D(AnchorNode); + Q_ASSERT(x >= 0); + model()->strategy()->setEntityProperty(handle(), ModelStrategy::P_Position, x); + } + + int AnchorNode::y() const { + Q_D(const AnchorNode); + return d->y; + } + + void AnchorNode::setY(int y) { + Q_D(AnchorNode); + model()->strategy()->setEntityProperty(handle(), ModelStrategy::P_Value, y); + } + + QDspx::AnchorNode AnchorNode::toQDspx() const { + Q_D(const AnchorNode); + return { + .interp = static_cast(d->interp), + .x = d->x, + .y = d->y, + }; + } + + void AnchorNode::fromQDspx(const QDspx::AnchorNode &node) { + Q_D(AnchorNode); + setInterp(static_cast(node.interp)); + setX(node.x); + setY(node.y); + } + + AnchorNodeSequence *AnchorNode::anchorNodeSequence() const { + Q_D(const AnchorNode); + return d->anchorNodeSequence; + } + + void AnchorNode::handleSetEntityProperty(int property, const QVariant &value) { + Q_D(AnchorNode); + switch (property) { + case ModelStrategy::P_Type: { + d->interp = value.value(); + Q_EMIT interpChanged(d->interp); + break; + } + case ModelStrategy::P_Position: { + d->x = value.toInt(); + Q_EMIT xChanged(d->x); + break; + } + case ModelStrategy::P_Value: { + d->y = value.toInt(); + Q_EMIT yChanged(d->y); + break; + } + default: + Q_UNREACHABLE(); + } + } + +} + +#include "moc_AnchorNode.cpp" diff --git a/src/libs/application/dspxmodel/src/AnchorNode.h b/src/libs/application/dspxmodel/src/AnchorNode.h new file mode 100644 index 00000000..8a8c7b3a --- /dev/null +++ b/src/libs/application/dspxmodel/src/AnchorNode.h @@ -0,0 +1,71 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_ANCHORNODE_H +#define DIFFSCOPE_DSPX_MODEL_ANCHORNODE_H + +#include + +#include + +namespace QDspx { + struct AnchorNode; +} + +namespace dspx { + + class AnchorNodeSequence; + + class AnchorNodeSequencePrivate; + + class AnchorNodePrivate; + + class DSPX_MODEL_EXPORT AnchorNode : public EntityObject { + Q_OBJECT + QML_ELEMENT + QML_UNCREATABLE("") + Q_DECLARE_PRIVATE(AnchorNode) + 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) + + public: + enum InterpolationMode { + None = 0, + Linear, + Hermite + }; + Q_ENUM(InterpolationMode) + + ~AnchorNode() override; + + InterpolationMode interp() const; + void setInterp(InterpolationMode interp); + + int x() const; + void setX(int x); + + int y() const; + void setY(int y); + + QDspx::AnchorNode toQDspx() const; + void fromQDspx(const QDspx::AnchorNode &node); + + AnchorNodeSequence *anchorNodeSequence() const; + + Q_SIGNALS: + void interpChanged(InterpolationMode interp); + void xChanged(int x); + void yChanged(int y); + void anchorNodeSequenceChanged(); + + protected: + void handleSetEntityProperty(int property, const QVariant &value) override; + + private: + friend class ModelPrivate; + explicit AnchorNode(Handle handle, Model *model); + QScopedPointer d_ptr; + }; + +} + +#endif //DIFFSCOPE_DSPX_MODEL_ANCHORNODE_H diff --git a/src/libs/application/dspxmodel/src/AnchorNodeSequence.cpp b/src/libs/application/dspxmodel/src/AnchorNodeSequence.cpp new file mode 100644 index 00000000..15f8f19d --- /dev/null +++ b/src/libs/application/dspxmodel/src/AnchorNodeSequence.cpp @@ -0,0 +1,114 @@ +#include "AnchorNodeSequence.h" +#include "AnchorNodeSequence_p.h" + +#include +#include + +#include + +#include +#include +#include +#include +#include + +namespace dspx { + + AnchorNodeSequence::AnchorNodeSequence(ParamCurveAnchor *paramCurveAnchor, Handle handle, Model *model) : EntityObject(handle, model), d_ptr(new AnchorNodeSequencePrivate) { + Q_D(AnchorNodeSequence); + Q_ASSERT(model->strategy()->getEntityType(handle) == ModelStrategy::ES_ParamCurveAnchorNodes); + d->q_ptr = this; + d->pModel = ModelPrivate::get(model); + d->paramCurveAnchor = paramCurveAnchor; + + d->init(model->strategy()->getEntitiesFromSequenceContainer(handle)); + } + + AnchorNodeSequence::~AnchorNodeSequence() = default; + + int AnchorNodeSequence::size() const { + Q_D(const AnchorNodeSequence); + return d->container.size(); + } + + AnchorNode *AnchorNodeSequence::firstItem() const { + Q_D(const AnchorNodeSequence); + return d->firstItem; + } + + AnchorNode *AnchorNodeSequence::lastItem() const { + Q_D(const AnchorNodeSequence); + return d->lastItem; + } + + AnchorNode *AnchorNodeSequence::previousItem(AnchorNode *item) const { + Q_D(const AnchorNodeSequence); + return d->container.previousItem(item); + } + + AnchorNode *AnchorNodeSequence::nextItem(AnchorNode *item) const { + Q_D(const AnchorNodeSequence); + return d->container.nextItem(item); + } + + QList AnchorNodeSequence::slice(int position, int length) const { + Q_D(const AnchorNodeSequence); + return d->container.slice(position, length); + } + + bool AnchorNodeSequence::contains(AnchorNode *item) const { + Q_D(const AnchorNodeSequence); + return d->container.contains(item); + } + + bool AnchorNodeSequence::insertItem(AnchorNode *item) { + Q_D(AnchorNodeSequence); + return d->pModel->strategy->insertIntoSequenceContainer(handle(), item->handle()); + } + + bool AnchorNodeSequence::removeItem(AnchorNode *item) { + Q_D(AnchorNodeSequence); + return d->pModel->strategy->takeFromSequenceContainer(handle(), item->handle()); + } + + QList AnchorNodeSequence::toQDspx() const { + Q_D(const AnchorNodeSequence); + QList ret; + ret.reserve(d->container.size()); + for (const auto &[_, item] : d->container.m_items) { + ret.append(item->toQDspx()); + } + return ret; + } + + void AnchorNodeSequence::fromQDspx(const QList &nodes) { + while (size()) { + removeItem(firstItem()); + } + for (const auto &node : nodes) { + auto item = model()->createAnchorNode(); + item->fromQDspx(node); + insertItem(item); + } + } + + void AnchorNodeSequence::handleInsertIntoSequenceContainer(Handle entity) { + Q_D(AnchorNodeSequence); + d->handleInsertIntoSequenceContainer(entity); + } + + void AnchorNodeSequence::handleTakeFromSequenceContainer(Handle takenEntity, Handle entity) { + Q_D(AnchorNodeSequence); + d->handleTakeFromSequenceContainer(takenEntity, entity); + } + + ParamCurveAnchor *AnchorNodeSequence::paramCurveAnchor() const { + Q_D(const AnchorNodeSequence); + return d->paramCurveAnchor; + } + + + +} + +#include "moc_AnchorNodeSequence.cpp" diff --git a/src/libs/application/dspxmodel/src/AnchorNodeSequence.h b/src/libs/application/dspxmodel/src/AnchorNodeSequence.h new file mode 100644 index 00000000..a965ba5f --- /dev/null +++ b/src/libs/application/dspxmodel/src/AnchorNodeSequence.h @@ -0,0 +1,74 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_ANCHORNODESEQUENCE_H +#define DIFFSCOPE_DSPX_MODEL_ANCHORNODESEQUENCE_H + +#include + +#include +#include + +namespace QDspx { + struct AnchorNode; +} + +namespace dspx { + + class AnchorNode; + class ParamCurveAnchor; + + class AnchorNodeSequencePrivate; + + class DSPX_MODEL_EXPORT AnchorNodeSequence : public EntityObject { + Q_OBJECT + QML_ELEMENT + QML_UNCREATABLE("") + Q_DECLARE_PRIVATE(AnchorNodeSequence) + Q_PROPERTY(int size READ size NOTIFY sizeChanged) + Q_PROPERTY(AnchorNode *firstItem READ firstItem NOTIFY firstItemChanged) + Q_PROPERTY(AnchorNode *lastItem READ lastItem NOTIFY lastItemChanged) + Q_PROPERTY(ParamCurveAnchor *paramCurveAnchor READ paramCurveAnchor CONSTANT) + Q_PRIVATE_PROPERTY(d_func(), QJSValue iterable READ iterable CONSTANT) + public: + ~AnchorNodeSequence() override; + + int size() const; + AnchorNode *firstItem() const; + AnchorNode *lastItem() const; + Q_INVOKABLE AnchorNode *previousItem(AnchorNode *item) const; + Q_INVOKABLE AnchorNode *nextItem(AnchorNode *item) const; + Q_INVOKABLE QList slice(int position, int length) const; + Q_INVOKABLE bool contains(AnchorNode *item) const; + + Q_INVOKABLE bool insertItem(AnchorNode *item); + Q_INVOKABLE bool removeItem(AnchorNode *item); + + QList toQDspx() const; + void fromQDspx(const QList &nodes); + + ParamCurveAnchor *paramCurveAnchor() const; + + auto asRange() const { + return impl::SequenceRange(this); + } + + Q_SIGNALS: + void itemAboutToInsert(AnchorNode *item); + void itemInserted(AnchorNode *item); + void itemAboutToRemove(AnchorNode *item); + void itemRemoved(AnchorNode *item); + void sizeChanged(int size); + void firstItemChanged(AnchorNode *item); + void lastItemChanged(AnchorNode *item); + + protected: + void handleInsertIntoSequenceContainer(Handle entity) override; + void handleTakeFromSequenceContainer(Handle takenEntity, Handle entity) override; + + private: + friend class ModelPrivate; + explicit AnchorNodeSequence(ParamCurveAnchor *paramCurveAnchor, Handle handle, Model *model); + QScopedPointer d_ptr; + }; + +} + +#endif //DIFFSCOPE_DSPX_MODEL_ANCHORNODESEQUENCE_H diff --git a/src/libs/application/dspxmodel/src/AnchorNodeSequence_p.h b/src/libs/application/dspxmodel/src/AnchorNodeSequence_p.h new file mode 100644 index 00000000..4fd3bb21 --- /dev/null +++ b/src/libs/application/dspxmodel/src/AnchorNodeSequence_p.h @@ -0,0 +1,19 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_ANCHORNODESEQUENCE_P_H +#define DIFFSCOPE_DSPX_MODEL_ANCHORNODESEQUENCE_P_H + +#include + +#include +#include + +namespace dspx { + + class AnchorNodeSequencePrivate : public PointSequenceData { + Q_DECLARE_PUBLIC(AnchorNodeSequence) + public: + ParamCurveAnchor *paramCurveAnchor{}; + }; + +} + +#endif //DIFFSCOPE_DSPX_MODEL_ANCHORNODESEQUENCE_P_H \ No newline at end of file diff --git a/src/libs/application/dspxmodel/src/AnchorNode_p.h b/src/libs/application/dspxmodel/src/AnchorNode_p.h new file mode 100644 index 00000000..a018f31f --- /dev/null +++ b/src/libs/application/dspxmodel/src/AnchorNode_p.h @@ -0,0 +1,24 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_ANCHORNODE_P_H +#define DIFFSCOPE_DSPX_MODEL_ANCHORNODE_P_H + +#include + +namespace dspx { + + class AnchorNodePrivate { + Q_DECLARE_PUBLIC(AnchorNode) + public: + AnchorNode *q_ptr; + AnchorNode::InterpolationMode interp; + int x; + int y; + AnchorNodeSequence *anchorNodeSequence{}; + + + + static void setAnchorNodeSequence(AnchorNode *item, AnchorNodeSequence *anchorNodeSequence); + }; + +} + +#endif //DIFFSCOPE_DSPX_MODEL_ANCHORNODE_P_H \ No newline at end of file diff --git a/src/libs/application/dspxmodel/src/AudioClip.cpp b/src/libs/application/dspxmodel/src/AudioClip.cpp new file mode 100644 index 00000000..72a6d736 --- /dev/null +++ b/src/libs/application/dspxmodel/src/AudioClip.cpp @@ -0,0 +1,71 @@ +#include "AudioClip.h" + +#include + +#include +#include +#include +#include +#include + +namespace dspx { + + class AudioClipPrivate { + Q_DECLARE_PUBLIC(AudioClip) + public: + AudioClip *q_ptr; + ModelPrivate *pModel; + QString path; + }; + + AudioClip::AudioClip(Handle handle, Model *model) : Clip(Audio, handle, model), d_ptr(new AudioClipPrivate) { + Q_D(AudioClip); + Q_ASSERT(model->strategy()->getEntityType(handle) == ModelStrategy::EI_AudioClip); + d->q_ptr = this; + d->pModel = ModelPrivate::get(model); + } + + AudioClip::~AudioClip() = default; + + QString AudioClip::path() const { + Q_D(const AudioClip); + return d->path; + } + + void AudioClip::setPath(const QString &path) { + Q_D(AudioClip); + d->pModel->strategy->setEntityProperty(handle(), ModelStrategy::P_Path, path); + } + + QDspx::AudioClip AudioClip::toQDspx() const { + return { + name(), + control()->toQDspx(), + time()->toQDspx(), + workspace()->toQDspx(), + path() + }; + } + + void AudioClip::fromQDspx(const QDspx::AudioClip &clip) { + setName(clip.name); + control()->fromQDspx(clip.control); + time()->fromQDspx(clip.time); + workspace()->fromQDspx(clip.workspace); + setPath(clip.path); + } + + void AudioClip::handleSetEntityProperty(int property, const QVariant &value) { + Q_D(AudioClip); + switch (property) { + case ModelStrategy::P_Path: { + d->path = value.toString(); + Q_EMIT pathChanged(d->path); + break; + } + default: + Clip::handleSetEntityProperty(property, value); + } + } + +} diff --git a/src/libs/application/dspxmodel/src/AudioClip.h b/src/libs/application/dspxmodel/src/AudioClip.h new file mode 100644 index 00000000..3c8db6cc --- /dev/null +++ b/src/libs/application/dspxmodel/src/AudioClip.h @@ -0,0 +1,46 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_AUDIOCLIP_H +#define DIFFSCOPE_DSPX_MODEL_AUDIOCLIP_H + +#include + +#include + +namespace QDspx { + struct AudioClip; +} + +namespace dspx { + + class AudioClipPrivate; + + class DSPX_MODEL_EXPORT AudioClip : public Clip { + Q_OBJECT + QML_ELEMENT + QML_UNCREATABLE("") + Q_DECLARE_PRIVATE(AudioClip) + Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged) + + public: + ~AudioClip() override; + + QString path() const; + void setPath(const QString &path); + + QDspx::AudioClip toQDspx() const; + void fromQDspx(const QDspx::AudioClip &clip); + + Q_SIGNALS: + void pathChanged(const QString &path); + + protected: + void handleSetEntityProperty(int property, const QVariant &value) override; + + private: + friend class ModelPrivate; + explicit AudioClip(Handle handle, Model *model); + QScopedPointer d_ptr; + }; + +} + +#endif //DIFFSCOPE_DSPX_MODEL_AUDIOCLIP_H diff --git a/src/libs/application/dspxmodel/src/BasicModelStrategy.cpp b/src/libs/application/dspxmodel/src/BasicModelStrategy.cpp new file mode 100644 index 00000000..a88d80df --- /dev/null +++ b/src/libs/application/dspxmodel/src/BasicModelStrategy.cpp @@ -0,0 +1,213 @@ +#include "BasicModelStrategy.h" + +#include + +#include +#include + +namespace dspx { + + BasicModelStrategy::BasicModelStrategy(QObject *parent) : ModelStrategy(parent) { + } + + BasicModelStrategy::~BasicModelStrategy() = default; + + Handle BasicModelStrategy::createEntity(Entity entityType) { + auto object = BasicModelStrategyEntity::createByType(entityType, this); + Handle entity{reinterpret_cast(object)}; + Q_EMIT createEntityNotified(entity, entityType); + return entity; + } + + void BasicModelStrategy::destroyEntity(Handle entity) { + auto object = handleCast(entity); + delete object; + Q_EMIT destroyEntityNotified(entity); + } + + ModelStrategy::Entity BasicModelStrategy::getEntityType(Handle entity) { + return reinterpret_cast(entity.d)->type; + } + + QList BasicModelStrategy::getEntitiesFromSequenceContainer(Handle sequenceContainerEntity) { + QList a; + std::ranges::transform(handleCast(sequenceContainerEntity)->sequence, std::back_inserter(a), [](auto *obj) { + return Handle{reinterpret_cast(obj)}; + }); + return a; + } + + QList BasicModelStrategy::getEntitiesFromListContainer(Handle listContainerEntity) { + QList a; + std::ranges::transform(handleCast(listContainerEntity)->list, std::back_inserter(a), [](auto *obj) { + return Handle{reinterpret_cast(obj)}; + }); + return a; + } + + QList> BasicModelStrategy::getEntitiesFromMapContainer(Handle mapContainerEntity) { + QList> a; + for (auto [key, value] : handleCast(mapContainerEntity)->map.asKeyValueRange()) { + a.append({key, Handle{reinterpret_cast(value)}}); + } + return a; + } + + bool BasicModelStrategy::insertIntoSequenceContainer(Handle sequenceContainerEntity, Handle entity) { + auto sequenceContainerObject = handleCast(sequenceContainerEntity); + auto object = reinterpret_cast(entity.d); + if (object->parent() != this) { + return false; + } + sequenceContainerObject->sequence.insert(object); + object->setParent(sequenceContainerObject); + Q_EMIT insertIntoSequenceContainerNotified(sequenceContainerEntity, entity); + return true; + } + + bool BasicModelStrategy::insertIntoListContainer(Handle listContainerEntity, Handle entity, int index) { + auto listContainerObject = handleCast(listContainerEntity); + if (index < 0 || index > listContainerObject->list.size()) { + return false; + } + auto object = reinterpret_cast(entity.d); + if (object->parent() != this) { + return false; + } + listContainerObject->list.insert(index, object); + object->setParent(listContainerObject); + Q_EMIT insertIntoListContainerNotified(listContainerEntity, entity, index); + return true; + } + + bool BasicModelStrategy::insertIntoMapContainer(Handle mapContainerEntity, Handle entity, const QString &key) { + auto mapContainerObject = handleCast(mapContainerEntity); + auto object = reinterpret_cast(entity.d); + if (object->parent() != this) { + return false; + } + if (mapContainerObject->map.contains(key)) { + auto handle = takeFromMapContainer(mapContainerEntity, key); + destroyEntity(handle); + } + mapContainerObject->map.insert(key, object); + object->setParent(mapContainerObject); + Q_EMIT insertIntoMapContainerNotified(mapContainerEntity, entity, key); + 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); + if (sequenceContainerObject->sequence.contains(object)) { + sequenceContainerObject->sequence.remove(object); + object->setParent(this); + Q_EMIT takeFromContainerNotified(entity, sequenceContainerEntity, entity); + return entity; + } + return {}; + } + + Handle BasicModelStrategy::takeFromListContainer(Handle listContainerEntity, int index) { + auto listContainerObject = handleCast(listContainerEntity); + if (index < 0 || index >= listContainerObject->list.size()) { + return {}; + } + auto object = listContainerObject->list.takeAt(index); + object->setParent(this); + Handle entity{reinterpret_cast(object)}; + Q_EMIT takeFromListContainerNotified(entity, listContainerEntity, index); + return entity; + } + + Handle BasicModelStrategy::takeFromMapContainer(Handle mapContainerEntity, const QString &key) { + auto mapContainerObject = handleCast(mapContainerEntity); + if (mapContainerObject->map.contains(key)) { + auto object = mapContainerObject->map.take(key); + object->setParent(this); + Handle entity{reinterpret_cast(object)}; + Q_EMIT takeFromMapContainerNotified(entity, mapContainerEntity, key); + return entity; + } + return {}; + } + + bool BasicModelStrategy::rotateListContainer(Handle listContainerEntity, int leftIndex, int middleIndex, int rightIndex) { + auto listContainerObject = handleCast(listContainerEntity); + if (leftIndex < 0 || leftIndex > listContainerObject->list.size() || middleIndex < leftIndex || middleIndex > listContainerObject->list.size() || rightIndex < middleIndex || rightIndex > listContainerObject->list.size()) { + return false; + } + std::rotate(listContainerObject->list.begin() + leftIndex, listContainerObject->list.begin() + middleIndex, listContainerObject->list.begin() + rightIndex); + Q_EMIT rotateListContainerNotified(listContainerEntity, leftIndex, middleIndex, rightIndex); + return true; + } + + void BasicModelStrategy::setEntityProperty(Handle entity, Property property, const QVariant &value) { + auto object = handleCast(entity); + Q_ASSERT(isEntityTypeAndPropertyTypeCompatible(object->type, property)); + object->properties.insert(property, value); + Q_EMIT setEntityPropertyNotified(entity, property, value); + } + + QVariant BasicModelStrategy::getEntityProperty(Handle entity, Property property) { + auto object = handleCast(entity); + Q_ASSERT(isEntityTypeAndPropertyTypeCompatible(object->type, property)); + return object->properties.value(property); + } + bool BasicModelStrategy::spliceDataArray(Handle dataArrayEntity, int index, int length, const QVariantList &values) { + auto &data = handleCast(dataArrayEntity)->data; + if (index < 0 || index > data.size() || length < 0 || index + length > data.size()) { + return false; + } + SpliceHelper::splice(data, data.begin() + index, data.begin() + index + length, values.begin(), values.end()); + Q_EMIT spliceDataArrayNotified(dataArrayEntity, index, length, values); + return true; + } + QVariantList BasicModelStrategy::sliceDataArray(Handle dataArrayEntity, int index, int length) { + const auto &data = handleCast(dataArrayEntity)->data; + if (index < 0 || index >= data.size()) { + return {}; + } + return data.mid(index, length); + } + int BasicModelStrategy::getSizeOfDataArray(Handle dataArrayEntity) { + const auto &data = handleCast(dataArrayEntity)->data; + return static_cast(data.size()); + } + bool BasicModelStrategy::rotateDataArray(Handle dataArrayEntity, int leftIndex, int middleIndex, int rightIndex) { + auto &data = handleCast(dataArrayEntity)->data; + if (leftIndex < 0 || leftIndex > data.size() || middleIndex < leftIndex || middleIndex > data.size() || rightIndex < middleIndex || rightIndex > data.size()) { + return false; + } + std::rotate(data.begin() + leftIndex, data.begin() + middleIndex, data.begin() + rightIndex); + Q_EMIT rotateDataArrayNotified(dataArrayEntity, leftIndex, middleIndex, rightIndex); + return true; + } + + Handle BasicModelStrategy::getAssociatedSubEntity(Handle entity, Relationship relationship) { + auto object = handleCast(entity); + auto subObject = object->associatedSubEntities.value(relationship); + if (!subObject) { + Entity subObjectType = getAssociatedSubEntityTypeFromEntityTypeAndRelationship(object->type, relationship); + subObject = BasicModelStrategyEntity::createByType(subObjectType, object); + object->associatedSubEntities.insert(relationship, subObject); + } + Handle subEntity{reinterpret_cast(subObject)}; + return subEntity; + } + +} diff --git a/src/libs/application/dspxmodel/src/BasicModelStrategy.h b/src/libs/application/dspxmodel/src/BasicModelStrategy.h new file mode 100644 index 00000000..0cf44804 --- /dev/null +++ b/src/libs/application/dspxmodel/src/BasicModelStrategy.h @@ -0,0 +1,39 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_BASICMODELSTRATEGY_H +#define DIFFSCOPE_DSPX_MODEL_BASICMODELSTRATEGY_H + +#include + +namespace dspx { + + class DSPX_MODEL_EXPORT BasicModelStrategy : public ModelStrategy { + Q_OBJECT + public: + explicit BasicModelStrategy(QObject *parent = nullptr); + ~BasicModelStrategy() override; + + Handle createEntity(Entity entityType) override; + void destroyEntity(Handle entity) override; + Entity getEntityType(Handle entity) override; + QList getEntitiesFromSequenceContainer(Handle sequenceContainerEntity) override; + QList getEntitiesFromListContainer(Handle listContainerEntity) override; + QList> getEntitiesFromMapContainer(Handle mapContainerEntity) override; + bool insertIntoSequenceContainer(Handle sequenceContainerEntity, Handle entity) override; + bool insertIntoListContainer(Handle listContainerEntity, Handle entity, int index) override; + bool insertIntoMapContainer(Handle mapContainerEntity, Handle entity, const QString &key) override; + 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; + bool rotateListContainer(Handle listContainerEntity, int leftIndex, int middleIndex, int rightIndex) override; + void setEntityProperty(Handle entity, Property property, const QVariant &value) override; + QVariant getEntityProperty(Handle entity, Property property) override; + bool spliceDataArray(Handle dataContainerEntity, int index, int length, const QVariantList &values) override; + QVariantList sliceDataArray(Handle dataContainerEntity, int index, int length) override; + int getSizeOfDataArray(Handle dataContainerEntity) override; + bool rotateDataArray(Handle dataContainerEntity, int leftIndex, int middleIndex, int rightIndex) override; + Handle getAssociatedSubEntity(Handle entity, Relationship relationship) override; + }; + +} + +#endif //DIFFSCOPE_DSPX_MODEL_BASICMODELSTRATEGY_H diff --git a/src/libs/application/dspxmodel/src/BasicModelStrategyEntity_p.h b/src/libs/application/dspxmodel/src/BasicModelStrategyEntity_p.h new file mode 100644 index 00000000..8b111d48 --- /dev/null +++ b/src/libs/application/dspxmodel/src/BasicModelStrategyEntity_p.h @@ -0,0 +1,123 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_BASICMODELSTRATEGYENTITY_P_H +#define DIFFSCOPE_DSPX_MODEL_BASICMODELSTRATEGYENTITY_P_H + +#include +#include +#include + +#include + +namespace dspx { + class BasicModelStrategyEntity : public QObject { + Q_OBJECT + public: + using QObject::QObject; + + ModelStrategy::Entity type{}; + + static inline BasicModelStrategyEntity *createByType(ModelStrategy::Entity type, QObject *parent); + }; + + class BasicModelStrategyItemEntity : public BasicModelStrategyEntity { + Q_OBJECT + public: + using BasicModelStrategyEntity::BasicModelStrategyEntity; + + QHash properties; + QHash associatedSubEntities; + }; + + class BasicModelStrategySequenceContainerEntity : public BasicModelStrategyEntity { + Q_OBJECT + public: + using BasicModelStrategyEntity::BasicModelStrategyEntity; + + QSet sequence; + }; + + class BasicModelStrategyListContainerEntity : public BasicModelStrategyEntity { + Q_OBJECT + public: + using BasicModelStrategyEntity::BasicModelStrategyEntity; + + QList list; + }; + + class BasicModelStrategyMapContainerEntity : public BasicModelStrategyEntity { + Q_OBJECT + public: + using BasicModelStrategyEntity::BasicModelStrategyEntity; + + QHash map; + }; + + class BasicModelStrategyDataArrayEntity : public BasicModelStrategyEntity { + Q_OBJECT + public: + using BasicModelStrategyEntity::BasicModelStrategyEntity; + + QVariantList data; + }; + + BasicModelStrategyEntity *BasicModelStrategyEntity::createByType(ModelStrategy::Entity type, QObject *parent) { + BasicModelStrategyEntity *obj; + switch (type) { + case ModelStrategy::EI_AudioClip: + case ModelStrategy::EI_Global: + case ModelStrategy::EI_Label: + case ModelStrategy::EI_Note: + case ModelStrategy::EI_Param: + case ModelStrategy::EI_ParamCurveAnchor: + case ModelStrategy::EI_ParamCurveFree: + case ModelStrategy::EI_ParamCurveAnchorNode: + case ModelStrategy::EI_Phoneme: + case ModelStrategy::EI_SingingClip: + case ModelStrategy::EI_Source: + case ModelStrategy::EI_Tempo: + case ModelStrategy::EI_TimeSignature: + case ModelStrategy::EI_Track: + case ModelStrategy::EI_WorkspaceInfo: + obj = new BasicModelStrategyItemEntity(parent); + obj->type = type; + break; + case ModelStrategy::ES_Clips: + case ModelStrategy::ES_Labels: + case ModelStrategy::ES_Notes: + case ModelStrategy::ES_ParamCurveAnchorNodes: + case ModelStrategy::ES_ParamCurves: + case ModelStrategy::ES_Tempos: + case ModelStrategy::ES_TimeSignatures: + obj = new BasicModelStrategySequenceContainerEntity(parent); + obj->type = type; + break; + case ModelStrategy::EL_Phonemes: + case ModelStrategy::EL_Tracks: + obj = new BasicModelStrategyListContainerEntity(parent); + obj->type = type; + break; + case ModelStrategy::ED_ParamCurveFreeValues: + case ModelStrategy::ED_VibratoPoints: + obj = new BasicModelStrategyDataArrayEntity(parent); + obj->type = type; + break; + case ModelStrategy::EM_Params: + case ModelStrategy::EM_Sources: + case ModelStrategy::EM_Workspace: + obj = new BasicModelStrategyMapContainerEntity(parent); + obj->type = type; + break; + default: + Q_UNREACHABLE(); + } + return obj; + } + + template + T *handleCast(Handle entity) { + auto obj = qobject_cast(reinterpret_cast(entity.d)); + Q_ASSERT(obj); + return obj; + } +} + +#endif //DIFFSCOPE_DSPX_MODEL_BASICMODELSTRATEGYENTITY_P_H diff --git a/src/libs/application/dspxmodel/src/BusControl.cpp b/src/libs/application/dspxmodel/src/BusControl.cpp new file mode 100644 index 00000000..ee5d174a --- /dev/null +++ b/src/libs/application/dspxmodel/src/BusControl.cpp @@ -0,0 +1,30 @@ +#include "BusControl.h" + +#include + +namespace dspx { + + BusControl::BusControl(Handle handle, Model *model) : Control(handle, model) { + } + + BusControl::~BusControl() = default; + + QDspx::BusControl BusControl::toQDspx() const { + return { + .gain = gain(), + .pan = pan(), + .mute = mute() + }; + } + + void BusControl::fromQDspx(const QDspx::BusControl &control) { + setGain(control.gain); + setPan(control.pan); + setMute(control.mute); + } + + void BusControl::handleProxySetEntityProperty(int property, const QVariant &value) { + Control::handleProxySetEntityProperty(property, value); + } + +} diff --git a/src/libs/application/dspxmodel/src/BusControl.h b/src/libs/application/dspxmodel/src/BusControl.h new file mode 100644 index 00000000..b395f809 --- /dev/null +++ b/src/libs/application/dspxmodel/src/BusControl.h @@ -0,0 +1,31 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_BUSCONTROL_H +#define DIFFSCOPE_DSPX_MODEL_BUSCONTROL_H + +#include + +namespace QDspx { + struct BusControl; +} + +namespace dspx { + + class BusControl : public Control { + Q_OBJECT + QML_ELEMENT + QML_UNCREATABLE("") + + public: + ~BusControl() override; + + QDspx::BusControl toQDspx() const; + void fromQDspx(const QDspx::BusControl &control); + + private: + friend class ModelPrivate; + explicit BusControl(Handle handle, Model *model); + void handleProxySetEntityProperty(int property, const QVariant &value); + }; + +} + +#endif //DIFFSCOPE_DSPX_MODEL_BUSCONTROL_H diff --git a/src/libs/application/dspxmodel/src/CMakeLists.txt b/src/libs/application/dspxmodel/src/CMakeLists.txt new file mode 100644 index 00000000..66c13391 --- /dev/null +++ b/src/libs/application/dspxmodel/src/CMakeLists.txt @@ -0,0 +1,48 @@ +project(dspxmodel VERSION ${APPLICATION_VERSION}) + +qm_import(Qml) + +file(GLOB_RECURSE _src *.h *.cpp) + +ck_add_library(${PROJECT_NAME} SHARED AUTOGEN) + +find_path(INTERVAL_TREE_INCLUDE_DIRS "interval-tree/interval_tree.hpp" REQUIRED) + +target_include_directories(${PROJECT_NAME} PUBLIC + "$" +) + +qm_configure_target(${PROJECT_NAME} + SOURCES ${_src} + LINKS opendspx::model + QT_LINKS Core Gui Qml + QT_INCLUDE_PRIVATE Core Gui Qml + INCLUDE_PRIVATE * + PREFIX DSPX_MODEL +) + +if(QT_KNOWN_POLICY_QTP0001) + qt_policy(SET QTP0001 NEW) +endif() +if(QT_KNOWN_POLICY_QTP0004) + qt_policy(SET QTP0004 NEW) +endif() + +file(GLOB_RECURSE _qml_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.qml *.js *.mjs) + +qt_add_qml_module(${PROJECT_NAME} + URI DiffScope.DspxModel + QML_FILES ${_qml_files} + NAMESPACE dspx + OUTPUT_DIRECTORY ${QMSETUP_BUILD_DIR}/qml/DiffScope/DspxModel +) + +qm_install_qml_modules(${PROJECT_NAME}) + +ck_sync_include(${PROJECT_NAME} FORCE) + +set_target_properties(${PROJECT_NAME} PROPERTIES + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED TRUE +) + diff --git a/src/libs/application/dspxmodel/src/Clip.cpp b/src/libs/application/dspxmodel/src/Clip.cpp new file mode 100644 index 00000000..4caf4492 --- /dev/null +++ b/src/libs/application/dspxmodel/src/Clip.cpp @@ -0,0 +1,160 @@ +#include "Clip.h" +#include "Clip_p.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace dspx { + + void ClipPrivate::setOverlapped(Clip *item, bool overlapped) { + auto d = item->d_func(); + if (d->overlapped != overlapped) { + d->overlapped = overlapped; + Q_EMIT item->overlappedChanged(overlapped); + } + } + + void ClipPrivate::setClipSequence(Clip *item, ClipSequence *clipSequence) { + auto d = item->d_func(); + if (d->clipSequence != clipSequence) { + d->clipSequence = clipSequence; + Q_EMIT item->clipSequenceChanged(); + } + } + + Clip::Clip(ClipType type, Handle handle, Model *model) : EntityObject(handle, model), d_ptr(new ClipPrivate) { + Q_D(Clip); + d->q_ptr = this; + d->pModel = ModelPrivate::get(model); + d->name = d->pModel->strategy->getEntityProperty(handle, ModelStrategy::P_Name).toString(); + d->control = d->pModel->createObject(handle); + d->time = d->pModel->createObject(handle); + d->type = type; + d->workspace = d->pModel->createObject(d->pModel->strategy->getAssociatedSubEntity(handle, ModelStrategy::R_Workspace)); + + connect(d->time, &ClipTime::startChanged, this, [this] { + Q_EMIT positionChanged(position()); + }); + connect(d->time, &ClipTime::clipStartChanged, this, [this] { + Q_EMIT positionChanged(position()); + }); + connect(d->time, &ClipTime::clipLenChanged, this, [this] { + Q_EMIT lengthChanged(length()); + }); + } + + Clip::~Clip() = default; + + QString Clip::name() const { + Q_D(const Clip); + return d->name; + } + + void Clip::setName(const QString &name) { + Q_D(Clip); + d->pModel->strategy->setEntityProperty(handle(), ModelStrategy::P_Name, name); + } + + BusControl *Clip::control() const { + Q_D(const Clip); + return d->control; + } + + ClipTime *Clip::time() const { + Q_D(const Clip); + return d->time; + } + + Clip::ClipType Clip::type() const { + Q_D(const Clip); + return d->type; + } + + Workspace *Clip::workspace() const { + Q_D(const Clip); + return d->workspace; + } + + QDspx::ClipRef Clip::toQDspx() const { + Q_D(const Clip); + switch (d->type) { + case Audio: + return QSharedPointer::create(static_cast(this)->toQDspx()); + case Singing: + return QSharedPointer::create(static_cast(this)->toQDspx()); + default: + Q_UNREACHABLE(); + } + } + + void Clip::fromQDspx(const QDspx::ClipRef &clip) { + switch (clip->type) { + case QDspx::Clip::Audio: + static_cast(this)->fromQDspx(*clip.staticCast()); + break; + case QDspx::Clip::Singing: + static_cast(this)->fromQDspx(*clip.staticCast()); + break; + default: + Q_UNREACHABLE(); + } + } + + ClipSequence *Clip::clipSequence() const { + Q_D(const Clip); + return d->clipSequence; + } + + int Clip::position() const { + Q_D(const Clip); + return d->time->start() + d->time->clipStart(); + } + + int Clip::length() const { + Q_D(const Clip); + return d->time->clipLen(); + } + + bool Clip::isOverlapped() const { + Q_D(const Clip); + return d->overlapped; + } + + void Clip::handleSetEntityProperty(int property, const QVariant &value) { + Q_D(Clip); + switch (property) { + case ModelStrategy::P_Name: { + d->name = value.toString(); + Q_EMIT nameChanged(d->name); + break; + } + case ModelStrategy::P_ControlGain: + case ModelStrategy::P_ControlPan: + case ModelStrategy::P_ControlMute: { + ModelPrivate::proxySetEntityPropertyNotify(d->control, property, value); + break; + } + case ModelStrategy::P_Position: + case ModelStrategy::P_Length: + case ModelStrategy::P_ClipStart: + case ModelStrategy::P_ClipLength: { + ModelPrivate::proxySetEntityPropertyNotify(d->time, property, value); + break; + } + default: + Q_UNREACHABLE(); + } + } +} + +#include "moc_Clip.cpp" diff --git a/src/libs/application/dspxmodel/src/Clip.h b/src/libs/application/dspxmodel/src/Clip.h new file mode 100644 index 00000000..c35024e9 --- /dev/null +++ b/src/libs/application/dspxmodel/src/Clip.h @@ -0,0 +1,82 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_CLIP_H +#define DIFFSCOPE_DSPX_MODEL_CLIP_H + +#include + +#include + +namespace QDspx { + struct Clip; + using ClipRef = QSharedPointer; +} + +namespace dspx { + + class BusControl; + class ClipTime; + class Workspace; + class ClipSequence; + + class ClipPrivate; + + class DSPX_MODEL_EXPORT Clip : public EntityObject { + Q_OBJECT + QML_ELEMENT + QML_UNCREATABLE("") + Q_DECLARE_PRIVATE(Clip) + Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) + Q_PROPERTY(BusControl *control READ control CONSTANT) + Q_PROPERTY(ClipTime *time READ time CONSTANT) + Q_PROPERTY(ClipType type READ type CONSTANT) + Q_PROPERTY(Workspace *workspace READ workspace CONSTANT) + Q_PROPERTY(ClipSequence *clipSequence READ clipSequence NOTIFY clipSequenceChanged) + Q_PROPERTY(bool overlapped READ isOverlapped NOTIFY overlappedChanged) + public: + enum ClipType { + Audio, + Singing, + }; + Q_ENUM(ClipType) + + ~Clip() override; + + QString name() const; + void setName(const QString &name); + + BusControl *control() const; + + ClipTime *time() const; + + ClipType type() const; + + Workspace *workspace() const; + + QDspx::ClipRef toQDspx() const; + void fromQDspx(const QDspx::ClipRef &clip); + + ClipSequence *clipSequence() const; + + int position() const; + + int length() const; + + bool isOverlapped() const; + + Q_SIGNALS: + void nameChanged(const QString &name); + void clipSequenceChanged(); + void positionChanged(int position); + void lengthChanged(int length); + void overlappedChanged(bool overlapped); + + protected: + explicit Clip(ClipType type, Handle handle, Model *model); + void handleSetEntityProperty(int property, const QVariant &value) override; + + private: + QScopedPointer d_ptr; + }; + +} // dspx + +#endif //DIFFSCOPE_DSPX_MODEL_CLIP_H diff --git a/src/libs/application/dspxmodel/src/ClipSequence.cpp b/src/libs/application/dspxmodel/src/ClipSequence.cpp new file mode 100644 index 00000000..16c2fb08 --- /dev/null +++ b/src/libs/application/dspxmodel/src/ClipSequence.cpp @@ -0,0 +1,198 @@ +#include "ClipSequence.h" +#include "ClipSequence_p.h" + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +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); + d->q_ptr = this; + d->pModel = ModelPrivate::get(model); + d->track = track; + + d->init(model->strategy()->getEntitiesFromSequenceContainer(handle)); + } + + ClipSequence::~ClipSequence() = default; + + int ClipSequence::size() const { + Q_D(const ClipSequence); + return d->pointContainer.size(); + } + + Clip *ClipSequence::firstItem() const { + Q_D(const ClipSequence); + return d->firstItem; + } + + Clip *ClipSequence::lastItem() const { + Q_D(const ClipSequence); + return d->lastItem; + } + + Track *ClipSequence::track() const { + Q_D(const ClipSequence); + return d->track; + } + + Clip *ClipSequence::previousItem(Clip *item) const { + Q_D(const ClipSequence); + return d->pointContainer.previousItem(item); + } + + Clip *ClipSequence::nextItem(Clip *item) const { + Q_D(const ClipSequence); + return d->pointContainer.nextItem(item); + } + + QList ClipSequence::slice(int position, int length) const { + Q_D(const ClipSequence); + return d->rangeContainer.slice(position, length); + } + + bool ClipSequence::contains(Clip *item) const { + Q_D(const ClipSequence); + return d->pointContainer.contains(item); + } + + bool ClipSequence::insertItem(Clip *item) { + Q_D(ClipSequence); + return d->pModel->strategy->insertIntoSequenceContainer(handle(), item->handle()); + } + + bool ClipSequence::removeItem(Clip *item) { + Q_D(ClipSequence); + 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; + ret.reserve(d->pointContainer.size()); + for (const auto &[_, item] : d->pointContainer.m_items) { + ret.append(item->toQDspx()); + } + return ret; + } + + void ClipSequence::fromQDspx(const QList &clips) { + while (size()) { + removeItem(firstItem()); + } + for (const auto &clip : clips) { + Clip *item = nullptr; + switch (clip->type) { + case QDspx::Clip::Audio: + item = model()->createAudioClip(); + break; + case QDspx::Clip::Singing: + item = model()->createSingingClip(); + break; + default: + Q_UNREACHABLE(); + } + item->fromQDspx(clip); + insertItem(item); + } + } + + void ClipSequence::handleInsertIntoSequenceContainer(Handle entity) { + Q_D(ClipSequence); + d->handleInsertIntoSequenceContainer(entity); + } + + void ClipSequence::handleTakeFromSequenceContainer(Handle takenEntity, Handle entity) { + 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); + } + +} + +#include "moc_ClipSequence.cpp" diff --git a/src/libs/application/dspxmodel/src/ClipSequence.h b/src/libs/application/dspxmodel/src/ClipSequence.h new file mode 100644 index 00000000..6a9123f8 --- /dev/null +++ b/src/libs/application/dspxmodel/src/ClipSequence.h @@ -0,0 +1,78 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_CLIPSEQUENCE_H +#define DIFFSCOPE_DSPX_MODEL_CLIPSEQUENCE_H + +#include + +#include +#include + +namespace QDspx { + struct Clip; + using ClipRef = QSharedPointer; +} + +namespace dspx { + + class Clip; + class Track; + + class ClipSequencePrivate; + + class DSPX_MODEL_EXPORT ClipSequence : public EntityObject { + Q_OBJECT + QML_ELEMENT + QML_UNCREATABLE("") + Q_DECLARE_PRIVATE(ClipSequence) + Q_PROPERTY(int size READ size NOTIFY sizeChanged) + Q_PROPERTY(Clip *firstItem READ firstItem NOTIFY firstItemChanged) + Q_PROPERTY(Clip *lastItem READ lastItem NOTIFY lastItemChanged) + Q_PROPERTY(Track *track READ track CONSTANT) + Q_PRIVATE_PROPERTY(d_func(), QJSValue iterable READ iterable CONSTANT) + public: + ~ClipSequence() override; + + int size() const; + Clip *firstItem() const; + Clip *lastItem() const; + Q_INVOKABLE Clip *previousItem(Clip *item) const; + Q_INVOKABLE Clip *nextItem(Clip *item) const; + Q_INVOKABLE QList slice(int position, int length) const; + Q_INVOKABLE bool contains(Clip *item) const; + + 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); + + Track *track() const; + + auto asRange() const { + return impl::SequenceRange(this); + } + + Q_SIGNALS: + 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); + + 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; + explicit ClipSequence(Track *track, Handle handle, Model *model); + QScopedPointer d_ptr; + }; + +} + +#endif //DIFFSCOPE_DSPX_MODEL_CLIPSEQUENCE_H diff --git a/src/libs/application/dspxmodel/src/ClipSequence_p.h b/src/libs/application/dspxmodel/src/ClipSequence_p.h new file mode 100644 index 00000000..dcea7702 --- /dev/null +++ b/src/libs/application/dspxmodel/src/ClipSequence_p.h @@ -0,0 +1,22 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_CLIPSEQUENCE_P_H +#define DIFFSCOPE_DSPX_MODEL_CLIPSEQUENCE_P_H + +#include + +#include +#include + +namespace dspx { + + class ClipSequencePrivate : public RangeSequenceData { + Q_DECLARE_PUBLIC(ClipSequence) + public: + Track *track{}; + + void handleMoveFromAnotherSequenceContainer(Handle entity, Handle otherSequenceContainerEntity); + void handleMoveToAnotherSequenceContainer(Handle entity, Handle otherSequenceContainerEntity); + }; + +} + +#endif //DIFFSCOPE_DSPX_MODEL_CLIPSEQUENCE_P_H diff --git a/src/libs/application/dspxmodel/src/ClipTime.cpp b/src/libs/application/dspxmodel/src/ClipTime.cpp new file mode 100644 index 00000000..50df3a7a --- /dev/null +++ b/src/libs/application/dspxmodel/src/ClipTime.cpp @@ -0,0 +1,128 @@ +#include "ClipTime.h" +#include + +#include + +#include +#include + +namespace dspx { + + class ClipTimePrivate { + Q_DECLARE_PUBLIC(ClipTime) + public: + ClipTime *q_ptr; + ModelPrivate *pModel; + Handle handle; + + int start; + int length; + int clipStart; + int clipLen; + }; + + + + ClipTime::ClipTime(Handle handle, Model *model) : QObject(model), d_ptr(new ClipTimePrivate) { + Q_D(ClipTime); + d->q_ptr = this; + d->pModel = ModelPrivate::get(model); + d->handle = handle; + d->start = d->pModel->strategy->getEntityProperty(handle, ModelStrategy::P_Position).toInt(); + d->length = d->pModel->strategy->getEntityProperty(handle, ModelStrategy::P_Length).toInt(); + d->clipStart = d->pModel->strategy->getEntityProperty(handle, ModelStrategy::P_ClipStart).toInt(); + d->clipLen = d->pModel->strategy->getEntityProperty(handle, ModelStrategy::P_ClipLength).toInt(); + } + + ClipTime::~ClipTime() = default; + + int ClipTime::start() const { + Q_D(const ClipTime); + return d->start; + } + + void ClipTime::setStart(int start) { + Q_D(ClipTime); + d->pModel->strategy->setEntityProperty(d->handle, ModelStrategy::P_Position, start); + } + + int ClipTime::length() const { + Q_D(const ClipTime); + return d->length; + } + + void ClipTime::setLength(int length) { + Q_D(ClipTime); + Q_ASSERT(length >= 0); + d->pModel->strategy->setEntityProperty(d->handle, ModelStrategy::P_Length, length); + } + + int ClipTime::clipStart() const { + Q_D(const ClipTime); + return d->clipStart; + } + + void ClipTime::setClipStart(int clipStart) { + Q_D(ClipTime); + Q_ASSERT(clipStart >= 0); + d->pModel->strategy->setEntityProperty(d->handle, ModelStrategy::P_ClipStart, clipStart); + } + + int ClipTime::clipLen() const { + Q_D(const ClipTime); + return d->clipLen; + } + + void ClipTime::setClipLen(int clipLen) { + Q_D(ClipTime); + Q_ASSERT(clipLen >= 0); + d->pModel->strategy->setEntityProperty(d->handle, ModelStrategy::P_ClipLength, clipLen); + } + + QDspx::ClipTime ClipTime::toQDspx() const { + return { + .start = start(), + .length = length(), + .clipStart = clipStart(), + .clipLen = clipLen() + }; + } + + void ClipTime::fromQDspx(const QDspx::ClipTime &clipTime) { + setStart(clipTime.start); + setLength(clipTime.length); + setClipStart(clipTime.clipStart); + setClipLen(clipTime.clipLen); + } + + void ClipTime::handleProxySetEntityProperty(int property, const QVariant &value) { + Q_D(ClipTime); + switch (property) { + case ModelStrategy::P_Position: { + d->start = value.toInt(); + Q_EMIT startChanged(d->start); + break; + } + case ModelStrategy::P_Length: { + d->length = value.toInt(); + Q_EMIT lengthChanged(d->length); + break; + } + case ModelStrategy::P_ClipStart: { + d->clipStart = value.toInt(); + Q_EMIT clipStartChanged(d->clipStart); + break; + } + case ModelStrategy::P_ClipLength: { + d->clipLen = value.toInt(); + Q_EMIT clipLenChanged(d->clipLen); + break; + } + default: + Q_UNREACHABLE(); + } + } + +} + +#include "moc_ClipTime.cpp" diff --git a/src/libs/application/dspxmodel/src/ClipTime.h b/src/libs/application/dspxmodel/src/ClipTime.h new file mode 100644 index 00000000..20375cdb --- /dev/null +++ b/src/libs/application/dspxmodel/src/ClipTime.h @@ -0,0 +1,64 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_CLIPTIME_H +#define DIFFSCOPE_DSPX_MODEL_CLIPTIME_H + +#include + +#include + +#include + +namespace QDspx { + struct ClipTime; +} + +namespace dspx { + + class Model; + class ModelPrivate; + + class ClipTimePrivate; + + 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_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; + + int start() const; + void setStart(int start); + + int length() const; + void setLength(int length); + + int clipStart() const; + void setClipStart(int clipStart); + + int clipLen() const; + void setClipLen(int clipLen); + + QDspx::ClipTime toQDspx() const; + void fromQDspx(const QDspx::ClipTime &clipTime); + + Q_SIGNALS: + void startChanged(int start); + void lengthChanged(int length); + void clipStartChanged(int clipStart); + void clipLenChanged(int clipLen); + + private: + friend class ModelPrivate; + explicit ClipTime(Handle handle, Model *model); + void handleProxySetEntityProperty(int property, const QVariant &value); + QScopedPointer d_ptr; + }; + +} + +#endif //DIFFSCOPE_DSPX_MODEL_CLIPTIME_H diff --git a/src/libs/application/dspxmodel/src/Clip_p.h b/src/libs/application/dspxmodel/src/Clip_p.h new file mode 100644 index 00000000..e3a3c02c --- /dev/null +++ b/src/libs/application/dspxmodel/src/Clip_p.h @@ -0,0 +1,27 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_CLIP_P_H +#define DIFFSCOPE_DSPX_MODEL_CLIP_P_H + +#include + +namespace dspx { + + class ClipPrivate { + Q_DECLARE_PUBLIC(Clip) + public: + Clip *q_ptr; + ModelPrivate *pModel; + QString name; + BusControl *control; + ClipTime *time; + Clip::ClipType type; + ClipSequence *clipSequence{}; + Workspace *workspace; + bool overlapped{}; + + static void setOverlapped(Clip *item, bool overlapped); + static void setClipSequence(Clip *item, ClipSequence *clipSequence); + }; + +} + +#endif //DIFFSCOPE_DSPX_MODEL_CLIP_P_H diff --git a/src/libs/application/dspxmodel/src/Control.cpp b/src/libs/application/dspxmodel/src/Control.cpp new file mode 100644 index 00000000..4d8beb45 --- /dev/null +++ b/src/libs/application/dspxmodel/src/Control.cpp @@ -0,0 +1,99 @@ +#include "Control.h" +#include + +#include +#include + +namespace dspx { + + class ControlPrivate { + Q_DECLARE_PUBLIC(Control) + public: + Control *q_ptr; + ModelPrivate *pModel; + Handle handle; + + double gain; + double pan; + bool mute; + + + }; + + + + Control::Control(Handle handle, Model *model) : QObject(model), d_ptr(new ControlPrivate) { + Q_D(Control); + d->q_ptr = this; + d->pModel = ModelPrivate::get(model); + d->handle = handle; + d->gain = d->pModel->strategy->getEntityProperty(handle, ModelStrategy::P_ControlGain).toDouble(); + d->pan = d->pModel->strategy->getEntityProperty(handle, ModelStrategy::P_ControlPan).toDouble(); + d->mute = d->pModel->strategy->getEntityProperty(handle, ModelStrategy::P_ControlMute).toBool(); + } + + Control::~Control() = default; + + double Control::gain() const { + Q_D(const Control); + return d->gain; + } + + void Control::setGain(double gain) { + Q_D(Control); + Q_ASSERT(gain >= 0.0); + d->pModel->strategy->setEntityProperty(d->handle, ModelStrategy::P_ControlGain, gain); + } + + double Control::pan() const { + Q_D(const Control); + return d->pan; + } + + void Control::setPan(double pan) { + Q_D(Control); + Q_ASSERT(pan >= -1.0 && pan <= 1.0); + d->pModel->strategy->setEntityProperty(d->handle, ModelStrategy::P_ControlPan, pan); + } + + bool Control::mute() const { + Q_D(const Control); + return d->mute; + } + + void Control::setMute(bool mute) { + Q_D(Control); + d->pModel->strategy->setEntityProperty(d->handle, ModelStrategy::P_ControlMute, mute); + } + + void Control::handleProxySetEntityProperty(int property, const QVariant &value) { + Q_D(Control); + switch (property) { + case ModelStrategy::P_ControlGain: { + d->gain = value.toDouble(); + Q_EMIT gainChanged(d->gain); + break; + } + case ModelStrategy::P_ControlPan: { + d->pan = value.toDouble(); + Q_EMIT panChanged(d->pan); + break; + } + case ModelStrategy::P_ControlMute: { + d->mute = value.toBool(); + Q_EMIT muteChanged(d->mute); + break; + } + default: + Q_UNREACHABLE(); + } + } + + Handle Control::handle() const { + Q_D(const Control); + return d->handle; + } + +} + +#include "moc_Control.cpp" diff --git a/src/libs/application/dspxmodel/src/Control.h b/src/libs/application/dspxmodel/src/Control.h new file mode 100644 index 00000000..d0330ec7 --- /dev/null +++ b/src/libs/application/dspxmodel/src/Control.h @@ -0,0 +1,54 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_CONTROL_H +#define DIFFSCOPE_DSPX_MODEL_CONTROL_H + +#include + +#include + +#include + +namespace dspx { + + class Model; + class ModelPrivate; + + class ControlPrivate; + + class DSPX_MODEL_EXPORT Control : public QObject { + Q_OBJECT + QML_ELEMENT + QML_UNCREATABLE("") + Q_DECLARE_PRIVATE(Control) + 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: + ~Control() override; + + double gain() const; + void setGain(double gain); + + double pan() const; + void setPan(double pan); + + bool mute() const; + void setMute(bool mute); + + Q_SIGNALS: + void gainChanged(double gain); + void panChanged(double pan); + void muteChanged(bool mute); + + protected: + explicit Control(Handle handle, Model *model); + void handleProxySetEntityProperty(int property, const QVariant &value); + Handle handle() const; + + private: + QScopedPointer d_ptr; + }; + +} + +#endif //DIFFSCOPE_DSPX_MODEL_CONTROL_H diff --git a/src/libs/application/dspxmodel/src/DataArrayData.cpp b/src/libs/application/dspxmodel/src/DataArrayData.cpp new file mode 100644 index 00000000..06a7f4df --- /dev/null +++ b/src/libs/application/dspxmodel/src/DataArrayData.cpp @@ -0,0 +1,23 @@ +#include "DataArrayData_p.h" + +#include + +namespace dspx { + + QJSValue DataArrayJSIterable::create(QObject *o) { + auto engine = qjsEngine(o); + if (!engine) { + qFatal() << "iterable() can only be called from QML"; + return {}; + } + auto listIterableConstructor = engine->importModule(":/qt/qml/DiffScope/DspxModel/qml/iterable.mjs").property("DataArrayIterable"); + Q_ASSERT(listIterableConstructor.isCallable()); + auto iterable = listIterableConstructor.callAsConstructor({engine->fromVariant(QVariant::fromValue(o))}); + if (iterable.isError()) { + qFatal() << iterable.property("message").toString(); + return {}; + } + return iterable; + } + +} diff --git a/src/libs/application/dspxmodel/src/DataArrayData_p.h b/src/libs/application/dspxmodel/src/DataArrayData_p.h new file mode 100644 index 00000000..0d7c261e --- /dev/null +++ b/src/libs/application/dspxmodel/src/DataArrayData_p.h @@ -0,0 +1,66 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_DATAARRAYDATA_P_H +#define DIFFSCOPE_DSPX_MODEL_DATAARRAYDATA_P_H + +#include + +#include +#include + +#include +#include + +namespace dspx { + + class DataArrayJSIterable { + public: + static QJSValue create(QObject *o); + }; + + template + class DataArrayData { + public: + DataArrayType *q_ptr; + ModelPrivate *pModel; + QList data; + QJSValue iterable_; + + void init(const QVariantList &values) { + for (auto v : values) { + data.append(v.value()); + } + } + + int size() const { + return data.size(); + } + + void handleSpliceDataArray(int index, int length, const QList &values) { + auto q = q_ptr; + Q_EMIT q->aboutToSplice(index, length, values); + SpliceHelper::splice(data, data.begin() + index, data.begin() + index + length, values.begin(), values.end()); + Q_EMIT q->spliced(index, length, values); + if (values.size() != length) { + Q_EMIT q->sizeChanged(size()); + } + } + + void handleRotateDataArray(int leftIndex, int middleIndex, int rightIndex) { + auto q = q_ptr; + Q_EMIT q->aboutToRotate(leftIndex, middleIndex, rightIndex); + std::rotate(data.begin() + leftIndex, data.begin() + middleIndex, data.begin() + rightIndex); + Q_EMIT q->rotated(leftIndex, middleIndex, rightIndex); + } + + QJSValue iterable() { + if (!iterable_.isUndefined()) { + return iterable_; + } + auto q = q_ptr; + iterable_ = DataArrayJSIterable::create(q); + return iterable_; + } + }; + +} + +#endif //DIFFSCOPE_DSPX_MODEL_DATAARRAYDATA_P_H diff --git a/src/libs/application/dspxmodel/src/DspxModelGlobal.h b/src/libs/application/dspxmodel/src/DspxModelGlobal.h new file mode 100644 index 00000000..b61a80f8 --- /dev/null +++ b/src/libs/application/dspxmodel/src/DspxModelGlobal.h @@ -0,0 +1,12 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_DSPXMODELGLOBAL_H +#define DIFFSCOPE_DSPX_MODEL_DSPXMODELGLOBAL_H + +#include + +#if defined(DSPXMODEL_LIBRARY) +# define DSPX_MODEL_EXPORT Q_DECL_EXPORT +#else +# define DSPX_MODEL_EXPORT Q_DECL_IMPORT +#endif + +#endif //DIFFSCOPE_DSPX_MODEL_DSPXMODELGLOBAL_H diff --git a/src/libs/application/dspxmodel/src/EntityObject.cpp b/src/libs/application/dspxmodel/src/EntityObject.cpp new file mode 100644 index 00000000..75019821 --- /dev/null +++ b/src/libs/application/dspxmodel/src/EntityObject.cpp @@ -0,0 +1,77 @@ +#include "EntityObject.h" +#include "EntityObject_p.h" + +#include +#include +#include + +namespace dspx { + + EntityObject::EntityObject(Handle handle, Model *model) : EntityObject(model) { + Q_D(EntityObject); + d->handle = handle; + d->model = model; + auto pModel = ModelPrivate::get(model); + pModel->objectMap.insert(handle, this); + pModel->handleMap.insert(this, handle); + } + EntityObject::EntityObject(QObject *parent) : QObject(parent), d_ptr(new EntityObjectPrivate) { + Q_D(EntityObject); + d->q_ptr = this; + } + 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."); + } + } + + Model *EntityObject::model() const { + Q_D(const EntityObject); + return d->model; + } + + Handle EntityObject::handle() const { + Q_D(const EntityObject); + return d->handle; + } + + void EntityObject::handleInsertIntoSequenceContainer(Handle entity) { + Q_UNREACHABLE(); + } + void EntityObject::handleInsertIntoListContainer(Handle entity, int index) { + Q_UNREACHABLE(); + } + 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(); + } + void EntityObject::handleTakeFromListContainer(Handle takenEntity, int index) { + Q_UNREACHABLE(); + } + void EntityObject::handleTakeFromMapContainer(Handle takenEntity, const QString &key) { + Q_UNREACHABLE(); + } + void EntityObject::handleRotateListContainer(int leftIndex, int middleIndex, int rightIndex) { + Q_UNREACHABLE(); + } + void EntityObject::handleSetEntityProperty(int property, const QVariant &value) { + Q_UNREACHABLE(); + } + void EntityObject::handleSpliceDataArray(int index, int length, const QVariantList &values) { + Q_UNREACHABLE(); + } + void EntityObject::handleRotateDataArray(int leftIndex, int middleIndex, int rightIndex) { + Q_UNREACHABLE(); + } +} + +#include "moc_EntityObject.cpp" diff --git a/src/libs/application/dspxmodel/src/EntityObject.h b/src/libs/application/dspxmodel/src/EntityObject.h new file mode 100644 index 00000000..c4cb85a8 --- /dev/null +++ b/src/libs/application/dspxmodel/src/EntityObject.h @@ -0,0 +1,56 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_ENTITYOBJECT_H +#define DIFFSCOPE_DSPX_MODEL_ENTITYOBJECT_H + +#include + +#include +#include + +namespace dspx { + + class Model; + class ModelPrivate; + + class EntityObjectPrivate; + + class DSPX_MODEL_EXPORT EntityObject : public QObject { + Q_OBJECT + Q_DECLARE_PRIVATE(EntityObject); + Q_PROPERTY(Model *model READ model CONSTANT) + public: + ~EntityObject() override; + + Handle handle() const; + + Model *model() const; + + protected: + virtual void handleInsertIntoSequenceContainer(Handle entity); + 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); + virtual void handleRotateListContainer(int leftIndex, int middleIndex, int rightIndex); + + virtual void handleSetEntityProperty(int property, const QVariant &value); + + virtual void handleSpliceDataArray(int index, int length, const QVariantList &values); + virtual void handleRotateDataArray(int leftIndex, int middleIndex, int rightIndex); + + explicit EntityObject(Handle handle, Model *model); + + private: + friend class Model; + friend class ModelPrivate; + explicit EntityObject(QObject *parent = nullptr); + QScopedPointer d_ptr; + }; + +} + +#endif //DIFFSCOPE_DSPX_MODEL_ENTITYOBJECT_H diff --git a/src/libs/application/dspxmodel/src/EntityObject_p.h b/src/libs/application/dspxmodel/src/EntityObject_p.h new file mode 100644 index 00000000..dd2c4783 --- /dev/null +++ b/src/libs/application/dspxmodel/src/EntityObject_p.h @@ -0,0 +1,20 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_ENTITYOBJECT_P_H +#define DIFFSCOPE_DSPX_MODEL_ENTITYOBJECT_P_H + +#include + +#include + +namespace dspx { + + class EntityObjectPrivate { + Q_DECLARE_PUBLIC(EntityObject) + public: + EntityObject *q_ptr; + QPointer model{}; + Handle handle; + }; + +} + +#endif //DIFFSCOPE_DSPX_MODEL_ENTITYOBJECT_P_H diff --git a/src/libs/application/dspxmodel/src/FreeValueDataArray.cpp b/src/libs/application/dspxmodel/src/FreeValueDataArray.cpp new file mode 100644 index 00000000..6f085afa --- /dev/null +++ b/src/libs/application/dspxmodel/src/FreeValueDataArray.cpp @@ -0,0 +1,83 @@ +#include "FreeValueDataArray.h" + +#include +#include +#include + +namespace dspx { + + class FreeValueDataArrayPrivate : public DataArrayData { + Q_DECLARE_PUBLIC(FreeValueDataArray) + public: + ParamCurveFree *paramCurveFree{}; + }; + + FreeValueDataArray::FreeValueDataArray(ParamCurveFree *paramCurveFree, Handle handle, Model *model) : EntityObject(handle, model), d_ptr(new FreeValueDataArrayPrivate) { + Q_D(FreeValueDataArray); + Q_ASSERT(model->strategy()->getEntityType(handle) == ModelStrategy::ED_ParamCurveFreeValues); + d->q_ptr = this; + d->pModel = ModelPrivate::get(model); + d->paramCurveFree = paramCurveFree; + + d->init(model->strategy()->sliceDataArray(handle, 0, model->strategy()->getSizeOfDataArray(handle))); + } + + FreeValueDataArray::~FreeValueDataArray() = default; + + int FreeValueDataArray::size() const { + Q_D(const FreeValueDataArray); + return d->size(); + } + + bool FreeValueDataArray::splice(int index, int length, const QList &values) { + Q_D(FreeValueDataArray); + QVariantList a; + a.resize(values.size()); + std::ranges::transform(values, a.begin(), [](int v) { return QVariant(v); }); + return d->pModel->strategy->spliceDataArray(handle(), index, length, a); + } + + QList FreeValueDataArray::slice(int index, int length) const { + Q_D(const FreeValueDataArray); + if (index < 0 || index >= size()) + return {}; + return d->data.mid(index, length); + } + + bool FreeValueDataArray::rotate(int leftIndex, int middleIndex, int rightIndex) { + Q_D(FreeValueDataArray); + return d->pModel->strategy->rotateDataArray(handle(), leftIndex, middleIndex, rightIndex); + } + + QList FreeValueDataArray::toQDspx() const { + Q_D(const FreeValueDataArray); + return d->data; + } + + void FreeValueDataArray::fromQDspx(const QList &values) { + splice(0, size(), values); + } + + void FreeValueDataArray::handleSpliceDataArray(int index, int length, const QVariantList &values) { + Q_D(FreeValueDataArray); + QList a; + a.resize(values.size()); + std::ranges::transform(values, a.begin(), [](const QVariant &v) { return v.toInt(); }); + d->handleSpliceDataArray(index, length, a); + } + + void FreeValueDataArray::handleRotateDataArray(int leftIndex, int middleIndex, int rightIndex) { + Q_D(FreeValueDataArray); + d->handleRotateDataArray(leftIndex, middleIndex, rightIndex); + } + + ParamCurveFree *FreeValueDataArray::paramCurveFree() const { + Q_D(const FreeValueDataArray); + return d->paramCurveFree; + } + + + +} + +#include "moc_FreeValueDataArray.cpp" diff --git a/src/libs/application/dspxmodel/src/FreeValueDataArray.h b/src/libs/application/dspxmodel/src/FreeValueDataArray.h new file mode 100644 index 00000000..2c9a263a --- /dev/null +++ b/src/libs/application/dspxmodel/src/FreeValueDataArray.h @@ -0,0 +1,55 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_FREEVALUEDATAARRAY_H +#define DIFFSCOPE_DSPX_MODEL_FREEVALUEDATAARRAY_H + +#include + +#include + +namespace dspx { + + class ParamCurveFree; + + class FreeValueDataArrayPrivate; + + class DSPX_MODEL_EXPORT FreeValueDataArray : public EntityObject { + Q_OBJECT + QML_ELEMENT + QML_UNCREATABLE("") + Q_DECLARE_PRIVATE(FreeValueDataArray) + Q_PROPERTY(int size READ size NOTIFY sizeChanged) + Q_PROPERTY(ParamCurveFree *paramCurveFree READ paramCurveFree CONSTANT) + Q_PRIVATE_PROPERTY(d_func(), QJSValue iterable READ iterable CONSTANT) + + public: + ~FreeValueDataArray() override; + + int size() const; + Q_INVOKABLE bool splice(int index, int length, const QList &values); + Q_INVOKABLE QList slice(int index, int length) const; + Q_INVOKABLE bool rotate(int leftIndex, int middleIndex, int rightIndex); + + QList toQDspx() const; + void fromQDspx(const QList &values); + + ParamCurveFree *paramCurveFree() const; + + Q_SIGNALS: + void sizeChanged(int size); + void aboutToSplice(int index, int length, const QList &values); + void spliced(int index, int length, const QList &values); + void aboutToRotate(int leftIndex, int middleIndex, int rightIndex); + void rotated(int leftIndex, int middleIndex, int rightIndex); + + protected: + void handleSpliceDataArray(int index, int length, const QVariantList &values) override; + void handleRotateDataArray(int leftIndex, int middleIndex, int rightIndex) override; + + private: + friend class ModelPrivate; + explicit FreeValueDataArray(ParamCurveFree *paramCurveFree, Handle handle, Model *model); + QScopedPointer d_ptr; + }; + +} + +#endif //DIFFSCOPE_DSPX_MODEL_FREEVALUEDATAARRAY_H diff --git a/src/libs/application/dspxmodel/src/Global.cpp b/src/libs/application/dspxmodel/src/Global.cpp new file mode 100644 index 00000000..fd35f5e5 --- /dev/null +++ b/src/libs/application/dspxmodel/src/Global.cpp @@ -0,0 +1,99 @@ +#include "Global.h" +#include + +#include + +#include +#include +#include + +namespace dspx { + + class GlobalPrivate { + Q_DECLARE_PUBLIC(Global) + public: + Global *q_ptr; + ModelPrivate *pModel; + Handle handle; + }; + + Global::Global(Model *model) : QObject(model), d_ptr(new GlobalPrivate) { + Q_D(Global); + d->q_ptr = this; + d->pModel = ModelPrivate::get(model); + d->handle = model->handle(); + } + Global::~Global() = default; + + QString Global::name() const { + Q_D(const Global); + return d->pModel->name; + } + void Global::setName(const QString &name) { + Q_D(Global); + d->pModel->strategy->setEntityProperty(d->handle, ModelStrategy::P_Name, name); + } + QString Global::author() const { + Q_D(const Global); + return d->pModel->author; + } + void Global::setAuthor(const QString &author) { + Q_D(Global); + d->pModel->strategy->setEntityProperty(d->handle, ModelStrategy::P_Author, author); + } + int Global::centShift() const { + Q_D(const Global); + return d->pModel->centShift; + } + void Global::setCentShift(int centShift) { + Q_D(Global); + Q_ASSERT(centShift >= -50 && centShift <= 50); + 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); + return d->pModel->editorId; + } + void Global::setEditorId(const QString &editorId) { + Q_D(Global); + d->pModel->strategy->setEntityProperty(d->handle, ModelStrategy::P_EditorId, editorId); + } + QString Global::editorName() const { + Q_D(const Global); + return d->pModel->editorName; + } + void Global::setEditorName(const QString &editorName) { + Q_D(Global); + d->pModel->strategy->setEntityProperty(d->handle, ModelStrategy::P_EditorName, editorName); + } + + QDspx::Global Global::toQDspx() const { + return { + .author = author(), + .name = name(), + .centShift = centShift(), + .editorId = editorId(), + .editorName = editorName() + }; + } + + void Global::fromQDspx(const QDspx::Global &global) { + setAuthor(global.author); + setName(global.name); + setCentShift(global.centShift); + setEditorId(global.editorId); + setEditorName(global.editorName); + } + +} + +#include "moc_Global.cpp" diff --git a/src/libs/application/dspxmodel/src/Global.h b/src/libs/application/dspxmodel/src/Global.h new file mode 100644 index 00000000..e30b1760 --- /dev/null +++ b/src/libs/application/dspxmodel/src/Global.h @@ -0,0 +1,80 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_GLOBAL_H +#define DIFFSCOPE_DSPX_MODEL_GLOBAL_H + +#include + +#include + +#include + +namespace QDspx { + struct Global; +} + +namespace dspx { + + class Model; + + class ModelPrivate; + + class GlobalPrivate; + + 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_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) + + public: + ~Global() override; + + QString name() const; + void setName(const QString &name); + + QString author() const; + void setAuthor(const QString &author); + + 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); + + QString editorName() const; + void setEditorName(const QString &editorName); + + QDspx::Global toQDspx() const; + void fromQDspx(const QDspx::Global &global); + + Q_SIGNALS: + 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); + + private: + friend class ModelPrivate; + explicit Global(Model *model); + + QScopedPointer d_ptr; + }; + +} + +#endif //DIFFSCOPE_DSPX_MODEL_GLOBAL_H diff --git a/src/libs/application/dspxmodel/src/Handle.h b/src/libs/application/dspxmodel/src/Handle.h new file mode 100644 index 00000000..6aeb83f5 --- /dev/null +++ b/src/libs/application/dspxmodel/src/Handle.h @@ -0,0 +1,38 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_HANDLE_H +#define DIFFSCOPE_DSPX_MODEL_HANDLE_H + +#include + +namespace dspx { + + struct Handle { + quintptr d; + + auto operator<=>(const Handle &) const = default; + bool operator==(const Handle &) const = default; + bool operator!=(const Handle &) const = default; + + friend size_t qHash(const Handle &handle, size_t seed = 0) { + return qHash(handle.d, seed); + } + + constexpr bool isNull() const { + return !d; + } + constexpr operator bool() const { + return d; + } + }; + +} + +namespace std { + template <> + struct hash { + size_t operator()(const dspx::Handle &handle) const noexcept { + return std::hash{}(handle.d); + } + }; +} + +#endif //DIFFSCOPE_DSPX_MODEL_HANDLE_H diff --git a/src/libs/application/dspxmodel/src/Label.cpp b/src/libs/application/dspxmodel/src/Label.cpp new file mode 100644 index 00000000..a5d784fa --- /dev/null +++ b/src/libs/application/dspxmodel/src/Label.cpp @@ -0,0 +1,91 @@ +#include "Label.h" +#include "Label_p.h" +#include + +#include + +#include +#include +#include + +namespace dspx { + + + + void LabelPrivate::setLabelSequence(Label *item, LabelSequence *labelSequence) { + auto d = item->d_func(); + if (d->labelSequence != labelSequence) { + d->labelSequence = labelSequence; + Q_EMIT item->labelSequenceChanged(); + } + } + + Label::Label(Handle handle, Model *model) : EntityObject(handle, model), d_ptr(new LabelPrivate) { + Q_D(Label); + Q_ASSERT(model->strategy()->getEntityType(handle) == ModelStrategy::EI_Label); + d->q_ptr = this; + d->pos = model->strategy()->getEntityProperty(handle, ModelStrategy::P_Position).toInt(); + d->text = model->strategy()->getEntityProperty(handle, ModelStrategy::P_Text).toString(); + } + + Label::~Label() = default; + + int Label::pos() const { + Q_D(const Label); + return d->pos; + } + + void Label::setPos(int pos) { + Q_D(Label); + Q_ASSERT(pos >= 0); + model()->strategy()->setEntityProperty(handle(), ModelStrategy::P_Position, pos); + } + + QString Label::text() const { + Q_D(const Label); + return d->text; + } + + void Label::setText(const QString &text) { + Q_D(Label); + model()->strategy()->setEntityProperty(handle(), ModelStrategy::P_Text, text); + } + + LabelSequence *Label::labelSequence() const { + Q_D(const Label); + return d->labelSequence; + } + + QDspx::Label Label::toQDspx() const { + return { + .pos = pos(), + .text = text(), + }; + } + + void Label::fromQDspx(const QDspx::Label &label) { + setPos(label.pos); + setText(label.text); + } + + void Label::handleSetEntityProperty(int property, const QVariant &value) { + Q_D(Label); + switch (property) { + case ModelStrategy::P_Position: { + d->pos = value.toInt(); + Q_EMIT posChanged(d->pos); + break; + } + case ModelStrategy::P_Text: { + d->text = value.toString(); + Q_EMIT textChanged(d->text); + break; + } + default: + Q_UNREACHABLE(); + } + } + +} + +#include "moc_Label.cpp" diff --git a/src/libs/application/dspxmodel/src/Label.h b/src/libs/application/dspxmodel/src/Label.h new file mode 100644 index 00000000..ba81bbfd --- /dev/null +++ b/src/libs/application/dspxmodel/src/Label.h @@ -0,0 +1,55 @@ +#ifndef DIFFSCOPE_DSPX_MODEL_LABEL_H +#define DIFFSCOPE_DSPX_MODEL_LABEL_H + +#include + +#include + +namespace QDspx { + struct Label; +} + +namespace dspx { + + class LabelSequence; + class LabelPrivate; + + class DSPX_MODEL_EXPORT Label : public EntityObject { + Q_OBJECT + QML_ELEMENT + QML_UNCREATABLE("") + Q_DECLARE_PRIVATE(Label); + 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) + public: + ~Label() override; + + int pos() const; + void setPos(int pos); + + QString text() const; + void setText(const QString &text); + + LabelSequence *labelSequence() const; + + QDspx::Label toQDspx() const; + void fromQDspx(const QDspx::Label &label); + + Q_SIGNALS: + void posChanged(int pos); + void textChanged(const QString &text); + void labelSequenceChanged(); + + protected: + void handleSetEntityProperty(int property, const QVariant &value) override; + + private: + friend class ModelPrivate; + explicit Label(Handle handle, Model *model); + QScopedPointer d_ptr; + }; + +} + +#endif //DIFFSCOPE_DSPX_MODEL_LABEL_H diff --git a/src/libs/application/dspxmodel/src/LabelSequence.cpp b/src/libs/application/dspxmodel/src/LabelSequence.cpp new file mode 100644 index 00000000..45ff44ef --- /dev/null +++ b/src/libs/application/dspxmodel/src/LabelSequence.cpp @@ -0,0 +1,105 @@ +#include "LabelSequence.h" +#include "LabelSequence_p.h" + +#include +#include + +#include + +#include +#include +#include +#include + +namespace dspx { + + LabelSequence::LabelSequence(Handle handle, Model *model) : EntityObject(handle, model), d_ptr(new LabelSequencePrivate) { + Q_D(LabelSequence); + Q_ASSERT(model->strategy()->getEntityType(handle) == ModelStrategy::ES_Labels); + d->q_ptr = this; + d->pModel = ModelPrivate::get(model); + + d->init(model->strategy()->getEntitiesFromSequenceContainer(handle)); + } + + LabelSequence::~LabelSequence() = default; + + int LabelSequence::size() const { + Q_D(const LabelSequence); + return d->container.size(); + } + + Label *LabelSequence::firstItem() const { + Q_D(const LabelSequence); + return d->firstItem; + } + + Label *LabelSequence::lastItem() const { + Q_D(const LabelSequence); + return d->lastItem; + } + + Label *LabelSequence::previousItem(Label *item) const { + Q_D(const LabelSequence); + return d->container.previousItem(item); + } + + Label *LabelSequence::nextItem(Label *item) const { + Q_D(const LabelSequence); + return d->container.nextItem(item); + } + + QList