diff --git a/.github/workflows/dev-build.yml b/.github/workflows/dev-build.yml index 6991dad0..2ace6d6a 100644 --- a/.github/workflows/dev-build.yml +++ b/.github/workflows/dev-build.yml @@ -1,13 +1,13 @@ name: Dev Build on: - push: - branches: - - main - paths-ignore: - - 'README.md' - - 'LICENSE' - - 'crowdin.yml' - - '.github/**' +# push: +# branches: +# - main +# paths-ignore: +# - 'README.md' +# - 'LICENSE' +# - 'crowdin.yml' +# - '.github/**' pull_request: branches: - main diff --git a/.gitmodules b/.gitmodules index 23f5e23a..4705759d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -25,3 +25,10 @@ [submodule "vcpkg"] path = scripts/vcpkg url = ../../stdware/vcpkg-overlay.git + +[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/scripts/icon-tools/Generate-ICNS.sh b/scripts/icon-tools/Generate-ICNS.sh deleted file mode 100644 index a3af50d8..00000000 --- a/scripts/icon-tools/Generate-ICNS.sh +++ /dev/null @@ -1,13 +0,0 @@ -mkdir -p /tmp/app.iconset -cp ./src/app/icons/app/16x16.png /tmp/app.iconset/icon_16x16.png -cp ./src/app/icons/app/32x32.png /tmp/app.iconset/icon_16x16@2x.png -cp ./src/app/icons/app/32x32.png /tmp/app.iconset/icon_32x32.png -cp ./src/app/icons/app/64x64.png /tmp/app.iconset/icon_32x32@2x.png -cp ./src/app/icons/app/128x128.png /tmp/app.iconset/icon_128x128.png -cp ./src/app/icons/app/256x256.png /tmp/app.iconset/icon_128x128@2x.png -cp ./src/app/icons/app/256x256.png /tmp/app.iconset/icon_256x256.png -cp ./src/app/icons/app/512x512.png /tmp/app.iconset/icon_256x256@2x.png -cp ./src/app/icons/app/512x512.png /tmp/app.iconset/icon_512x512.png -cp ./src/app/icons/app/1024x1024.png /tmp/app.iconset/icon_512x512@2x.png -iconutil -c icns -o ./src/app/app.icns /tmp/app.iconset -rm -rf /tmp/app.iconset \ No newline at end of file diff --git a/scripts/icon-tools/Generate-ICO.ps1 b/scripts/icon-tools/Generate-ICO.ps1 deleted file mode 100644 index c8b4e59b..00000000 --- a/scripts/icon-tools/Generate-ICO.ps1 +++ /dev/null @@ -1 +0,0 @@ -magick convert .\src\app\icons\app\16x16.png .\src\app\icons\app\24x24.png .\src\app\icons\app\32x32.png .\src\app\icons\app\48x48.png .\src\app\icons\app\64x64.png .\src\app\icons\app\96x96.png .\src\app\icons\app\128x128.png .\src\app\icons\app\256x256.png .\src\app\app.ico \ No newline at end of file diff --git a/scripts/vcpkg-manifest/vcpkg.json b/scripts/vcpkg-manifest/vcpkg.json index a923ccdd..ed64d7cc 100644 --- a/scripts/vcpkg-manifest/vcpkg.json +++ b/scripts/vcpkg-manifest/vcpkg.json @@ -26,7 +26,8 @@ { "name": "glib", "platform": "linux" - } + }, + "wolf-midi" ], "vcpkg-configuration": { "overlay-ports": [ diff --git a/src/app/config.json.in b/src/app/config.json.in index 815ba95d..80c7086e 100644 --- a/src/app/config.json.in +++ b/src/app/config.json.in @@ -1,13 +1,35 @@ { "splashImage": "@APP_CONFIG_SPLASH_IMAGE@", - "splashSize": [600, 336], + "splashSize": [600, 400], "splashSettings": { "texts": { + "appName": { + "pos": [300, 240], + "alignment": 132, + "text": "@APPLICATION_DISPLAY_NAME@", + "fontSize": 40, + "fontColor": "#dadada" + }, + "appVersion": { + "pos": [300, 280], + "alignment": 132, + "text": "", + "fontSize": 12, + "fontColor": "#dadada" + }, "_status": { - "pos": [84, -49], - "anchor": [1, -1], + "pos": [300, 320], + "alignment": 132, "text": "Loading...", - "fontColor": "#FFFFFF" + "fontsize": 12, + "fontColor": "#dadada" + }, + "copyright": { + "pos": [300, 360], + "alignment": 132, + "text": "", + "fontsize": 12, + "fontColor": "#a0dadada" } } } diff --git a/src/app/main.cpp b/src/app/main.cpp index 43a487f7..cf55ff35 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -22,6 +22,7 @@ #include #include +#include #ifdef APPLICATION_ENABLE_BREAKPAD # include @@ -72,6 +73,15 @@ class MyLoaderSpec : public Loader::LoaderSpec { // Do nothing } + void splashShown(QSplashScreen *screen) override { + QMetaObject::invokeMethod(screen, "setText", QStringLiteral("appVersion"), QApplication::translate("Application", "Version %1").arg(APPLICATION_SEMVER)); + QMetaObject::invokeMethod(screen, "setText", QStringLiteral("copyright"), QApplication::translate("Application", "Copyright \u00a9 %1-%2 %3. All rights reserved.").arg( + QLocale().toString(QDate(QStringLiteral(APPLICATION_DEV_START_YEAR).toInt(), 1, 1), "yyyy"), + QLocale().toString(QDate(QStringLiteral(APPLICATION_BUILD_YEAR).toInt(), 1, 1), "yyyy"), + APPLICATION_VENDOR_NAME + )); + } + void beforeLoadPlugins() override { RuntimeInterface::setQmlEngine(engine); auto settings = RuntimeInterface::settings(); diff --git a/src/app/splash.png b/src/app/splash.png index 3d79e73e..af7f8525 100644 Binary files a/src/app/splash.png and b/src/app/splash.png differ diff --git a/src/libs/3rdparty/CMakeLists.txt b/src/libs/3rdparty/CMakeLists.txt index 4b30d3c4..0bbdb7d2 100644 --- a/src/libs/3rdparty/CMakeLists.txt +++ b/src/libs/3rdparty/CMakeLists.txt @@ -4,12 +4,14 @@ if(APPLICATION_INSTALL AND APPLICATION_ENABLE_DEVEL) set(SCOPIC_FLOW_INSTALL ON) set(QACTIONKIT_INSTALL ON) set(TALCS_INSTALL ON) + set(OPENDSPX_INSTALL ON) else() set(CHORUSKIT_INSTALL OFF) set(SVSCRAFT_INSTALL OFF) set(SCOPIC_FLOW_INSTALL OFF) set(QACTIONKIT_INSTALL OFF) set(TALCS_INSTALL OFF) + set(OPENDSPX_INSTALL OFF) endif() set(CK_CMAKE_MODULES_DIR ${CMAKE_CURRENT_LIST_DIR}/choruskit/cmake PARENT_SCOPE) @@ -27,4 +29,6 @@ set(QAK_AEC_EXECUTABLE "$" PARENT_SCOPE) set(QAK_AEC_EXECUTABLE "$") include("${CMAKE_CURRENT_LIST_DIR}/qactionkit/src/QActionKitMacros.cmake") -add_subdirectory(talcs) \ No newline at end of file +add_subdirectory(talcs) + +add_subdirectory(opendspx) \ No newline at end of file diff --git a/src/libs/3rdparty/choruskit b/src/libs/3rdparty/choruskit index 643a0416..00cbb74c 160000 --- a/src/libs/3rdparty/choruskit +++ b/src/libs/3rdparty/choruskit @@ -1 +1 @@ -Subproject commit 643a041665f7761fd2e7fc97b1380e676d336a03 +Subproject commit 00cbb74c1c39b54bbd7178fbd4bc9e5f6cbdb1d9 diff --git a/src/libs/3rdparty/opendspx b/src/libs/3rdparty/opendspx new file mode 160000 index 00000000..1fd1b5e3 --- /dev/null +++ b/src/libs/3rdparty/opendspx @@ -0,0 +1 @@ +Subproject commit 1fd1b5e3d73d15c99377e8bfcf33bc1b7f86e91f diff --git a/src/libs/3rdparty/scopicflow b/src/libs/3rdparty/scopicflow index 4b6a5624..5b545596 160000 --- a/src/libs/3rdparty/scopicflow +++ b/src/libs/3rdparty/scopicflow @@ -1 +1 @@ -Subproject commit 4b6a56241300d7a8597fc47e5191aaea27e35096 +Subproject commit 5b5455963b11d3549127a33dfe39c05393d8cd7f diff --git a/src/libs/3rdparty/svscraft b/src/libs/3rdparty/svscraft index cffdb32c..db265541 160000 --- a/src/libs/3rdparty/svscraft +++ b/src/libs/3rdparty/svscraft @@ -1 +1 @@ -Subproject commit cffdb32c8453421e83d435d7e6ea07b257891169 +Subproject commit db26554132eebdc78f7f2ecfa684e8920f1e2bfb diff --git a/src/libs/application/uishell/src/CMakeLists.txt b/src/libs/application/uishell/src/CMakeLists.txt index f2c34e12..2fe57d59 100644 --- a/src/libs/application/uishell/src/CMakeLists.txt +++ b/src/libs/application/uishell/src/CMakeLists.txt @@ -30,18 +30,8 @@ if(QT_KNOWN_POLICY_QTP0004) qt_policy(SET QTP0004 NEW) endif() -file(GLOB_RECURSE _qml_files_abs *.qml *.js *.mjs) -set(_qml_files) -foreach(_file IN LISTS _qml_files_abs) - file(RELATIVE_PATH _rel_path ${CMAKE_CURRENT_SOURCE_DIR} ${_file}) - list(APPEND _qml_files ${_rel_path}) -endforeach() -file(GLOB_RECURSE _resource_files_abs assets/*) -set(_resource_files) -foreach(_file IN LISTS _resource_files_abs) - file(RELATIVE_PATH _rel_path ${CMAKE_CURRENT_SOURCE_DIR} ${_file}) - list(APPEND _resource_files ${_rel_path}) -endforeach() +file(GLOB_RECURSE _qml_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.qml *.js *.mjs) +file(GLOB_RECURSE _resource_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} assets/*) qt_add_qml_module(${PROJECT_NAME} URI DiffScope.UIShell diff --git a/src/libs/application/uishell/src/RecentFilesProxyModel.cpp b/src/libs/application/uishell/src/RecentFilesProxyModel.cpp index c4effbd4..1a0c93cc 100644 --- a/src/libs/application/uishell/src/RecentFilesProxyModel.cpp +++ b/src/libs/application/uishell/src/RecentFilesProxyModel.cpp @@ -17,6 +17,7 @@ namespace UIShell { {USDef::RF_LastModifiedTextRole, "lastModifiedText"}, {USDef::RF_ThumbnailRole, "thumbnail"}, {USDef::RF_IconRole, "icon"}, + {USDef::RF_ColorizeRole, "colorize"}, }; return m; } diff --git a/src/libs/application/uishell/src/USDef.h b/src/libs/application/uishell/src/USDef.h index cfb731f6..8f653c75 100644 --- a/src/libs/application/uishell/src/USDef.h +++ b/src/libs/application/uishell/src/USDef.h @@ -21,6 +21,7 @@ namespace UIShell { RF_LastModifiedTextRole, RF_ThumbnailRole, RF_IconRole, + RF_ColorizeRole, }; Q_ENUM_NS(RecentFileRole) diff --git a/src/libs/application/uishell/src/qml/HomeWindow.qml b/src/libs/application/uishell/src/qml/HomeWindow.qml index 4a378041..a15c4ebd 100644 --- a/src/libs/application/uishell/src/qml/HomeWindow.qml +++ b/src/libs/application/uishell/src/qml/HomeWindow.qml @@ -23,7 +23,7 @@ Window { property var recoveryFilesModel: null property var navigationActionsModel: null property var toolActionsModel: null - property var macosMenusModel: null + property var menusModel: null readonly property bool isMacOS: Qt.platform.os === "osx" || Qt.platform.os === "macos" @@ -47,6 +47,8 @@ Window { signal newFileRequested() signal openRecentFileRequested(int index) signal openRecoveryFileRequested(int index) + signal removeRecentFileRequested(int index) + signal removeRecoveryFileRequested(int index) function setupFrameless() { if (frameless && !windowAgent.framelessSetup) { @@ -56,6 +58,7 @@ Window { windowAgent.setSystemButton(WindowAgent.Minimize, minimizeSystemButton) windowAgent.setSystemButton(WindowAgent.Maximize, maximizeSystemButton) windowAgent.setSystemButton(WindowAgent.Close, closeSystemButton) + windowAgent.setHitTestVisible(menuBar) windowAgent.setHitTestVisible(Overlay.overlay) } } @@ -146,11 +149,12 @@ Window { color: Theme.foregroundSecondaryColor } // fallback display icon as thumbnail - Image { + ColorImage { width: 80 height: 80 anchors.centerIn: parent source: cell.modelData.icon + color: cell.modelData.colorize ? Theme.foregroundSecondaryColor : "transparent" sourceSize.width: 80 sourceSize.height: 80 } @@ -159,6 +163,8 @@ Window { anchors.fill: parent fillMode: Image.PreserveAspectCrop source: cell.modelData.thumbnail + cache: false + mipmap: true } } Label { @@ -219,9 +225,10 @@ Window { sourceSize.width: 48 sourceSize.height: 48 } - Image { + ColorImage { anchors.fill: parent source: cell.modelData.icon + color: cell.modelData.colorize ? Theme.foregroundSecondaryColor : "transparent" sourceSize.width: 48 sourceSize.height: 48 } @@ -296,8 +303,11 @@ Window { text: tapHandler.recovery ? qsTr('Remove from "Recovery Files"') : qsTr('Remove from "Recent Files"') icon.source: "qrc:/qt/qml/DiffScope/UIShell/assets/DocumentDismiss16Filled.svg" onTriggered: () => { - const model = tapHandler.recovery ? recoveryFilesProxyModel : recentFilesProxyModel - model.remove(tapHandler.index) + if (tapHandler.recovery) { + window.removeRecoveryFileRequested(recoveryFilesProxyModel.mapIndexToSource(tapHandler.index)) + } else { + window.removeRecentFileRequested(recentFilesProxyModel.mapIndexToSource(tapHandler.index)) + } } } } @@ -313,28 +323,6 @@ Window { property bool framelessSetup: false } - MenuBar { - id: menuBar - visible: window.isMacOS - Instantiator { - model: window.macosMenusModel - onObjectAdded: (index, object) => { - if (object instanceof Menu) { - menuBar.insertMenu(index, object) - } else { - throw new TypeError("Unsupported menu type") - } - } - onObjectRemoved: (index, object) => { - if (object instanceof Menu) { - menuBar.removeMenu(object) - } else { - throw new TypeError("Unsupported menu type") - } - } - } - } - Item { id: titleBarArea width: window.width @@ -342,6 +330,49 @@ Window { visible: windowAgent.framelessSetup && (!window.isMacOS || window.visibility !== Window.FullScreen) z: 1 Accessible.role: Accessible.TitleBar + Rectangle { + id: menuBarBackground + width: parent.width + height: menuBar.height + visible: menuBar.height !== 0 + anchors.top: parent.top + anchors.topMargin: menuBar.anchors.topMargin + color: Theme.backgroundQuaternaryColor + } + MenuBar { + id: menuBar + anchors.left: parent.left + anchors.top: parent.top + anchors.topMargin: activeFocus || menus.some(menu => menu.visible) || children.some(item => item.activeFocus) ? 0 : -height + ThemedItem.backgroundLevel: SVS.BL_Quaternary + Behavior on anchors.topMargin { + id: topMarginBehavior + enabled: false + NumberAnimation { + duration: Theme.visualEffectAnimationDuration + easing.type: Easing.OutCubic + } + } + Component.onCompleted: Qt.callLater(() => topMarginBehavior.enabled = true) + Instantiator { + model: window.menusModel + onObjectAdded: (index, object) => { + if (object instanceof Menu) { + console.log(object) + menuBar.insertMenu(index, object) + } else { + throw new TypeError("Unsupported menu type") + } + } + onObjectRemoved: (index, object) => { + if (object instanceof Menu) { + menuBar.removeMenu(object) + } else { + throw new TypeError("Unsupported menu type") + } + } + } + } RowLayout { anchors.right: parent.right visible: !window.isMacOS diff --git a/src/libs/application/uishell/tests/HomeWindow/CMakeLists.txt b/src/libs/application/uishell/tests/HomeWindow/CMakeLists.txt index a97b51ac..df2aa1d2 100644 --- a/src/libs/application/uishell/tests/HomeWindow/CMakeLists.txt +++ b/src/libs/application/uishell/tests/HomeWindow/CMakeLists.txt @@ -4,12 +4,7 @@ set(CMAKE_AUTORCC ON) set(CMAKE_AUTOMOC ON) file(GLOB _src *.h *.cpp *.qrc) -file(GLOB_RECURSE _qml_files_abs *.qml *.js *.mjs) -set(_qml_files) -foreach(_file IN LISTS _qml_files_abs) - file(RELATIVE_PATH _rel_path ${CMAKE_CURRENT_SOURCE_DIR} ${_file}) - list(APPEND _qml_files ${_rel_path}) -endforeach() +file(GLOB_RECURSE _qml_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.qml *.js *.mjs) add_executable(${PROJECT_NAME} ${_src}) diff --git a/src/libs/application/uishell/tests/LyricPronunciationEditor/CMakeLists.txt b/src/libs/application/uishell/tests/LyricPronunciationEditor/CMakeLists.txt index f930716d..9fdb2325 100644 --- a/src/libs/application/uishell/tests/LyricPronunciationEditor/CMakeLists.txt +++ b/src/libs/application/uishell/tests/LyricPronunciationEditor/CMakeLists.txt @@ -4,12 +4,7 @@ set(CMAKE_AUTORCC ON) set(CMAKE_AUTOMOC ON) file(GLOB _src *.h *.cpp *.qrc) -file(GLOB_RECURSE _qml_files_abs *.qml *.js *.mjs) -set(_qml_files) -foreach(_file IN LISTS _qml_files_abs) - file(RELATIVE_PATH _rel_path ${CMAKE_CURRENT_SOURCE_DIR} ${_file}) - list(APPEND _qml_files ${_rel_path}) -endforeach() +file(GLOB_RECURSE _qml_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.qml *.js *.mjs) add_executable(${PROJECT_NAME} ${_src}) diff --git a/src/libs/application/uishell/tests/PluginView/CMakeLists.txt b/src/libs/application/uishell/tests/PluginView/CMakeLists.txt index 8fc06607..acc823f0 100644 --- a/src/libs/application/uishell/tests/PluginView/CMakeLists.txt +++ b/src/libs/application/uishell/tests/PluginView/CMakeLists.txt @@ -4,12 +4,7 @@ set(CMAKE_AUTORCC ON) set(CMAKE_AUTOMOC ON) file(GLOB _src *.h *.cpp *.qrc) -file(GLOB_RECURSE _qml_files_abs *.qml *.js *.mjs) -set(_qml_files) -foreach(_file IN LISTS _qml_files_abs) - file(RELATIVE_PATH _rel_path ${CMAKE_CURRENT_SOURCE_DIR} ${_file}) - list(APPEND _qml_files ${_rel_path}) -endforeach() +file(GLOB_RECURSE _qml_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.qml *.js *.mjs) add_executable(${PROJECT_NAME} ${_src}) diff --git a/src/libs/application/uishell/tests/ProjectWindow/CMakeLists.txt b/src/libs/application/uishell/tests/ProjectWindow/CMakeLists.txt index b55463dc..86d39a85 100644 --- a/src/libs/application/uishell/tests/ProjectWindow/CMakeLists.txt +++ b/src/libs/application/uishell/tests/ProjectWindow/CMakeLists.txt @@ -4,12 +4,7 @@ set(CMAKE_AUTORCC ON) set(CMAKE_AUTOMOC ON) file(GLOB _src *.h *.cpp *.qrc) -file(GLOB_RECURSE _qml_files_abs *.qml *.js *.mjs) -set(_qml_files) -foreach(_file IN LISTS _qml_files_abs) - file(RELATIVE_PATH _rel_path ${CMAKE_CURRENT_SOURCE_DIR} ${_file}) - list(APPEND _qml_files ${_rel_path}) -endforeach() +file(GLOB_RECURSE _qml_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.qml *.js *.mjs) add_executable(${PROJECT_NAME} ${_src}) diff --git a/src/libs/application/uishell/tests/SettingDialog/CMakeLists.txt b/src/libs/application/uishell/tests/SettingDialog/CMakeLists.txt index ef09cb46..c8d5c301 100644 --- a/src/libs/application/uishell/tests/SettingDialog/CMakeLists.txt +++ b/src/libs/application/uishell/tests/SettingDialog/CMakeLists.txt @@ -4,12 +4,7 @@ set(CMAKE_AUTORCC ON) set(CMAKE_AUTOMOC ON) file(GLOB _src *.h *.cpp *.qrc) -file(GLOB_RECURSE _qml_files_abs *.qml *.js *.mjs) -set(_qml_files) -foreach(_file IN LISTS _qml_files_abs) - file(RELATIVE_PATH _rel_path ${CMAKE_CURRENT_SOURCE_DIR} ${_file}) - list(APPEND _qml_files ${_rel_path}) -endforeach() +file(GLOB_RECURSE _qml_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.qml *.js *.mjs) add_executable(${PROJECT_NAME} ${_src}) diff --git a/src/plugins/achievement/CMakeLists.txt b/src/plugins/achievement/CMakeLists.txt index 8c8059dd..0f805033 100644 --- a/src/plugins/achievement/CMakeLists.txt +++ b/src/plugins/achievement/CMakeLists.txt @@ -66,12 +66,7 @@ if(QT_KNOWN_POLICY_QTP0004) qt_policy(SET QTP0004 NEW) endif() -file(GLOB_RECURSE _qml_files_abs *.qml *.js *.mjs) -set(_qml_files) -foreach(_file IN LISTS _qml_files_abs) - file(RELATIVE_PATH _rel_path ${CMAKE_CURRENT_SOURCE_DIR} ${_file}) - list(APPEND _qml_files ${_rel_path}) -endforeach() +file(GLOB_RECURSE _qml_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.qml *.js *.mjs) qt_add_qml_module(${PROJECT_NAME} URI DiffScope.Achievement diff --git a/src/plugins/audio/CMakeLists.txt b/src/plugins/audio/CMakeLists.txt index 3eed09e4..5830eef6 100644 --- a/src/plugins/audio/CMakeLists.txt +++ b/src/plugins/audio/CMakeLists.txt @@ -70,12 +70,7 @@ if(QT_KNOWN_POLICY_QTP0004) qt_policy(SET QTP0004 NEW) endif() -file(GLOB_RECURSE _qml_files_abs *.qml *.js *.mjs) -set(_qml_files) -foreach(_file IN LISTS _qml_files_abs) - file(RELATIVE_PATH _rel_path ${CMAKE_CURRENT_SOURCE_DIR} ${_file}) - list(APPEND _qml_files ${_rel_path}) -endforeach() +file(GLOB_RECURSE _qml_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.qml *.js *.mjs) qt_add_qml_module(${PROJECT_NAME} URI DiffScope.Audio diff --git a/src/plugins/coreplugin/CMakeLists.txt b/src/plugins/coreplugin/CMakeLists.txt index 1d712f4e..53943ea9 100644 --- a/src/plugins/coreplugin/CMakeLists.txt +++ b/src/plugins/coreplugin/CMakeLists.txt @@ -27,13 +27,6 @@ qak_add_action_extension( res/core_actions.xml ) list(APPEND _src ${_core_actions_src}) -if(APPLE) - qak_add_action_extension( - _core_macos_actions_src - res/core_macos_actions.xml - ) - list(APPEND _src ${_core_macos_actions_src}) -endif() qm_configure_target(${PROJECT_NAME} SOURCES ${_src} @@ -66,7 +59,7 @@ qm_configure_target(${PROJECT_NAME} # ChorusKit::AppCore # JetBrainsDockingSystem # dspxmodel - # opendspx::opendspx + opendspx::opendspx INCLUDE_PRIVATE core windows @@ -80,12 +73,7 @@ if(QT_KNOWN_POLICY_QTP0004) qt_policy(SET QTP0004 NEW) endif() -file(GLOB_RECURSE _qml_files_abs *.qml *.js *.mjs) -set(_qml_files) -foreach(_file IN LISTS _qml_files_abs) - file(RELATIVE_PATH _rel_path ${CMAKE_CURRENT_SOURCE_DIR} ${_file}) - list(APPEND _qml_files ${_rel_path}) -endforeach() +file(GLOB_RECURSE _qml_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.qml *.js *.mjs) qt_add_qml_module(${PROJECT_NAME} URI DiffScope.Core diff --git a/src/plugins/coreplugin/core/coreinterface.cpp b/src/plugins/coreplugin/core/coreinterface.cpp index d2817bec..74ab64cd 100644 --- a/src/plugins/coreplugin/core/coreinterface.cpp +++ b/src/plugins/coreplugin/core/coreinterface.cpp @@ -3,23 +3,20 @@ #include #include +#include -#include "QStyleFactory" #include #include #include #include -#include -#include #include -#include -#include -#include #include #include #include #include #include +#include +#include #include @@ -31,14 +28,20 @@ #include #include +#include + #include #include +#include +#include +#include #include #include #include #include #include +#include namespace Core { @@ -143,12 +146,13 @@ namespace Core { "Application", "

A professional singing-voice-synthesis editor powered by DiffSinger

" "

Version %1

" - "

Copyright \u00a9 %2-%3 Team OpenVPI. All rights reserved.

" - "

Visit %4 for more information.

") + "

Copyright \u00a9 %2-%3 %4. All rights reserved.

" + "

Visit %5 for more information.

") .arg( QApplication::applicationVersion(), QLocale().toString(QDate(QStringLiteral(APPLICATION_DEV_START_YEAR).toInt(), 1, 1), "yyyy"), QLocale().toString(QDate(QStringLiteral(APPLICATION_BUILD_YEAR).toInt(), 1, 1), "yyyy"), + APPLICATION_VENDOR_NAME, QStringLiteral(APPLICATION_URL)); QString licenseInfo = @@ -204,35 +208,70 @@ namespace Core { QMessageBox::aboutQt(parent->property("invisibleCentralWidget").value()); } + static void raiseWindow(QWindow *window) { + if (window->visibility() == QWindow::Minimized) { + window->showNormal(); + } + window->raise(); // TODO: what does the previous QMView::raiseWindow do to the window? + window->requestActivate(); + } + void CoreInterface::showHome() { qCInfo(lcCoreInterface) << "Show home"; auto inst = HomeWindowInterface::instance(); if (inst) { qCInfo(lcCoreInterface) << "Home window already exists, raising it"; - if (inst->window()->visibility() == QWindow::Minimized) { - inst->window()->showNormal(); - } - inst->window() - ->raise(); // TODO: what does the previous QMView::raiseWindow do to the window? - inst->window()->requestActivate(); + raiseWindow(inst->window()); return; } qCInfo(lcCoreInterface) << "Creating home window"; auto windowInterface = HomeWindowInterfaceRegistry::instance()->create(); - Q_UNUSED(windowInterface); + QQmlEngine::setObjectOwnership(windowInterface, QQmlEngine::CppOwnership); } - ProjectWindowInterface *CoreInterface::newFile() { - qCInfo(lcCoreInterface) << "New file"; + QString CoreInterface::dspxFileFilter(bool withAllFiles) { + auto dspxFileFilter = tr("DiffScope Project Exchange Format (*.dspx)"); + auto allFileFilter = tr("All Files (*)"); + return withAllFiles ? dspxFileFilter + ";;" + allFileFilter : dspxFileFilter; + } + + static ProjectWindowInterface *createProjectWindow(ProjectDocumentContext *projectDocumentContext) { Internal::ProjectStartupTimerAddOn::startTimer(); - // TODO: temporarily creates a project window for testing - auto windowInterface = ProjectWindowInterfaceRegistry::instance()->create(); + auto windowInterface = ProjectWindowInterfaceRegistry::instance()->create(projectDocumentContext); + QQmlEngine::setObjectOwnership(windowInterface, QQmlEngine::CppOwnership); + projectDocumentContext->setParent(windowInterface); auto win = static_cast(windowInterface->window()); win->show(); if (HomeWindowInterface::instance() && (Internal::BehaviorPreference::startupBehavior() & Internal::BehaviorPreference::SB_CloseHomeWindowAfterOpeningProject)) { qCInfo(lcCoreInterface) << "Closing home window"; HomeWindowInterface::instance()->quit(); } + return windowInterface; + } + + static QString promptOpenDspxFile(QWindow *parent) { + auto settings = RuntimeInterface::settings(); + settings->beginGroup(CoreInterface::staticMetaObject.className()); + auto defaultOpenDir = settings->value(QStringLiteral("defaultOpenDir"), QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)).toString(); + settings->endGroup(); + + auto path = QFileDialog::getOpenFileName( + nullptr, + {}, + defaultOpenDir, + CoreInterface::dspxFileFilter(true) + ); + if (path.isEmpty()) + return {}; + + settings->beginGroup(CoreInterface::staticMetaObject.className()); + settings->setValue(QStringLiteral("defaultOpenDir"), QFileInfo(path).absolutePath()); + settings->endGroup(); + + return path; + } + + static void triggerAchievementAfterNewProjectWindowOpened(QWindow *win) { class ExposedListener : public QObject { public: explicit ExposedListener(QObject *parent) : QObject(parent) { @@ -249,16 +288,65 @@ namespace Core { }; auto listener = new ExposedListener(win); win->installEventFilter(listener); + } + + ProjectWindowInterface *CoreInterface::newFile(QWindow *parent) { + static QDspxModel defaultModel; + qCInfo(lcCoreInterface) << "New file"; + auto projectDocumentContext = std::make_unique(); + projectDocumentContext->newFile(defaultModel, false, parent); + auto windowInterface = createProjectWindow(projectDocumentContext.release()); + triggerAchievementAfterNewProjectWindowOpened(windowInterface->window()); return windowInterface; } - bool CoreInterface::openFile(const QString &fileName, QWidget *parent) { - // auto docMgr = CoreInterface::instance()->documentSystem(); - // if (fileName.isEmpty()) { - // return docMgr->openFileBrowse(parent, DspxSpec::instance()); - // } - // return DspxSpec::instance()->open(fileName, parent); - return false; + ProjectWindowInterface *CoreInterface::newFileFromTemplate(const QString &templateFilePath_, QWindow *parent) { + qCInfo(lcCoreInterface) << "New file from template" << templateFilePath_; + auto templateFilePath = templateFilePath_; + if (templateFilePath.isEmpty()) { + templateFilePath = promptOpenDspxFile(parent); + if (templateFilePath.isEmpty()) + return nullptr; + } + auto projectDocumentContext = std::make_unique(); + if (!projectDocumentContext->newFile(templateFilePath, false, nullptr)) { + return nullptr; + } + auto windowInterface = createProjectWindow(projectDocumentContext.release()); + triggerAchievementAfterNewProjectWindowOpened(windowInterface->window()); + return windowInterface; + } + + ProjectWindowInterface *CoreInterface::openFile(const QString &filePath_, QWindow *parent) { + qCInfo(lcCoreInterface) << "Open file" << filePath_; + auto filePath = filePath_; + if (filePath.isEmpty()) { + filePath = promptOpenDspxFile(parent); + if (filePath.isEmpty()) + return nullptr; + } + + auto windows = windowSystem()->windows(); + auto openedWindow = std::ranges::find_if(windows, [filePath](WindowInterface *windowInterface) { + if (auto projectWindowInterface = qobject_cast(windowInterface)) { + if (!projectWindowInterface->projectDocumentContext()->fileLocker() || projectWindowInterface->projectDocumentContext()->fileLocker()->path().isEmpty()) + return false; + return QFileInfo(projectWindowInterface->projectDocumentContext()->fileLocker()->path()).canonicalFilePath() == QFileInfo(filePath).canonicalFilePath(); + } + return false; + }); + if (openedWindow != windows.end()) { + qCInfo(lcCoreInterface) << "File already opened" << filePath; + raiseWindow((*openedWindow)->window()); + return qobject_cast(*openedWindow); + } + auto projectDocumentContext = std::make_unique(); + if (!projectDocumentContext->openFile(filePath, parent)) { + return nullptr; + } + auto windowInterface = createProjectWindow(projectDocumentContext.release()); + recentFileCollection()->addRecentFile(filePath, {}); + return windowInterface; } CoreInterface::CoreInterface(QObject *parent) : CoreInterface(*new CoreInterfacePrivate(), parent) { diff --git a/src/plugins/coreplugin/core/coreinterface.h b/src/plugins/coreplugin/core/coreinterface.h index d50bcb51..a33cbe94 100644 --- a/src/plugins/coreplugin/core/coreinterface.h +++ b/src/plugins/coreplugin/core/coreinterface.h @@ -43,8 +43,10 @@ namespace Core { Q_INVOKABLE static void showHome(); public: - Q_INVOKABLE static ProjectWindowInterface *newFile(); - static bool openFile(const QString &fileName, QWidget *parent = nullptr); + Q_INVOKABLE static QString dspxFileFilter(bool withAllFiles = false); + Q_INVOKABLE static ProjectWindowInterface *newFile(QWindow *parent = nullptr); + Q_INVOKABLE static ProjectWindowInterface *newFileFromTemplate(const QString &templateFilePath, QWindow *parent = nullptr); + Q_INVOKABLE static ProjectWindowInterface *openFile(const QString &filePath, QWindow *parent = nullptr); Q_SIGNALS: void resetAllDoNotShowAgainRequested(); diff --git a/src/plugins/coreplugin/core/projectdocumentcontext.cpp b/src/plugins/coreplugin/core/projectdocumentcontext.cpp new file mode 100644 index 00000000..eba8b8bb --- /dev/null +++ b/src/plugins/coreplugin/core/projectdocumentcontext.cpp @@ -0,0 +1,161 @@ +#include "projectdocumentcontext.h" +#include "projectdocumentcontext_p.h" + +#include +#include + +#include + +#include +#include + +#include + +namespace Core { + + Q_LOGGING_CATEGORY(lcProjectDocumentContext, "diffscope.core.projectdocumentcontext") + + void ProjectDocumentContextPrivate::markSaved() { + // TODO + } + + QByteArray ProjectDocumentContextPrivate::serializeDocument() const { + // TODO + return fileData_TODO; + } + + void ProjectDocumentContextPrivate::deserializeDocument(const QByteArray &data) { + // TODO + fileData_TODO = data; + } + + ProjectDocumentContext::ProjectDocumentContext(QObject *parent) : QObject(parent), d_ptr(new ProjectDocumentContextPrivate) { + Q_D(ProjectDocumentContext); + d->q_ptr = this; + } + + ProjectDocumentContext::~ProjectDocumentContext() = default; + + FileLocker *ProjectDocumentContext::fileLocker() const { + Q_D(const ProjectDocumentContext); + return d->fileLocker; + } + + DspxDocument *ProjectDocumentContext::document() const { + Q_D(const ProjectDocumentContext); + return nullptr; // TODO + } + + bool ProjectDocumentContext::openFile(const QString &filePath, QWindow *parent) { + Q_D(ProjectDocumentContext); + if (false) { + return false; // TODO document should not be opened + } + d->fileLocker = new FileLocker(this); + if (!d->fileLocker->open(filePath)) { + qCCritical(lcProjectDocumentContext) << "Failed to open file:" << filePath; + SVS::MessageBox::critical(RuntimeInterface::qmlEngine(), parent, + tr("Failed to open file"), + QStringLiteral("%1\n\n%2").arg(QDir::toNativeSeparators(filePath), d->fileLocker->errorString())); + return false; + } + // TODO initialize document + bool ok; + auto data = d->fileLocker->readData(&ok); + if ( +#ifdef Q_OS_WIN + !(Internal::BehaviorPreference::fileOption() & Internal::BehaviorPreference::FO_LockOpenedFiles) +#else + true +#endif + ) { + d->fileLocker->release(); + } + if (!ok) { + qCCritical(lcProjectDocumentContext) << "Failed to read file:" << filePath; + SVS::MessageBox::critical(RuntimeInterface::qmlEngine(), parent, + tr("Failed to read file"), + QStringLiteral("%1\n\n%2").arg(QDir::toNativeSeparators(filePath), d->fileLocker->errorString())); + return false; + } + d->deserializeDocument(data); + return true; + } + + void ProjectDocumentContext::newFile(const QDspx::Model &templateModel, bool isNonFileDocument, QWindow *parent) { + Q_D(ProjectDocumentContext); + if (false) { + return; // TODO document should not be opened + } + if (!isNonFileDocument) { + d->fileLocker = new FileLocker(this); + } + // TODO initialize document + d->fileData_TODO = {"{}"}; + } + + bool ProjectDocumentContext::newFile(const QString &templateFilePath, bool isNonFileDocument, QWindow *parent) { + Q_D(ProjectDocumentContext); + if (false) { + return false; // TODO document should not be opened + } + if (!isNonFileDocument) { + d->fileLocker = new FileLocker(this); + } + // TODO initialize document + d->fileData_TODO = {"{}"}; + return true; + } + + bool ProjectDocumentContext::save(QWindow *parent) { + Q_D(ProjectDocumentContext); + if (!d->fileLocker || d->fileLocker->path().isEmpty()) + return false; + auto data = d->serializeDocument(); + bool isSuccess = d->fileLocker->save(data); + if (!isSuccess) { + qCCritical(lcProjectDocumentContext) << "Failed to save file:" << d->fileLocker->path(); + SVS::MessageBox::critical(RuntimeInterface::qmlEngine(), parent, + tr("Failed to save file"), + QStringLiteral("%1\n\n%2").arg(QDir::toNativeSeparators(d->fileLocker->path()), d->fileLocker->errorString())); + return false; + } + d->markSaved(); + return true; + } + + bool ProjectDocumentContext::saveAs(const QString &filePath, QWindow *parent) { + Q_D(ProjectDocumentContext); + if (!d->fileLocker) + return false; + auto data = d->serializeDocument(); + bool isSuccess = d->fileLocker->saveAs(filePath, data); + if (!isSuccess) { + qCCritical(lcProjectDocumentContext) << "Failed to save file as:" << d->fileLocker->path(); + SVS::MessageBox::critical(RuntimeInterface::qmlEngine(), parent, + tr("Failed to save file"), + QStringLiteral("%1\n\n%2").arg(QDir::toNativeSeparators(d->fileLocker->path()), d->fileLocker->errorString())); + return false; + } + d->markSaved(); + return true; + } + + bool ProjectDocumentContext::saveCopy(const QString &filePath, QWindow *parent) { + Q_D(ProjectDocumentContext); + FileLocker copyFileLocker; + auto data = d->serializeDocument(); + bool isSuccess = copyFileLocker.saveAs(filePath, data); + if (!isSuccess) { + qCCritical(lcProjectDocumentContext) << "Failed to save copy file:" << d->fileLocker->path(); + SVS::MessageBox::critical(RuntimeInterface::qmlEngine(), parent, + tr("Failed to save file"), + QStringLiteral("%1\n\n%2").arg(QDir::toNativeSeparators(d->fileLocker->path()), d->fileLocker->errorString())); + return false; + } + return true; + } + +} + +#include "moc_projectdocumentcontext.cpp" \ No newline at end of file diff --git a/src/plugins/coreplugin/core/projectdocumentcontext.h b/src/plugins/coreplugin/core/projectdocumentcontext.h new file mode 100644 index 00000000..a456ea2d --- /dev/null +++ b/src/plugins/coreplugin/core/projectdocumentcontext.h @@ -0,0 +1,45 @@ +#ifndef DIFFSCOPE_COREPLUGIN_PROJECTDOCUMENTCONTEXT_H +#define DIFFSCOPE_COREPLUGIN_PROJECTDOCUMENTCONTEXT_H + +#include + +class QWindow; + +namespace QDspx { + struct Model; +} + +namespace Core { + + class DspxDocument; + class FileLocker; + + class ProjectDocumentContextPrivate; + + class ProjectDocumentContext : public QObject { + Q_OBJECT + Q_DECLARE_PRIVATE(ProjectDocumentContext) + Q_PROPERTY(FileLocker *fileLocker READ fileLocker CONSTANT) + public: + explicit ProjectDocumentContext(QObject *parent = nullptr); + ~ProjectDocumentContext() override; + + FileLocker *fileLocker() const; + + DspxDocument *document() const; + + bool openFile(const QString &filePath, QWindow *parent = nullptr); + void newFile(const QDspx::Model &templateModel, bool isNonFileDocument, QWindow *parent = nullptr); + bool newFile(const QString &templateFilePath, bool isNonFileDocument, QWindow *parent = nullptr); + bool save(QWindow *parent = nullptr); + bool saveAs(const QString &filePath, QWindow *parent = nullptr); + bool saveCopy(const QString &filePath, QWindow *parent = nullptr); + + private: + QScopedPointer d_ptr; + + }; + +} + +#endif //DIFFSCOPE_COREPLUGIN_PROJECTDOCUMENTCONTEXT_H diff --git a/src/plugins/coreplugin/core/projectdocumentcontext_p.h b/src/plugins/coreplugin/core/projectdocumentcontext_p.h new file mode 100644 index 00000000..979af881 --- /dev/null +++ b/src/plugins/coreplugin/core/projectdocumentcontext_p.h @@ -0,0 +1,25 @@ +#ifndef DIFFSCOPE_COREPLUGIN_PROJECTDOCUMENTCONTEXT_P_H +#define DIFFSCOPE_COREPLUGIN_PROJECTDOCUMENTCONTEXT_P_H + +#include + +namespace Core { + + class ProjectDocumentContextPrivate { + Q_DECLARE_PUBLIC(ProjectDocumentContext) + public: + ProjectDocumentContext *q_ptr; + + FileLocker *fileLocker{}; + + QByteArray fileData_TODO; // TODO + + void markSaved(); + QByteArray serializeDocument() const; + void deserializeDocument(const QByteArray &data); + + }; + +} + +#endif //DIFFSCOPE_COREPLUGIN_PROJECTDOCUMENTCONTEXT_P_H diff --git a/src/plugins/coreplugin/internal/addon/metadataaddon.cpp b/src/plugins/coreplugin/internal/addon/metadataaddon.cpp new file mode 100644 index 00000000..b1538cd0 --- /dev/null +++ b/src/plugins/coreplugin/internal/addon/metadataaddon.cpp @@ -0,0 +1,42 @@ +#include "metadataaddon.h" + +#include +#include + +#include + +#include + +#include +#include + +namespace Core::Internal { + MetadataAddOn::MetadataAddOn(QObject *parent) : WindowInterfaceAddOn(parent) { + } + + MetadataAddOn::~MetadataAddOn() = default; + + void MetadataAddOn::initialize() { + auto windowInterface = windowHandle()->cast(); + + // Create MetadataPanel component and add it to action context + QQmlComponent component(RuntimeInterface::qmlEngine(), "DiffScope.Core", "MetadataPanel", this); + if (component.isError()) { + qFatal() << component.errorString(); + } + auto o = component.createWithInitialProperties({ + {"addOn", QVariant::fromValue(this)}, + }, RuntimeInterface::qmlEngine()->rootContext()); + o->setParent(this); + windowInterface->actionContext()->addAction("core.panel.metadata", o->property("metadataPanelComponent").value()); + } + + void MetadataAddOn::extensionsInitialized() { + } + + bool MetadataAddOn::delayedInitialize() { + return WindowInterfaceAddOn::delayedInitialize(); + } +} + +#include "moc_metadataaddon.cpp" \ No newline at end of file diff --git a/src/plugins/coreplugin/internal/addon/metadataaddon.h b/src/plugins/coreplugin/internal/addon/metadataaddon.h new file mode 100644 index 00000000..06487b9b --- /dev/null +++ b/src/plugins/coreplugin/internal/addon/metadataaddon.h @@ -0,0 +1,21 @@ +#ifndef DIFFSCOPE_COREPLUGIN_METADATAADDON_H +#define DIFFSCOPE_COREPLUGIN_METADATAADDON_H + +#include + +namespace Core::Internal { + + class MetadataAddOn : public WindowInterfaceAddOn { + Q_OBJECT + public: + explicit MetadataAddOn(QObject *parent = nullptr); + ~MetadataAddOn() override; + + void initialize() override; + void extensionsInitialized() override; + bool delayedInitialize() override; + }; + +} + +#endif //DIFFSCOPE_COREPLUGIN_METADATAADDON_H \ No newline at end of file diff --git a/src/plugins/coreplugin/internal/addon/projectwindownavigatoraddon.cpp b/src/plugins/coreplugin/internal/addon/projectwindownavigatoraddon.cpp new file mode 100644 index 00000000..bf824a50 --- /dev/null +++ b/src/plugins/coreplugin/internal/addon/projectwindownavigatoraddon.cpp @@ -0,0 +1,120 @@ +#include "projectwindownavigatoraddon.h" + +#include +#include + +#include +#include +#include + +#include +#include + +#include + +#include +#include + +namespace Core::Internal { + + Q_STATIC_LOGGING_CATEGORY(lcProjectWindowNavigatorAddOn, "diffscope.core.projectwindownavigatoraddon") + + ProjectWindowNavigatorAddOn::ProjectWindowNavigatorAddOn(QObject *parent) : WindowInterfaceAddOn(parent) { + m_quickPickCommandModel = new QStandardItemModel(this); + } + + ProjectWindowNavigatorAddOn::~ProjectWindowNavigatorAddOn() = default; + + void ProjectWindowNavigatorAddOn::initialize() { + auto windowInterface = windowHandle()->cast(); + QQmlComponent component(RuntimeInterface::qmlEngine(), "DiffScope.Core", "ProjectWindowNavigatorAddOnActions"); + if (component.isError()) { + qFatal() << component.errorString(); + } + auto o = component.createWithInitialProperties({ + {"addOn", QVariant::fromValue(this)}, + }); + o->setParent(this); + QMetaObject::invokeMethod(o, "registerToContext", windowInterface->actionContext()); + } + + void ProjectWindowNavigatorAddOn::extensionsInitialized() { + auto windowSystem = CoreInterface::windowSystem(); + + // Connect to window creation and destruction signals + connect(windowSystem, &WindowSystem::windowCreated, this, [this](WindowInterface *windowInterface) { + if (qobject_cast(windowInterface)) { + updateProjectWindows(); + } + }); + + connect(windowSystem, &WindowSystem::windowAboutToDestroy, this, [this](WindowInterface *windowInterface) { + if (qobject_cast(windowInterface)) { + updateProjectWindows(); + } + }); + + // Initialize the list with current windows + updateProjectWindows(); + } + + bool ProjectWindowNavigatorAddOn::delayedInitialize() { + return WindowInterfaceAddOn::delayedInitialize(); + } + + QList ProjectWindowNavigatorAddOn::projectWindows() const { + return m_projectWindows; + } + + void ProjectWindowNavigatorAddOn::raiseWindow(ProjectWindowInterface *windowInterface) { + qCDebug(lcProjectWindowNavigatorAddOn) << "Raising project window" << windowInterface; + auto window = windowInterface->window(); + if (window->visibility() == QWindow::Minimized) { + window->showNormal(); + } + window->raise(); // TODO: what does the previous QMView::raiseWindow do to the window? + window->requestActivate(); + } + + void ProjectWindowNavigatorAddOn::navigateToWindow(int step) const { + if (auto windowInterface = qobject_cast(windowHandle())) { + auto index = m_projectWindows.indexOf(windowInterface); + if (index == -1) + return; + index = (index + step + m_projectWindows.size()) % m_projectWindows.size(); + auto target = m_projectWindows[index]; + raiseWindow(target); + } else if (!m_projectWindows.isEmpty()) { + auto target = m_projectWindows.first(); + raiseWindow(target); + } + } + + void ProjectWindowNavigatorAddOn::showSwitchToProjectWindowCommand() const { + auto windowInterface = windowHandle()->cast(); + auto index = windowInterface->execQuickPick(m_quickPickCommandModel, "Switch to project window", 0); + if (index == -1) + return; + raiseWindow(m_projectWindows.at(index)); + } + + void ProjectWindowNavigatorAddOn::updateProjectWindows() { + m_projectWindows.clear(); + auto windows = CoreInterface::windowSystem()->windows(); + std::ranges::transform( + windows | std::views::filter([](auto w) { return qobject_cast(w); }), + std::back_inserter(m_projectWindows), + [](auto w) { return qobject_cast(w); } + ); + m_quickPickCommandModel->clear(); + for (auto windowInterface : m_projectWindows) { + auto item = new QStandardItem; + item->setData(windowInterface->window()->property("documentName"), SVS::SVSCraft::CP_TitleRole); + m_quickPickCommandModel->appendRow(item); + } + Q_EMIT projectWindowsChanged(); + } + +} + +#include "moc_projectwindownavigatoraddon.cpp" \ No newline at end of file diff --git a/src/plugins/coreplugin/internal/addon/projectwindownavigatoraddon.h b/src/plugins/coreplugin/internal/addon/projectwindownavigatoraddon.h new file mode 100644 index 00000000..09b4c81e --- /dev/null +++ b/src/plugins/coreplugin/internal/addon/projectwindownavigatoraddon.h @@ -0,0 +1,43 @@ +#ifndef DIFFSCOPE_COREPLUGIN_PROJECTWINDOWNAVIGATORADDON_H +#define DIFFSCOPE_COREPLUGIN_PROJECTWINDOWNAVIGATORADDON_H + +#include + +class QStandardItemModel; + +namespace Core { + class ProjectWindowInterface; +} + +namespace Core::Internal { + + class ProjectWindowNavigatorAddOn : public WindowInterfaceAddOn { + Q_OBJECT + Q_PROPERTY(QList projectWindows READ projectWindows NOTIFY projectWindowsChanged) + public: + explicit ProjectWindowNavigatorAddOn(QObject *parent = nullptr); + ~ProjectWindowNavigatorAddOn() override; + + void initialize() override; + void extensionsInitialized() override; + bool delayedInitialize() override; + + QList projectWindows() const; + + Q_INVOKABLE static void raiseWindow(ProjectWindowInterface *windowInterface); + Q_INVOKABLE void navigateToWindow(int step) const; + Q_INVOKABLE void showSwitchToProjectWindowCommand() const; + + Q_SIGNALS: + void projectWindowsChanged(); + + private: + void updateProjectWindows(); + + QList m_projectWindows; + QStandardItemModel *m_quickPickCommandModel; + }; + +} + +#endif //DIFFSCOPE_COREPLUGIN_PROJECTWINDOWNAVIGATORADDON_H \ No newline at end of file diff --git a/src/plugins/coreplugin/internal/addon/recentfileaddon.cpp b/src/plugins/coreplugin/internal/addon/recentfileaddon.cpp new file mode 100644 index 00000000..a19e9ba8 --- /dev/null +++ b/src/plugins/coreplugin/internal/addon/recentfileaddon.cpp @@ -0,0 +1,96 @@ +#include "recentfileaddon.h" + +#include +#include +#include +#include + +#include + +#include +#include + +#include + +#include +#include +#include + +namespace Core::Internal { + + class RecentFilesModel : public QStandardItemModel { + public: + using QStandardItemModel::QStandardItemModel; + + QHash roleNames() const override { + static const QHash m { + {UIShell::USDef::RF_NameRole, "name"}, + {UIShell::USDef::RF_PathRole, "path"}, + }; + return m; + } + }; + + RecentFileAddOn::RecentFileAddOn(QObject *parent) : WindowInterfaceAddOn(parent), m_recentFilesModel(new RecentFilesModel(this)) { + connect(CoreInterface::recentFileCollection(), &RecentFileCollection::recentFilesChanged, this, &RecentFileAddOn::updateRecentFilesModel); + updateRecentFilesModel(); + } + + RecentFileAddOn::~RecentFileAddOn() = default; + + void RecentFileAddOn::initialize() { + auto windowInterface = windowHandle()->cast(); + QQmlComponent component(RuntimeInterface::qmlEngine(), "DiffScope.Core", "RecentFileAddOnActions"); + if (component.isError()) { + qFatal() << component.errorString(); + } + auto o = component.createWithInitialProperties({ + {"addOn", QVariant::fromValue(this)}, + }); + o->setParent(this); + QMetaObject::invokeMethod(o, "registerToContext", windowInterface->actionContext()); + if (auto homeWindowInterface = qobject_cast(windowInterface)) { + auto win = homeWindowInterface->window(); + win->setProperty("recentFilesModel", QVariant::fromValue(m_recentFilesModel)); + connect(win, SIGNAL(openRecentFileRequested(int)), this, SLOT(openRecentFile(int))); + connect(win, SIGNAL(removeRecentFileRequested(int)), this, SLOT(removeRecentFile(int))); + } + } + + void RecentFileAddOn::extensionsInitialized() { + } + + bool RecentFileAddOn::delayedInitialize() { + return WindowInterfaceAddOn::delayedInitialize(); + } + + QAbstractItemModel *RecentFileAddOn::recentFilesModel() const { + return m_recentFilesModel; + } + + void RecentFileAddOn::updateRecentFilesModel() const { + static const QUrl dspxIconUrl{"image://appicon/dspx"}; + static const QUrl nonExistFileIconUrl{"qrc:/diffscope/coreplugin/icons/DocumentError48Regular.svg"}; + m_recentFilesModel->clear(); + for (const auto &file : CoreInterface::recentFileCollection()->recentFiles()) { + QFileInfo fileInfo(file); + auto item = new QStandardItem; + item->setData(fileInfo.baseName(), UIShell::USDef::RF_NameRole); + item->setData(QDir::toNativeSeparators(fileInfo.absoluteFilePath()), UIShell::USDef::RF_PathRole); + item->setData(fileInfo.exists() ? QLocale().toString(fileInfo.lastModified(), QLocale::ShortFormat) : tr("File moved or deleted"), UIShell::USDef::RF_LastModifiedTextRole); + item->setData(QUrl::fromLocalFile(CoreInterface::recentFileCollection()->thumbnailPath(file)), UIShell::USDef::RF_ThumbnailRole); + item->setData(fileInfo.exists() ? dspxIconUrl : nonExistFileIconUrl, UIShell::USDef::RF_IconRole); + item->setData(!fileInfo.exists(), UIShell::USDef::RF_ColorizeRole); + m_recentFilesModel->appendRow(item); + } + } + + void RecentFileAddOn::openRecentFile(int index) { + auto filePath = CoreInterface::recentFileCollection()->recentFiles().at(index); + CoreInterface::openFile(filePath); + } + + void RecentFileAddOn::removeRecentFile(int index) { + CoreInterface::recentFileCollection()->removeRecentFile(CoreInterface::recentFileCollection()->recentFiles().at(index)); + } +} \ No newline at end of file diff --git a/src/plugins/coreplugin/internal/addon/recentfileaddon.h b/src/plugins/coreplugin/internal/addon/recentfileaddon.h new file mode 100644 index 00000000..4a444b0a --- /dev/null +++ b/src/plugins/coreplugin/internal/addon/recentfileaddon.h @@ -0,0 +1,40 @@ +#ifndef DIFFSCOPE_COREPLUGIN_RECENTFILEADDON_H +#define DIFFSCOPE_COREPLUGIN_RECENTFILEADDON_H + +#include + +#include + +class QAbstractItemModel; +class QStandardItemModel; + +namespace Core::Internal { + + class RecentFileAddOn : public WindowInterfaceAddOn { + Q_OBJECT + QML_ELEMENT + QML_UNCREATABLE("") + Q_PROPERTY(QAbstractItemModel *recentFilesModel READ recentFilesModel CONSTANT) + public: + explicit RecentFileAddOn(QObject *parent = nullptr); + ~RecentFileAddOn() override; + + void initialize() override; + void extensionsInitialized() override; + bool delayedInitialize() override; + + QAbstractItemModel *recentFilesModel() const; + + private: + QStandardItemModel *m_recentFilesModel; + + void updateRecentFilesModel() const; + + private Q_SLOTS: + static void openRecentFile(int index); + static void removeRecentFile(int index); + }; + +} + +#endif //DIFFSCOPE_COREPLUGIN_RECENTFILEADDON_H \ No newline at end of file diff --git a/src/plugins/coreplugin/internal/behaviorpreference.cpp b/src/plugins/coreplugin/internal/behaviorpreference.cpp index 3b3a16b1..81e6d326 100644 --- a/src/plugins/coreplugin/internal/behaviorpreference.cpp +++ b/src/plugins/coreplugin/internal/behaviorpreference.cpp @@ -24,6 +24,7 @@ namespace Core::Internal { bool proxyHasAuthentication{}; QString proxyUsername{}; QString proxyPassword{}; + BehaviorPreference::FileOption fileOption{}; bool useCustomFont{}; QString fontFamily{}; QString fontStyle{}; @@ -83,6 +84,8 @@ namespace Core::Internal { emit proxyUsernameChanged(); d->proxyPassword = settings->value("proxyPassword").toString(); emit proxyPasswordChanged(); + d->fileOption = settings->value("fileOption", QVariant::fromValue(FO_LockOpenedFiles | FO_CheckForExternalChangedOnSave)).value(); + emit fileOptionChanged(); d->useCustomFont = settings->value("useCustomFont", false).toBool(); emit useCustomFontChanged(); d->fontFamily = settings->value("fontFamily", QApplication::font().family()).toString(); @@ -128,6 +131,7 @@ namespace Core::Internal { settings->setValue("proxyHasAuthentication", d->proxyHasAuthentication); settings->setValue("proxyUsername", d->proxyUsername); settings->setValue("proxyPassword", d->proxyPassword); + settings->setValue("fileOption", static_cast(d->fileOption)); settings->setValue("useCustomFont", d->useCustomFont); settings->setValue("fontFamily", d->fontFamily); settings->setValue("fontStyle", d->fontStyle); @@ -291,6 +295,17 @@ namespace Core::Internal { d->proxyPassword = proxyPassword; emit m_instance->proxyPasswordChanged(); } + BehaviorPreference::FileOption BehaviorPreference::fileOption() { + M_INSTANCE_D; + return d->fileOption; + } + void BehaviorPreference::setFileOption(FileOption fileOption) { + M_INSTANCE_D; + if (d->fileOption == fileOption) + return; + d->fileOption = fileOption; + emit m_instance->fileOptionChanged(); + } bool BehaviorPreference::useCustomFont() { M_INSTANCE_D; return d->useCustomFont; diff --git a/src/plugins/coreplugin/internal/behaviorpreference.h b/src/plugins/coreplugin/internal/behaviorpreference.h index d4653a96..7d642387 100644 --- a/src/plugins/coreplugin/internal/behaviorpreference.h +++ b/src/plugins/coreplugin/internal/behaviorpreference.h @@ -31,6 +31,7 @@ namespace Core::Internal { Q_PROPERTY(bool proxyHasAuthentication READ proxyHasAuthentication WRITE setProxyHasAuthentication NOTIFY proxyHasAuthenticationChanged) Q_PROPERTY(QString proxyUsername READ proxyUsername WRITE setProxyUsername NOTIFY proxyUsernameChanged) Q_PROPERTY(QString proxyPassword READ proxyPassword WRITE setProxyPassword NOTIFY proxyPasswordChanged) + Q_PROPERTY(BehaviorPreference::FileOption fileOption READ fileOption WRITE setFileOption NOTIFY fileOptionChanged) Q_PROPERTY(bool useCustomFont READ useCustomFont WRITE setUseCustomFont NOTIFY useCustomFontChanged) Q_PROPERTY(QString fontFamily READ fontFamily WRITE setFontFamily NOTIFY fontFamilyChanged) Q_PROPERTY(QString fontStyle READ fontStyle WRITE setFontStyle NOTIFY fontStyleChanged) @@ -117,6 +118,15 @@ namespace Core::Internal { static QString proxyPassword(); static void setProxyPassword(const QString &proxyPassword); + enum FileOptionFlag { + FO_LockOpenedFiles = 0x01, + FO_CheckForExternalChangedOnSave = 0x02, + }; + Q_ENUM(FileOptionFlag) + Q_DECLARE_FLAGS(FileOption, FileOptionFlag) + static FileOption fileOption(); + static void setFileOption(FileOption fileOption); + static bool useCustomFont(); static void setUseCustomFont(bool useCustomFont); @@ -192,6 +202,7 @@ namespace Core::Internal { void proxyHasAuthenticationChanged(); void proxyUsernameChanged(); void proxyPasswordChanged(); + void fileOptionChanged(); void useCustomFontChanged(); void fontFamilyChanged(); void fontStyleChanged(); @@ -219,5 +230,6 @@ namespace Core::Internal { Q_DECLARE_OPERATORS_FOR_FLAGS(Core::Internal::BehaviorPreference::StartupBehavior) Q_DECLARE_OPERATORS_FOR_FLAGS(Core::Internal::BehaviorPreference::UIBehavior) Q_DECLARE_OPERATORS_FOR_FLAGS(Core::Internal::BehaviorPreference::GraphicsBehavior) +Q_DECLARE_OPERATORS_FOR_FLAGS(Core::Internal::BehaviorPreference::FileOption) #endif //DIFFSCOPE_COREPLUGIN_BEHAVIORPREFERENCE_H diff --git a/src/plugins/coreplugin/internal/coreplugin.cpp b/src/plugins/coreplugin/internal/coreplugin.cpp index 0c76bea2..64bbd365 100644 --- a/src/plugins/coreplugin/internal/coreplugin.cpp +++ b/src/plugins/coreplugin/internal/coreplugin.cpp @@ -51,6 +51,7 @@ #include #include #include +#include #include #include #include @@ -61,17 +62,14 @@ #include #include #include +#include +#include +#include static auto getCoreActionExtension() { return QAK_STATIC_ACTION_EXTENSION(core_actions); } -#ifdef Q_OS_MAC -static auto getCoreMacOSActionExtension() { - return QAK_STATIC_ACTION_EXTENSION(core_macos_actions); -} -#endif - namespace Core::Internal { Q_STATIC_LOGGING_CATEGORY(lcCorePlugin, "diffscope.core.coreplugin") @@ -315,9 +313,6 @@ namespace Core::Internal { void CorePlugin::initializeActions() { CoreInterface::actionRegistry()->addExtension(::getCoreActionExtension()); -#ifdef Q_OS_MAC - CoreInterface::actionRegistry()->addExtension(::getCoreMacOSActionExtension()); -#endif // TODO: move to icon manifest later const auto addIcon = [&](const QString &id, const QString &iconName) { @@ -342,6 +337,7 @@ namespace Core::Internal { addIcon("core.edit.paste", "ClipboardPaste16Filled"); addIcon("core.edit.delete", "Delete16Filled"); addIcon("core.panel.properties", "TextBulletListSquareEdit20Filled"); + addIcon("core.panel.metadata", "TextBulletListSquareEdit20Filled"); addIcon("core.panel.plugins", "PuzzlePiece16Filled"); addIcon("core.panel.arrangement", "GanttChart16Filled"); addIcon("core.panel.mixer", "OptionsVertical16Filled"); @@ -357,6 +353,7 @@ namespace Core::Internal { auto sc = CoreInterface::settingCatalog(); auto generalPage = new GeneralPage; generalPage->addPage(new LogPage); + generalPage->addPage(new FileBackupPage); sc->addPage(generalPage); auto appearancePage = new AppearancePage; appearancePage->addPage(new ColorSchemePage); @@ -376,6 +373,11 @@ namespace Core::Internal { ProjectWindowInterfaceRegistry::instance()->attach(); ProjectWindowInterfaceRegistry::instance()->attach(); ProjectWindowInterfaceRegistry::instance()->attach(); + HomeWindowInterfaceRegistry::instance()->attach(); + ProjectWindowInterfaceRegistry::instance()->attach(); + ProjectWindowInterfaceRegistry::instance()->attach(); + HomeWindowInterfaceRegistry::instance()->attach(); + ProjectWindowInterfaceRegistry::instance()->attach(); } void CorePlugin::initializeBehaviorPreference() { diff --git a/src/plugins/coreplugin/internal/settings/filebackuppage.cpp b/src/plugins/coreplugin/internal/settings/filebackuppage.cpp new file mode 100644 index 00000000..fb1cdaa9 --- /dev/null +++ b/src/plugins/coreplugin/internal/settings/filebackuppage.cpp @@ -0,0 +1,77 @@ +#include "filebackuppage.h" + +#include +#include +#include +#include + +#include + +#include + +namespace Core::Internal { + + Q_STATIC_LOGGING_CATEGORY(lcFileBackupPage, "diffscope.core.filebackuppage") + + FileBackupPage::FileBackupPage(QObject *parent) : ISettingPage("core.FileBackup", parent) { + setTitle(tr("File and Backup")); + setDescription(tr("Configure file handling and backup behaviors of %1").arg(QApplication::applicationDisplayName())); + } + + FileBackupPage::~FileBackupPage() { + delete m_widget; + } + + bool FileBackupPage::matches(const QString &word) { + return ISettingPage::matches(word) || widgetMatches(word); + } + + QString FileBackupPage::sortKeyword() const { + return QStringLiteral("FileBackup"); + } + + QObject *FileBackupPage::widget() { + if (m_widget) + return m_widget; + qCDebug(lcFileBackupPage) << "Creating widget"; + QQmlComponent component(RuntimeInterface::qmlEngine(), "DiffScope.Core", "FileBackupPage"); + if (component.isError()) { + qFatal() << component.errorString(); + } + m_widget = component.createWithInitialProperties({{"pageHandle", QVariant::fromValue(this)}}); + m_widget->setParent(this); + return m_widget; + } + + void FileBackupPage::beginSetting() { + qCInfo(lcFileBackupPage) << "Beginning setting"; + widget(); + m_widget->setProperty("fileOption", BehaviorPreference::instance()->property("fileOption")); + qCDebug(lcFileBackupPage) << "fileOption" << m_widget->property("fileOption"); + m_widget->setProperty("started", true); + ISettingPage::beginSetting(); + } + + bool FileBackupPage::accept() { + qCInfo(lcFileBackupPage) << "Accepting"; + qCDebug(lcFileBackupPage) << "fileOption" << m_widget->property("fileOption"); + BehaviorPreference::instance()->setProperty("fileOption", m_widget->property("fileOption")); + BehaviorPreference::instance()->save(); + return ISettingPage::accept(); + } + + void FileBackupPage::endSetting() { + qCInfo(lcFileBackupPage) << "Ending setting"; + m_widget->setProperty("started", false); + ISettingPage::endSetting(); + } + + bool FileBackupPage::widgetMatches(const QString &word) { + widget(); + auto matcher = m_widget->property("matcher").value(); + bool ret = false; + QMetaObject::invokeMethod(matcher, "matches", qReturnArg(ret), word); + return ret; + } + +} \ No newline at end of file diff --git a/src/plugins/coreplugin/internal/settings/filebackuppage.h b/src/plugins/coreplugin/internal/settings/filebackuppage.h new file mode 100644 index 00000000..fe16c526 --- /dev/null +++ b/src/plugins/coreplugin/internal/settings/filebackuppage.h @@ -0,0 +1,31 @@ +#ifndef DIFFSCOPE_COREPLUGIN_FILEBACKUPPAGE_H +#define DIFFSCOPE_COREPLUGIN_FILEBACKUPPAGE_H + +#include + +namespace Core::Internal { + + class CorePlugin; + + class FileBackupPage : public ISettingPage { + Q_OBJECT + public: + explicit FileBackupPage(QObject *parent = nullptr); + ~FileBackupPage() override; + + bool matches(const QString &word) override; + QString sortKeyword() const override; + QObject *widget() override; + void beginSetting() override; + bool accept() override; + void endSetting() override; + + private: + friend class CorePlugin; + bool widgetMatches(const QString &word); + QObject *m_widget{}; + }; + +} + +#endif //DIFFSCOPE_COREPLUGIN_FILEBACKUPPAGE_H \ No newline at end of file diff --git a/src/plugins/coreplugin/internal/settings/generalpage.cpp b/src/plugins/coreplugin/internal/settings/generalpage.cpp index f0b72754..6b5e05dd 100644 --- a/src/plugins/coreplugin/internal/settings/generalpage.cpp +++ b/src/plugins/coreplugin/internal/settings/generalpage.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -74,6 +75,8 @@ namespace Core::Internal { qCDebug(lcGeneralPage) << "proxyUsername" << m_widget->property("proxyUsername"); m_widget->setProperty("proxyPassword", BehaviorPreference::instance()->property("proxyPassword")); qCDebug(lcGeneralPage) << "proxyPassword" << m_widget->property("proxyPassword"); + m_widget->setProperty("shouldStoreGeometry", CoreInterface::windowSystem()->shouldStoreGeometry()); + qCDebug(lcGeneralPage) << "shouldStoreGeometry" << m_widget->property("shouldStoreGeometry"); m_widget->setProperty("started", true); ISettingPage::beginSetting(); } @@ -107,6 +110,8 @@ namespace Core::Internal { BehaviorPreference::instance()->setProperty("proxyUsername", m_widget->property("proxyUsername")); qCDebug(lcGeneralPage) << "proxyPassword" << m_widget->property("proxyPassword"); BehaviorPreference::instance()->setProperty("proxyPassword", m_widget->property("proxyPassword")); + qCDebug(lcGeneralPage) << "shouldStoreGeometry" << m_widget->property("shouldStoreGeometry"); + CoreInterface::windowSystem()->setShouldStoreGeometry(m_widget->property("shouldStoreGeometry").toBool()); BehaviorPreference::instance()->save(); if (promptRestartForLanguage) { qCInfo(lcGeneralPage) << "Language changed" << m_widget->property("localeName").toString() << QLocale().name(); diff --git a/src/plugins/coreplugin/internal/workspace/projectwindowworkspacemanager.cpp b/src/plugins/coreplugin/internal/workspace/projectwindowworkspacemanager.cpp index dae1828f..b7d622f4 100644 --- a/src/plugins/coreplugin/internal/workspace/projectwindowworkspacemanager.cpp +++ b/src/plugins/coreplugin/internal/workspace/projectwindowworkspacemanager.cpp @@ -46,6 +46,7 @@ namespace Core::Internal { layout.setViewSpec(ProjectWindowWorkspaceLayout::LeftTop, { { {"core.panel.properties", true}, + {"core.panel.metadata", true}, {"core.panel.plugins", true}, }, 400, 400, 0 }); diff --git a/src/plugins/coreplugin/qml/actions/GlobalActions.qml b/src/plugins/coreplugin/qml/actions/GlobalActions.qml index 827db140..c685312a 100644 --- a/src/plugins/coreplugin/qml/actions/GlobalActions.qml +++ b/src/plugins/coreplugin/qml/actions/GlobalActions.qml @@ -15,14 +15,27 @@ ActionCollection { ActionItem { actionId: "core.file.new" Action { - onTriggered: CoreInterface.newFile() + onTriggered: (o) => { + CoreInterface.newFile(o?.Window.window ?? null) + } } } ActionItem { - actionId: "core.file.open" + actionId: "core.file.newFromTemplate" Action { + onTriggered: (o) => { + CoreInterface.newFileFromTemplate("", o?.Window.window ?? null) + } + } + } + ActionItem { + actionId: "core.file.open" + Action { + onTriggered: (o) => { + CoreInterface.openFile("", o?.Window.window ?? null) + } } } @@ -73,27 +86,6 @@ ActionCollection { } } - ActionItem { - actionId: "core.window.nextProjectWindow" - Action { - - } - } - - ActionItem { - actionId: "core.window.previousProjectWindow" - Action { - - } - } - - ActionItem { - actionId: "core.window.projectWindows" - Menu { - - } - } - ActionItem { actionId: "core.aboutApp" Action { diff --git a/src/plugins/coreplugin/qml/actions/ProjectActions.qml b/src/plugins/coreplugin/qml/actions/ProjectActions.qml index fbe1060b..abc22661 100644 --- a/src/plugins/coreplugin/qml/actions/ProjectActions.qml +++ b/src/plugins/coreplugin/qml/actions/ProjectActions.qml @@ -16,6 +16,29 @@ ActionCollection { required property ProjectWindowInterface windowHandle property Window window: windowHandle?.window ?? null + ActionItem { + actionId: "core.file.save" + Action { + enabled: d.windowHandle.projectDocumentContext.fileLocker + onTriggered: Qt.callLater(() => d.windowHandle.save()) + } + } + + ActionItem { + actionId: "core.file.saveAs" + Action { + enabled: d.windowHandle.projectDocumentContext.fileLocker + onTriggered: Qt.callLater(() => d.windowHandle.saveAs()) + } + } + + ActionItem { + actionId: "core.file.saveCopy" + Action { + onTriggered: Qt.callLater(() => d.windowHandle.saveCopy()) + } + } + ActionItem { actionId: "core.statusText" Label { diff --git a/src/plugins/coreplugin/qml/actions/ProjectWindowNavigatorAddOnActions.qml b/src/plugins/coreplugin/qml/actions/ProjectWindowNavigatorAddOnActions.qml new file mode 100644 index 00000000..ca02af82 --- /dev/null +++ b/src/plugins/coreplugin/qml/actions/ProjectWindowNavigatorAddOnActions.qml @@ -0,0 +1,71 @@ +import QtQml +import QtQml.Models +import QtQuick +import QtQuick.Controls + +import SVSCraft.UIComponents +import SVSCraft.UIComponents.impl + +import QActionKit + +import DiffScope.UIShell +import DiffScope.Core + +ActionCollection { + id: d + required property QtObject addOn + + ActionItem { + actionId: "core.window.nextProjectWindow" + Action { + enabled: d.addOn.projectWindows.length !== 0 + onTriggered: d.addOn.navigateToWindow(1) + } + } + + ActionItem { + actionId: "core.window.previousProjectWindow" + Action { + enabled: d.addOn.projectWindows.length !== 0 + onTriggered: d.addOn.navigateToWindow(-1) + } + } + + ActionItem { + actionId: "core.window.projectWindows" + Menu { + id: menu + Instantiator { + model: DelegateModel { + model: d.addOn.projectWindows + delegate: Action { + required property int index + required property ProjectWindowInterface modelData + text: (index < 9 ? `&${Qt.locale().toString(index + 1)}. ` : "") + modelData.window.documentName + checkable: true + checked: d.addOn.windowHandle === modelData + onTriggered: () => { + d.addOn.raiseWindow(modelData) + checked = d.addOn.windowHandle === modelData + } + } + } + onObjectAdded: (index, object) => { + menu.insertAction(index, object) + } + onObjectRemoved: (index, object) => { + menu.removeAction(object) + } + } + } + } + + ActionItem { + actionId: "core.window.switchToProjectWindow" + Action { + enabled: d.addOn.projectWindows.length !== 0 + onTriggered: d.addOn.showSwitchToProjectWindowCommand() + } + } + +} \ No newline at end of file diff --git a/src/plugins/coreplugin/qml/actions/RecentFileAddOnActions.qml b/src/plugins/coreplugin/qml/actions/RecentFileAddOnActions.qml new file mode 100644 index 00000000..1f92b0b9 --- /dev/null +++ b/src/plugins/coreplugin/qml/actions/RecentFileAddOnActions.qml @@ -0,0 +1,51 @@ +import QtQml +import QtQml.Models +import QtQuick +import QtQuick.Controls + +import SVSCraft.UIComponents +import SVSCraft.UIComponents.impl + +import QActionKit + +import DiffScope.UIShell +import DiffScope.Core + +ActionCollection { + id: d + + required property RecentFileAddOn addOn + + ActionItem { + actionId: "core.file.openRecentFile" + Menu { + id: recentFilesMenu + Instantiator { + model: DelegateModel { + model: d.addOn.recentFilesModel + delegate: Action { + required property int index + required property var modelData + text: `${index < 9 ? "&" + Qt.locale().toString(index + 1) + ". " : ""}${modelData.name} [${modelData.path}]` + onTriggered: (o) => { + Qt.callLater(() => CoreInterface.openFile(modelData.path, o.Window.window)) + } + } + } + onObjectAdded: (index, object) => { + recentFilesMenu.insertAction(index, object) + } + onObjectRemoved: (index, object) => { + recentFilesMenu.removeAction(object) + } + } + MenuSeparator { + } + Action { + text: "Clear Recent Files" + onTriggered: CoreInterface.recentFileCollection.clearRecentFile() + } + } + } + +} \ No newline at end of file diff --git a/src/plugins/coreplugin/qml/dialogs/PluginDialog.qml b/src/plugins/coreplugin/qml/dialogs/PluginDialog.qml index 32962080..dd368294 100644 --- a/src/plugins/coreplugin/qml/dialogs/PluginDialog.qml +++ b/src/plugins/coreplugin/qml/dialogs/PluginDialog.qml @@ -1,6 +1,8 @@ import QtQml import QtQuick +import ChorusKit.AppCore + import DiffScope.UIShell import DiffScope.Core @@ -10,6 +12,10 @@ Window { flags: Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint | Qt.WindowCloseButtonHint modality: Qt.ApplicationModal title: qsTr("Plugins") + + WindowSystem.windowSystem: CoreInterface.windowSystem + WindowSystem.id: "org.diffscope.core.plugindialog" + signal finished() onClosing: finished() PluginView { diff --git a/src/plugins/coreplugin/qml/dialogs/SettingDialog.qml b/src/plugins/coreplugin/qml/dialogs/SettingDialog.qml index 3791d857..31939a5a 100644 --- a/src/plugins/coreplugin/qml/dialogs/SettingDialog.qml +++ b/src/plugins/coreplugin/qml/dialogs/SettingDialog.qml @@ -10,6 +10,10 @@ import DiffScope.Core SettingDialog { id: dialog + + WindowSystem.windowSystem: CoreInterface.windowSystem + WindowSystem.id: "org.diffscope.core.settingdialog" + settingCatalog: CoreInterface.settingCatalog Settings { settings: RuntimeInterface.settings diff --git a/src/plugins/coreplugin/qml/panels/MetadataPanel.qml b/src/plugins/coreplugin/qml/panels/MetadataPanel.qml new file mode 100644 index 00000000..468bf112 --- /dev/null +++ b/src/plugins/coreplugin/qml/panels/MetadataPanel.qml @@ -0,0 +1,138 @@ +import QtQml +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SVSCraft +import SVSCraft.UIComponents + +import DiffScope.UIShell + +QtObject { + id: d + required property QtObject addOn + readonly property QtObject fileLocker: d.addOn?.windowHandle.projectDocumentContext.fileLocker ?? null + + // TODO add these components to SVSCraft + component SelectableLabel: TextEdit { + readOnly: true + color: Theme.foregroundColor(ThemedItem.foregroundLevel) + Accessible.role: Accessible.StaticText + Accessible.name: text + selectionColor: Theme.accentColor + } + component InfoCard: Frame { + id: card + Layout.fillWidth: true + padding: 8 + property string title: "" + property string text: "" + background: Rectangle { + color: Theme.backgroundPrimaryColor + border.width: 1 + border.color: Theme.borderColor + radius: 4 + } + ColumnLayout { + spacing: 4 + anchors.fill: parent + Label { + text: card.title + font.pixelSize: 14 + font.weight: Font.DemiBold + } + SelectableLabel { + Layout.fillWidth: true + ThemedItem.foregroundLevel: SVS.FL_Secondary + text: card.text + wrapMode: Text.Wrap + } + } + } + + readonly property Component metadataPanelComponent: ActionDockingPane { + id: pane + header: Item { + anchors.fill: parent + ToolButton { + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + icon.source: "qrc:/diffscope/coreplugin/icons/Edit16Filled.svg" + text: qsTr("Edit") + onClicked: () => { + + } + } + } + ColumnLayout { + anchors.fill: parent + anchors.margins: 16 + spacing: 0 + ScrollView { + id: scrollView + Layout.fillWidth: true + Layout.fillHeight: true + contentWidth: availableWidth + ColumnLayout { + width: scrollView.width + ColumnLayout { + Layout.fillWidth: true + Layout.bottomMargin: 16 + spacing: 16 + Frame { + Layout.fillWidth: true + padding: 8 + background: Rectangle { + color: Theme.backgroundPrimaryColor + border.width: 1 + border.color: Theme.borderColor + radius: 4 + } + RowLayout { + spacing: 4 + anchors.fill: parent + ColumnLayout { + spacing: 4 + Layout.fillWidth: true + Label { + text: qsTr("Path") + font.pixelSize: 14 + font.weight: Font.DemiBold + } + SelectableLabel { + Layout.fillWidth: true + ThemedItem.foregroundLevel: SVS.FL_Secondary + text: d.fileLocker?.path || qsTr("Unspecified") + wrapMode: Text.Wrap + } + } + ToolButton { + icon.source: "qrc:/diffscope/coreplugin/icons/Open16Filled.svg" + display: AbstractButton.IconOnly + text: qsTr("Reveal in %1").arg(DesktopServices.fileManagerName) + enabled: Boolean(d.fileLocker?.path) + onClicked: () => { + DesktopServices.reveal(d.fileLocker.path) + } + } + } + + } + InfoCard { + title: qsTr("Title") + text: "" + } + InfoCard { + title: qsTr("Author") + text: "" + } + InfoCard { + title: qsTr("Cent Shift") + text: "" + } + } + } + } + } + } +} \ No newline at end of file diff --git a/src/plugins/coreplugin/qml/settings/FileBackupPage.qml b/src/plugins/coreplugin/qml/settings/FileBackupPage.qml new file mode 100644 index 00000000..98c5e666 --- /dev/null +++ b/src/plugins/coreplugin/qml/settings/FileBackupPage.qml @@ -0,0 +1,73 @@ +import QtQml +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SVSCraft +import SVSCraft.UIComponents + +import DiffScope.Core + +ScrollView { + id: page + + required property QtObject pageHandle + property bool started: false + property int fileOption + + onFileOptionChanged: if (started) pageHandle.markDirty() + + anchors.fill: parent + contentWidth: availableWidth + + readonly property TextMatcher matcher: TextMatcher {} + + ColumnLayout { + width: page.width + ColumnLayout { + Layout.fillWidth: true + Layout.margins: 12 + spacing: 32 + GroupBox { + title: qsTr("File") + TextMatcherItem on title { matcher: page.matcher } + Layout.fillWidth: true + ColumnLayout { + anchors.fill: parent + CheckBox { + text: qsTr("Lock opened files") + TextMatcherItem on text { matcher: page.matcher } + checked: page.fileOption & BehaviorPreference.FO_LockOpenedFiles + visible: Qt.platform.os === "windows" + onClicked: () => { + if (checked) { + page.fileOption |= BehaviorPreference.FO_LockOpenedFiles + } else { + page.fileOption &= ~BehaviorPreference.FO_LockOpenedFiles + } + } + } + Label { + ThemedItem.foregroundLevel: SVS.FL_Secondary + Layout.fillWidth: true + wrapMode: Text.Wrap + visible: Qt.platform.os === "windows" + text: qsTr("Locking an open file prevents it from being modified by other programs. Change to this option will take effect only for projects opened after the change") + } + CheckBox { + text: qsTr("Check for external modifications when saving a file") + TextMatcherItem on text { matcher: page.matcher } + checked: page.fileOption & BehaviorPreference.FO_CheckForExternalChangedOnSave + onClicked: () => { + if (checked) { + page.fileOption |= BehaviorPreference.FO_CheckForExternalChangedOnSave + } else { + page.fileOption &= ~BehaviorPreference.FO_CheckForExternalChangedOnSave + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/src/plugins/coreplugin/qml/settings/GeneralPage.qml b/src/plugins/coreplugin/qml/settings/GeneralPage.qml index 5a41c54e..e8dcb14c 100644 --- a/src/plugins/coreplugin/qml/settings/GeneralPage.qml +++ b/src/plugins/coreplugin/qml/settings/GeneralPage.qml @@ -26,8 +26,7 @@ ScrollView { property bool proxyHasAuthentication property string proxyUsername property string proxyPassword - property bool autoCheckForUpdates - property int updateOption + property bool shouldStoreGeometry onStartupBehaviorChanged: if (started) pageHandle.markDirty() onUseSystemLanguageChanged: if (started) pageHandle.markDirty() @@ -41,8 +40,7 @@ ScrollView { onProxyHasAuthenticationChanged: if (started) pageHandle.markDirty() onProxyUsernameChanged: if (started) pageHandle.markDirty() onProxyPasswordChanged: if (started) pageHandle.markDirty() - onAutoCheckForUpdatesChanged: if (started) pageHandle.markDirty() - onUpdateOptionChanged: if (started) pageHandle.markDirty() + onShouldStoreGeometryChanged: if (started) pageHandle.markDirty() anchors.fill: parent contentWidth: availableWidth @@ -211,6 +209,20 @@ ScrollView { } } } + GroupBox { + title: qsTr("Window") + TextMatcherItem on title { matcher: page.matcher } + Layout.fillWidth: true + ColumnLayout { + anchors.fill: parent + CheckBox { + text: qsTr("Memorize window position and size") + TextMatcherItem on text { matcher: page.matcher } + checked: page.shouldStoreGeometry + onClicked: page.shouldStoreGeometry = checked + } + } + } GroupBox { title: qsTr("Find Actions") TextMatcherItem on title { matcher: page.matcher } diff --git a/src/plugins/coreplugin/qml/windows/HomeWindow.qml b/src/plugins/coreplugin/qml/windows/HomeWindow.qml index 5df8821d..39717f92 100644 --- a/src/plugins/coreplugin/qml/windows/HomeWindow.qml +++ b/src/plugins/coreplugin/qml/windows/HomeWindow.qml @@ -27,8 +27,12 @@ HomeWindow { return "qrc:/diffscope/coreplugin/logos/BannerLight.png"; } } + + WindowSystem.windowSystem: CoreInterface.windowSystem + WindowSystem.id: "org.diffscope.core.homewindow" + onNewFileRequested: () => { - windowHandle.triggerAction("core.file.new") + windowHandle.triggerAction("core.file.new", homeWindow.contentItem) } navigationActionsModel: ObjectModel { property ActionInstantiator instantiator: ActionInstantiator { @@ -54,15 +58,15 @@ HomeWindow { } } } - macosMenusModel: ObjectModel { + menusModel: ObjectModel { property ActionInstantiator instantiator: ActionInstantiator { - actionId: homeWindow.isMacOS ? "core.homeMenu" : "" + actionId: "core.homeMenu" context: homeWindow.windowHandle.actionContext onObjectAdded: (index, object) => { - homeWindow.macosMenusModel.insert(index, object) + homeWindow.menusModel.insert(index, object) } onObjectRemoved: (index, object) => { - homeWindow.macosMenusModel.remove(index) + homeWindow.menusModel.remove(index) } } } diff --git a/src/plugins/coreplugin/qml/windows/ProjectWindow.qml b/src/plugins/coreplugin/qml/windows/ProjectWindow.qml index a11e1d4a..82dbe0e2 100644 --- a/src/plugins/coreplugin/qml/windows/ProjectWindow.qml +++ b/src/plugins/coreplugin/qml/windows/ProjectWindow.qml @@ -9,6 +9,8 @@ import SVSCraft.UIComponents import QActionKit +import ChorusKit.AppCore + import DiffScope.UIShell import DiffScope.Core @@ -18,9 +20,16 @@ ProjectWindow { required property ProjectWindowInterface windowHandle frameless: BehaviorPreference.uiBehavior & BehaviorPreference.UB_Frameless useSeparatedMenu: !(BehaviorPreference.uiBehavior & BehaviorPreference.UB_MergeMenuAndTitleBar) + documentName: [ + ((BehaviorPreference.uiBehavior & BehaviorPreference.UB_FullPath) ? windowHandle.projectDocumentContext.fileLocker?.path : windowHandle.projectDocumentContext.fileLocker?.entryName) || qsTr("Untitled"), + windowHandle.projectDocumentContext.fileLocker.fileModifiedSinceLastSave ? "Modified Externally" : "" + ].filter(x => x).join(" - ") icon: "image://appicon/dspx" + WindowSystem.windowSystem: CoreInterface.windowSystem + WindowSystem.id: "org.diffscope.core.projectwindow" + signal beforeTerminated() onClosing: () => { diff --git a/src/plugins/coreplugin/res/core_actions.xml b/src/plugins/coreplugin/res/core_actions.xml index 11edc8a9..030ce897 100644 --- a/src/plugins/coreplugin/res/core_actions.xml +++ b/src/plugins/coreplugin/res/core_actions.xml @@ -16,8 +16,12 @@ + + + + @@ -128,6 +132,7 @@ + @@ -155,6 +160,7 @@ + @@ -163,8 +169,14 @@ + + + + + + @@ -350,6 +362,7 @@ + @@ -359,6 +372,24 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/plugins/coreplugin/res/core_macos_actions.xml b/src/plugins/coreplugin/res/core_macos_actions.xml deleted file mode 100644 index beb4a79d..00000000 --- a/src/plugins/coreplugin/res/core_macos_actions.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - 1.0 - org.diffscope.core.macos - - core.mainMenu - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/plugins/coreplugin/res/icons/DocumentError48Regular.svg b/src/plugins/coreplugin/res/icons/DocumentError48Regular.svg new file mode 100644 index 00000000..59522ce4 --- /dev/null +++ b/src/plugins/coreplugin/res/icons/DocumentError48Regular.svg @@ -0,0 +1 @@ + diff --git a/src/plugins/coreplugin/res/icons/Open16Filled.svg b/src/plugins/coreplugin/res/icons/Open16Filled.svg new file mode 100644 index 00000000..80016d77 --- /dev/null +++ b/src/plugins/coreplugin/res/icons/Open16Filled.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/plugins/coreplugin/windows/projectwindowinterface.cpp b/src/plugins/coreplugin/windows/projectwindowinterface.cpp index 345d76ed..2b6e2990 100644 --- a/src/plugins/coreplugin/windows/projectwindowinterface.cpp +++ b/src/plugins/coreplugin/windows/projectwindowinterface.cpp @@ -5,12 +5,20 @@ #include #include #include +#include +#include +#include +#include +#include #include +#include #include #include +#include +#include #include #include @@ -20,11 +28,31 @@ #include #include #include +#include +#include + namespace Core { + Q_LOGGING_CATEGORY(lcProjectWindow, "diffscope.core.projectwindow") + static ProjectWindowInterface *m_instance = nullptr; + class MessageBoxDialogDoneListener : public QObject { + Q_OBJECT + public: + inline explicit MessageBoxDialogDoneListener(QEventLoop *eventLoop) : eventLoop(eventLoop) { + } + + public slots: + void done(const QVariant &id) const { + eventLoop->exit(id.toInt()); + } + + private: + QEventLoop *eventLoop; + }; + class ProjectWindowInterfacePrivate { Q_DECLARE_PUBLIC(ProjectWindowInterface) public: @@ -32,6 +60,7 @@ namespace Core { Internal::NotificationManager *notificationManager; ProjectTimeline *projectTimeline; EditActionsHandlerRegistry *mainEditActionsHandlerRegistry; + ProjectDocumentContext *projectDocumentContext; void init() { Q_Q(ProjectWindowInterface); initActionContext(); @@ -53,16 +82,87 @@ namespace Core { o->setParent(q); QMetaObject::invokeMethod(o, "registerToContext", actionContext); } + + QString promptSaveDspxFile() const { + auto settings = RuntimeInterface::settings(); + settings->beginGroup(ProjectWindowInterface::staticMetaObject.className()); + auto defaultSaveDir = projectDocumentContext->fileLocker() && !projectDocumentContext->fileLocker()->path().isEmpty() ? + projectDocumentContext->fileLocker()->path() : + settings->value(QStringLiteral("defaultSaveDir"), QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)).toString(); + settings->endGroup(); + + auto path = QFileDialog::getSaveFileName( + nullptr, + {}, + defaultSaveDir, + CoreInterface::dspxFileFilter(true) + ); + if (path.isEmpty()) + return {}; + + settings->beginGroup(ProjectWindowInterface::staticMetaObject.className()); + settings->setValue(QStringLiteral("defaultSaveDir"), QFileInfo(path).absolutePath()); + settings->endGroup(); + + return path; + } + + void updateRecentFile() const { + Q_Q(const ProjectWindowInterface); + auto win = q->window(); + auto pixmap = win->screen()->grabWindow(win->winId()); + CoreInterface::recentFileCollection()->addRecentFile(projectDocumentContext->fileLocker()->path(), pixmap); + } + + enum ExternalChangeOperation { + SaveAs, + Overwrite, + Cancel = SVS::SVSCraft::Cancel + }; + + ExternalChangeOperation promptFileExternalChange() const { + Q_Q(const ProjectWindowInterface); + QQmlComponent component(RuntimeInterface::qmlEngine(), "SVSCraft.UIComponents", "MessageBoxDialog"); + std::unique_ptr mb(qobject_cast(component.createWithInitialProperties({ + {"text", Core::ProjectWindowInterface::tr("File Modified Externally")}, + {"informativeText", Core::ProjectWindowInterface::tr("The file has been modified by another program since it was last saved.\n\nDo you want to save as a new file or overwrite it?")}, + {"buttons", QVariantList { + QVariantMap { + {"id", SaveAs}, {"text", Core::ProjectWindowInterface::tr("Save As...")}, + }, + QVariantMap { + {"id", Overwrite}, {"text", Core::ProjectWindowInterface::tr("Overwrite")}, + }, + SVS::SVSCraft::Cancel, + }}, + {"primaryButton", SaveAs}, + {"icon", SVS::SVSCraft::Warning}, + {"transientParent", QVariant::fromValue(q->window())} + }))); + Q_ASSERT(mb); + QEventLoop eventLoop; + MessageBoxDialogDoneListener listener(&eventLoop); + QObject::connect(mb.get(), SIGNAL(done(QVariant)), &listener, SLOT(done(QVariant))); + mb->show(); + return static_cast(eventLoop.exec()); + } + }; ProjectWindowInterface *ProjectWindowInterface::instance() { return m_instance; } + ProjectTimeline *ProjectWindowInterface::projectTimeline() const { Q_D(const ProjectWindowInterface); return d->projectTimeline; } + ProjectDocumentContext *ProjectWindowInterface::projectDocumentContext() const { + Q_D(const ProjectWindowInterface); + return d->projectDocumentContext; + } + EditActionsHandlerRegistry * ProjectWindowInterface::mainEditActionsHandlerRegistry() const { Q_D(const ProjectWindowInterface); return d->mainEditActionsHandlerRegistry; @@ -81,6 +181,49 @@ namespace Core { connect(message, &NotificationMessage::closed, message, &QObject::deleteLater); sendNotification(message, mode); } + + bool ProjectWindowInterface::save() { + Q_D(ProjectWindowInterface); + if (!d->projectDocumentContext->fileLocker() || d->projectDocumentContext->fileLocker()->path().isEmpty()) + return saveAs(); + if (d->projectDocumentContext->fileLocker()->isFileModifiedSinceLastSave() && (Internal::BehaviorPreference::fileOption() & Internal::BehaviorPreference::FO_CheckForExternalChangedOnSave)) { + auto op = d->promptFileExternalChange(); + if (op == ProjectWindowInterfacePrivate::Cancel) { + return false; + } + if (op == ProjectWindowInterfacePrivate::SaveAs) { + return saveAs(); + } + } + bool isSuccess = d->projectDocumentContext->save(window()); + if (isSuccess) { + d->updateRecentFile(); + return true; + } + return false; + } + + bool ProjectWindowInterface::saveAs() { + Q_D(ProjectWindowInterface); + auto path = d->promptSaveDspxFile(); + if (path.isEmpty()) + return false; + bool isSuccess = d->projectDocumentContext->saveAs(path, window()); + if (isSuccess) { + d->updateRecentFile(); + return true; + } + return false; + } + + bool ProjectWindowInterface::saveCopy() { + Q_D(ProjectWindowInterface); + auto path = d->promptSaveDspxFile(); + if (path.isEmpty()) + return false; + return d->projectDocumentContext->saveCopy(path, window()); + } + QWindow *ProjectWindowInterface::createWindow(QObject *parent) const { Q_D(const ProjectWindowInterface); QQmlComponent component(RuntimeInterface::qmlEngine(), "DiffScope.Core", "ProjectWindow"); @@ -95,8 +238,10 @@ namespace Core { SVS::StatusTextContext::setContextHelpContext(win, new SVS::StatusTextContext(win)); return win; } - ProjectWindowInterface::ProjectWindowInterface(QObject *parent) : ProjectWindowInterface(*new ProjectWindowInterfacePrivate, parent) { + ProjectWindowInterface::ProjectWindowInterface(ProjectDocumentContext *projectDocumentContext, QObject *parent) : ProjectWindowInterface(*new ProjectWindowInterfacePrivate, parent) { + Q_D(ProjectWindowInterface); m_instance = this; + d->projectDocumentContext = projectDocumentContext; } ProjectWindowInterface::ProjectWindowInterface(ProjectWindowInterfacePrivate &d, QObject *parent) : ActionWindowInterfaceBase(parent), d_ptr(&d) { d.q_ptr = this; @@ -105,10 +250,12 @@ namespace Core { ProjectWindowInterface::~ProjectWindowInterface() { m_instance = nullptr; } + ProjectWindowInterfaceRegistry *ProjectWindowInterfaceRegistry::instance() { static ProjectWindowInterfaceRegistry reg; return ® } } +#include "projectwindowinterface.moc" #include "moc_projectwindowinterface.cpp" diff --git a/src/plugins/coreplugin/windows/projectwindowinterface.h b/src/plugins/coreplugin/windows/projectwindowinterface.h index 2a08ee90..642d9af3 100644 --- a/src/plugins/coreplugin/windows/projectwindowinterface.h +++ b/src/plugins/coreplugin/windows/projectwindowinterface.h @@ -16,12 +16,18 @@ namespace QAK { class QAbstractItemModel; class QJSValue; +namespace QDspx { + class Model; +} + namespace Core { class NotificationMessage; class ProjectTimeline; class EditActionsHandlerRegistry; + class ProjectDocumentContext; + class ProjectWindowInterfacePrivate; class CORE_EXPORT ProjectWindowInterface : public ActionWindowInterfaceBase { @@ -29,6 +35,7 @@ namespace Core { QML_ELEMENT QML_UNCREATABLE("") Q_PROPERTY(ProjectTimeline *projectTimeline READ projectTimeline CONSTANT) + Q_PROPERTY(ProjectDocumentContext *projectDocumentContext READ projectDocumentContext CONSTANT) Q_PROPERTY(EditActionsHandlerRegistry *mainEditActionsHandlerRegistry READ mainEditActionsHandlerRegistry CONSTANT) Q_DECLARE_PRIVATE(ProjectWindowInterface) public: @@ -36,6 +43,8 @@ namespace Core { ProjectTimeline *projectTimeline() const; + ProjectDocumentContext *projectDocumentContext() const; + EditActionsHandlerRegistry *mainEditActionsHandlerRegistry() const; enum NotificationBubbleMode { @@ -47,10 +56,14 @@ namespace Core { Q_INVOKABLE void sendNotification(NotificationMessage *message, NotificationBubbleMode mode = NormalBubble); Q_INVOKABLE void sendNotification(SVS::SVSCraft::MessageBoxIcon icon, const QString &title, const QString &text, NotificationBubbleMode mode = NormalBubble); + Q_INVOKABLE bool save(); + Q_INVOKABLE bool saveAs(); + Q_INVOKABLE bool saveCopy(); + protected: QWindow *createWindow(QObject *parent) const override; - explicit ProjectWindowInterface(QObject *parent = nullptr); + explicit ProjectWindowInterface(ProjectDocumentContext *projectDocumentContext, QObject *parent = nullptr); explicit ProjectWindowInterface(ProjectWindowInterfacePrivate &d, QObject *parent = nullptr); ~ProjectWindowInterface() override; diff --git a/src/plugins/maintenance/CMakeLists.txt b/src/plugins/maintenance/CMakeLists.txt index 94475718..5e4bdd5c 100644 --- a/src/plugins/maintenance/CMakeLists.txt +++ b/src/plugins/maintenance/CMakeLists.txt @@ -68,12 +68,7 @@ if(QT_KNOWN_POLICY_QTP0004) qt_policy(SET QTP0004 NEW) endif() -file(GLOB_RECURSE _qml_files_abs *.qml *.js *.mjs) -set(_qml_files) -foreach(_file IN LISTS _qml_files_abs) - file(RELATIVE_PATH _rel_path ${CMAKE_CURRENT_SOURCE_DIR} ${_file}) - list(APPEND _qml_files ${_rel_path}) -endforeach() +file(GLOB_RECURSE _qml_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.qml *.js *.mjs) qt_add_qml_module(${PROJECT_NAME} URI DiffScope.Maintenance diff --git a/src/plugins/welcomewizard/CMakeLists.txt b/src/plugins/welcomewizard/CMakeLists.txt index a7097166..46a318d7 100644 --- a/src/plugins/welcomewizard/CMakeLists.txt +++ b/src/plugins/welcomewizard/CMakeLists.txt @@ -66,12 +66,7 @@ if(QT_KNOWN_POLICY_QTP0004) qt_policy(SET QTP0004 NEW) endif() -file(GLOB_RECURSE _qml_files_abs *.qml *.js *.mjs) -set(_qml_files) -foreach(_file IN LISTS _qml_files_abs) - file(RELATIVE_PATH _rel_path ${CMAKE_CURRENT_SOURCE_DIR} ${_file}) - list(APPEND _qml_files ${_rel_path}) -endforeach() +file(GLOB_RECURSE _qml_files RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.qml *.js *.mjs) qt_add_qml_module(${PROJECT_NAME} URI DiffScope.WelcomeWizard diff --git a/src/share/install.cmake b/src/share/install.cmake index f115c74e..3695ec4f 100644 --- a/src/share/install.cmake +++ b/src/share/install.cmake @@ -70,4 +70,11 @@ qm_deploy_directory(${CMAKE_INSTALL_PREFIX} if(MSVC AND APPLICATION_INSTALL_MSVC_RUNTIME) set(CMAKE_INSTALL_SYSTEM_RUNTIME_DESTINATION "${OUTPUT_DIR}") include(InstallRequiredSystemLibraries) -endif() \ No newline at end of file +endif() + +if(APPLE) + install(DIRECTORY ${CMAKE_INSTALL_PREFIX}/qml/ + DESTINATION ${CK_INSTALL_QML_DIR} + USE_SOURCE_PERMISSIONS + ) +endif()