diff --git a/.appveyor.yml b/.appveyor.yml index f4d317bc78c6f..4a4b29d153ceb 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -5,6 +5,7 @@ configuration: Release clone_folder: c:\stellarium # set clone depth clone_depth: 5 +shallow_clone: true # Do not build feature branch with open Pull Requests skip_branch_with_pr: true # Do not build on tags @@ -32,20 +33,20 @@ environment: QT_VERSION_MAJOR: 6 QT_BASEDIR: C:\Qt\6.8\msvc2022_64 CMAKE_GENERATOR: Visual Studio 17 2022 - CMAKE_ARGS: -A x64 + CMAKE_ARGS: -A x64 -DSCM_SHOULD_ENABLE_CONVERTER=ON exiv2url: https://github.com/Exiv2/exiv2/releases/download/v0.28.0/exiv2-0.28.0-2019msvc64.zip exiv2baseName: exiv2-0.28.0-2019msvc64 - scConverterEnabled: 1 + scConverterEnabled: true - BUILD_NAME: Win64 Qt5.12 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 VSPATH: C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\SDK\ScopeCppSDK\VC\bin QT_VERSION_MAJOR: 5 QT_BASEDIR: C:\Qt\5.12\msvc2017_64 CMAKE_GENERATOR: Visual Studio 15 2017 Win64 - CMAKE_ARGS: + CMAKE_ARGS: -- exiv2url: https://github.com/10110111/exiv2/releases/download/ver0.28.0-final/exiv2-0.28.0-2017msvc64.zip exiv2baseName: exiv2-0.28.0-2017msvc64 - scConverterEnabled: 0 + scConverterEnabled: false # - BUILD_NAME: Win64 Qt5.15 # APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022 # VSPATH: C:\Program Files\Microsoft Visual Studio\2022\Community\SDK\ScopeCppSDK\vc15\VC\bin @@ -55,7 +56,7 @@ environment: # CMAKE_ARGS: -A x64 # exiv2url: https://github.com/Exiv2/exiv2/releases/download/v0.28.0/exiv2-0.28.0-2019msvc64.zip # exiv2baseName: exiv2-0.28.0-2019msvc64 -# scConverterEnabled: 0 +# scConverterEnabled: false - BUILD_NAME: Arm64 Qt6.5 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022 VSPATH: C:\Program Files\Microsoft Visual Studio\2022\Community\SDK\ScopeCppSDK\vc15\VC\bin @@ -63,7 +64,7 @@ environment: QT_BASEDIR: C:\Qt\6.5\msvc2019_arm64 CMAKE_GENERATOR: Visual Studio 17 2022 CMAKE_ARGS: -A ARM64 -DSTELLARIUM_BUILD_ARM64=ON -DQT_HOST_PATH=C:\Qt\6.5\msvc2019_64 -DQt6LinguistTools_DIR=C:\Qt\6.5\msvc2019_64\lib\cmake\Qt6LinguistTools -DENABLE_QTWEBENGINE=OFF - scConverterEnabled: 0 + scConverterEnabled: false # - BUILD_NAME: Arm64 Qt6.8 # APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022 # VSPATH: C:\Program Files\Microsoft Visual Studio\2022\Community\SDK\ScopeCppSDK\vc15\VC\bin @@ -71,7 +72,7 @@ environment: # QT_BASEDIR: C:\Qt\6.8\msvc2022_arm64 # CMAKE_GENERATOR: Visual Studio 17 2022 # CMAKE_ARGS: -A ARM64 -DSTELLARIUM_BUILD_ARM64=ON -DQT_HOST_PATH=C:\Qt\6.8\msvc2022_64 -DQt6LinguistTools_DIR=C:\Qt\6.8\msvc2022_64\lib\cmake\Qt6LinguistTools -DENABLE_QTWEBENGINE=OFF -# scConverterEnabled: 0 +# scConverterEnabled: false before_build: - set DEBUG_WINDEPLOYQT=false - set PUBLISH_BINARY=false @@ -80,8 +81,10 @@ before_build: - set USE_EXT_LIBSSL=false - set USE_EXT_LIBGLES=false - set SIGNING=false + - set INSTALL_CONVERTER=false + - set INSTALL_CONVERTER_DEPS=false - set INNSPATH=C:\Program Files (x86)\Inno Setup 5;C:\Program Files (x86)\Inno Setup 6 - - set PATH=%VSPATH%;%QT_BASEDIR%\bin;c:\%exiv2baseName%\bin;%INNSPATH%;%PATH% + - set PATH=%VSPATH%;%QT_BASEDIR%\bin;c:\%exiv2baseName%\bin;%INNSPATH%;c:\%gettextBaseName%\bin;c:\%tidyBaseName%\bin;c:\skyculture-converter;%PATH% - ps: if($env:QT_BASEDIR.contains('_64') -or $env:QT_BASEDIR.contains('arm64')) { $env:BITS=64 } else { $env:BITS=32 } - ps: if($env:QT_BASEDIR.contains('_64')) { $env:PKGARCH="x64" } else { $env:PKGARCH="x86" } @@ -98,6 +101,8 @@ before_build: - ps: if($env:PUBLISH_BINARY -eq "true" -and $env:BUILD_NAME.contains('Win')) { $env:USE_MESA = 'true' } - ps: if($env:APPVEYOR_REPO_BRANCH -eq "stellarium-stable" -and $env:QT_VERSION_MAJOR -eq "6") { $env:SIGNING = 'true' } - ps: if($env:APPVEYOR_REPO_BRANCH -eq "stellarium-oldstable" -and $env:QT_VERSION_MAJOR -eq "5") { $env:SIGNING = 'true' } + - ps: if($env:CMAKE_ARGS.contains('SCM_SHOULD_ENABLE_CONVERTER')) { $env:INSTALL_CONVERTER_DEPS = 'true' } + - ps: if($env:PUBLISH_BINARY -eq 'true' -and $env:scConverterEnabled -eq 'true') { $env:INSTALL_CONVERTER = 'true' } - ps: if($env:exiv2url -ne $null) { appveyor DownloadFile $env:exiv2url -FileName c:\$env:exiv2baseName.zip } - ps: if($env:exiv2url -ne $null) { 7z e c:\$env:exiv2baseName.zip -spf -oc:\ } @@ -106,32 +111,32 @@ before_build: - if [%USE_EXT_WEBENGINE%]==[true] appveyor DownloadFile https://github.com/Stellarium/stellarium-data/releases/download/qt-5.6/6.8.1-0-202411221529qtwebengine-Windows-Windows_11_23H2-MSVC2022-Windows-Windows_11_23H2-X86_64.7z -FileName c:\qtwebengine.7z - if [%USE_EXT_WEBENGINE%]==[true] 7z e c:\qtwebengine.7z -spf -o%QT_BASEDIR% - - ps: if ($env:scConverterEnabled) { appveyor DownloadFile $env:gettextURL -FileName c:\$env:gettextBaseName.zip } - - ps: if ($env:scConverterEnabled) { 7z e c:\$env:gettextBaseName.zip -spf -oc:\$env:gettextBaseName } - - ps: if ($env:scConverterEnabled) { mkdir c:\$env:gettextBaseName\include } - - ps: if ($env:scConverterEnabled) { appveyor DownloadFile $env:gettextSrcURL -FileName c:\$env:gettextSrcBaseName.tar.xz } - - ps: if ($env:scConverterEnabled) { 7z e c:\$env:gettextSrcBaseName.tar.xz -spf -oc:\ } - - ps: if ($env:scConverterEnabled) { 7z e c:\$env:gettextSrcBaseName.tar -spf -oc:\ } - - ps: if ($env:scConverterEnabled) { (cat c:\$env:gettextSrcBaseName\gettext-tools\libgettextpo\gettext-po.in.h) -replace "extern ([^()]*);","extern __declspec (dllimport) `$1;" > c:\$env:gettextBaseName\include\gettext-po.h } - - ps: if ($env:scConverterEnabled) { echo EXPORTS > libgettextpo.def } - - ps: if ($env:scConverterEnabled) { cp c:\$env:gettextBaseName\bin\libgettextpo-0.dll c:\$env:gettextBaseName\bin\libgettextpo.dll } - - ps: if ($env:scConverterEnabled) { (dumpbin /EXPORTS c:\$env:gettextBaseName\bin\libgettextpo.dll) -match "^.*\b(po_.*)$" -replace "^.*\b(po_.*)$","`$1" >> libgettextpo.def } - - ps: if ($env:scConverterEnabled) { lib /def:libgettextpo.def /out:c:\$env:gettextBaseName\lib\libgettextpo.lib } + - ps: if($env:INSTALL_CONVERTER_DEPS -eq "true") { appveyor DownloadFile $env:gettextURL -FileName c:\$env:gettextBaseName.zip } + - ps: if($env:INSTALL_CONVERTER_DEPS -eq "true") { 7z e c:\$env:gettextBaseName.zip -spf -oc:\$env:gettextBaseName } + - ps: if($env:INSTALL_CONVERTER_DEPS -eq "true") { mkdir c:\$env:gettextBaseName\include } + - ps: if($env:INSTALL_CONVERTER_DEPS -eq "true") { appveyor DownloadFile $env:gettextSrcURL -FileName c:\$env:gettextSrcBaseName.tar.xz } + - ps: if($env:INSTALL_CONVERTER_DEPS -eq "true") { 7z e c:\$env:gettextSrcBaseName.tar.xz -spf -oc:\ } + - ps: if($env:INSTALL_CONVERTER_DEPS -eq "true") { 7z e c:\$env:gettextSrcBaseName.tar -spf -oc:\ } + - ps: if($env:INSTALL_CONVERTER_DEPS -eq "true") { (cat c:\$env:gettextSrcBaseName\gettext-tools\libgettextpo\gettext-po.in.h) -replace "extern ([^()]*);","extern __declspec (dllimport) `$1;" > c:\$env:gettextBaseName\include\gettext-po.h } + - ps: if($env:INSTALL_CONVERTER_DEPS -eq "true") { echo EXPORTS > libgettextpo.def } + - ps: if($env:INSTALL_CONVERTER_DEPS -eq "true") { cp c:\$env:gettextBaseName\bin\libgettextpo-0.dll c:\$env:gettextBaseName\bin\libgettextpo.dll } + - ps: if($env:INSTALL_CONVERTER_DEPS -eq "true") { (dumpbin /EXPORTS c:\$env:gettextBaseName\bin\libgettextpo.dll) -match "^.*\b(po_.*)$" -replace "^.*\b(po_.*)$","`$1" >> libgettextpo.def } + - ps: if($env:INSTALL_CONVERTER_DEPS -eq "true") { lib /def:libgettextpo.def /out:c:\$env:gettextBaseName\lib\libgettextpo.lib } - - ps: appveyor DownloadFile $env:tidyURL -FileName c:\$env:tidyBaseName.zip - - ps: 7z e c:\$env:tidyBaseName.zip -spf -oc:\ + - ps: if($env:INSTALL_CONVERTER_DEPS -eq "true") { appveyor DownloadFile $env:tidyURL -FileName c:\$env:tidyBaseName.zip } + - ps: if($env:INSTALL_CONVERTER_DEPS -eq "true") { 7z e c:\$env:tidyBaseName.zip -spf -oc:\ } - - if [1]==[%scConverterEnabled%] git clone -q --depth=2 https://github.com/Stellarium/stellarium-skyculture-converter c:\skyculture-converter - - if [1]==[%scConverterEnabled%] mkdir c:\skyculture-converter\build - - if [1]==[%scConverterEnabled%] cd c:\skyculture-converter\build - - if [1]==[%scConverterEnabled%] git show --summary - - if [1]==[%scConverterEnabled%] cmake -DCMAKE_PREFIX_PATH=c:\%gettextBaseName%;c:\%tidyBaseName% -DCMAKE_INSTALL_PREFIX=c:\sc-converter -G "%CMAKE_GENERATOR%" %CMAKE_ARGS% .. - - if [1]==[%scConverterEnabled%] if [%PUBLISH_BINARY%]==[true] cmake --build . --config %configuration% --target install -- /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" +# - if [%INSTALL_CONVERTER%]==[true] git clone -q --depth=2 https://github.com/Stellarium/stellarium-skyculture-converter c:\skyculture-converter +# - if [%INSTALL_CONVERTER%]==[true] mkdir c:\skyculture-converter\build +# - if [%INSTALL_CONVERTER%]==[true] cd c:\skyculture-converter\build +# - if [%INSTALL_CONVERTER%]==[true] git show --summary +# - if [%INSTALL_CONVERTER%]==[true] cmake -DCMAKE_PREFIX_PATH=c:\%gettextBaseName%;c:\%tidyBaseName% -DCMAKE_INSTALL_PREFIX=c:\sc-converter -G "%CMAKE_GENERATOR%" %CMAKE_ARGS% .. +# - if [%INSTALL_CONVERTER%]==[true] cmake --build . --config %configuration% --target install -- /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" - cd c:\stellarium - mkdir build && cd build - - if [%PUBLISH_BINARY%]==[true] cmake -DCMAKE_PREFIX_PATH=c:\%exiv2baseName% -DENABLE_MEDIA=On -DENABLE_PODIR=Off -DCMAKE_INSTALL_PREFIX=c:\stellarium-package -G "%CMAKE_GENERATOR%" %CMAKE_ARGS% .. - - if [%PUBLISH_BINARY%]==[false] cmake -DCMAKE_PREFIX_PATH=c:\%exiv2baseName% -DENABLE_MEDIA=On -DENABLE_TESTING=On -DENABLE_NLS=Off -DCMAKE_INSTALL_PREFIX=c:\stellarium-package -G "%CMAKE_GENERATOR%" %CMAKE_ARGS% .. + - if [%PUBLISH_BINARY%]==[true] cmake -DCMAKE_PREFIX_PATH=c:\%exiv2baseName%;c:\%gettextBaseName%;c:\%tidyBaseName% -DENABLE_MEDIA=On -DENABLE_PODIR=Off -DCMAKE_INSTALL_PREFIX=c:\stellarium-package -G "%CMAKE_GENERATOR%" %CMAKE_ARGS% .. + - if [%PUBLISH_BINARY%]==[false] cmake -DCMAKE_PREFIX_PATH=c:\%exiv2baseName%;c:\%gettextBaseName%;c:\%tidyBaseName% -DENABLE_MEDIA=On -DENABLE_TESTING=On -DENABLE_NLS=Off -DCMAKE_INSTALL_PREFIX=c:\stellarium-package -G "%CMAKE_GENERATOR%" %CMAKE_ARGS% .. build: project: c:\stellarium\build\Stellarium.sln @@ -145,12 +150,17 @@ after_test: - if [%PUBLISH_BINARY%]==[true] cmake --build c:\stellarium\build\ --config %configuration% --target install - if [%DEBUG_WINDEPLOYQT%]==[true] cd c:\stellarium\build\ - if [%DEBUG_WINDEPLOYQT%]==[true] for %%i in (windeployqt.*) do appveyor PushArtifact %%i - - if [%PUBLISH_BINARY%]==[true] if [%scConverterEnabled%]==[1] copy c:\%gettextBaseName%\bin\libgettextpo-0.dll c:\stellarium-package\bin\libgettextpo.dll - - if [%PUBLISH_BINARY%]==[true] if [%scConverterEnabled%]==[1] copy c:\%gettextBaseName%\bin\libintl-8.dll c:\stellarium-package\bin\ - - if [%PUBLISH_BINARY%]==[true] if [%scConverterEnabled%]==[1] copy c:\%gettextBaseName%\bin\libiconv-2.dll c:\stellarium-package\bin\ - - if [%PUBLISH_BINARY%]==[true] if [%scConverterEnabled%]==[1] copy c:\%tidyBaseName%\bin\tidy.dll c:\stellarium-package\bin\ - - if [%PUBLISH_BINARY%]==[true] if [%scConverterEnabled%]==[1] copy c:\sc-converter\skyculture-converter.exe c:\stellarium-package\bin\ - - if [%PUBLISH_BINARY%]==[true] if [%scConverterEnabled%]==[1] copy c:\sc-converter\Qt6Xml.dll c:\stellarium-package\qtstuff\ + - if [%INSTALL_CONVERTER%]==[true] appveyor DownloadFile https://github.com/Stellarium/stellarium-skyculture-converter/releases/download/v0.0.1/stellarium-skyculture-converter-0.0.1-win64.zip -FileName c:\sc-converter.zip + - if [%INSTALL_CONVERTER%]==[true] 7z e c:\sc-converter.zip -aoa -oc:\sc-converter + - if [%INSTALL_CONVERTER%]==[true] copy c:\sc-converter\lib*.dll c:\stellarium-package\bin\ + - if [%INSTALL_CONVERTER%]==[true] copy c:\sc-converter\tidy.dll c:\stellarium-package\bin\ + - if [%INSTALL_CONVERTER%]==[true] copy c:\sc-converter\skyculture-converter.exe c:\stellarium-package\bin\ + - if [%INSTALL_CONVERTER%]==[true] copy c:\sc-converter\Qt6Xml.dll c:\stellarium-package\qtstuff\ +# - if [%INSTALL_CONVERTER%]==[true] copy c:\%gettextBaseName%\bin\libgettextpo-0.dll c:\stellarium-package\bin\libgettextpo.dll +# - if [%INSTALL_CONVERTER%]==[true] copy c:\%gettextBaseName%\bin\libintl-8.dll c:\stellarium-package\bin\ +# - if [%INSTALL_CONVERTER%]==[true] copy c:\%gettextBaseName%\bin\libiconv-2.dll c:\stellarium-package\bin\ +# - if [%INSTALL_CONVERTER%]==[true] copy c:\%tidyBaseName%\bin\tidy.dll c:\stellarium-package\bin\ +# - if [%INSTALL_CONVERTER%]==[true] copy c:\sc-converter\Qt6Xml.dll c:\stellarium-package\qtstuff\ - if [%USE_EXT_LIBGLES%]==[true] appveyor DownloadFile https://github.com/Stellarium/stellarium-data/releases/download/qt-5.6/libGLES-Win%BITS%.zip -FileName c:\stellarium\build\libGLES.zip - if [%USE_EXT_LIBGLES%]==[true] 7z e c:\stellarium\build\libGLES.zip -aoa -oc:\stellarium-package\qtstuff - if [%USE_MESA%]==[true] appveyor DownloadFile https://github.com/Stellarium/stellarium-data/releases/download/mesa-win-20.1.8/opengl32sw-%PKGARCH%.dll -FileName c:\stellarium-package\qtstuff\opengl32sw.dll diff --git a/.clang-tidy b/.clang-tidy index 863bd8cc84de5..afee8d18250d9 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -34,4 +34,3 @@ CheckOptions: - key: modernize-use-default-member-init.UseAssignment value: '1' ... - diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 17119b2c509d7..2925e0d869848 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,7 +38,10 @@ jobs: run: | mkdir -p build cd build - cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DSTELLARIUM_RELEASE_BUILD=Off -DENABLE_TESTING=On "${{ github.workspace }}" + cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DSTELLARIUM_RELEASE_BUILD=Off \ + -DENABLE_TESTING=On \ + "${{ github.workspace }}" - name: Compile working-directory: build @@ -68,7 +71,7 @@ jobs: qt6-qpa-plugins qt6-image-formats-plugins qt6-l10n-tools qt6-webengine-dev qt6-webengine-dev-tools libqt6charts6-dev \ libqt6charts6 libqt6opengl6-dev libqt6positioning6-plugins libqt6serialport6-dev qt6-base-dev libqt6webenginecore6-bin \ libqt6webengine6-data libexiv2-dev libnlopt-cxx-dev zlib1g-dev libgl1-mesa-dev libdrm-dev libglx-dev libxkbcommon-x11-dev \ - libgps-dev libmd4c-dev libmd4c-html0-dev + libgps-dev libmd4c-dev libmd4c-html0-dev cmake gettext libgettextpo-dev libtidy-dev libunarr-dev - name: Checkout repository uses: actions/checkout@v4 @@ -78,7 +81,11 @@ jobs: run: | mkdir -p build cd build - cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DSTELLARIUM_RELEASE_BUILD=Off -DENABLE_TESTING=On "${{ github.workspace }}" + cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DSTELLARIUM_RELEASE_BUILD=Off \ + -DENABLE_TESTING=On \ + -DSCM_SHOULD_ENABLE_CONVERTER=On \ + "${{ github.workspace }}" - name: Compile working-directory: build @@ -123,7 +130,7 @@ jobs: -DUSE_PLUGIN_LENSDISTORTIONESTIMATOR=Off -DUSE_PLUGIN_NEBULATEXTURES=Off -DUSE_PLUGIN_NAVSTARS=Off -DUSE_PLUGIN_NOVAE=Off -DUSE_PLUGIN_OBSERVABILITY=Off \ -DUSE_PLUGIN_OCULARS=Off -DUSE_PLUGIN_ONLINEQUERIES=Off -DUSE_PLUGIN_POINTERCOORDINATES=Off -DUSE_PLUGIN_PULSARS=Off -DUSE_PLUGIN_QUASARS=Off \ -DUSE_PLUGIN_REMOTECONTROL=Off -DUSE_PLUGIN_REMOTESYNC=Off -DUSE_PLUGIN_SATELLITES=Off -DUSE_PLUGIN_SCENERY3D=Off -DUSE_PLUGIN_SOLARSYSTEMEDITOR=Off \ - -DUSE_PLUGIN_SUPERNOVAE=Off -DUSE_PLUGIN_TELESCOPECONTROL=Off -DUSE_PLUGIN_TEXTUSERINTERFACE=Off \ + -DUSE_PLUGIN_SUPERNOVAE=Off -DUSE_PLUGIN_TELESCOPECONTROL=Off -DUSE_PLUGIN_TEXTUSERINTERFACE=Off -DUSE_PLUGIN_SKYCULTUREMAKER=Off \ "${{ github.workspace }}" - name: Compile @@ -160,10 +167,14 @@ jobs: - name: Configure CMake shell: bash run: | - export PATH="/opt/homebrew/opt/qt@5/bin:/usr/local/opt/qt@5/bin:$PATH" + export PATH="/usr/local/opt/qt@5/bin:$PATH" mkdir -p build cd build - cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DSTELLARIUM_RELEASE_BUILD=Off -DENABLE_TESTING=On ${{ github.workspace }} + cmake -DCMAKE_PREFIX_PATH="/usr/local" \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DSTELLARIUM_RELEASE_BUILD=Off \ + -DENABLE_TESTING=On \ + "${{ github.workspace }}" - name: Compile working-directory: build @@ -191,7 +202,7 @@ jobs: run: | # brew update # brew upgrade - brew install qt@6 nlopt exiv2 + brew install qt@6 nlopt exiv2 tidy-html5 - name: Checkout repository uses: actions/checkout@v4 @@ -202,7 +213,12 @@ jobs: export PATH="/usr/local/opt/qt@6/bin:$PATH" mkdir -p build cd build - cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DSTELLARIUM_RELEASE_BUILD=Off -DENABLE_TESTING=On ${{ github.workspace }} + cmake -DCMAKE_PREFIX_PATH="/usr/local" \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DSTELLARIUM_RELEASE_BUILD=Off \ + -DSCM_SHOULD_ENABLE_CONVERTER=On \ + -DENABLE_TESTING=On \ + "${{ github.workspace }}" - name: Compile working-directory: build @@ -233,7 +249,7 @@ jobs: run: | # brew update # brew upgrade - brew install qt@6 nlopt exiv2 + brew install qt@6 nlopt exiv2 tidy-html5 - name: Checkout repository uses: actions/checkout@v4 @@ -241,10 +257,15 @@ jobs: - name: Configure CMake shell: bash run: | - export PATH="/usr/local/opt/qt@6/bin:$PATH" + export PATH="/opt/homebrew/opt/qt@6/bin:$PATH" mkdir -p build cd build - cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DSTELLARIUM_RELEASE_BUILD=Off -DENABLE_TESTING=On ${{ github.workspace }} + cmake -DCMAKE_PREFIX_PATH="/opt/homebrew" \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DSTELLARIUM_RELEASE_BUILD=Off \ + -DENABLE_TESTING=On \ + -DSCM_SHOULD_ENABLE_CONVERTER=On \ + "${{ github.workspace }}" - name: Compile working-directory: build @@ -285,7 +306,11 @@ jobs: export DISPLAY=:0 mkdir builds cd builds - cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DSTELLARIUM_RELEASE_BUILD=Off -DENABLE_TESTING=On -DENABLE_QT6=Off "${{ github.workspace }}" + cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DSTELLARIUM_RELEASE_BUILD=Off \ + -DENABLE_TESTING=On \ + -DENABLE_QT6=Off \ + "${{ github.workspace }}" make -j3 Xvfb :0 -ac -screen 0 1024x768x24+32 >/dev/null 2>&1 & sleep 3 @@ -313,14 +338,21 @@ jobs: # When using rsync, you can define copyback: false to not copy files back from the VM in to the host. copyback: false prepare: | - pkg install -y cmake git eigen glm exiv2 nlopt fast_float md4c qxlsx-qt6 perl5 xorg-vfbserver gettext qt6 + pkg install -y cmake git eigen glm exiv2 nlopt fast_float md4c qxlsx-qt6 perl5 xorg-vfbserver gettext qt6 tidy-html5 run: | set -e -x export DISPLAY=:0 + export CMAKE_PREFIX_PATH="/usr/local:$CMAKE_PREFIX_PATH" mkdir builds cd builds - cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DSTELLARIUM_RELEASE_BUILD=Off -DENABLE_TESTING=On "${{ github.workspace }}" + cmake -DCMAKE_PREFIX_PATH="/usr/local" \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DSTELLARIUM_RELEASE_BUILD=Off \ + -DENABLE_TESTING=On \ + -DLIBTIDY_LIBRARY="/usr/local/lib/libtidy5.so" \ + -DSCM_SHOULD_ENABLE_CONVERTER=On \ + "${{ github.workspace }}" make -j3 Xvfb :0 -ac -screen 0 1024x768x24+32 >/dev/null 2>&1 & sleep 3 diff --git a/.gitignore b/.gitignore index de9d5f0af4a99..9398fd8be5a15 100644 --- a/.gitignore +++ b/.gitignore @@ -75,3 +75,5 @@ qrc_*.cpp .kdev4 # Ignore Eclipse project files .project +# Ignore vscode project files +.vscode diff --git a/BUILDING.md b/BUILDING.md index 7d2af236b7144..f6aecff0e60ca 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -444,6 +444,7 @@ List of supported parameters (passed as `-DPARAMETER=VALUE`): | USE_PLUGIN_SATELLITES | bool | ON | Enable building the Satellites plugin | USE_PLUGIN_SCENERY3D | bool | ON | Enable building the 3D Scenery plugin | USE_PLUGIN_SIMPLEDRAWLINE | bool | OFF | Enable building the SimpleDrawLine plugin (example of simple graphics plugin) +| USE_PLUGIN_SKYCULTUREMAKER | bool | ON | Enable building the Sky Culture Maker plugin | USE_PLUGIN_SOLARSYSTEMEDITOR | bool | ON | Enable building the Solar System Editor plugin | USE_PLUGIN_SUPERNOVAE | bool | ON | Enable building the Historical Supernovae plugin | USE_PLUGIN_TELESCOPECONTROL | bool | ON | Enable building the Telescope Control plugin diff --git a/CMakeLists.txt b/CMakeLists.txt index 506854abedcb6..74563873d9c11 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -300,6 +300,7 @@ ENDIF() SET(CMAKE_CXX_STANDARD 17) SET(CMAKE_CXX_STANDARD_REQUIRED ON) SET(CMAKE_CXX_EXTENSIONS OFF) + # NOTE: C_STANDARD 17 and 23 values added in CMake 3.21 # https://gitlab.kitware.com/cmake/cmake/-/issues/22366 # Ubuntu 18.04 have GCC 7.5 - so, C11 only @@ -330,8 +331,15 @@ IF(WIN32) # C4305: type truncation # C4351: "new" behaviour, member array default initialization. Required since at least C++98, but funny MSVC throws a warning. # C4996: deprecated POSIX names (used in zlib) - # C5105: defines in macros - SET(STEL_MSVC_FLAGS "/wd4244 /wd4305 /wd4351 /wd4996 /wd5105 /utf-8") + # C5105: defines in macros + # Zc:_cplusplus: Qt >5.9.0 requires a C++17 compiler, because cmake has a bug: https://gitlab.kitware.com/cmake/cmake/-/issues/1883 + SET(STEL_MSVC_FLAGS "/wd4244 /wd4305 /wd4351 /wd4996 /wd5105 /utf-8 /Zc:__cplusplus") + + # permissive: required by Qt >=6.9.0 (see qt6\QtCore\qcompilerdetection.h for more information: https://github.com/qt/qtbase/blob/v6.9.0/src/corelib/global/qcompilerdetection.h#L1304-L1316) + IF(QT_VERSION VERSION_GREATER_EQUAL 6.9.0) + SET(STEL_MSVC_FLAGS "${STEL_MSVC_FLAGS} /permissive-") + ENDIF() + # Avoid type conflict with C++17 standard # SET(STEL_MSVC_FLAGS "${STEL_MSVC_FLAGS} /D_HAS_STD_BYTE=0") # Don't do this in Qt6. Just avoid "using namespace std" anywhere! https://developercommunity.visualstudio.com/t/error-c2872-byte-ambiguous-symbol/93889 # Set multiprocessing and minimal version of Windows @@ -568,6 +576,7 @@ ENDIF() ADD_PLUGIN(RemoteSync 1) ADD_PLUGIN(Satellites 1) ADD_PLUGIN(Scenery3d 1) +ADD_PLUGIN(SkyCultureMaker 1) ADD_PLUGIN(SolarSystemEditor 1) ADD_PLUGIN(Supernovae 1) ADD_PLUGIN(LensDistortionEstimator 1) diff --git a/plugins/NebulaTextures/src/gui/NebulaTexturesDialog.cpp b/plugins/NebulaTextures/src/gui/NebulaTexturesDialog.cpp index b327e2a9cc7e8..06ad705ed0b71 100644 --- a/plugins/NebulaTextures/src/gui/NebulaTexturesDialog.cpp +++ b/plugins/NebulaTextures/src/gui/NebulaTexturesDialog.cpp @@ -365,7 +365,7 @@ void NebulaTexturesDialog::initializeRefreshIfNeeded() */ void NebulaTexturesDialog::openImageFile() { - QString fileName = QFileDialog::getOpenFileName(&StelMainView::getInstance(), q_("Open Image"), lastOpenedDirectoryPath, tr("Images (*.png *.jpg *.gif *.tif *.tiff *.jpeg)")); + QString fileName = QFileDialog::getOpenFileName(&StelMainView::getInstance(), q_("Open Image"), lastOpenedDirectoryPath, q_("Images (*.png *.jpg *.gif *.tif *.tiff *.jpeg)")); if (!fileName.isEmpty()) { ui->lineEditImagePath->setText(fileName); diff --git a/plugins/SkyCultureMaker/CHANGELOG.md b/plugins/SkyCultureMaker/CHANGELOG.md new file mode 100644 index 0000000000000..0d30b3ecdc754 --- /dev/null +++ b/plugins/SkyCultureMaker/CHANGELOG.md @@ -0,0 +1,5 @@ +# Sky Culture Maker Plugin - Changelog + +## Version 1.0.0 + +* Initial release of the Sky Culture Maker Plugin \ No newline at end of file diff --git a/plugins/SkyCultureMaker/CMakeLists.txt b/plugins/SkyCultureMaker/CMakeLists.txt new file mode 100644 index 0000000000000..d496184ac3e84 --- /dev/null +++ b/plugins/SkyCultureMaker/CMakeLists.txt @@ -0,0 +1,77 @@ +# This is the cmake config file for the Sky Culture Maker plugin +SET(SCM_VERSION "1.0.0") + +# Option to manually control converter, defaults to FALSE +OPTION(SCM_SHOULD_ENABLE_CONVERTER "Attempt to enable Sky Culture Converter" FALSE) +SET(SCM_CONVERTER_ENABLED FALSE) # Default to disabled + +# Detect Qt5 +IF(QT_VERSION_MAJOR EQUAL "5") + SET(IS_QT_5 TRUE) +ELSE() + SET(IS_QT_5 FALSE) +ENDIF() + +# Detect Windows ARM64 +IF(WIN32 AND (CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64|arm64|aarch64" OR CMAKE_VS_PLATFORM_NAME MATCHES "ARM64|arm64")) + SET(IS_WIN_ARM64 TRUE) +ELSE() + SET(IS_WIN_ARM64 FALSE) +ENDIF() + +# Disable converter for Qt5 +# Disable converter for Windows ARM64 builds due to missing ARM64 libraries +IF(SCM_SHOULD_ENABLE_CONVERTER AND NOT IS_QT_5 AND NOT IS_WIN_ARM64) + SET(SCM_CONVERTER_ENABLED TRUE) + MESSAGE(STATUS "Sky Culture Converter will be enabled.") + + # download https://github.com/Stellarium/stellarium-skyculture-converter + CPMFindPackage( + NAME StellariumSkyCultureConverter + URL https://github.com/Stellarium/stellarium-skyculture-converter/archive/refs/tags/v0.0.1.zip + URL_HASH SHA256=07c4f5b7f4cbf3d1d62b2eed30237d04b979f72415476c2f401a3c6198eee2ea + EXCLUDE_FROM_ALL yes + OPTIONS + "SKYCULTURE_CONVERTER_BUILD_TESTS OFF" + ) + + # download https://github.com/selmf/unarr for archives + # Using commit 1df8ab3 to allow CMake support up to 4.0 + CPMFindPackage( + NAME unarr + URL https://github.com/selmf/unarr/archive/1df8ab3d281409e9fe6bed8bf485976bb47f5bef.zip + URL_HASH SHA256=f89f602184c90b01b47eb76f1f6334801c3d22eb6f5deddcff92c9ea79992903 + EXCLUDE_FROM_ALL yes + OPTIONS + "USE_SYSTEM_BZ2 OFF" + "USE_SYSTEM_LZMA OFF" + ) + + IF(WIN32) + # Patch unarr's common/stream.c to include after windows.h + # Ensure this runs after unarr source is available + IF(TARGET unarr AND EXISTS "${unarr_SOURCE_DIR}/common/stream.c") + FILE(READ "${unarr_SOURCE_DIR}/common/stream.c" _stream_c_content) + STRING(REPLACE "#define COBJMACROS\n#include \n" + "#define COBJMACROS\n#include \n#include \n" + _stream_c_content "${_stream_c_content}") + FILE(WRITE "${unarr_SOURCE_DIR}/common/stream.c" "${_stream_c_content}") + ELSE() + MESSAGE(WARNING "unarr source directory or stream.c not found for patching. This might be an issue on Windows if the converter is enabled.") + ENDIF() + ENDIF(WIN32) + ADD_DEFINITIONS(-DSCM_CONVERTER_ENABLED_CPP) # Define for C++ +ELSE() + IF(IS_QT_5) + MESSAGE(STATUS "Sky Culture Converter is DISABLED because it requires Qt 6 or later.") + ELSEIF(IS_WIN_ARM64) + MESSAGE(STATUS "Sky Culture Converter is DISABLED for Windows ARM64 builds (missing ARM64 tidy/gettext libraries).") + ELSE() + MESSAGE(STATUS "Sky Culture Converter is DISABLED.") + ENDIF() +ENDIF() + +ADD_DEFINITIONS(-DSKYCULTUREMAKER_PLUGIN_VERSION="${SCM_VERSION}") +ADD_DEFINITIONS(-DSKYCULTUREMAKER_PLUGIN_LICENSE="GNU GPLv2 or later") + +ADD_SUBDIRECTORY( src ) diff --git a/plugins/SkyCultureMaker/README.md b/plugins/SkyCultureMaker/README.md new file mode 100644 index 0000000000000..bd3173f9530b8 --- /dev/null +++ b/plugins/SkyCultureMaker/README.md @@ -0,0 +1,233 @@ +# Sky Culture Maker Plugin + +## Introduction + +*Sky Culture Maker* is a plugin for **Stellarium**, designed to simplify the creation and customization of sky cultures. With this tool, users can easily define new sky cultures, draw constellations, and visualize their arrangements directly within Stellarium. + +The plugin provides an intuitive interface for both amateur astronomers and advanced users to: +- Create sky cultures directly in Stellarium with real-time visualization +- Define new constellations by drawing directly in the Stellarium sky view +- Convert `.fab` data into JSON + +This documentation explains how to activate and use the Sky Culture Maker plugin, as well as its functionalities. + +--- + +## Table of Contents + +1. **Installation** + - Prerequisites + - Activating the Plugin in Stellarium +2. **Getting Started** + - Overview of the User Interface + - Creating a New Sky Culture + - Drawing Constellations + - Exporting Your Sky Culture +3. **File Structure and Output** + - Directory Layout of Generated Sky Cultures + - File Types and Their Purpose +4. **Best Practices** + - Recommended Naming Conventions + - Tips for Accurate Constellation Design +5. **Future Roadmap** +6. **Credits** +7. **License Information** + +
+ +--- + +## 1. Installation + +The Sky Culture Maker plugin is embedded in the Stellarium project. Therefore, no additional installation is necessary. + +### 1.1 Prerequisites + +The Sky Culture Maker currently consists of two main features. The first is the Maker, which is used to create new sky cultures within Stellarium. The second is a converter that transforms old `.fab` sky culture files into the new JSON format. + +**Maker** + +- Stellarium already comes with everything you need. + +**Converter** + +- [Tidy HTML](https://www.html-tidy.org/) (used for formatting HTML descriptions) +- [gettext](https://www.gnu.org/software/gettext/) +- [Zlib](https://www.zlib.net) + +### 1.2 Activating the Plugin in Stellarium + +After building or installing Stellarium: + +1. Launch Stellarium. +2. Navigate to **Settings → Plugins**. +3. Locate **Sky Culture Maker** in the plugin list. +4. Enable the plugin and restart Stellarium for changes to take effect. + +The Sky Culture Maker icon should now appear in the Stellarium toolbar. + +
+ +drawing + +--- + +## 2. Getting Started + +Sky Culture Maker provides a seamless workflow to create custom sky cultures directly within Stellarium. This section will guide you through the initial steps to get familiar with the interface and start creating your first sky culture and how to use the converter. + +After activating the plugin, you can start it via the Stellarium toolbar. After starting the Sky Culture Maker, the Start dialog window will appear. There, you will find the options **Create**, **Convert**, and **Cancel**. Let's look at **Create** first. + +### Maker + +Clicking **Create** will start the Maker and open the three windows. +1. The _Location window_ so that the user can navigate to the correct location where to create the sky culture +2. The _Date/time window_ so that the user can choose the correct date and time of the sky +3. The main window of the maker, which provides several tabs dedicated to different aspects of sky culture creation. + +- **Overview:** Manage constellations, remove existing ones, and save your sky culture. The sky culture name and license must be specified here. +- **Description**: Enter essential information such as authors, cultural descriptions, and constellations. Refer to the Stellarium Guide for details on the description fields. **Important**: All fields must be filled out, and a classification must be selected from the dropdown menu at the bottom of the description tab. +- **Boundaries:** (Planned for the future) Define boundaries for constellations or cultural sky regions. +- **Common Names:** (Planned for the future) Specify alternative names or traditional star names. +- **Geolocation:** (Planned for the future) Assign geographic or cultural origin information. + +--- + +#### Creating a New Sky Culture + +1. Open the Sky Culture Maker interface. +2. Click **Create New Sky Culture**. +3. Fill in the required fields under the **Description** tab (name, author, etc.). +4. Set the license in the **Overview** tab. + +Once the basic information is complete, you can begin drawing constellations. + +--- + +#### Drawing Constellations + +The constellation editor provides tools for interactive design: + +- **Pen Tool:** Right click on stars to draw constellation lines between points. You can also use the search function to locate specific stars by name. Once found, those stars can be selected directly to create lines between them. +- **Erase Tool:** Remove lines or incorrect segments from the constellation. +- **Undo:** Revert the most recent drawing action. + +For each constellation, the following information must be provided: + +- **ID:** Unique identifier for the constellation . If not set it will automatically be generated based on the **Name** +- **Name:** Display name for the constellation +- **Native Name (optional):** Name in the original language or script +- **Pronunciation (optional):** Phonetic pronunciation guide +- **Constellation Image (optional):** Upload an image to be anchored to the constellation + +If your constellation consists of multiple separated areas, disable the pen tool after completing one area to unlock the drawing cursor from the most recent star. + + +#### Uploading and Anchoring an Image + +You can upload a constellation image and align it with specific stars in the sky. Follow these steps to add and anchor your artwork: + +1. Open the **Artwork** tab within the Constellation Editor. +2. Click on **Upload Image** and select the desired constellation image from your files. +3. After uploading, three anchor points will appear at the center of the image. You can use these points to align the image precisely with the stars. + +To bind an anchor to a star: + +- Click on one of the anchor points. The selected anchor will turn green to indicate it is active. +- Select a star of your choice in the sky. +- Click **Bind Star** to link the anchor to the selected star. + +When an anchor is successfully bound to a star, it will be highlighted in a brighter color. Selecting the anchor again will automatically highlight the corresponding star. + +**Note:** +If an anchor is already bound to a star, clicking **Bind Star** again will overwrite the existing binding with the currently selected star. + + +--- + +#### Exporting the Sky Culture + +Once your sky culture is saved, it can be exported as a JSON package. The exported files are stored in Stellarium's `skycultures` directory. + +The `description.md` file contains all general information provided in the *Overview* tab. +All created constellations, along with their properties, are exported into the `index.json` file. + +These files allow your sky culture to be loaded and displayed within Stellarium or shared with others. + +--- + +### Converting a Sky Culture (Converter) + +To convert a sky culture: + +1. Click on **Convert** in the main window. +2. Select an archive file containing the sky culture files. + **Supported formats:** `.zip`, `.rar`, `.tar`, `.7z` + > ⚠️ **Note:** Only regular `.tar` archives are supported. + > Compressed formats like `.tar.gz` or `.tar.bz2` are **not** supported. + +3. The converted files will be saved in Stellarium's sky culture folder. + +> **Case Sensitivity:** +> The constellation names are **case-sensitive**. +> For example, `chinese_chenzhuo` is **not** the same as `chinese_Chenzhuo`. + +> Important: +> The conversion process **requires Qt6 or later**. +> If you are using an older version of Qt, the converter will **not** be built and the conversion feature will be **disabled**. + +--- + +## 3. File Structure and Output + +After creating or converting a sky culture, the files are stored in Stellarium's `skycultures` directory by default. + +If the user does not have permission to write to Stellarium's directory, a dialog will prompt the user to select an alternative existing folder where the sky culture can be saved. + +Generated sky culture files follow the Stellarium standards for directories, metadata, and constellation definitions. + +--- + +## 4. Best Practices + +### Recommended Naming Conventions + +- Follow the standards outlined in the [Stellarium Guide](https://stellarium.org/files/guide.pdf) +- Use consistent and culturally appropriate names for constellations +- Ensure unique IDs for each constellation + +### Tips for Accurate Constellation Design + +- When drawing constellations with separate, disconnected regions, disable the pen tool after completing one area to prevent lines from unintentionally connecting distant stars. +- Use high-contrast colors for constellation lines to ensure visibility in Stellarium's sky view. +- Anchor images carefully to avoid visual misalignment during zooming or panning. +- Zooming in on the sky allows for more precise use of the pen tool. The star selection becomes less sensitive to nearby stars, making it easier to accurately connect the intended points. +- To avoid the pen tool snapping to nearby stars while drawing, press and hold **CTRL**. This allows for free placement of lines without automatic star locking. + + +--- + +## 5 Credits +- Vincent Gerlach ([RivinHD@GitHub](https://github.com/RivinHD)) +- Luca-Philipp Grumbach ([xLPMG@GitHub](https://github.com/xLPMG)) +- Fabian Hofer ([Integer-Ctrl@GitHub](https://github.com/Integer-Ctrl)) +- Mher Mnatsakanyan ([MherMnatsakanyan03@GitHub](https://github.com/MherMnatsakanyan03)) +- Richard Hofmann ([ZeyxRew@GitHub](https://github.com/ZeyxRew)) + +--- + +## 6. **License Information** +Copyright (C) 2025 The Sky Culture Maker Contributors + +This plugin is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This plugin is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this plugin; if not, see https://www.gnu.org/licenses/. \ No newline at end of file diff --git a/plugins/SkyCultureMaker/icons.svg b/plugins/SkyCultureMaker/icons.svg new file mode 100644 index 0000000000000..e9eb87f3bccbd --- /dev/null +++ b/plugins/SkyCultureMaker/icons.svg @@ -0,0 +1,4291 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + Made with Inkscape, Export with 96dpi. Read the Doxygen documentation too + Some text icon based on DejaVu Sans font"?" icon based on Tex Gyre Adventor font Licensed with Gust font licenseDSS and HiPS icons shapes based on images from https://wiki.ivoa.net/internal/IVOA/InterOpMay2019Apps/HiPSatWWT_20190514.pdf + Bar icons glow:- Group all elements of the icon and copy to "glows" layer- Filters -> Shadows and Glows -> Drop shadow - Glow radius: 0.5px - Offset: 0 - Shadow type: Shadow only - Color: #c3c3c3ff- Select each blur individually- Check if bounding box looks big enough to hold the blur without cutting- Check if bounding box is small enough to fit on the boundaries of the icon- To adjust the bounding box: Filters -> Filter Editor - Filter General Settings (on bottom) - Adjust coordinates and dimensions manually for each icon- Now you have one "glow" object, we are using three of these superposedto get a stronger effect, so we need two more - Select the object and press Ctrl-D two times to create two additional copies + Bar icons shadow:- Group all elements of the icon and copy to "shadows" layer- Filters -> Shadows and Glows -> Drop shadow - Glow radius: 0.1px - Offset: X=0px, Y=0.2px - Shadow type: Shadow only - Color: #000000ff- Check if it looks OK, otherwise check boundaries as you do with glows + Export an icon:- Make every layer visible except "background"- Lock every layer except "boundaries"- Click the icon to export, the boundary rectangle of the given icon will be selected- Make the "boundaries" layer invisible, keep the clicked rectangle selected- File -> Export PNG Image - Export area: Selection - Set DPI to 480 (96×5 for high-DPI scaling support with up to 500% scaling factor) - Width and height should automatically be the size you expect, like 56x56px + Documentation: + Notes: + Some icons are pixel-aligned, enable the grid if necessaryFor example take a look at the icon that says "orion", both the text andthe lines are pixel-aligned + Colors:- Disabled: 6d6d6dff- Enabled: ffffffff- Disabled variation: cacacaff- Enabled variation: 8a8a8aff- Shadow: 000000ff- Glow: c3c3c3ff + Tab icons shadow:- Same procedure as Bar icons shadow but with these settings - Glow radius: 0.2px - Offset: X=0px, Y=0.1px - Shadow type: Shadow only - Color: #000000ff + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/SkyCultureMaker/resources/SkyCultureMaker.qrc b/plugins/SkyCultureMaker/resources/SkyCultureMaker.qrc new file mode 100644 index 0000000000000..90b2c8878c06b --- /dev/null +++ b/plugins/SkyCultureMaker/resources/SkyCultureMaker.qrc @@ -0,0 +1,6 @@ + + + bt_SCM_Off.png + bt_SCM_On.png + + diff --git a/plugins/SkyCultureMaker/resources/bt_SCM_Off.png b/plugins/SkyCultureMaker/resources/bt_SCM_Off.png new file mode 100644 index 0000000000000..6b84ad15277d3 Binary files /dev/null and b/plugins/SkyCultureMaker/resources/bt_SCM_Off.png differ diff --git a/plugins/SkyCultureMaker/resources/bt_SCM_On.png b/plugins/SkyCultureMaker/resources/bt_SCM_On.png new file mode 100644 index 0000000000000..bac710a63c910 Binary files /dev/null and b/plugins/SkyCultureMaker/resources/bt_SCM_On.png differ diff --git a/plugins/SkyCultureMaker/src/CMakeLists.txt b/plugins/SkyCultureMaker/src/CMakeLists.txt new file mode 100644 index 0000000000000..cbf4caec3a70d --- /dev/null +++ b/plugins/SkyCultureMaker/src/CMakeLists.txt @@ -0,0 +1,89 @@ +INCLUDE_DIRECTORIES( + . + gui + ${CMAKE_BINARY_DIR}/plugins/SkyCultureMaker/src + ${CMAKE_BINARY_DIR}/plugins/SkyCultureMaker/src/gui +) + +LINK_DIRECTORIES(${CMAKE_BINARY_DIR}/src) + +SET( SkyCultureMaker_SRCS + SkyCultureMaker.hpp + SkyCultureMaker.cpp + + ScmDraw.hpp + ScmDraw.cpp + ScmConstellation.hpp + ScmConstellation.cpp + ScmConstellationArtwork.hpp + ScmConstellationArtwork.cpp + ScmSkyCulture.hpp + ScmSkyCulture.cpp + + gui/ScmStartDialog.hpp + gui/ScmStartDialog.cpp + gui/ScmConstellationDialog.hpp + gui/ScmConstellationDialog.cpp + gui/ScmSkyCultureDialog.hpp + gui/ScmSkyCultureDialog.cpp + gui/ScmConstellationImageAnchor.hpp + gui/ScmConstellationImageAnchor.cpp + gui/ScmConstellationImage.hpp + gui/ScmConstellationImage.cpp + gui/ScmSkyCultureExportDialog.hpp + gui/ScmSkyCultureExportDialog.cpp + gui/ScmHideOrAbortMakerDialog.hpp + gui/ScmHideOrAbortMakerDialog.cpp + + types/CoordinateLine.hpp + types/Drawing.hpp + types/DialogID.hpp + types/DrawTools.hpp + types/Lines.hpp + types/StarLine.hpp + types/SkyPoint.hpp + types/Anchor.hpp +) + +SET( SCM_UIS + gui/scmConstellationDialog.ui + gui/scmSkyCultureDialog.ui + gui/scmSkyCultureExportDialog.ui + gui/scmHideOrAbortMakerDialog.ui + gui/scmStartDialog.ui +) + +if(SCM_CONVERTER_ENABLED) + list(APPEND SkyCultureMaker_SRCS + gui/ScmConvertDialog.hpp + gui/ScmConvertDialog.cpp + ) + list(APPEND SCM_UIS + gui/scmConvertDialog.ui + ) +endif() + +################# compiles resources files ############ +SET(SkyCultureMaker_RES ../resources/SkyCultureMaker.qrc) +IF (${QT_VERSION_MAJOR} EQUAL "5") + QT5_WRAP_UI(SCM_UIS_H ${SCM_UIS}) + QT5_ADD_RESOURCES(SkyCultureMaker_RES_CXX ${SkyCultureMaker_RES}) +ELSE() + QT_WRAP_UI(SCM_UIS_H ${SCM_UIS}) + QT_ADD_RESOURCES(SkyCultureMaker_RES_CXX ${SkyCultureMaker_RES}) +ENDIF() + +ADD_LIBRARY(SkyCultureMaker-static STATIC ${SkyCultureMaker_SRCS} ${SkyCultureMaker_RES_CXX} ${SCM_UIS}) +SET_TARGET_PROPERTIES(SkyCultureMaker-static PROPERTIES OUTPUT_NAME "SkyCultureMaker") + +list(APPEND SKY_CULTURE_MAKER_PLUGIN_LINK_LIBS Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Widgets) +# SCM_CONVERTER_ENABLED is inherited from the parent CMakeLists.txt scope +if(SCM_CONVERTER_ENABLED) + list(APPEND SKY_CULTURE_MAKER_PLUGIN_LINK_LIBS libskycultureconverter unarr) +endif() + +TARGET_LINK_LIBRARIES(SkyCultureMaker-static ${SKY_CULTURE_MAKER_PLUGIN_LINK_LIBS}) +SET_TARGET_PROPERTIES(SkyCultureMaker-static PROPERTIES COMPILE_FLAGS "-DQT_STATICPLUGIN") +ADD_DEPENDENCIES(AllStaticPlugins SkyCultureMaker-static) + +SET_TARGET_PROPERTIES(SkyCultureMaker-static PROPERTIES FOLDER "plugins/SkyCultureMaker") diff --git a/plugins/SkyCultureMaker/src/ScmConstellation.cpp b/plugins/SkyCultureMaker/src/ScmConstellation.cpp new file mode 100644 index 0000000000000..b64abe391491c --- /dev/null +++ b/plugins/SkyCultureMaker/src/ScmConstellation.cpp @@ -0,0 +1,286 @@ +/* + * Sky Culture Maker plug-in for Stellarium + * + * Copyright (C) 2025 Vincent Gerlach + * Copyright (C) 2025 Luca-Philipp Grumbach + * Copyright (C) 2025 Fabian Hofer + * Copyright (C) 2025 Mher Mnatsakanyan + * Copyright (C) 2025 Richard Hofmann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ScmConstellation.hpp" +#include +#include + +scm::ScmConstellation::ScmConstellation(const QString &id, const std::vector &coordinates, + const std::vector &stars, const bool isDarkConstellation) + : id(id) + , coordinates(coordinates) + , stars(stars) + , isDarkConstellation(isDarkConstellation) +{ + QSettings *conf = StelApp::getInstance().getSettings(); + constellationNameFont.setPixelSize(conf->value("viewing/constellation_font_size", 15).toInt()); + + QString defaultColor = conf->value("color/default_color", "0.5,0.5,0.7").toString(); + defaultConstellationLineColor = Vec3f(conf->value("color/const_lines_color", defaultColor).toString()); + defaultConstellationNameColor = Vec3f(conf->value("color/const_names_color", defaultColor).toString()); + + updateTextPosition(); +} + +QString scm::ScmConstellation::getId() const +{ + return id; +} + +void scm::ScmConstellation::setEnglishName(const QString &name) +{ + englishName = name; +} + +QString scm::ScmConstellation::getEnglishName() const +{ + return englishName; +} + +void scm::ScmConstellation::setNativeName(const std::optional &name) +{ + nativeName = name; +} + +std::optional scm::ScmConstellation::getNativeName() const +{ + return nativeName; +} + +void scm::ScmConstellation::setPronounce(const std::optional &pronounce) +{ + ScmConstellation::pronounce = pronounce; +} + +std::optional scm::ScmConstellation::getPronounce() const +{ + return pronounce; +} + +void scm::ScmConstellation::setIPA(const std::optional &ipa) +{ + ScmConstellation::ipa = ipa; +} + +std::optional scm::ScmConstellation::getIPA() const +{ + return ipa; +} + +void scm::ScmConstellation::setArtwork(const ScmConstellationArtwork &artwork) +{ + ScmConstellation::artwork = artwork; +} + +const scm::ScmConstellationArtwork &scm::ScmConstellation::getArtwork() const +{ + return artwork; +} + +void scm::ScmConstellation::setConstellation(const std::vector &coordinates, + const std::vector &stars) +{ + scm::ScmConstellation::coordinates = coordinates; + scm::ScmConstellation::stars = stars; + + updateTextPosition(); +} + +const std::vector &scm::ScmConstellation::getCoordinates() const +{ + return coordinates; +} + +const std::vector &scm::ScmConstellation::getStars() const +{ + return stars; +} + +void scm::ScmConstellation::drawConstellation(StelCore *core, const Vec3f &lineColor, const Vec3f &nameColor) const +{ + if (isHidden) + { + return; + } + StelPainter painter(core->getProjection(drawFrame)); + painter.setBlending(true); + painter.setLineSmooth(true); + painter.setFont(constellationNameFont); + + painter.setColor(lineColor, 1.0f); + + for (CoordinateLine p : coordinates) + { + painter.drawGreatCircleArc(p.start, p.end); + } + + drawNames(core, painter, nameColor); + + artwork.draw(core, painter); +} + +void scm::ScmConstellation::drawConstellation(StelCore *core) const +{ + if (isHidden) + { + return; + } + drawConstellation(core, defaultConstellationLineColor, defaultConstellationNameColor); +} + +void scm::ScmConstellation::drawNames(StelCore *core, StelPainter &sPainter, const Vec3f &nameColor) const +{ + if (isHidden) + { + return; + } + + sPainter.setBlending(true); + + Vec3d velocityObserver(0.); + if (core->getUseAberration()) + { + velocityObserver = core->getAberrationVec(core->getJDE()); + } + + Vec3d namePose = XYZname; + namePose += velocityObserver; + namePose.normalize(); + + Vec3d XYname; + if (!sPainter.getProjector()->projectCheck(XYZname, XYname)) + { + return; + } + + sPainter.setColor(nameColor, 1.0f); + sPainter.drawText(static_cast(XYname[0]), static_cast(XYname[1]), englishName, 0., + -sPainter.getFontMetrics().boundingRect(englishName).width() / 2, 0, false); +} + +void scm::ScmConstellation::drawNames(StelCore *core, StelPainter &sPainter) const +{ + if (isHidden) + { + return; + } + + drawNames(core, sPainter, defaultConstellationNameColor); +} + +QJsonObject scm::ScmConstellation::toJson(const QString &skyCultureId) const +{ + QJsonObject json; + + // Assemble lines object + QJsonArray linesArray; + + if (!isDarkConstellation) + { + // not a dark constellation, so we can add stars + for (const auto &star : stars) + { + linesArray.append(star.toJson()); + } + } + else + { + // dark constellation, so only add coordinates + for (const auto &coord : coordinates) + { + linesArray.append(coord.toJson()); + } + } + + json["id"] = "CON " + skyCultureId + " " + id; + json["lines"] = linesArray; + if (artwork.getHasArt() && !artworkPath.isEmpty()) + { + QFileInfo fileInfo(artworkPath); + // the '/' separator is default in all sky cultures + json["image"] = artwork.toJson("illustrations/" + fileInfo.fileName()); + } + + // Assemble common name object + QJsonObject commonNameObj; + commonNameObj["english"] = englishName; + if (nativeName.has_value()) + { + commonNameObj["native"] = nativeName.value(); + } + if (pronounce.has_value()) + { + commonNameObj["pronounce"] = pronounce.value(); + } + if (ipa.has_value()) + { + commonNameObj["ipa"] = ipa.value(); + } + if (references.has_value() && !references->isEmpty()) + { + QJsonArray refsArray; + for (const auto &ref : references.value()) + { + refsArray.append(ref); + } + commonNameObj["references"] = refsArray; + } + json["common_name"] = commonNameObj; + + return json; +} + +bool scm::ScmConstellation::saveArtwork(const QString &directory) +{ + if (!artwork.getHasArt()) + { + qWarning() << "SkyCultureMaker: The artwork of the constellation " << id << " has no art."; + return true; // Not an error just a warning + } + + QString filename = id.split(" ").back(); // Last part of id as usually used as the illustrations name + QString filepath = directory + QDir::separator() + filename + ".png"; // Write every illustrations as png + artworkPath = filepath; + return artwork.save(filepath); +} + +void scm::ScmConstellation::updateTextPosition() +{ + XYZname.set(0., 0., 0.); + for (CoordinateLine p : coordinates) + { + XYZname += p.end; + XYZname += p.start; + } + XYZname.normalize(); +} + +void scm::ScmConstellation::hide() +{ + isHidden = true; +} + +void scm::ScmConstellation::show() +{ + isHidden = false; +} diff --git a/plugins/SkyCultureMaker/src/ScmConstellation.hpp b/plugins/SkyCultureMaker/src/ScmConstellation.hpp new file mode 100644 index 0000000000000..1814a0128a6c9 --- /dev/null +++ b/plugins/SkyCultureMaker/src/ScmConstellation.hpp @@ -0,0 +1,273 @@ +/* + * Sky Culture Maker plug-in for Stellarium + * + * Copyright (C) 2025 Vincent Gerlach + * Copyright (C) 2025 Luca-Philipp Grumbach + * Copyright (C) 2025 Fabian Hofer + * Copyright (C) 2025 Mher Mnatsakanyan + * Copyright (C) 2025 Richard Hofmann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SCM_CONSTELLATION_HPP +#define SCM_CONSTELLATION_HPP + +#include "ScmConstellationArtwork.hpp" +#include "VecMath.hpp" +#include "types/CoordinateLine.hpp" +#include "types/StarLine.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace scm +{ + +class ScmConstellation +{ +public: + ScmConstellation(const QString &id, const std::vector &coordinates, + const std::vector &stars, const bool isDarkConstellation); + + /// The frame that is used for calculation and is drawn on. + static const StelCore::FrameType drawFrame = StelCore::FrameJ2000; + + /** + * @brief Gets the id of the constellation + * + * @return id + */ + QString getId() const; + + /** + * @brief Sets the english name of the constellation + * + * @param name The english name + */ + void setEnglishName(const QString &name); + + /** + * @brief Gets the english name of the constellation + * + * @return The english name + */ + QString getEnglishName() const; + + /** + * @brief Sets the native name of the constellation + * + * @param name The native name + */ + void setNativeName(const std::optional &name); + + /** + * @brief Gets the native name of the constellation + * + * @return The native name + */ + std::optional getNativeName() const; + + /** + * @brief Sets the pronounciation of the constellation + * + * @param pronounce The pronounciation + */ + void setPronounce(const std::optional &pronounce); + + /** + * @brief Gets the pronounciation of the constellation + * + * @return The pronounciation + */ + std::optional getPronounce() const; + + /** + * @brief Sets the IPA. + * + * @param ipa The optional ipa + */ + void setIPA(const std::optional &ipa); + + /** + * @brief Gets the IPA. + * + * @return The optional ipa + */ + std::optional getIPA() const; + + /** + * @brief Sets the artwork. + * + * @param artwork The artwork. + */ + void setArtwork(const ScmConstellationArtwork &artwork); + + /** + * @brief Gets the artwork. + * + * @return The artwork. + */ + const ScmConstellationArtwork &getArtwork() const; + + /** + * @brief Sets the coordinate lines and star lines of the constellation. + * + * @param coordinates The coordinates of the constellation. + * @param stars The equivalent stars to the coordinates. + */ + void setConstellation(const std::vector &coordinates, const std::vector &stars); + + /** + * @brief Gets the coordinates of the constellation. + * + * @return The coordinates of the constellation. + */ + const std::vector &getCoordinates() const; + + /** + * @brief Gets the stars of the constellation. + * + * @return The stars of the constellation. + */ + const std::vector &getStars() const; + + /** + * @brief Draws the constellation based on the coordinates. + * + * @param core The core used for drawing. + * @param color The color to use for drawing the constellation. + */ + void drawConstellation(StelCore *core, const Vec3f &lineColor, const Vec3f &labelColor) const; + + /** + * @brief Draws the constellation based on the coordinates using the default color. + * + * @param core The core used for drawing. + */ + void drawConstellation(StelCore *core) const; + + /** + * @brief Draws the label of the constellation. + * + * @param core The core used for drawing. + * @param painter The painter used for drawing. + * @param labelColor The color of the label. + */ + void drawNames(StelCore *core, StelPainter &painter, const Vec3f &labelColor) const; + + /** + * @brief Draws the label of the constellation using the default color. + * + * @param core The core used for drawing. + * @param painter The painter used for drawing. + */ + void drawNames(StelCore *core, StelPainter &painter) const; + + /** + * @brief Returns the constellation data as a JSON object. + * + * @param skyCultureId The ID of the sky culture to which this constellation belongs. + * @return QJsonObject + */ + QJsonObject toJson(const QString &skyCultureId) const; + + /** + * @brief Saves the artwork of this constellation, if art is attached, to the give filepath. + * + * @param directory The directory to the illustrations. + * @return true Successful saved. + * @return false Failed to save. + */ + bool saveArtwork(const QString &directory); + + /** + * @brief Hides the constellation from being drawn. + */ + void hide(); + + /** + * @brief Enables the constellation to be drawn. + */ + void show(); + + /** + * @brief Returns whether the constellation is a dark constellation. + * + * @return true If the constellation is a dark constellation, false otherwise. + */ + bool getIsDarkConstellation() const { return isDarkConstellation; } + +private: + /// Identifier of the constellation + QString id; + + /// The english name + QString englishName; + + /// The native name + std::optional nativeName; + + /// Native name in European glyphs, if needed. For Chinese, expect Pinyin here. + std::optional pronounce; + + /// The native name in IPA (International Phonetic Alphabet) + std::optional ipa; + + /// References to the sources of the name spellings + std::optional> references; + + /// List of coordinates forming the segments. + std::vector coordinates; + + /// List of stars forming the segments. Might be empty. + std::vector stars; + + /// Direction vector pointing on constellation name drawing position + Vec3d XYZname; + + /// The font used for constellation names + QFont constellationNameFont; + + /// The default color used for drawing the constellation + Vec3f defaultConstellationLineColor = Vec3f(0.0f, 0.0f, 0.0f); + + /// The default color used for drawing the constellation names + Vec3f defaultConstellationNameColor = Vec3f(0.0f, 0.0f, 0.0f); + + /// Holds the artwork of this constellation. + ScmConstellationArtwork artwork; + + /// Holds the path the artwork was saved to. + QString artworkPath; + + /// Whether the constellation should be drawn or not. + bool isHidden = false; + + /// Indicates if the constellation is a dark constellation. + bool isDarkConstellation = false; + + /** + * @brief Updates the XYZname that is used for the text position. + */ + void updateTextPosition(); +}; + +} // namespace scm + +#endif // SCM_CONSTELLATION_HPP diff --git a/plugins/SkyCultureMaker/src/ScmConstellationArtwork.cpp b/plugins/SkyCultureMaker/src/ScmConstellationArtwork.cpp new file mode 100644 index 0000000000000..261de634a0688 --- /dev/null +++ b/plugins/SkyCultureMaker/src/ScmConstellationArtwork.cpp @@ -0,0 +1,298 @@ +/* + * Sky Culture Maker plug-in for Stellarium + * + * Copyright (C) 2025 Vincent Gerlach + * Copyright (C) 2025 Luca-Philipp Grumbach + * Copyright (C) 2025 Fabian Hofer + * Copyright (C) 2025 Mher Mnatsakanyan + * Copyright (C) 2025 Richard Hofmann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ScmConstellationArtwork.hpp" +#include "StarMgr.hpp" +#include "StelApp.hpp" +#include "StelModuleMgr.hpp" +#include "StelTextureMgr.hpp" +#include +#include +#include +#include + +scm::ScmConstellationArtwork::ScmConstellationArtwork(const std::array &anchors, const QImage &artwork) + : anchors(anchors) + , artwork(artwork) + , hasArt(true) +{ +} + +scm::ScmConstellationArtwork::ScmConstellationArtwork() + : hasArt(false) +{ +} + +void scm::ScmConstellationArtwork::setupArt() +{ + if (hasArt == false) + { + qWarning() << "SkyCultureMaker: Failed to setup the artwork, because it has no art"; + return; + } + + StelApp &app = StelApp::getInstance(); + StarMgr *starMgr = GETSTELMODULE(StarMgr); + StelCore *core = app.getCore(); + + if (starMgr == nullptr) + { + qWarning() << "SkyCultureMaker: Failed to setup the artwork, because the starMgr is not available"; + return; + } + + artTexture = app.getTextureManager().createTexture(artwork, StelTexture::StelTextureParams(true)); + + // Coded is copied from src/modules/ConstellationMgr.cpp from loadlinesNamesAndArt + // This is done because the functions are public and not separable to only showing the Art + StelObjectP s1obj = starMgr->searchHP(anchors[0].hip); + StelObjectP s2obj = starMgr->searchHP(anchors[1].hip); + StelObjectP s3obj = starMgr->searchHP(anchors[2].hip); + + // check for null pointers + if (s1obj.isNull() || s2obj.isNull() || s3obj.isNull()) + { + qWarning() << "SkyCultureMaker: could not find stars:" << anchors[0].hip << ", " << anchors[1].hip + << "or " << anchors[2].hip; + return; + } + + const Vec3d s1 = s1obj->getJ2000EquatorialPos(core); + const Vec3d s2 = s2obj->getJ2000EquatorialPos(core); + const Vec3d s3 = s3obj->getJ2000EquatorialPos(core); + + const Vec2i &xy1 = anchors[0].position; + const Vec2i &xy2 = anchors[1].position; + const Vec2i &xy3 = anchors[2].position; + const int x1 = xy1[0]; + const int y1 = xy1[1]; + const int x2 = xy2[0]; + const int y2 = xy2[1]; + const int x3 = xy3[0]; + const int y3 = xy3[1]; + + const int texSizeX = artwork.width(); + const int texSizeY = artwork.height(); + + // To transform from texture coordinate to 2d coordinate we need to find X with XA = B + // A formed of 4 points in texture coordinate, B formed with 4 points in 3d coordinate space + // We need 3 stars and the 4th point is deduced from the others to get a normal base + // X = B inv(A) + Vec3d s4 = s1 + ((s2 - s1) ^ (s3 - s1)); + Mat4d B(s1[0], s1[1], s1[2], 1, s2[0], s2[1], s2[2], 1, s3[0], s3[1], s3[2], 1, s4[0], s4[1], s4[2], 1); + Mat4d A(x1, texSizeY - static_cast(y1), 0., 1., x2, texSizeY - static_cast(y2), 0., 1., x3, + texSizeY - static_cast(y3), 0., 1., x1, texSizeY - static_cast(y1), texSizeX, 1.); + Mat4d X = B * A.inverse(); + + // Tessellate on the plane assuming a tangential projection for the image + static const int nbPoints = 5; + QVector texCoords; + texCoords.reserve(nbPoints * nbPoints * 6); + for (int j = 0; j < nbPoints; ++j) + { + for (int i = 0; i < nbPoints; ++i) + { + texCoords << Vec2f((static_cast(i)) / nbPoints, (static_cast(j)) / nbPoints); + texCoords << Vec2f((static_cast(i) + 1.f) / nbPoints, (static_cast(j)) / nbPoints); + texCoords << Vec2f((static_cast(i)) / nbPoints, (static_cast(j) + 1.f) / nbPoints); + texCoords << Vec2f((static_cast(i) + 1.f) / nbPoints, (static_cast(j)) / nbPoints); + texCoords << Vec2f((static_cast(i) + 1.f) / nbPoints, + (static_cast(j) + 1.f) / nbPoints); + texCoords << Vec2f((static_cast(i)) / nbPoints, (static_cast(j) + 1.f) / nbPoints); + } + } + + QVector contour; + contour.reserve(texCoords.size()); + for (const auto &v : std::as_const(texCoords)) + { + Vec3d vertex = X * + Vec3d(static_cast(v[0]) * texSizeX, static_cast(v[1]) * texSizeY, 0.); + // Originally the projected texture plane remained as tangential plane. + // The vertices should however be reduced to the sphere for correct aberration: + vertex.normalize(); + contour << vertex; + } + + artPolygon.vertex = contour; + artPolygon.texCoords = texCoords; + artPolygon.primitiveType = StelVertexArray::Triangles; + + Vec3d tmp(X * Vec3d(0.5 * texSizeX, 0.5 * texSizeY, 0.)); + tmp.normalize(); + Vec3d tmp2(X * Vec3d(0., 0., 0.)); + tmp2.normalize(); + boundingCap.n = tmp; + boundingCap.d = tmp * tmp2; + + isSetup = true; +} + +void scm::ScmConstellationArtwork::setAnchor(int index, const Anchor &anchor) +{ + if (index < 0 || index >= static_cast(anchors.size())) + { + qDebug() << "SkyCultureMaker: Index ouf of bounds for setting an anchor."; + return; + } + + anchors[index] = anchor; +} + +const std::array &scm::ScmConstellationArtwork::getAnchors() const +{ + return anchors; +} + +void scm::ScmConstellationArtwork::setArtwork(const QImage &artwork) +{ + ScmConstellationArtwork::artwork = artwork; + hasArt = true; +} + +const QImage &scm::ScmConstellationArtwork::getArtwork() const +{ + return artwork; +} + +bool scm::ScmConstellationArtwork::getHasArt() const +{ + return hasArt; +} + +void scm::ScmConstellationArtwork::reset() +{ + hasArt = false; + isSetup = false; +} + +void scm::ScmConstellationArtwork::draw(StelCore *core) const +{ + const StelProjectorP prj = core->getProjection(StelCore::FrameJ2000); + StelPainter painter(prj); + draw(core, painter); +} + +void scm::ScmConstellationArtwork::draw(StelCore *core, StelPainter &painter) const +{ + if (hasArt == false) + { + return; + } + + if (isSetup == false) + { + qWarning() << "SkyCultureMaker: Failed to draw the artwork: call setup first"; + return; + } + + painter.setBlending(true, GL_ONE, GL_ONE); + painter.setCullFace(true); + + Vec3d vel(0.); + if (core->getUseAberration()) + { + vel = core->getAberrationVec(core->getJDE()); + } + + SphericalRegionP region = painter.getProjector()->getViewportConvexPolygon(); + drawOptimized(painter, *region, vel); +} + +QJsonObject scm::ScmConstellationArtwork::toJson(const QString &relativePath) const +{ + QJsonObject json; + + json["file"] = relativePath; + + QJsonArray artworkSizeJson; + artworkSizeJson.append(artwork.width()); + artworkSizeJson.append(artwork.height()); + json["size"] = artworkSizeJson; + + QJsonArray anchorsJson; + + for (const auto &anchor : anchors) + { + QJsonObject anchorJson; + + auto &pos = anchor.position; + QJsonArray posAnchorJson; + posAnchorJson.append(pos[0]); + posAnchorJson.append(pos[1]); + anchorJson["pos"] = posAnchorJson; + + anchorJson["hip"] = anchor.hip; + + anchorsJson.append(anchorJson); + } + + json["anchors"] = anchorsJson; + + return json; +} + +bool scm::ScmConstellationArtwork::save(const QString &filepath) const +{ + QFileInfo fileInfo(filepath); + + // Create folder structure if not existing + bool success = fileInfo.absoluteDir().mkpath(fileInfo.absolutePath()); + if (success == false) + { + qWarning() << "SkyCultureMaker: Failed to create the directory structure for: '" + << fileInfo.absolutePath() << "'"; + return false; + } + + success = artwork.save(fileInfo.absoluteFilePath()); + if (success == false) + { + qWarning() << "SkyCultureMaker: Failed to save the image to the given path: '" + << fileInfo.absoluteFilePath() << "'"; + return false; + } + + return true; +} + +void scm::ScmConstellationArtwork::drawOptimized(StelPainter &sPainter, const SphericalRegion ®ion, + const Vec3d &obsVelocity) const +{ + const float intensity = artOpacity * artIntensityFovScale; + if (artTexture && intensity > 0.0f && region.intersects(boundingCap)) + { + sPainter.setColor(intensity, intensity, intensity); + + // The texture is not fully loaded + if (artTexture->bind() == false) return; + +#ifdef Q_OS_LINUX + // Unfortunately applying aberration to the constellation artwork causes ugly artifacts visible on Linux. + // It is better to disable aberration in this case and have a tiny texture shift where it usually does not need to critically match. + sPainter.drawStelVertexArray(artPolygon, false, Vec3d(0.)); +#else + sPainter.drawStelVertexArray(artPolygon, false, obsVelocity); +#endif + } +} diff --git a/plugins/SkyCultureMaker/src/ScmConstellationArtwork.hpp b/plugins/SkyCultureMaker/src/ScmConstellationArtwork.hpp new file mode 100644 index 0000000000000..58a79f61f6cc0 --- /dev/null +++ b/plugins/SkyCultureMaker/src/ScmConstellationArtwork.hpp @@ -0,0 +1,161 @@ +/* + * Sky Culture Maker plug-in for Stellarium + * + * Copyright (C) 2025 Vincent Gerlach + * Copyright (C) 2025 Luca-Philipp Grumbach + * Copyright (C) 2025 Fabian Hofer + * Copyright (C) 2025 Mher Mnatsakanyan + * Copyright (C) 2025 Richard Hofmann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SCMCONSTELLATIONARTWORK_H +#define SCMCONSTELLATIONARTWORK_H + +#include "StelCore.hpp" +#include "StelPainter.hpp" +#include "StelSphereGeometry.hpp" +#include "VecMath.hpp" +#include "types/Anchor.hpp" +#include +#include +#include + +namespace scm +{ +class ScmConstellationArtwork +{ +public: + ScmConstellationArtwork(); + ScmConstellationArtwork(const std::array &anchors, const QImage &artwork); + + /** + * @brief Setups the artwork in the frame based on the anchors. + */ + void setupArt(); + + /** + * @brief Sets an anchor value at the selected index. + * + * @param index The index to set the anchor. + * @param anchor The anchor to be set. + */ + void setAnchor(int index, const Anchor &anchor); + + /** + * @brief Gets the anchors of the artwork. + * + * @return const std::array& The artwork anchors. + */ + const std::array &getAnchors() const; + + /** + * @brief Sets the size of the artwork in pixels. + * + * @param Artwork The size of the artwork. + */ + void setArtwork(const QImage &artwork); + + /** + * @brief Gets the artwork. + * + * @return const QPixmap& The artwork. + */ + const QImage &getArtwork() const; + + /** + * @brief Get the indicator if the artwork contains art. + * + * @return true Contains art. + * @return false Does not contain art. + */ + bool getHasArt() const; + + /** + * @brief Draws the artwork. + * + * @param core The core to use to draw the artwork. + */ + void draw(StelCore *core) const; + + /** + * @brief Draws the artwork. + * + * @param core The core to use to draw the artwork. + */ + void draw(StelCore *core, StelPainter &painter) const; + + /** + * @brief Converts the artwork into stalleriums json format. + * + * @param relativePath The relative path to the artwork on disk. + * @return QJsonObject The json format. + */ + QJsonObject toJson(const QString &relativePath) const; + + /** + * @brief Writes the image of the artwork to the disk. + * + * @param filepath The filepath to write the artwork to. + * @return true Successful saved. + * @return false Failed to save. + */ + bool save(const QString &filepath) const; + + /** + * @brief Resets the artwork state. + */ + void reset(); + +private: + /** + * @brief Draw the artwork in an optimized manner. + * + * @param sPainter The painter to draw with. + * @param region The region to draw in. + * @param obsVelocity The velocity of the observer for correction. + */ + void drawOptimized(StelPainter &sPainter, const SphericalRegion ®ion, const Vec3d &obsVelocity) const; + + /// Holds the anchors of the artwork. + std::array anchors; + + /// Holds the the artwork. + QImage artwork; + + /// Holds the artwork as texture to draw. + StelTextureSP artTexture; + + /// Holds the vertices the artwork texture is drawn on. + StelVertexArray artPolygon; + + /// Holds the bounding cap of the artwork. + SphericalCap boundingCap; + + /// Holds the intensity scale based on the Field of View. + float artIntensityFovScale = 1.0f; + + /// Holds the opacity of the art. + float artOpacity = 0.42; + + /// Indicates if the artwork has an image that can be drawn. + bool hasArt = false; + + /// Indicates if the setup was run. + bool isSetup = false; +}; +} // namespace scm + +#endif // SCMCONSTELLATIONARTWORK_H diff --git a/plugins/SkyCultureMaker/src/ScmDraw.cpp b/plugins/SkyCultureMaker/src/ScmDraw.cpp new file mode 100644 index 0000000000000..1f46e010c9017 --- /dev/null +++ b/plugins/SkyCultureMaker/src/ScmDraw.cpp @@ -0,0 +1,518 @@ +/* + * Sky Culture Maker plug-in for Stellarium + * + * Copyright (C) 2025 Vincent Gerlach + * Copyright (C) 2025 Luca-Philipp Grumbach + * Copyright (C) 2025 Fabian Hofer + * Copyright (C) 2025 Mher Mnatsakanyan + * Copyright (C) 2025 Richard Hofmann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ScmDraw.hpp" +#include "StelActionMgr.hpp" +#include "StelModule.hpp" +#include "StelMovementMgr.hpp" +#include "StelProjector.hpp" +#include +#include +#include +#include +#include + +void scm::ScmDraw::setSearchMode(bool active) +{ + // search mode deactivates before the star is set by the search + if (inSearchMode == true && active == false) + { + // only allow search and find for normal constellations + if(drawingMode == DrawingMode::StarsAndDSO) + { + selectedStarIsSearched = true; + } + + // HACK an Ctrl + Release is not triggered if Ctrl + F is trigger it manually + QKeyEvent release = QKeyEvent(QEvent::KeyRelease, Qt::Key_Control, Qt::NoModifier); + QWidget *mainView = qApp->activeWindow(); + + if (mainView) + { + QApplication::sendEvent(mainView, &release); + } + else + { + qDebug() << "SkyCultureMaker: Failed to release Ctrl key"; + } + } + + inSearchMode = active; +} + +void scm::ScmDraw::appendDrawPoint(const Vec3d &point, const std::optional &starID) +{ + if (hasFlag(drawState, (Drawing::hasStart | Drawing::hasFloatingEnd))) + { + std::get(currentLine).end = point; + std::get(currentLine).end = starID; + drawState = Drawing::hasEnd; + + drawnLines.coordinates.push_back(std::get(currentLine)); + drawnLines.stars.push_back(std::get(currentLine)); + std::get(currentLine).start = point; + std::get(currentLine).start = starID; + drawState = drawState | Drawing::hasStart; + } + else + { + std::get(currentLine).start = point; + std::get(currentLine).start = starID; + drawState = Drawing::hasStart; + } +} + +void scm::ScmDraw::setMoveToAnotherStart() +{ + if (selectedStarIsSearched == true) + { + if (activeTool == DrawTools::Pen) + { + StelApp &app = StelApp::getInstance(); + StelCore *core = app.getCore(); + + StelObjectMgr &objectMgr = app.getStelObjectMgr(); + + if (objectMgr.getWasSelected()) + { + StelObjectP stelObj = objectMgr.getLastSelectedObject(); + Vec3d stelPos = stelObj->getJ2000EquatorialPos(core); + appendDrawPoint(stelPos, stelObj->getID()); + } + } + + selectedStarIsSearched = false; + } +} + +const Vec2d scm::ScmDraw::defaultLastEraserPos(std::nan("1"), std::nan("1")); + +bool scm::ScmDraw::segmentIntersect(const Vec2d &startA, const Vec2d &directionA, const Vec2d &startB, + const Vec2d &directionB) +{ + if (std::abs(directionA.dot(directionB)) < std::numeric_limits::epsilon()) // check with near zero value + { + // No intersection if lines are parallel + return false; + } + + // Also see: https://www.sunshine2k.de/coding/javascript/lineintersection2d/LineIntersect2D.html + // endA = startA + s * directionA with s=1 + double s = perpDot(directionB, startB - startA) / perpDot(directionB, directionA); + // endB = startB + t * directionB with t=1 + double t = perpDot(directionA, startA - startB) / perpDot(directionA, directionB); + + return 0 <= s && s <= 1 && 0 <= t && t <= 1; +} + +scm::ScmDraw::ScmDraw() + : drawState(Drawing::None) + , drawingMode(DrawingMode::StarsAndDSO) +{ + QSettings *conf = StelApp::getInstance().getSettings(); + conf->beginGroup("SkyCultureMaker"); + fixedLineColor = Vec3f(conf->value("fixedLineColor", "1.0,0.5,0.5").toString()); + fixedLineAlpha = conf->value("fixedLineAlpha", 1.0).toFloat(); + floatingLineColor = Vec3f(conf->value("floatingLineColor", "1.0,0.7,0.7").toString()); + floatingLineAlpha = conf->value("floatingLineAlpha", 0.5).toFloat(); + maxSnapRadiusInPixels = conf->value("maxSnapRadiusInPixels", 25).toUInt(); + conf->endGroup(); + + std::get(currentLine).start.set(0, 0, 0); + std::get(currentLine).end.set(0, 0, 0); + lastEraserPos.set(std::nan("1"), std::nan("1")); + + StelApp &app = StelApp::getInstance(); + StelCore *core = app.getCore(); + maxSnapRadiusInPixels *= core->getCurrentStelProjectorParams().devicePixelsPerPixel; + + StelActionMgr *actionMgr = app.getStelActionManager(); + auto action = actionMgr->findAction(id_search_window); + connect(action, &StelAction::toggled, this, &ScmDraw::setSearchMode); + + StelMovementMgr *mvmMgr = core->getMovementMgr(); + connect(mvmMgr, &StelMovementMgr::flagTrackingChanged, this, &ScmDraw::setMoveToAnotherStart); +} + +void scm::ScmDraw::drawLine(StelCore *core) const +{ + StelPainter painter(core->getProjection(drawFrame)); + painter.setBlending(true); + painter.setLineSmooth(true); + painter.setColor(fixedLineColor, fixedLineAlpha); + + for (CoordinateLine p : drawnLines.coordinates) + { + painter.drawGreatCircleArc(p.start, p.end); + } + + if (hasFlag(drawState, Drawing::hasFloatingEnd)) + { + painter.setColor(floatingLineColor, floatingLineAlpha); + painter.drawGreatCircleArc(std::get(currentLine).start, + std::get(currentLine).end); + } +} + +void scm::ScmDraw::handleMouseClicks(class QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) + { + // do not interfere with navigation and draw or erase anything + isNavigating |= (event->type() == QEvent::MouseButtonPress); + isNavigating &= (event->type() != QEvent::MouseButtonRelease); + + return; + } + + if (isNavigating) + { + return; + } + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + qreal x = event->position().x(), y = event->position().y(); +#else + qreal x = event->x(), y = event->y(); +#endif + + if (activeTool == DrawTools::Pen) + { + // Draw line + if (event->button() == Qt::RightButton && event->type() == QEvent::MouseButtonPress) + { + StelApp &app = StelApp::getInstance(); + StelCore *core = app.getCore(); + + if (drawingMode == DrawingMode::StarsAndDSO) + { + StelObjectMgr &objectMgr = app.getStelObjectMgr(); + if (objectMgr.getWasSelected()) + { + StelObjectP stelObj = objectMgr.getLastSelectedObject(); + if ((stelObj->getType() == "Star" || stelObj->getType() == "Nebula") && + !stelObj->getID().trimmed().isEmpty()) + { + appendDrawPoint(stelObj->getJ2000EquatorialPos(core), stelObj->getID()); + qDebug() + << "SkyCultureMaker: Added star/nebula to constellation with ID" + << stelObj->getID(); + } + else if (stelObj->getID().trimmed().isEmpty()) + { + qDebug() << "SkyCultureMaker: Ignored star/nebula with empty ID"; + } + } + } + else if (drawingMode == DrawingMode::Coordinates) + { + StelProjectorP prj = core->getProjection(drawFrame); + Vec3d point; + prj->unProject(x, y, point); + + // Snap to nearest point if close enough + if (auto nearest = findNearestPoint(x, y, prj); nearest.has_value()) + { + point = nearest->coordinate; + } + qDebug() << "SkyCultureMaker: Added point to constellation at" + << QString::number(point.v[0]) + "," + QString::number(point.v[1]) + "," + + QString::number(point.v[2]); + appendDrawPoint(point, std::nullopt); + } + + event->accept(); + return; + } + + // Reset line drawing + if (event->button() == Qt::RightButton && event->type() == QEvent::MouseButtonDblClick && + hasFlag(drawState, Drawing::hasEnd)) + { + if (!drawnLines.coordinates.empty()) + { + drawnLines.coordinates.pop_back(); + drawnLines.stars.pop_back(); + } + drawState = Drawing::None; + event->accept(); + return; + } + } + else if (activeTool == DrawTools::Eraser) + { + if (event->button() == Qt::RightButton) + { + if (event->type() == QEvent::MouseButtonPress) + { + lastEraserPos = Vec2d(x, y); + } + else if (event->type() == QEvent::MouseButtonRelease) + { + lastEraserPos = defaultLastEraserPos; + } + } + } +} + +bool scm::ScmDraw::handleMouseMoves(int x, int y, Qt::MouseButtons b) +{ + StelApp &app = StelApp::getInstance(); + StelCore *core = app.getCore(); + + if (activeTool == DrawTools::Pen) + { + Vec3d position(0, 0, 0); + + if (drawingMode == DrawingMode::StarsAndDSO) + { + // this wouldve been easier with cleverFind but that is private + StelObjectMgr &objectMgr = app.getStelObjectMgr(); + bool found = objectMgr.findAndSelect(core, x, y); + if (found && objectMgr.getWasSelected()) + { + StelObjectP stelObj = objectMgr.getLastSelectedObject(); + // only keep the selection if a star or nebula was selected + if (stelObj->getType() != "Star" && stelObj->getType() != "Nebula") + { + objectMgr.unSelect(); + } + // also unselect if the id is empty or only whitespace + else if (stelObj->getID().trimmed().isEmpty()) + { + objectMgr.unSelect(); + } + // snap to the star and update the line position + else + { + position = stelObj->getJ2000EquatorialPos(core); + } + } + } + + if (hasFlag(drawState, (Drawing::hasStart | Drawing::hasFloatingEnd))) + { + // no selection, compute the position from the mouse cursor + if (position == Vec3d(0, 0, 0)) + { + std::get(currentLine).end = position; + StelProjectorP prj = core->getProjection(drawFrame); + prj->unProject(x, y, position); + } + std::get(currentLine).end = position; + drawState = Drawing::hasFloatingEnd; + } + } + else if (activeTool == DrawTools::Eraser) + { + if (b.testFlag(Qt::MouseButton::RightButton)) + { + Vec2d currentPos(x, y); + + if (lastEraserPos != defaultLastEraserPos && lastEraserPos != currentPos) + { + StelApp &app = StelApp::getInstance(); + StelCore *core = app.getCore(); + StelProjectorP prj = core->getProjection(drawFrame); + auto mouseDirection = lastEraserPos - currentPos; + + std::vector erasedIndices; + + for (auto line = drawnLines.coordinates.begin(); line != drawnLines.coordinates.end(); + ++line) + { + Vec3d lineEnd, lineStart; + prj->project(line->start, lineStart); + prj->project(line->end, lineEnd); + Vec2d lineStart2d(lineStart.v[0], lineStart.v[1]); + Vec2d lineEnd2d(lineEnd.v[0], lineEnd.v[1]); + auto lineDirection = lineEnd2d - lineStart2d; + + bool intersect = segmentIntersect(currentPos, mouseDirection, lineStart2d, + lineDirection); + if (intersect) + { + erasedIndices.push_back( + std::distance(drawnLines.coordinates.begin(), line)); + } + } + + for (auto index : erasedIndices) + { + drawnLines.coordinates[index] = drawnLines.coordinates.back(); + drawnLines.coordinates.pop_back(); + } + } + + lastEraserPos = currentPos; + } + } + + // We always return false as we still want to navigate in Stellarium with left mouse button + return false; +} + +void scm::ScmDraw::handleKeys(QKeyEvent *e) +{ + if (activeTool == DrawTools::Pen) + { + if (e->key() == Qt::Key::Key_Z && e->modifiers() == Qt::Modifier::CTRL) + { + undoLastLine(); + e->accept(); + } + } +} + +void scm::ScmDraw::undoLastLine() +{ + if (!drawnLines.coordinates.empty()) + { + currentLine = std::make_tuple(drawnLines.coordinates.back(), drawnLines.stars.back()); + drawnLines.coordinates.pop_back(); + drawnLines.stars.pop_back(); + drawState = Drawing::hasFloatingEnd; + } + else + { + drawState = Drawing::None; + } +} + +std::vector scm::ScmDraw::getStars() const +{ + bool all_stars = std::all_of(drawnLines.stars.begin(), drawnLines.stars.end(), [](const StarLine &star) + { return star.start.has_value() && star.end.has_value(); }); + + if (all_stars) + { + return drawnLines.stars; + } + + return std::vector(); +} + +std::vector scm::ScmDraw::getCoordinates() const +{ + return drawnLines.coordinates; +} + +void scm::ScmDraw::loadLines(const std::vector &coordinates, const std::vector &stars) +{ + // copy the coordinates and stars to drawnLines + drawnLines.coordinates = coordinates; + drawnLines.stars = stars; + + if (!drawnLines.coordinates.empty()) + { + currentLine = std::make_tuple(drawnLines.coordinates.back(), drawnLines.stars.back()); + drawState = Drawing::hasFloatingEnd; + } + else + { + currentLine = std::make_tuple(CoordinateLine(), StarLine()); + drawState = Drawing::None; + } +} + +void scm::ScmDraw::setTool(scm::DrawTools tool) +{ + activeTool = tool; + lastEraserPos = defaultLastEraserPos; + drawState = Drawing::None; +} + +std::optional scm::ScmDraw::findNearestPoint(int x, int y, StelProjectorP prj) const +{ + if (drawnLines.coordinates.empty()) + { + return {}; + } + + auto min = drawnLines.coordinates.begin(); + Vec3d position(x, y, 0); + Vec3d minPosition; + prj->project(min->start, minPosition); + double minDistance = (minPosition - position).dot(minPosition - position); + bool isStartPoint = true; + + for (auto line = drawnLines.coordinates.begin(); line != drawnLines.coordinates.end(); ++line) + { + Vec3d iPosition; + if (prj->project(line->start, iPosition)) + { + double distance = (iPosition - position).dot(iPosition - position); + if (distance < minDistance) + { + min = line; + minPosition = iPosition; + minDistance = distance; + isStartPoint = true; + } + } + + if (prj->project(line->end, iPosition)) + { + double distance = (iPosition - position).dot(iPosition - position); + if (distance < minDistance) + { + min = line; + minPosition = iPosition; + minDistance = distance; + isStartPoint = false; + } + } + } + + if (minDistance < maxSnapRadiusInPixels * maxSnapRadiusInPixels) + { + if (isStartPoint) + { + SkyPoint point = {min->start, + drawnLines.stars.at(std::distance(drawnLines.coordinates.begin(), min)).start}; + return point; + } + else + { + SkyPoint point = {min->end, + drawnLines.stars.at(std::distance(drawnLines.coordinates.begin(), min)).end}; + return point; + } + } + + return {}; +} + +void scm::ScmDraw::resetDrawing() +{ + drawnLines.coordinates.clear(); + drawnLines.stars.clear(); + drawState = Drawing::None; + lastEraserPos = defaultLastEraserPos; + activeTool = DrawTools::None; + drawingMode = DrawingMode::StarsAndDSO; + std::get(currentLine).start.set(0, 0, 0); + std::get(currentLine).end.set(0, 0, 0); + std::get(currentLine).start.reset(); + std::get(currentLine).end.reset(); +} diff --git a/plugins/SkyCultureMaker/src/ScmDraw.hpp b/plugins/SkyCultureMaker/src/ScmDraw.hpp new file mode 100644 index 0000000000000..b641427ed642f --- /dev/null +++ b/plugins/SkyCultureMaker/src/ScmDraw.hpp @@ -0,0 +1,228 @@ +/* + * Sky Culture Maker plug-in for Stellarium + * + * Copyright (C) 2025 Vincent Gerlach + * Copyright (C) 2025 Luca-Philipp Grumbach + * Copyright (C) 2025 Fabian Hofer + * Copyright (C) 2025 Mher Mnatsakanyan + * Copyright (C) 2025 Richard Hofmann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SCMDRAW_H +#define SCMDRAW_H + +#include "StelCore.hpp" +#include "StelObjectMgr.hpp" +#include "StelObjectType.hpp" +#include "enumBitops.hpp" +#include "types/CoordinateLine.hpp" +#include "types/DrawTools.hpp" +#include "types/Drawing.hpp" +#include "types/DrawingMode.hpp" +#include "types/Lines.hpp" +#include "types/StarLine.hpp" +#include "types/SkyPoint.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace scm +{ + +class ScmDraw : public QObject +{ +private: + static constexpr const char id_search_window[] = "actionShow_Search_Window_Global"; + static const Vec2d defaultLastEraserPos; + + /// Color of fixed drawn lines. + Vec3f fixedLineColor = Vec3f(1.0f, 0.5f, 0.5f); + /// Alpha of fixed drawn lines. + float fixedLineAlpha = 1.0f; + /// Color of floating drawn lines. + Vec3f floatingLineColor = Vec3f(1.0f, 0.7f, 0.7f); + /// Alpha of floating drawn lines. + float floatingLineAlpha = 0.5f; + + /// The search radius to attach to a point on a existing line. + uint32_t maxSnapRadiusInPixels = 25; + + /// Indicates that the startPoint has been set. + Drawing drawState = Drawing::None; + + /// The current drawing mode. + DrawingMode drawingMode = DrawingMode::StarsAndDSO; + + /// The current pending point. + std::tuple currentLine; + + /// The fixed points. + Lines drawnLines; + + /// The current active tool. + DrawTools activeTool = DrawTools::None; + + /// Indicates if the user is navigating in stellarium i.e. changing position of camera + bool isNavigating = false; + + /// Indicates if the users searches for a star. + bool inSearchMode = false; + + /// Indicates if the currently selected star was searched. + bool selectedStarIsSearched = false; + + /// Holds the position of the eraser on the last frame. + Vec2d lastEraserPos = ScmDraw::defaultLastEraserPos; + + /** + * @brief Appends a draw point to the list of drawn points. + * + * @param point The coordinate in J2000 frame. + * @param starID The id of the star to use. + */ + void appendDrawPoint(const Vec3d &point, const std::optional &starID); + + /** + * @brief Indicates if two segments intersect. + * + * @param startA The start point of A. + * @param directionA The direction vector of A pointing to the end point of A. + * @param startB The start point of B. + * @param directionB The direction vector of B pointing to the end point of B. + * @return true When both segments intersect. + * @return false When both segments do NOT intersect. + */ + static bool segmentIntersect(const Vec2d &startA, const Vec2d &directionA, const Vec2d &startB, + const Vec2d &directionB); + + /** + * @brief Calculates the perpendicular dot product vector of a and b i.e. a^T dot b + * + * @tparam T The type of the vector + * @param a The first vector. + * @param b The second vector. + * @return T The perp dot product of a and b. + */ + template + static T perpDot(const Vector2 &a, const Vector2 &b) + { + return -a.v[1] * b.v[0] + a.v[0] * b.v[1]; + } + +public slots: + /** + * @brief Is called when the search dialog is opend and closed. + */ + void setSearchMode(bool b); + + /** + * @brief Is called when the the user is moved to another star. + */ + void setMoveToAnotherStart(); + +public: + /// The frame that is used for calculation and is drawn on. + static const StelCore::FrameType drawFrame = StelCore::FrameJ2000; + + ScmDraw(); + + /** + * @brief Draws the line between the start and the current end point. + * + * @param core The core used for drawing the line. + */ + void drawLine(StelCore *core) const; + + /// Handle mouse clicks. Please note that most of the interactions will be done through the GUI module. + /// @return set the event as accepted if it was intercepted + void handleMouseClicks(QMouseEvent *); + + /// Handle mouse moves. Please note that most of the interactions will be done through the GUI module. + /// @return true if the event was intercepted + bool handleMouseMoves(int x, int y, Qt::MouseButtons b); + + /// Handle key events. Please note that most of the interactions will be done through the GUI module. + /// @param e the Key event + /// @return set the event as accepted if it was intercepted + void handleKeys(QKeyEvent *e); + + /** + * @brief Finds the nearest sky point to the given position. + * + * @param x The x viewport coordinate of the mouse. + * @param y The y viewport coordinate of the mouse. + * @param prj The projector to use for the calculation. + * @return std::optional A point in the sky or std::nullopt if no point was found. + */ + std::optional findNearestPoint(int x, int y, StelProjectorP prj) const; + + /// Undo the last drawn line. + void undoLastLine(); + + /** + * @brief Get the drawn stick figures as stars if available. + * + * @return std::vector The optional filled vector of stars matching the coordinates. + */ + std::vector getStars() const; + + /** + * @brief Get the drawn stick figures as coordinates. + * + * @return std::vector The drawn coordinates. + */ + std::vector getCoordinates() const; + + /** + * @brief Loads lines into the buffer from a tuple of coordinates and stars. + * + */ + void loadLines(const std::vector &coordinates, const std::vector &stars); + + /** + * @brief Set the active draw tool + * + * @param tool The tool to be used. + */ + void setTool(DrawTools tool); + + /** + * @brief Sets the drawing mode. + * + * @param mode The drawing mode to use. + */ + void setDrawingMode(DrawingMode mode) { drawingMode = mode; } + + /** + * @brief Resets the currently drawn lines. + */ + void resetDrawing(); +}; + +} // namespace scm + +// Opt In for Drawing to use bitops & and | +template<> +struct generic_enum_bitops::allow_bitops +{ + static constexpr bool value = true; +}; + +#endif // SCMDRAW_H diff --git a/plugins/SkyCultureMaker/src/ScmSkyCulture.cpp b/plugins/SkyCultureMaker/src/ScmSkyCulture.cpp new file mode 100644 index 0000000000000..f7ceab8652562 --- /dev/null +++ b/plugins/SkyCultureMaker/src/ScmSkyCulture.cpp @@ -0,0 +1,200 @@ +/* + * Sky Culture Maker plug-in for Stellarium + * + * Copyright (C) 2025 Vincent Gerlach + * Copyright (C) 2025 Luca-Philipp Grumbach + * Copyright (C) 2025 Fabian Hofer + * Copyright (C) 2025 Mher Mnatsakanyan + * Copyright (C) 2025 Richard Hofmann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ScmSkyCulture.hpp" +#include "types/Classification.hpp" +#include +#include + +void scm::ScmSkyCulture::setId(const QString &id) +{ + ScmSkyCulture::id = id; +} + +void scm::ScmSkyCulture::setFallbackToInternationalNames(bool fallback) +{ + ScmSkyCulture::fallbackToInternationalNames = fallback; +} + +scm::ScmConstellation &scm::ScmSkyCulture::addConstellation(const QString &id, + const std::vector &coordinates, + const std::vector &stars, + const bool isDarkConstellation) +{ + scm::ScmConstellation constellationObj(id, coordinates, stars, isDarkConstellation); + constellations.push_back(std::move(constellationObj)); + return constellations.back(); +} + +void scm::ScmSkyCulture::removeConstellation(const QString &id) +{ + constellations.erase(remove_if(begin(constellations), end(constellations), + [id](ScmConstellation const &c) { return c.getId() == id; }), + end(constellations)); +} + +scm::ScmConstellation *scm::ScmSkyCulture::getConstellation(const QString &id) +{ + for (auto &constellation : constellations) + { + if (constellation.getId() == id) return &constellation; + } + return nullptr; +} + +std::vector *scm::ScmSkyCulture::getConstellations() +{ + return &constellations; +} + +QJsonObject scm::ScmSkyCulture::toJson() const +{ + QJsonObject scJsonObj; + scJsonObj["id"] = id; + // for some reason, the classification is inside an array, eg. ["historical"] + QJsonArray classificationArray = QJsonArray::fromStringList( + QStringList() << classificationTypeToString(description.classification)); + scJsonObj["classification"] = classificationArray; + scJsonObj["fallback_to_international_names"] = fallbackToInternationalNames; + QJsonArray constellationsArray; + for (const auto &constellation : constellations) + { + constellationsArray.append(constellation.toJson(id)); + } + scJsonObj["constellations"] = constellationsArray; + + return scJsonObj; +} + +void scm::ScmSkyCulture::draw(StelCore *core) const +{ + for (auto &constellation : constellations) + { + constellation.drawConstellation(core); + } +} + +void scm::ScmSkyCulture::setDescription(const scm::Description &description) +{ + ScmSkyCulture::description = description; +} + +bool scm::ScmSkyCulture::saveDescriptionAsMarkdown(QFile &file) +{ + if (file.open(QIODevice::WriteOnly | QIODevice::Text)) + { + const scm::Description &desc = ScmSkyCulture::description; + const scm::License license = scm::LICENSES.at(desc.license); + // the sky heading is not only needed for the sky description, but also for the subsections + const bool hasSkyHeading = !desc.moonAndSun.trimmed().isEmpty() || !desc.planets.trimmed().isEmpty() || + !desc.zodiac.trimmed().isEmpty() || + !desc.constellations.trimmed().isEmpty() || + !desc.milkyWay.trimmed().isEmpty() || !desc.otherObjects.trimmed().isEmpty(); + + QTextStream out(&file); + out << "# " << desc.name << "\n\n"; + + out << "## Culture Description\n" << desc.cultureDescription << "\n\n"; + + if (hasSkyHeading) + { + out << "## Sky\n"; + } + if (!desc.sky.trimmed().isEmpty()) + { + out << desc.sky << "\n\n"; + } + else if (hasSkyHeading) + { + // add a newline if there is a sky heading but no sky description + out << "\n"; + } + if (!desc.moonAndSun.trimmed().isEmpty()) + { + out << "### Moon and Sun\n" << desc.moonAndSun << "\n\n"; + } + if (!desc.planets.trimmed().isEmpty()) + { + out << "### Planets\n" << desc.planets << "\n\n"; + } + if (!desc.zodiac.trimmed().isEmpty()) + { + out << "### Zodiac\n" << desc.zodiac << "\n\n"; + } + if (!desc.constellations.trimmed().isEmpty()) + { + out << "### Constellations\n" << desc.constellations << "\n\n"; + } + if (!desc.milkyWay.trimmed().isEmpty()) + { + out << "### Milky Way\n" << desc.milkyWay << "\n\n"; + } + if (!desc.otherObjects.trimmed().isEmpty()) + { + out << "### Other Celestial Objects\n" << desc.otherObjects << "\n\n"; + } + + out << "## About\n" << desc.about << "\n\n"; + out << "### Authors\n" << desc.authors << "\n\n"; + if (!desc.acknowledgements.trimmed().isEmpty()) + { + out << "### Acknowledgements\n" << desc.acknowledgements << "\n\n"; + } + + out << "## References\n" << desc.references << "\n\n"; + + out << "## License\n\n" << license.name << "\n\n"; + + try + { + file.close(); + return true; // successfully saved + } + catch (const std::exception &e) + { + qWarning("SkyCultureMaker: Error closing file: %s", e.what()); + return false; // error occurred while closing the file + } + } + else + { + qWarning("SkyCultureMaker: Could not open file for writing: %s", qPrintable(file.fileName())); + return false; // file could not be opened + } +} + +bool scm::ScmSkyCulture::saveIllustrations(const QString &directory) +{ + bool success = true; + for (auto &constellation : constellations) + { + success &= constellation.saveArtwork(directory); + } + + return success; +} + +const QString &scm::ScmSkyCulture::getId() const +{ + return id; +} diff --git a/plugins/SkyCultureMaker/src/ScmSkyCulture.hpp b/plugins/SkyCultureMaker/src/ScmSkyCulture.hpp new file mode 100644 index 0000000000000..4d0df71393537 --- /dev/null +++ b/plugins/SkyCultureMaker/src/ScmSkyCulture.hpp @@ -0,0 +1,118 @@ +/* + * Sky Culture Maker plug-in for Stellarium + * + * Copyright (C) 2025 Vincent Gerlach + * Copyright (C) 2025 Luca-Philipp Grumbach + * Copyright (C) 2025 Fabian Hofer + * Copyright (C) 2025 Mher Mnatsakanyan + * Copyright (C) 2025 Richard Hofmann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SCM_SKYCULTURE_HPP +#define SCM_SKYCULTURE_HPP + +#include +// #include +#include + +#include "ScmConstellation.hpp" +#include "StelCore.hpp" +#include "StelSkyCultureMgr.hpp" +#include "types/Classification.hpp" +#include "types/CoordinateLine.hpp" +#include "types/Description.hpp" +#include "types/License.hpp" +#include "types/StarLine.hpp" + +namespace scm +{ + +class ScmSkyCulture +{ +public: + /// Sets the id of the sky culture + void setId(const QString &id); + + /** + * @brief Gets the id of the sky culture. + */ + const QString &getId() const; + + /// Sets whether to show common names in addition to the culture-specific ones + void setFallbackToInternationalNames(bool fallback); + + /// Adds a constellation to the sky culture + ScmConstellation &addConstellation(const QString &id, const std::vector &coordinates, + const std::vector &stars, const bool isDarkConstellation); + + /// Removes a constellation from the sky culture by its ID + void removeConstellation(const QString &id); + + /// Gets a constellation from the sky culture by its ID + ScmConstellation *getConstellation(const QString &id); + + /// Returns a pointer to the constellations of the sky culture + std::vector *getConstellations(); + + /** + * @brief Returns the sky culture as a JSON object + */ + QJsonObject toJson() const; + + /** + * @brief Draws the sky culture. + */ + void draw(StelCore *core) const; + + /** + * @brief Sets the description of the sky culture. + * @param description The description to set. + */ + void setDescription(const scm::Description &description); + + /** + * @brief Saves the current sky culture description as markdown text. + * @param file The file to save the description to. + * @return true if the description was saved successfully, false otherwise. + */ + bool saveDescriptionAsMarkdown(QFile &file); + + /** + * @brief Saves all illustrations to the directory. No subdirectory is saved. + * + * @param directory The directory the illustrations are saved in. + * @return true Successful saved. + * @return false Failed to save. + */ + bool saveIllustrations(const QString &directory); + +private: + /// Sky culture identifier + QString id; + + /// Whether to show common names in addition to the culture-specific ones + bool fallbackToInternationalNames = false; + + /// The constellations of the sky culture + std::vector constellations; + + /// The description of the sky culture + scm::Description description; +}; + +} // namespace scm + +#endif // SCM_SKYCULTURE_HPP diff --git a/plugins/SkyCultureMaker/src/SkyCultureMaker.cpp b/plugins/SkyCultureMaker/src/SkyCultureMaker.cpp new file mode 100644 index 0000000000000..336c0d5c47703 --- /dev/null +++ b/plugins/SkyCultureMaker/src/SkyCultureMaker.cpp @@ -0,0 +1,600 @@ +/* + * Sky Culture Maker plug-in for Stellarium + * + * Copyright (C) 2025 Vincent Gerlach + * Copyright (C) 2025 Luca-Philipp Grumbach + * Copyright (C) 2025 Fabian Hofer + * Copyright (C) 2025 Mher Mnatsakanyan + * Copyright (C) 2025 Richard Hofmann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "SkyCultureMaker.hpp" +#include "StelActionMgr.hpp" +#include "StelApp.hpp" +#include "StelCore.hpp" +#include "StelGui.hpp" +#include "StelGuiItems.hpp" +#include "StelLocaleMgr.hpp" +#include "StelModuleMgr.hpp" +#include "StelPainter.hpp" +#include "StelProjector.hpp" +#include "gui/ScmConstellationDialog.hpp" +#include "gui/ScmHideOrAbortMakerDialog.hpp" +#include "gui/ScmSkyCultureDialog.hpp" +#include "gui/ScmSkyCultureExportDialog.hpp" +#include "gui/ScmStartDialog.hpp" + +#include "ScmDraw.hpp" +#include +#include +#include +#include +#include +#include + +/************************************************************************* + This method is the one called automatically by the StelModuleMgr just + after loading the dynamic library +*************************************************************************/ +StelModule *SkyCultureMakerStelPluginInterface::getStelModule() const +{ + return new SkyCultureMaker(); +} + +StelPluginInfo SkyCultureMakerStelPluginInterface::getPluginInfo() const +{ + // Allow to load the resources when used as a static plugin + Q_INIT_RESOURCE(SkyCultureMaker); + + StelPluginInfo info; + info.id = "SkyCultureMaker"; + info.displayedName = N_("Sky Culture Maker"); + info.authors = "Vincent Gerlach (RivinHD), Luca-Philipp Grumbach (xLPMG), Fabian Hofer (Integer-Ctrl), Richard " + "Hofmann (ZeyxRew), Mher Mnatsakanyan (MherMnatsakanyan03)"; + info.contact = N_("Contact us using our GitHub usernames, via an Issue or the Discussion tab in the Stellarium " + "repository."); + info.description = N_("Plugin to draw and export sky cultures in Stellarium."); + info.version = SKYCULTUREMAKER_PLUGIN_VERSION; + info.license = SKYCULTUREMAKER_PLUGIN_LICENSE; + return info; +} + +/************************************************************************* + Constructor +*************************************************************************/ +SkyCultureMaker::SkyCultureMaker() + : isScmEnabled(false) + , isLineDrawEnabled(false) +{ + qDebug() << "SkyCultureMaker constructed"; + + setObjectName("SkyCultureMaker"); + + drawObj = new scm::ScmDraw(); + scmStartDialog = new ScmStartDialog(this); + scmSkyCultureDialog = new ScmSkyCultureDialog(this); + scmConstellationDialog = new ScmConstellationDialog(this); + scmSkyCultureExportDialog = new ScmSkyCultureExportDialog(this); + scmHideOrAbortMakerDialog = new ScmHideOrAbortMakerDialog(this); + + // Settings + QSettings *conf = StelApp::getInstance().getSettings(); + conf->beginGroup("SkyCultureMaker"); + + initSetting(conf, "fixedLineColor", "1.0,0.5,0.5"); + initSetting(conf, "fixedLineAlpha", 1.0); + initSetting(conf, "floatingLineColor", "1.0,0.7,0.7"); + initSetting(conf, "floatingLineAlpha", 0.5); + initSetting(conf, "maxSnapRadiusInPixels", 25); + + conf->endGroup(); +} + +/************************************************************************* + Destructor +*************************************************************************/ +SkyCultureMaker::~SkyCultureMaker() +{ + // Initalized on start + delete drawObj; + delete scmStartDialog; + delete scmSkyCultureDialog; + delete scmConstellationDialog; + delete scmSkyCultureExportDialog; + delete scmHideOrAbortMakerDialog; + + if (currentSkyCulture != nullptr) + { + delete currentSkyCulture; + } + + qDebug() << "Unloaded plugin \"SkyCultureMaker\""; +} + +void SkyCultureMaker::setActionToggle(const QString &id, bool toggle) +{ + StelActionMgr *actionMgr = StelApp::getInstance().getStelActionManager(); + auto action = actionMgr->findAction(id); + if (action) + { + action->setChecked(toggle); + } + else + { + qDebug() << "SkyCultureMaker: Could not find action: " << id; + } +} + +/************************************************************************* + Reimplementation of the getCallOrder method +*************************************************************************/ +double SkyCultureMaker::getCallOrder(StelModuleActionName actionName) const +{ + if (actionName == StelModule::ActionDraw) + return StelApp::getInstance().getModuleMgr().getModule("NebulaMgr")->getCallOrder(actionName) + 10.; + if (actionName == StelModule::ActionHandleMouseClicks) return -11; + return 0; +} + +/************************************************************************* + Init our module +*************************************************************************/ +void SkyCultureMaker::init() +{ + qDebug() << "SkyCultureMaker: Init called for SkyCultureMaker"; + + StelApp &app = StelApp::getInstance(); + + addAction(actionIdLine, groupId, N_("Sky Culture Maker"), "enabledScm"); + + // Add a SCM toolbar button for starting creation process + try + { + QPixmap iconScmDisabled(":/SkyCultureMaker/bt_SCM_Off.png"); + QPixmap iconScmEnabled(":/SkyCultureMaker/bt_SCM_On.png"); + qDebug() << "SkyCultureMaker: " + << (iconScmDisabled.isNull() ? "Failed to load image: bt_SCM_Off.png" + : "Loaded image: bt_SCM_Off.png"); + qDebug() << "SkyCultureMaker: " + << (iconScmEnabled.isNull() ? "Failed to load image: bt_SCM_On.png" + : "Loaded image: bt_SCM_On.png"); + + StelGui *gui = dynamic_cast(app.getGui()); + if (gui != Q_NULLPTR) + { + toolbarButton = new StelButton(Q_NULLPTR, iconScmEnabled, iconScmDisabled, + QPixmap(":/graphicGui/miscGlow32x32.png"), actionIdLine, false); + gui->getButtonBar()->addButton(toolbarButton, "065-pluginsGroup"); + } + } + catch (std::runtime_error &e) + { + qWarning() << "SkyCultureMaker: Unable create toolbar button for SkyCultureMaker plugin: " << e.what(); + } +} + +/*********************** + Manage creation process +***********************/ + +void SkyCultureMaker::setToolbarButtonState(bool b) +{ + setActionToggle(actionIdLine, b); + toolbarButton->setChecked(b); +} + +void SkyCultureMaker::startScmProcess() +{ + if (!isScmEnabled) + { + isScmEnabled = true; + emit eventIsScmEnabled(true); + setToolbarButtonState(true); // Toggle the toolbar button to enabled + } + + if (isAnyDialogHidden()) + { + restoreScmDialogVisibilityState(); + } + else + { + setStartDialogVisibility(true); + } +} + +void SkyCultureMaker::stopScmProcess() +{ + // If the start dialog is visible, hide it + if (scmStartDialog->visible()) + { + setStartDialogVisibility(false); + setToolbarButtonState(false); // Turn OFF the toolbar button (image OFF) + return; + } + + // If the converter dialog is visible, hide it + if (scmStartDialog->isConverterDialogVisible()) + { + scmStartDialog->setConverterDialogVisibility(false); + if (isScmEnabled) + { + isScmEnabled = false; + emit eventIsScmEnabled(false); + setToolbarButtonState(false); // Toggle the toolbar button to disabled + } + } + + // If any other dialog is visible, don't stop the process — just keep UI state ON + if (isAnyDialogVisible()) + { + setHideOrAbortMakerDialogVisibility(true); + setToolbarButtonState(true); // Keep the toolbar button ON + return; + } + + // Otherwise, actually stop the process + if (isScmEnabled) + { + isScmEnabled = false; + emit eventIsScmEnabled(false); + setToolbarButtonState(false); // Toggle the toolbar button to disabled + } +} + +void SkyCultureMaker::draw(StelCore *core) +{ + if (isLineDrawEnabled && drawObj != nullptr) + { + drawObj->drawLine(core); + } + + if (isScmEnabled && currentSkyCulture != nullptr) + { + currentSkyCulture->draw(core); + } + + if (isScmEnabled && tempArtwork != nullptr) + { + tempArtwork->draw(core); + } +} + +bool SkyCultureMaker::handleMouseMoves(int x, int y, Qt::MouseButtons b) +{ + if (isLineDrawEnabled) + { + if (drawObj->handleMouseMoves(x, y, b)) + { + return true; + } + } + + return false; +} + +void SkyCultureMaker::handleMouseClicks(QMouseEvent *event) +{ + if (isLineDrawEnabled) + { + drawObj->handleMouseClicks(event); + if (event->isAccepted()) + { + return; + } + } + + // Continue any other events to be handled... +} +void SkyCultureMaker::handleKeys(QKeyEvent *e) +{ + if (isLineDrawEnabled) + { + drawObj->handleKeys(e); + if (e->isAccepted()) + { + return; + } + } +} + +void SkyCultureMaker::setIsScmEnabled(bool b) +{ + if (b == true) + { + startScmProcess(); + } + else + { + stopScmProcess(); + } +} + +void SkyCultureMaker::setStartDialogVisibility(bool b) +{ + if (b != scmStartDialog->visible()) + { + scmStartDialog->setVisible(b); + } +} + +void SkyCultureMaker::setSkyCultureDialogVisibility(bool b) +{ + if (b != scmSkyCultureDialog->visible()) + { + scmSkyCultureDialog->setVisible(b); + } +} + +void SkyCultureMaker::setConstellationDialogVisibility(bool b) +{ + if (b != scmConstellationDialog->visible()) + { + scmConstellationDialog->setVisible(b); + } + + // Disable the add constellation buttons when the dialog is opened + scmSkyCultureDialog->updateAddConstellationButtons(!b); + setIsLineDrawEnabled(b); +} + +void SkyCultureMaker::setConstellationDialogIsDarkConstellation(bool isDarkConstellation) +{ + if (scmConstellationDialog != nullptr) + { + scmConstellationDialog->setIsDarkConstellation(isDarkConstellation); + } +} + +void SkyCultureMaker::setSkyCultureExportDialogVisibility(bool b) +{ + if (b != scmSkyCultureExportDialog->visible()) + { + scmSkyCultureExportDialog->setVisible(b); + } +} + +void SkyCultureMaker::setHideOrAbortMakerDialogVisibility(bool b) +{ + if (b != scmHideOrAbortMakerDialog->visible()) + { + scmHideOrAbortMakerDialog->setVisible(b); + } +} + +void SkyCultureMaker::setIsLineDrawEnabled(bool b) +{ + isLineDrawEnabled = b; +} + +void SkyCultureMaker::triggerDrawUndo() +{ + if (isLineDrawEnabled) + { + drawObj->undoLastLine(); + } +} + +void SkyCultureMaker::setDrawTool(scm::DrawTools tool) +{ + drawObj->setTool(tool); +} + +void SkyCultureMaker::setNewSkyCulture() +{ + if (currentSkyCulture != nullptr) + { + delete currentSkyCulture; + } + currentSkyCulture = new scm::ScmSkyCulture(); +} + +scm::ScmSkyCulture *SkyCultureMaker::getCurrentSkyCulture() +{ + return currentSkyCulture; +} + +scm::ScmDraw *SkyCultureMaker::getScmDraw() +{ + return drawObj; +} + +void SkyCultureMaker::resetScmDraw() +{ + if (drawObj != nullptr) + { + drawObj->resetDrawing(); + } +} + +void SkyCultureMaker::updateSkyCultureDialog() +{ + if (scmSkyCultureDialog == nullptr || currentSkyCulture == nullptr) + { + return; + } + scmSkyCultureDialog->setConstellations(currentSkyCulture->getConstellations()); +} + +void SkyCultureMaker::setSkyCultureDescription(const scm::Description &description) +{ + if (currentSkyCulture != nullptr) + { + currentSkyCulture->setDescription(description); + } +} + +void SkyCultureMaker::setTempArtwork(const scm::ScmConstellationArtwork *artwork) +{ + tempArtwork = artwork; +} + +bool SkyCultureMaker::saveSkyCultureDescription(const QDir &directory) +{ + if (currentSkyCulture != nullptr) + { + QFile descriptionFile = QFile(directory.absoluteFilePath("description.md")); + return currentSkyCulture->saveDescriptionAsMarkdown(descriptionFile); + } + + return false; +} + +void SkyCultureMaker::hideAllDialogs() +{ + setHideOrAbortMakerDialogVisibility(false); + setSkyCultureDialogVisibility(false); + setConstellationDialogVisibility(false); + setSkyCultureExportDialogVisibility(false); +} + +void SkyCultureMaker::saveScmDialogVisibilityState() +{ + if (scmSkyCultureDialog != nullptr) + { + scmDialogVisibilityMap[scm::DialogID::SkyCultureDialog] = scmSkyCultureDialog->visible(); + } + if (scmConstellationDialog != nullptr) + { + scmDialogVisibilityMap[scm::DialogID::ConstellationDialog] = scmConstellationDialog->visible(); + } + if (scmSkyCultureExportDialog != nullptr) + { + scmDialogVisibilityMap[scm::DialogID::SkyCultureExportDialog] = scmSkyCultureExportDialog->visible(); + } +} + +void SkyCultureMaker::restoreScmDialogVisibilityState() +{ + if (scmSkyCultureDialog != nullptr) + { + setSkyCultureDialogVisibility(scmDialogVisibilityMap[scm::DialogID::SkyCultureDialog]); + } + if (scmConstellationDialog != nullptr) + { + setConstellationDialogVisibility(scmDialogVisibilityMap[scm::DialogID::ConstellationDialog]); + } + if (scmSkyCultureExportDialog != nullptr) + { + setSkyCultureExportDialogVisibility(scmDialogVisibilityMap[scm::DialogID::SkyCultureExportDialog]); + } +} + +bool SkyCultureMaker::isAnyDialogHidden() const +{ + for (bool visible : scmDialogVisibilityMap.values()) + { + if (visible) + { + return true; + } + } + return false; +} + +void SkyCultureMaker::resetScmDialogsVisibilityState() +{ + for (auto key : scmDialogVisibilityMap.keys()) + { + scmDialogVisibilityMap[key] = false; + } +} + +bool SkyCultureMaker::isAnyDialogVisible() const +{ + const StelDialog *dialogs[] = {scmSkyCultureDialog, scmConstellationDialog, scmSkyCultureExportDialog, + scmHideOrAbortMakerDialog, scmStartDialog}; + + for (const StelDialog *dialog : dialogs) + { + if (dialog != nullptr && dialog->visible()) + { + return true; + } + } + return false; +} + +void SkyCultureMaker::resetScmDialogs() +{ + resetScmDialogsVisibilityState(); // Reset the visibility state of all dialogs + scmSkyCultureDialog->resetDialog(); + scmConstellationDialog->resetDialog(); +} + +void SkyCultureMaker::openConstellationDialog(const QString &constellationId) +{ + if (scmConstellationDialog != nullptr) + { + // Load the necessary data + scm::ScmSkyCulture *skyCulture = getCurrentSkyCulture(); + if (skyCulture == nullptr) + { + qDebug() << "SkyCultureMaker: Current Sky Culture is not initialized."; + return; + } + + scm::ScmConstellation *constellation = skyCulture->getConstellation(constellationId); + if (constellation != nullptr) + { + scmConstellationDialog->loadFromConstellation(constellation); + setConstellationDialogVisibility(true); + qDebug() << "SkyCultureMaker: Opened constellation dialog for ID:" << constellationId; + } + else + { + qWarning() << "SkyCultureMaker: Constellation with ID" << constellationId << "not found."; + } + } + else + { + qWarning() << "SkyCultureMaker: Constellation dialog is not initialized."; + } +} + +void SkyCultureMaker::initSetting(QSettings *conf, const QString key, const QVariant &defaultValue) +{ + if (conf == nullptr) + { + qWarning() << "SkyCultureMaker: QSettings pointer is null."; + return; + } + + if (!conf->contains(key)) + { + conf->setValue(key, defaultValue); + } +} + +void SkyCultureMaker::showUserInfoMessage(QWidget *parent, const QString &dialogName, const QString &message) +{ + const QString level = q_("INFO"); + const QString title = dialogName.isEmpty() ? level : dialogName + ": " + level; + QMessageBox::information(parent, title, message); +} + +void SkyCultureMaker::showUserWarningMessage(QWidget *parent, const QString &dialogName, const QString &message) +{ + const QString level = q_("WARNING"); + const QString title = dialogName.isEmpty() ? level : dialogName + ": " + level; + QMessageBox::warning(parent, title, message); +} + +void SkyCultureMaker::showUserErrorMessage(QWidget *parent, const QString &dialogName, const QString &message) +{ + const QString level = q_("ERROR"); + const QString title = dialogName.isEmpty() ? level : dialogName + ": " + level; + QMessageBox::critical(parent, title, message); +} diff --git a/plugins/SkyCultureMaker/src/SkyCultureMaker.hpp b/plugins/SkyCultureMaker/src/SkyCultureMaker.hpp new file mode 100644 index 0000000000000..7aa854302ecfe --- /dev/null +++ b/plugins/SkyCultureMaker/src/SkyCultureMaker.hpp @@ -0,0 +1,354 @@ +/* + * Sky Culture Maker plug-in for Stellarium + * + * Copyright (C) 2025 Vincent Gerlach + * Copyright (C) 2025 Luca-Philipp Grumbach + * Copyright (C) 2025 Fabian Hofer + * Copyright (C) 2025 Mher Mnatsakanyan + * Copyright (C) 2025 Richard Hofmann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SKYCULTUREMAKER_HPP +#define SKYCULTUREMAKER_HPP + +#include "ScmConstellation.hpp" +#include "ScmConstellationArtwork.hpp" +#include "ScmDraw.hpp" +#include "ScmSkyCulture.hpp" +#include "StelCore.hpp" +#include "StelModule.hpp" +#include "StelObjectModule.hpp" +#include "StelTranslator.hpp" +#include "VecMath.hpp" +#include "types/DialogID.hpp" +#include +#include +#include +#include + +class QPixmap; +class StelButton; +class ScmSkyCultureDialog; +class ScmConstellationDialog; +class ScmStartDialog; +class ScmSkyCultureExportDialog; +class ScmHideOrAbortMakerDialog; + +class SkyCultureMaker : public StelModule +{ + Q_OBJECT + // TODO: var - getter - setter - trigger + Q_PROPERTY(bool enabledScm READ getIsScmEnabled WRITE setIsScmEnabled NOTIFY eventIsScmEnabled) +public: + SkyCultureMaker(); + ~SkyCultureMaker() override; + + /// @brief Set the toggle value for a given action. + /// @param toggle The toggled value to be set. + static void setActionToggle(const QString &id, bool toggle); + + void init() override; + // Activate only if update() does something. + // void update(double deltaTime) override {} + void draw(StelCore *core) override; + double getCallOrder(StelModuleActionName actionName) const override; + + /// Handle mouse clicks. Please note that most of the interactions will be done through the GUI module. + /// @return set the event as accepted if it was intercepted + void handleMouseClicks(QMouseEvent *) override; + + /// Handle mouse moves. Please note that most of the interactions will be done through the GUI module. + /// @return true if the event was intercepted + bool handleMouseMoves(int x, int y, Qt::MouseButtons b) override; + + /// Handle key events. Please note that most of the interactions will be done through the GUI module. + /// @param e the Key event + /// @return set the event as accepted if it was intercepted + void handleKeys(QKeyEvent *e) override; + + /** + * @brief Sets the toolbar button state. + * @param b The boolean value to be set. + */ + void setToolbarButtonState(bool b); + + /** + * @brief Shows the start dialog for the sky culture maker. + * + * @param b The boolean value to be set. + */ + void setStartDialogVisibility(bool b); + + /** + * @brief Shows the sky culture dialog. + * + * @param b The boolean value to be set. + */ + void setSkyCultureDialogVisibility(bool b); + + /** + * @brief Shows the constellation dialog. + * + * @param b The boolean value to be set. + */ + void setConstellationDialogVisibility(bool b); + + /** + * @brief Sets whether the constellation dialog is for a dark constellation. + * + * @param isDarkConstellation The boolean value to be set. + */ + void setConstellationDialogIsDarkConstellation(bool isDarkConstellation); + + /** + * @brief Shows the sky culture export dialog. + * + * @param b The boolean value to be set. + */ + void setSkyCultureExportDialogVisibility(bool b); + + /** + * @brief Shows the hide or abort maker dialog. + * + * @param b The boolean value to be set. + */ + void setHideOrAbortMakerDialogVisibility(bool b); + + /** + * @brief Set the visibility of all dialogs. + * + * @param b The boolean value to be set. + */ + void hideAllDialogs(); + + /** + * @brief Toggles the usage of the line draw. + * + * @param b The boolean value to be set. + */ + void setIsLineDrawEnabled(bool b); + + /** + * @brief Triggers a single undo operation in the line draw. + */ + void triggerDrawUndo(); + + /** + * @brief Sets the active used draw tool. + * + * @param tool The tool to be used. + */ + void setDrawTool(scm::DrawTools tool); + + /** + * @brief Sets a new sky culture object + */ + void setNewSkyCulture(); + + /** + * @brief Gets the current set sky culture. + */ + scm::ScmSkyCulture *getCurrentSkyCulture(); + + /** + * @brief Gets the SCM drawing object. + */ + scm::ScmDraw *getScmDraw(); + + /** + * @brief Resets the SCM drawing object. + */ + void resetScmDraw(); + + /** + * @brief Triggers an update of the sky culture dialog. + */ + void updateSkyCultureDialog(); + + /** + * @brief Sets the current sky culture description. + * @param description The description to set. + */ + void setSkyCultureDescription(const scm::Description &description); + + /** + * @brief Saves the current sky culture description as markdown text. + * @param directory The directory to save the description in. + * @return true if the description was saved successfully, false otherwise. + */ + bool saveSkyCultureDescription(const QDir &directory); + + /** + * @brief Saves the visibility state of the SCM dialogs. + */ + void saveScmDialogVisibilityState(); + + /** + * @brief Restores the visibility state of the SCM dialogs. + */ + void restoreScmDialogVisibilityState(); + + /** + * @brief Checks if any SCM dialog is currently hidden. + */ + bool isAnyDialogHidden() const; + + /** + * @brief Resets all SCM dialogs content and visibility states. + */ + void resetScmDialogs(); + + /** + * @brief Resets the visibility state of the SCM dialogs. + */ + void resetScmDialogsVisibilityState(); + + /** + * @brief Checks if any SCM dialog is currently visible. + * @return true if any dialog is visible, false otherwise. + */ + bool isAnyDialogVisible() const; + /** + * @brief Sets the temporary artwork that should be drawn. + * @param artwork The artwork to draw. + */ + void setTempArtwork(const scm::ScmConstellationArtwork *artwork); + + /** + * @brief Opens the constellation dialog with data for a given constellation. + * @param constellationId The ID of the constellation to open the dialog for. + */ + void openConstellationDialog(const QString &constellationId); + + /** + * @brief Displays an information message to the user. + * + * @param parent The parent widget of the message box. + * @param dialogName The name of the dialog to be shown in the title bar. + * @param message The message to be displayed. + */ + void showUserInfoMessage(QWidget *parent, const QString &dialogName, const QString &message); + + /** + * @brief Displays a warning message to the user. + * + * @param parent The parent widget of the message box. + * @param dialogName The name of the dialog to be shown in the title bar. + * @param message The message to be displayed. + */ + void showUserWarningMessage(QWidget *parent, const QString &dialogName, const QString &message); + + /** + * @brief Displays an error message to the user. + * + * @param parent The parent widget of the message box. + * @param dialogName The name of the dialog to be shown in the title bar. + * @param message The message to be displayed. + */ + void showUserErrorMessage(QWidget *parent, const QString &dialogName, const QString &message); + +signals: + void eventIsScmEnabled(bool b); + +public slots: + bool getIsScmEnabled() const { return isScmEnabled; } + + void setIsScmEnabled(bool b); + +private: + const QString groupId = N_("Sky Culture Maker"); + const QString actionIdLine = "actionShow_SkyCultureMaker_Line"; + + /// Indicates that SCM creation process is enabled (QT Signal) + bool isScmEnabled = false; + + /// Indicates that line drawing can be done (QT Signal) + bool isLineDrawEnabled = false; + + /// The button to activate line drawing. + StelButton *toolbarButton = nullptr; + + /// Font used for displaying our text + QFont font; + + /// The object used for drawing constellations + scm::ScmDraw *drawObj = nullptr; + + /// Toogle SCM creation process on + void startScmProcess(); + + /// Toogle SCM creation process off + void stopScmProcess(); + + /// Dialog for starting/editing/cancel creation process + ScmStartDialog *scmStartDialog = nullptr; + + /// Dialog for creating/editing a sky culture + ScmSkyCultureDialog *scmSkyCultureDialog = nullptr; + + /// Dialog for creating/editing a constellation + ScmConstellationDialog *scmConstellationDialog = nullptr; + + /// Dialog for exporting a sky culture + ScmSkyCultureExportDialog *scmSkyCultureExportDialog = nullptr; + + /// Dialog for hiding or aborting maker process + ScmHideOrAbortMakerDialog *scmHideOrAbortMakerDialog = nullptr; + + /// The current sky culture + scm::ScmSkyCulture *currentSkyCulture = nullptr; + + /** + * Store the visibility state of the SCM dialogs + * The key is the dialog ID, the value is true if the dialog was hidden, false if newly created. + */ + QMap scmDialogVisibilityMap = { + {scm::DialogID::StartDialog, false}, + {scm::DialogID::SkyCultureDialog, false}, + {scm::DialogID::ConstellationDialog, false}, + {scm::DialogID::SkyCultureExportDialog, false}, + {scm::DialogID::HideOrAbortMakerDialog, false} + }; + + /// The artwork to temporary draw on the sky. + const scm::ScmConstellationArtwork *tempArtwork = nullptr; + + /** + * @brief Initializes a setting with a default value if it does not exist. + * This does not open or close the settings group. + * @param key The key of the setting. + * @param defaultValue The default value to set if the setting does not exist. + */ + void initSetting(QSettings *conf, const QString key, const QVariant &defaultValue); +}; + +#include "StelPluginInterface.hpp" +#include + +/// This class is used by Qt to manage a plug-in interface +class SkyCultureMakerStelPluginInterface : public QObject, + public StelPluginInterface +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID StelPluginInterface_iid) + Q_INTERFACES(StelPluginInterface) +public: + StelModule *getStelModule() const override; + StelPluginInfo getPluginInfo() const override; + QObjectList getExtensionList() const override { return QObjectList(); } +}; + +#endif /* SKYCULTUREMAKER_HPP */ diff --git a/plugins/SkyCultureMaker/src/enumBitops.hpp b/plugins/SkyCultureMaker/src/enumBitops.hpp new file mode 100644 index 0000000000000..6a03ff90ca211 --- /dev/null +++ b/plugins/SkyCultureMaker/src/enumBitops.hpp @@ -0,0 +1,55 @@ +/* + * Sky Culture Maker plug-in for Stellarium + * + * Copyright (C) 2025 Vincent Gerlach + * Copyright (C) 2025 Luca-Philipp Grumbach + * Copyright (C) 2025 Fabian Hofer + * Copyright (C) 2025 Mher Mnatsakanyan + * Copyright (C) 2025 Richard Hofmann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +namespace generic_enum_bitops +{ +// Extension point +template +struct allow_bitops +{ + static constexpr bool value = false; +}; +} // namespace generic_enum_bitops + +template::value, int> = 0> +T operator|(T a, T b) +{ + using I = std::underlying_type_t; + return static_cast(static_cast(a) | static_cast(b)); +} + +template::value, int> = 0> +T operator&(T a, T b) +{ + using I = std::underlying_type_t; + return static_cast(static_cast(a) & static_cast(b)); +} + +template::value, int> = 0> +bool hasFlag(T a, T b) +{ + using I = std::underlying_type_t; + return static_cast(static_cast(a) & static_cast(b)); +} diff --git a/plugins/SkyCultureMaker/src/gui/ScmConstellationDialog.cpp b/plugins/SkyCultureMaker/src/gui/ScmConstellationDialog.cpp new file mode 100644 index 0000000000000..7e22da091bb66 --- /dev/null +++ b/plugins/SkyCultureMaker/src/gui/ScmConstellationDialog.cpp @@ -0,0 +1,580 @@ +/* + * Sky Culture Maker plug-in for Stellarium + * + * Copyright (C) 2025 Vincent Gerlach + * Copyright (C) 2025 Luca-Philipp Grumbach + * Copyright (C) 2025 Fabian Hofer + * Copyright (C) 2025 Mher Mnatsakanyan + * Copyright (C) 2025 Richard Hofmann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ScmConstellationDialog.hpp" +#include "StelApp.hpp" +#include "StelGui.hpp" +#include "StelObjectMgr.hpp" +#include "types/DrawingMode.hpp" +#include "ui_scmConstellationDialog.h" +#include +#include +#include +#include +#include +#include +#include + +ScmConstellationDialog::ScmConstellationDialog(SkyCultureMaker *maker) + : StelDialogSeparate("ScmConstellationDialog") + , maker(maker) +{ + assert(maker != nullptr); + + ui = new Ui_scmConstellationDialog; + imageItem = new ScmConstellationImage; +} + +ScmConstellationDialog::~ScmConstellationDialog() +{ + delete ui; + qDebug() << "SkyCultureMaker: Unloaded the ScmConstellationDialog"; +} + +void ScmConstellationDialog::loadFromConstellation(scm::ScmConstellation *constellation) +{ + if (constellation == nullptr) + { + qWarning() << "ScmConstellationDialog::loadFromConstellation: constellation is null"; + return; + } + + if (!isDialogInitialized) + { + createDialogContent(); + } + else + { + resetDialog(); + } + setIsDarkConstellation(constellation->getIsDarkConstellation()); + + // Save the constellation that is currently being edited + constellationBeingEdited = constellation; + + constellationId = constellation->getId(); + constellationEnglishName = constellation->getEnglishName(); + constellationPlaceholderId = constellation->getId(); + constellationNativeName = constellation->getNativeName(); + constellationPronounce = constellation->getPronounce(); + constellationIPA = constellation->getIPA(); + + ui->enNameLE->setText(constellationEnglishName); + ui->idLE->setText(constellationId); + ui->natNameLE->setText(constellationNativeName.value_or("")); + ui->pronounceLE->setText(constellationPronounce.value_or("")); + ui->ipaLE->setText(constellationIPA.value_or("")); + + // Hide the original constellation while editing + constellation->hide(); + // Load the coordinates and stars to ScmDraw + maker->getScmDraw()->loadLines(constellation->getCoordinates(), constellation->getStars()); + + // Loads the artwork + imageItem->setArtwork(constellation->getArtwork()); + ui->artwork_image->centerOn(imageItem); + ui->artwork_image->fitInView(imageItem, Qt::KeepAspectRatio); + ui->artwork_image->show(); + + updateArtwork(); +} + +void ScmConstellationDialog::setIsDarkConstellation(bool isDark) +{ + // make sure the dialog is initialized to avoid any ui null pointers + if (!isDialogInitialized) + { + createDialogContent(); + } + + scm::ScmDraw *draw = maker->getScmDraw(); + if (draw == nullptr) + { + qWarning() << "SkyCultureMaker: ScmConstellationDialog::setIsDarkConstellation: ScmDraw is null"; + return; + } + + // the value changed, so we should reset some data from the previous mode + if (isDarkConstellation != isDark) + { + // reset drawn lines as they are not compatible between modes + draw->resetDrawing(); + + // reset artwork as well + ui->bind_star->setEnabled(false); + imageItem->hide(); + imageItem->resetAnchors(); + maker->setTempArtwork(nullptr); + + activeTool = scm::DrawTools::None; + ui->penBtn->setChecked(false); + ui->eraserBtn->setChecked(false); + maker->setDrawTool(scm::DrawTools::None); + + isDarkConstellation = isDark; + } + + if (draw != nullptr) + { + draw->setDrawingMode(isDark ? scm::DrawingMode::Coordinates : scm::DrawingMode::StarsAndDSO); + } + + if (ui != nullptr) + { + ui->titleBar->setTitle(isDark ? q_("SCM: Dark Constellation Editor") : q_("SCM: Constellation Editor")); + ui->labelsTitle->setText(isDark ? q_("Please name your Dark Constellation") + : q_("Please name your Constellation")); + } +} + +void ScmConstellationDialog::retranslate() +{ + if (dialog) + { + ui->retranslateUi(dialog); + } +} + +void ScmConstellationDialog::close() +{ + maker->setConstellationDialogVisibility(false); +} + +void ScmConstellationDialog::createDialogContent() +{ + isDialogInitialized = true; + ui->setupUi(dialog); + imageItem->hide(); + ui->artwork_image->setScene(imageItem->scene()); + ui->bind_star->setEnabled(false); + + setIsDarkConstellation(false); + + connect(&StelApp::getInstance(), SIGNAL(languageChanged()), this, SLOT(retranslate())); + connect(ui->titleBar, SIGNAL(movedTo(QPoint)), this, SLOT(handleMovedTo(QPoint))); + connect(ui->titleBar, &TitleBar::closeClicked, this, &ScmConstellationDialog::close); + connect(ui->tabs, &QTabWidget::currentChanged, this, &ScmConstellationDialog::tabChanged); + + connect(ui->penBtn, &QPushButton::toggled, this, &ScmConstellationDialog::togglePen); + connect(ui->eraserBtn, &QPushButton::toggled, this, &ScmConstellationDialog::toggleEraser); + connect(ui->undoBtn, &QPushButton::clicked, this, &ScmConstellationDialog::triggerUndo); + + connect(ui->upload_image, &QPushButton::clicked, this, &ScmConstellationDialog::triggerUploadImage); + connect(ui->remove_image, &QPushButton::clicked, this, &ScmConstellationDialog::triggerRemoveImage); + connect(ui->bind_star, &QPushButton::clicked, this, &ScmConstellationDialog::bindSelectedStar); + imageItem->setAnchorSelectionChangedCallback( + [this]() + { + this->ui->bind_star->setEnabled(this->imageItem->hasAnchorSelection()); + this->updateArtwork(); + }); + imageItem->setAnchorPositionChangedCallback([this]() { this->updateArtwork(); }); + + ui->tooltipBtn->setToolTip(artworkToolTip); + + connect(ui->saveBtn, &QPushButton::clicked, this, &ScmConstellationDialog::saveConstellation); + connect(ui->cancelBtn, &QPushButton::clicked, this, &ScmConstellationDialog::cancel); + + connect(&StelApp::getInstance(), &StelApp::fontChanged, this, &ScmConstellationDialog::handleFontChanged); + connect(&StelApp::getInstance(), &StelApp::guiFontSizeChanged, this, &ScmConstellationDialog::handleFontChanged); + + handleFontChanged(); + + // LABELS TAB + + connect(ui->enNameLE, &QLineEdit::textChanged, this, + [this]() + { + constellationEnglishName = ui->enNameLE->text(); + + QString newConstId = constellationEnglishName.toLower().replace(" ", "_"); + constellationPlaceholderId = newConstId; + ui->idLE->setPlaceholderText(newConstId); + }); + connect(ui->idLE, &QLineEdit::textChanged, this, [this]() { constellationId = ui->idLE->text(); }); + connect(ui->natNameLE, &QLineEdit::textChanged, this, + [this]() + { + constellationNativeName = ui->natNameLE->text(); + if (constellationNativeName->isEmpty()) + { + constellationNativeName = std::nullopt; + } + }); + connect(ui->pronounceLE, &QLineEdit::textChanged, this, + [this]() + { + constellationPronounce = ui->pronounceLE->text(); + if (constellationPronounce->isEmpty()) + { + constellationPronounce = std::nullopt; + } + }); + connect(ui->ipaLE, &QLineEdit::textChanged, this, + [this]() + { + constellationIPA = ui->ipaLE->text(); + if (constellationIPA->isEmpty()) + { + constellationIPA = std::nullopt; + } + }); +} + +void ScmConstellationDialog::handleFontChanged() +{ + QFont labelsTitleFont = QApplication::font(); + labelsTitleFont.setPixelSize(labelsTitleFont.pixelSize() + 2); + labelsTitleFont.setBold(true); + ui->labelsTitle->setFont(labelsTitleFont); +} + +void ScmConstellationDialog::togglePen(bool checked) +{ + if (checked) + { + ui->eraserBtn->setChecked(false); + activeTool = scm::DrawTools::Pen; + maker->setDrawTool(activeTool); + ui->drawInfoBox->setText(helpDrawInfoPen); + } + else + { + activeTool = scm::DrawTools::None; + maker->setDrawTool(activeTool); + ui->drawInfoBox->setText(""); + } +} + +void ScmConstellationDialog::toggleEraser(bool checked) +{ + if (checked) + { + ui->penBtn->setChecked(false); + activeTool = scm::DrawTools::Eraser; + maker->setDrawTool(activeTool); + ui->drawInfoBox->setText(helpDrawInfoEraser); + } + else + { + activeTool = scm::DrawTools::None; + maker->setDrawTool(activeTool); + ui->drawInfoBox->setText(""); + } +} + +void ScmConstellationDialog::triggerUndo() +{ + maker->triggerDrawUndo(); + togglePen(true); +} + +void ScmConstellationDialog::triggerUploadImage() +{ + QString filePath = QFileDialog::getOpenFileName(ui->artworkTab, q_("Open Artwork"), lastUsedImageDirectory, + q_("Images (*.png *.jpg *.jpeg)")); + QFileInfo fileInfo(filePath); + lastUsedImageDirectory = fileInfo.absolutePath(); + + if (!fileInfo.isFile()) + { + maker->showUserErrorMessage(this->dialog, ui->titleBar->title(), + q_("Chosen path is not a valid file:\n") + filePath); + return; + } + + if (!(fileInfo.suffix().compare("PNG", Qt::CaseInsensitive) == 0 || + fileInfo.suffix().compare("JPG", Qt::CaseInsensitive) == 0 || + fileInfo.suffix().compare("JPEG", Qt::CaseInsensitive) == 0)) + { + maker->showUserErrorMessage(this->dialog, ui->titleBar->title(), + q_("Chosen file is not a PNG, JPG or JPEG image:\n") + filePath); + return; + } + + QPixmap image = QPixmap(fileInfo.absoluteFilePath()); + imageItem->setImage(image); + imageItem->show(); + ui->artwork_image->centerOn(imageItem); + ui->artwork_image->fitInView(imageItem, Qt::KeepAspectRatio); + ui->artwork_image->show(); + + updateArtwork(); +} + +void ScmConstellationDialog::triggerRemoveImage() +{ + imageItem->resetArtwork(); + imageItem->resetAnchors(); + + updateArtwork(); +} + +void ScmConstellationDialog::bindSelectedStar() +{ + if (!imageItem->hasAnchorSelection()) + { + maker->showUserErrorMessage(this->dialog, ui->titleBar->title(), + q_("No anchor was selected. Please select an anchor to bind to.")); + qDebug() << "SkyCultureMaker: No anchor was selected."; + return; + } + + StelApp &app = StelApp::getInstance(); + StelObjectMgr &objectMgr = app.getStelObjectMgr(); + + if (!objectMgr.getWasSelected()) + { + maker->showUserErrorMessage(this->dialog, ui->titleBar->title(), + q_("No star was selected to bind to the current selected anchor.")); + qDebug() << "SkyCultureMaker: No star was selected to bind to."; + return; + } + + StelObjectP stelObj = objectMgr.getLastSelectedObject(); + assert(stelObj != nullptr); // Checked through getWasSelected + if (stelObj->getType().compare("star", Qt::CaseInsensitive) != 0 && + stelObj->getType().compare("nebula", Qt::CaseInsensitive) != 0) + { + maker->showUserErrorMessage(this->dialog, ui->titleBar->title(), + q_("The selected object must be of type Star or Nebula.")); + qDebug() << "SkyCultureMaker: The selected object is not of type Star, got " << stelObj->getType(); + return; + } + + ScmConstellationImageAnchor *anchor = imageItem->getSelectedAnchor(); + if (anchor == nullptr) + { + maker->showUserErrorMessage(this->dialog, ui->titleBar->title(), + q_("No anchor was selected. Please select an anchor to bind to.")); + qDebug() << "SkyCultureMaker: No anchor was selected"; + return; + } + + bool success = anchor->trySetStarHip(stelObj->getID()); + if (success == false) + { + maker->showUserErrorMessage(this->dialog, ui->titleBar->title(), + q_("The selected object must contain a HIP number.")); + qDebug() << "SkyCultureMaker: The object does not contain a HIP, id = " << stelObj->getID(); + return; + } + + updateArtwork(); +} + +void ScmConstellationDialog::tabChanged(int index) +{ + Q_UNUSED(index); + ui->penBtn->setChecked(false); + ui->eraserBtn->setChecked(false); + maker->setDrawTool(scm::DrawTools::None); +} + +bool ScmConstellationDialog::canConstellationBeSaved() const +{ + // shouldnt happen + scm::ScmSkyCulture *currentSkyCulture = maker->getCurrentSkyCulture(); + if (currentSkyCulture == nullptr) + { + maker->showUserErrorMessage(this->dialog, ui->titleBar->title(), + q_("Could not save: Sky Culture is not set")); + qDebug() << "SkyCultureMaker: Could not save: Sky Culture is not set"; + return false; + } + + if (constellationEnglishName.isEmpty()) + { + maker->showUserErrorMessage(this->dialog, ui->titleBar->title(), + q_("Could not save: English name is empty")); + qDebug() << "SkyCultureMaker: Could not save: English name is empty"; + return false; + } + + // It is okay for the constellationId to be empty, as long as the constellationPlaceholderId is set + QString finalId = constellationId.isEmpty() ? constellationPlaceholderId : constellationId; + if (finalId.isEmpty()) + { + maker->showUserErrorMessage(this->dialog, ui->titleBar->title(), + q_("Could not save: Constellation ID is empty")); + qDebug() << "SkyCultureMaker: Could not save: Constellation ID is empty"; + return false; + } + + // Not editing a constellation, but the ID already exists + if (constellationBeingEdited == nullptr && currentSkyCulture->getConstellation(finalId) != nullptr) + { + maker->showUserErrorMessage(this->dialog, ui->titleBar->title(), + q_("Could not save: Constellation with this ID already exists")); + qDebug() << "SkyCultureMaker: Could not save: Constellation with this ID already exists, id = " + << finalId; + return false; + } + // Editing a constellation, but the ID already exists and is not the same as the one being edited + else if (constellationBeingEdited != nullptr && constellationBeingEdited->getId() != finalId && + currentSkyCulture->getConstellation(finalId) != nullptr) + { + maker->showUserErrorMessage(this->dialog, ui->titleBar->title(), + q_("Could not save: Constellation with this ID already exists")); + qDebug() << "SkyCultureMaker: Could not save: Constellation with this ID already exists, id = " + << finalId; + return false; + } + + // Check if drawnStars is empty + auto drawnConstellation = maker->getScmDraw()->getCoordinates(); + if (drawnConstellation.empty()) + { + maker->showUserErrorMessage(this->dialog, ui->titleBar->title(), + q_("Could not save: The constellation does not contain any drawings")); + qDebug() << "SkyCultureMaker: Could not save: The constellation does not contain any drawings"; + return false; + } + + // Check if an artwork was added and all anchors have a binding + if (imageItem->isVisible()) + { + if (!imageItem->isImageAnchored()) + { + maker->showUserErrorMessage(this->dialog, ui->titleBar->title(), + q_("Could not save: An artwork is attached, but not all " + "anchors have a star bound.")); + qDebug() << "SkyCultureMaker: Could not save: An artwork is attached, but not all " + "anchors have a star bound."; + return false; + } + } + + return true; +} + +void ScmConstellationDialog::cancel() +{ + if (constellationBeingEdited != nullptr) + { + // If we are editing a constellation, we need to show the original one again + constellationBeingEdited->show(); + } + resetDialog(); + ScmConstellationDialog::close(); +} + +void ScmConstellationDialog::saveConstellation() +{ + if (canConstellationBeSaved()) + { + auto coordinates = maker->getScmDraw()->getCoordinates(); + auto stars = maker->getScmDraw()->getStars(); + QString id = constellationId.isEmpty() ? constellationPlaceholderId : constellationId; + + scm::ScmSkyCulture *culture = maker->getCurrentSkyCulture(); + assert(culture != nullptr); // already checked by canConstellationBeSaved + + // delete the original constellation if we are editing one + if (constellationBeingEdited != nullptr) + { + culture->removeConstellation(constellationBeingEdited->getId()); + } + + scm::ScmConstellation &constellation = culture->addConstellation(id, coordinates, stars, + isDarkConstellation); + + constellation.setEnglishName(constellationEnglishName); + constellation.setNativeName(constellationNativeName); + constellation.setPronounce(constellationPronounce); + constellation.setIPA(constellationIPA); + if (imageItem->isVisible() && imageItem->getArtwork().getHasArt()) + { + constellation.setArtwork(imageItem->getArtwork()); + } + + maker->updateSkyCultureDialog(); + resetDialog(); + ScmConstellationDialog::close(); + } +} + +void ScmConstellationDialog::resetDialog() +{ + // If the dialog was not initialized, the ui elements do not exist yet. + if (!isDialogInitialized) + { + return; + } + + ui->tabs->setCurrentIndex(0); + + activeTool = scm::DrawTools::None; + + ui->penBtn->setChecked(false); + ui->eraserBtn->setChecked(false); + maker->setDrawTool(scm::DrawTools::None); + setIsDarkConstellation(false); + + constellationId.clear(); + ui->idLE->clear(); + + constellationPlaceholderId.clear(); + ui->idLE->setPlaceholderText(""); + + constellationEnglishName.clear(); + ui->enNameLE->clear(); + + constellationNativeName = std::nullopt; + ui->natNameLE->clear(); + + constellationPronounce = std::nullopt; + ui->pronounceLE->clear(); + + constellationIPA = std::nullopt; + ui->ipaLE->clear(); + + ui->bind_star->setEnabled(false); + imageItem->hide(); + imageItem->resetAnchors(); + maker->setTempArtwork(nullptr); + + constellationBeingEdited = nullptr; + + // reset ScmDraw + maker->resetScmDraw(); +} + +void ScmConstellationDialog::updateArtwork() +{ + if (!imageItem->isVisible() || !imageItem->isImageAnchored()) + { + maker->setTempArtwork(nullptr); + return; + } + + imageItem->updateAnchors(); + maker->setTempArtwork(&(imageItem->getArtwork())); +} + +void ScmConstellationDialog::handleDialogSizeChanged(QSizeF size) +{ + StelDialog::handleDialogSizeChanged(size); + + ui->artwork_image->fitInView(imageItem, Qt::KeepAspectRatio); +} diff --git a/plugins/SkyCultureMaker/src/gui/ScmConstellationDialog.hpp b/plugins/SkyCultureMaker/src/gui/ScmConstellationDialog.hpp new file mode 100644 index 0000000000000..b7f42d5b886ff --- /dev/null +++ b/plugins/SkyCultureMaker/src/gui/ScmConstellationDialog.hpp @@ -0,0 +1,151 @@ +/* + * Sky Culture Maker plug-in for Stellarium + * + * Copyright (C) 2025 Vincent Gerlach + * Copyright (C) 2025 Luca-Philipp Grumbach + * Copyright (C) 2025 Fabian Hofer + * Copyright (C) 2025 Mher Mnatsakanyan + * Copyright (C) 2025 Richard Hofmann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SCM_CONSTELLATION_DIALOG_HPP +#define SCM_CONSTELLATION_DIALOG_HPP + +#include "ScmConstellationImage.hpp" +#include "SkyCultureMaker.hpp" +#include "StelDialogSeparate.hpp" +#include "types/DrawTools.hpp" +#include +#include +#include +#include +#include + +class Ui_scmConstellationDialog; + +class ScmConstellationDialog : public StelDialogSeparate +{ +protected: + void createDialogContent() override; + void handleDialogSizeChanged(QSizeF size) override; + +public: + ScmConstellationDialog(SkyCultureMaker *maker); + ~ScmConstellationDialog() override; + void loadFromConstellation(scm::ScmConstellation *constellation); + void setIsDarkConstellation(bool isDarkConstellation); + +public slots: + void retranslate() override; + void close() override; + + /** + * @brief Resets the constellation dialog data. + */ + void resetDialog(); + +protected slots: + void handleFontChanged(); + +private slots: + void togglePen(bool checked); + void toggleEraser(bool checked); + void triggerUndo(); + void triggerUploadImage(); + void triggerRemoveImage(); + void bindSelectedStar(); + void tabChanged(int index); + +private: + Ui_scmConstellationDialog *ui = nullptr; + SkyCultureMaker *maker = nullptr; + scm::DrawTools activeTool = scm::DrawTools::None; + bool isDialogInitialized = false; + + /// Identifier of the constellation + QString constellationId; + /// Placeholder identifier of the constellation + QString constellationPlaceholderId; + /// English name of the constellation + QString constellationEnglishName; + /// Native name of the constellation + std::optional constellationNativeName; + /// Pronunciation of the constellation + std::optional constellationPronounce; + /// IPA representation of the constellation + std::optional constellationIPA; + /// The currently displayed artwork + ScmConstellationImage *imageItem; + /// Holds the last used directory + QString lastUsedImageDirectory; +#if defined(Q_OS_MAC) + /// Help text on how to use the pen for Mac users. + const QString helpDrawInfoPen = q_("Use RightClick or Control + Click to draw a connected line.\n" + "Use Double-RightClick or Control + Double-Click to stop drawing the line.\n" + "Use Command + F to search and connect stars."); + /// Help text on how to use the eraser for Mac users. + const QString helpDrawInfoEraser = q_( + "Hold RightClick or Control + Click to delete the line under the cursor.\n"); +#else + /// Help text on how to use the pen for non-Mac users. + const QString helpDrawInfoPen = q_("Use RightClick to draw a connected line.\n" + "Use Double-RightClick to stop drawing the line.\n" + "Use CTRL + F to search and connect stars."); + /// Help text on how to use the eraser for non-Mac users. + const QString helpDrawInfoEraser = q_("Hold RightClick to delete the line under the cursor.\n"); +#endif + + /// Help text on how to use the artwork tool. + const QString artworkToolTip = q_( + "Usage:\n" + "1. Upload an image\n" + "2. Three anchor points appear in the center of the image\n" + " - An anchor is green when selected\n" + "3. Select a star of your choice\n" + "4. Click the 'Bind Star' button\n" + "5. The anchor is shown in a brighter color when bound to a star\n" + " - The corresponding bound star is automatically selected when an anchor is selected\n" + " - 'Bind Star' will overwrite the current binding if it already exists" + ); + + /// The constellation that is currently being edited + scm::ScmConstellation *constellationBeingEdited = nullptr; + + /// Indicates whether the constellation is a dark constellation + bool isDarkConstellation = false; + + /** + * @brief Checks whether the current data is enough for the constellation to be saved. + */ + bool canConstellationBeSaved() const; + + /** + * @brief Saves the constellation data as an object in the current sky culture. + */ + void saveConstellation(); + + /** + * @brief Resets and closes the dialog. + */ + void cancel(); + + /** + * @brief Updates the state of the artwork. + */ + void updateArtwork(); +}; + +#endif // SCM_CONSTELLATION_DIALOG_HPP diff --git a/plugins/SkyCultureMaker/src/gui/ScmConstellationImage.cpp b/plugins/SkyCultureMaker/src/gui/ScmConstellationImage.cpp new file mode 100644 index 0000000000000..38b8a6bdb704e --- /dev/null +++ b/plugins/SkyCultureMaker/src/gui/ScmConstellationImage.cpp @@ -0,0 +1,193 @@ +/* + * Sky Culture Maker plug-in for Stellarium + * + * Copyright (C) 2025 Vincent Gerlach + * Copyright (C) 2025 Luca-Philipp Grumbach + * Copyright (C) 2025 Fabian Hofer + * Copyright (C) 2025 Mher Mnatsakanyan + * Copyright (C) 2025 Richard Hofmann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ScmConstellationImage.hpp" +#include "StarWrapper.hpp" +#include "StelApp.hpp" +#include "types/Anchor.hpp" +#include + +ScmConstellationImage::ScmConstellationImage() + : QGraphicsPixmapItem() +{ + setZValue(1); + drawScene.addItem(this); + for (auto &anchor : anchorItems) + { + drawScene.addItem(&anchor); + anchor.setParentItem(this); + anchor.setSelectionReference(selectedAnchor); + } +} + +ScmConstellationImage::~ScmConstellationImage() +{ + qDebug() << "SkyCultureMaker: Unloaded the ScmConstellationImage"; +} + +void ScmConstellationImage::setImage(const QPixmap &image) +{ + setPixmap(image); + + const qreal &anchorHeight = image.height() * anchorScale; + const qreal &anchorWidth = image.width() * anchorScale; + const qreal diameter = std::min(anchorWidth, anchorHeight); + + drawScene.setSceneRect(0, 0, image.width(), image.height()); + + // Fixes the offset that occurs through the setSceneRect + const QPointF &offsetFix = QPointF(-image.width() * 0.25, -image.height() * 0.25); + const QPointF &positionCenter = scenePos() - offsetFix; // - offsetFix = (Width * 0.5, Height * 0.5) + offsetFix + + // offset to place the anchors in the center next to each other + qreal offset = 0.5 * anchorItems.size() * -diameter; + for (auto &anchor : anchorItems) + { + anchor.setPosDiameter(positionCenter.x() + offset, positionCenter.y(), diameter); + anchor.setMovementBounds(QRectF(offsetFix.x() - offset, offsetFix.y(), image.width(), image.height())); + offset += diameter; + } + + artwork.setArtwork(image.toImage()); + + if (isImageAnchored()) + { + artwork.setupArt(); + } +} + +bool ScmConstellationImage::hasAnchorSelection() const +{ + return selectedAnchor != nullptr; +} + +ScmConstellationImageAnchor *ScmConstellationImage::getSelectedAnchor() const +{ + return selectedAnchor; +} + +void ScmConstellationImage::setAnchorSelectionChangedCallback(std::function func) +{ + for (auto &anchor : anchorItems) + { + anchor.setSelectionChangedCallback(func); + } +} + +void ScmConstellationImage::setAnchorPositionChangedCallback(std::function func) +{ + for (auto &anchor : anchorItems) + { + anchor.setPositionChangedCallback(func); + } +} + +void ScmConstellationImage::resetAnchors() +{ + selectedAnchor = nullptr; + for (auto &anchor : anchorItems) + { + anchor.setStarHip(0); + anchor.deselect(); + } +} + +void ScmConstellationImage::resetArtwork() +{ + artwork.reset(); + hide(); +} + +const std::vector &ScmConstellationImage::getAnchors() const +{ + return anchorItems; +} + +bool ScmConstellationImage::isImageAnchored() +{ + for (const auto &anchor : anchorItems) + { + if (anchor.getStarHip() == 0) + { + return false; + } + } + return true; +} + +void ScmConstellationImage::updateAnchors() +{ + for (size_t i = 0; i < anchorItems.size(); ++i) + { + scm::Anchor anchor; + anchor.position = anchorItems[i].getPosition(); + anchor.hip = anchorItems[i].getStarHip(); + artwork.setAnchor(i, anchor); + } + + artwork.setupArt(); +} + +void ScmConstellationImage::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + QGraphicsItem::mousePressEvent(event); +} + +void ScmConstellationImage::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + QGraphicsItem::mouseReleaseEvent(event); +} + +void ScmConstellationImage::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + QGraphicsItem::mouseMoveEvent(event); +} + +const scm::ScmConstellationArtwork &ScmConstellationImage::getArtwork() const +{ + return artwork; +} + +void ScmConstellationImage::setArtwork(const scm::ScmConstellationArtwork &artwork) +{ + resetAnchors(); + resetArtwork(); + + if (!artwork.getHasArt()) + { + return; + } + + QPixmap pixmap = QPixmap::fromImage(artwork.getArtwork()); + setImage(pixmap); + + const auto &anchors = artwork.getAnchors(); + for (size_t i = 0; i < artwork.getAnchors().size(); i++) + { + anchorItems[i].setPosition(anchors[i].position); + anchorItems[i].setStarHip(anchors[i].hip); + } + + updateAnchors(); + show(); +} diff --git a/plugins/SkyCultureMaker/src/gui/ScmConstellationImage.hpp b/plugins/SkyCultureMaker/src/gui/ScmConstellationImage.hpp new file mode 100644 index 0000000000000..5035893dd60dd --- /dev/null +++ b/plugins/SkyCultureMaker/src/gui/ScmConstellationImage.hpp @@ -0,0 +1,137 @@ +/* + * Sky Culture Maker plug-in for Stellarium + * + * Copyright (C) 2025 Vincent Gerlach + * Copyright (C) 2025 Luca-Philipp Grumbach + * Copyright (C) 2025 Fabian Hofer + * Copyright (C) 2025 Mher Mnatsakanyan + * Copyright (C) 2025 Richard Hofmann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SCM_CONSTELLATION_IMAGE_H +#define SCM_CONSTELLATION_IMAGE_H + +#include "ScmConstellationArtwork.hpp" +#include "ScmConstellationImageAnchor.hpp" +#include +#include +#include +#include +#include + +class ScmConstellationImage : public QGraphicsPixmapItem +{ +public: + ScmConstellationImage(); + ~ScmConstellationImage(); + + /** + * @brief Set the image that is shown by this item. + * + * @param image The image that is shown. + */ + void setImage(const QPixmap &image); + + /** + * @brief Gets the indicator if any anchor is selected. + * + * @return true Anchor is selected. + * @return false no Anchor is selected. + */ + bool hasAnchorSelection() const; + + /** + * @brief Get the selected anchor. + * + * @return ScmConstellationImageAnchor* The selected anchor pointer or nullptr. + */ + ScmConstellationImageAnchor *getSelectedAnchor() const; + + /** + * @brief Set the anchor selection changed callback function. + */ + void setAnchorSelectionChangedCallback(std::function func); + + /** + * @brief Set the anchor position changed callback function. + */ + void setAnchorPositionChangedCallback(std::function func); + + /** + * @brief Resets the anchors to default. + */ + void resetAnchors(); + + /** + * @brief Resets the artwork. + */ + void resetArtwork(); + + /** + * @brief Gets the anchors of this object. + * + * @return const std::vector& The anchors of this object. + */ + const std::vector &getAnchors() const; + + /** + * @brief Indicates if all anchors have a star they are bound too. + * + * @return true All anchors have a bound star. + * @return false Not all anchors have a bound star. + */ + bool isImageAnchored(); + + /** + * @brief Updates the anchors of this object with the drawn artwork. + */ + void updateAnchors(); + + /** + * @brief Gets the artwork; + */ + const scm::ScmConstellationArtwork &getArtwork() const; + + /** + * @brief Set the artwork in the constellation image. + * + * @param artwork The artwork to use. + */ + void setArtwork(const scm::ScmConstellationArtwork &artwork); + +protected: + void mousePressEvent(QGraphicsSceneMouseEvent *event) override; + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; + void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; + +private: + /// Holds the scene that is drawn on. + QGraphicsScene drawScene; + + /// The anchors in the graphics view. + std::vector anchorItems{3}; // 3 anchors + + /// The scale of the anchor relative to the image size. + const qreal anchorScale = 1 / 50.0; + + // Holds the selected anchor + ScmConstellationImageAnchor *selectedAnchor = nullptr; + + /// Holds the current artwork + scm::ScmConstellationArtwork artwork; +}; + +#endif // SCM_CONSTELLATION_IMAGE_H diff --git a/plugins/SkyCultureMaker/src/gui/ScmConstellationImageAnchor.cpp b/plugins/SkyCultureMaker/src/gui/ScmConstellationImageAnchor.cpp new file mode 100644 index 0000000000000..40ace933fbc1d --- /dev/null +++ b/plugins/SkyCultureMaker/src/gui/ScmConstellationImageAnchor.cpp @@ -0,0 +1,235 @@ +/* + * Sky Culture Maker plug-in for Stellarium + * + * Copyright (C) 2025 Vincent Gerlach + * Copyright (C) 2025 Luca-Philipp Grumbach + * Copyright (C) 2025 Fabian Hofer + * Copyright (C) 2025 Mher Mnatsakanyan + * Copyright (C) 2025 Richard Hofmann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ScmConstellationImageAnchor.hpp" +#include "StelApp.hpp" +#include "StelObjectMgr.hpp" +#include +#include +#include +#include + +void ScmConstellationImageAnchor::setSelectionReference(ScmConstellationImageAnchor *&anchor) +{ + selection = &anchor; +} + +void ScmConstellationImageAnchor::select() +{ + if (selection == nullptr) + { + return; + } + + if (*selection != nullptr) + { + (*selection)->deselect(); + } + + isSelected = true; + *selection = this; + updateColor(); + + if (selectionChangedCallback != nullptr) + { + selectionChangedCallback(); + } + + // select the bound star + if (hip != 0) + { + StelApp &app = StelApp::getInstance(); + StelObjectMgr &objectMgr = app.getStelObjectMgr(); + objectMgr.findAndSelect(QString("HIP ") + std::to_string(hip).c_str()); + } +} + +void ScmConstellationImageAnchor::deselect() +{ + isSelected = false; + updateColor(); +} + +void ScmConstellationImageAnchor::setSelectionChangedCallback(std::function func) +{ + selectionChangedCallback = func; +} + +void ScmConstellationImageAnchor::setPositionChangedCallback(std::function func) +{ + positionChangedCallback = func; +} + +void ScmConstellationImageAnchor::setMovementBounds(const QRectF &bounds) +{ + movementBound = bounds; +} + +void ScmConstellationImageAnchor::setStarHip(StarId hip) +{ + ScmConstellationImageAnchor::hip = hip; +} + +bool ScmConstellationImageAnchor::trySetStarHip(const QString &id) +{ + QRegularExpression hipExpression(R"(HIP\s+(\d+))"); + + QRegularExpressionMatch hipMatch = hipExpression.match(id); + if (hipMatch.hasMatch()) + { + setStarHip(hipMatch.captured(1).toInt()); + updateColor(); + return true; + } + + return false; +} + +const StarId &ScmConstellationImageAnchor::getStarHip() const +{ + return hip; +} + +void ScmConstellationImageAnchor::updateColor() +{ + if (isSelected == true) + { + setBrush(hip == 0 ? selectedColorNoStar : selectedColor); + } + else + { + setBrush(hip == 0 ? colorNoStar : color); + } +} + +Vec2i ScmConstellationImageAnchor::getPosition() const +{ + auto position = pos(); + auto origin = movementBound.topLeft(); + auto anchorRadius = rect().size() * 0.5f; + // offset by the origin of the texture so we have exact pixel mapping starting in the top left corner. + return Vec2i(static_cast(position.x() - origin.x() + anchorRadius.width()), + static_cast(position.y() - origin.y() + anchorRadius.height())); +} + +void ScmConstellationImageAnchor::setPosition(const Vec2i &position) +{ + auto origin = movementBound.topLeft(); + auto anchorRadius = rect().size() * 0.5f; + + qreal x = position[0] - anchorRadius.width() + origin.x(); + qreal y = position[1] - anchorRadius.height() + origin.y(); + + setPos(x, y); +} + +void ScmConstellationImageAnchor::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + QGraphicsItem::mousePressEvent(event); + + if (event->button() == Qt::MouseButton::LeftButton) + { + select(); + } +} + +void ScmConstellationImageAnchor::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + QGraphicsItem::mouseReleaseEvent(event); +} + +void ScmConstellationImageAnchor::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + QGraphicsItem::mouseMoveEvent(event); + + if (isSelected == false) + { + return; + } + + if (movementBound.isEmpty()) + { + return; + } + + // Pos (x,y) is the upper left corner + qreal new_x = x(); + qreal new_y = y(); + if (new_x < movementBound.left()) + { + new_x = movementBound.left(); + } + else if (new_x + rect().width() > movementBound.right()) + { + new_x = movementBound.right() - rect().width(); + } + + if (new_y < movementBound.top()) + { + new_y = movementBound.top(); + } + else if (new_y + rect().height() > movementBound.bottom()) + { + new_y = movementBound.bottom() - rect().height(); + } + + setPos(new_x, new_y); + + if (positionChangedCallback != nullptr) + { + positionChangedCallback(); + } +} + +ScmConstellationImageAnchor::ScmConstellationImageAnchor() + : QGraphicsEllipseItem() +{ + setFlag(ItemIsMovable, true); + setBrush(QBrush(colorNoStar)); + setZValue(10); +} + +ScmConstellationImageAnchor::ScmConstellationImageAnchor(QPointF position, qreal diameter) + : QGraphicsEllipseItem(position.x(), position.y(), diameter, diameter) +{ + setFlag(ItemIsMovable, true); + setBrush(QBrush(colorNoStar)); + setZValue(10); +} + +ScmConstellationImageAnchor::~ScmConstellationImageAnchor() +{ + qDebug() << "SkyCultureMaker: Unloaded the ScmConstellationImageAnchor"; +} + +void ScmConstellationImageAnchor::setDiameter(qreal diameter) +{ + QPointF position = pos(); + setPosDiameter(position.x(), position.y(), diameter); +} + +void ScmConstellationImageAnchor::setPosDiameter(qreal x, qreal y, qreal diameter) +{ + setRect(x, y, diameter, diameter); + setPos(x, y); +} diff --git a/plugins/SkyCultureMaker/src/gui/ScmConstellationImageAnchor.hpp b/plugins/SkyCultureMaker/src/gui/ScmConstellationImageAnchor.hpp new file mode 100644 index 0000000000000..c18f88fd79b89 --- /dev/null +++ b/plugins/SkyCultureMaker/src/gui/ScmConstellationImageAnchor.hpp @@ -0,0 +1,159 @@ +/* + * Sky Culture Maker plug-in for Stellarium + * + * Copyright (C) 2025 Vincent Gerlach + * Copyright (C) 2025 Luca-Philipp Grumbach + * Copyright (C) 2025 Fabian Hofer + * Copyright (C) 2025 Mher Mnatsakanyan + * Copyright (C) 2025 Richard Hofmann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SCM_CONSTELLATION_IMAGE_ANCHOR_H +#define SCM_CONSTELLATION_IMAGE_ANCHOR_H + +#include "StarMgr.hpp" +#include "VecMath.hpp" +#include +#include +#include +#include + +class ScmConstellationImageAnchor : public QGraphicsEllipseItem +{ +public: + ScmConstellationImageAnchor(); + ScmConstellationImageAnchor(QPointF position, qreal diameter); + ~ScmConstellationImageAnchor(); + + /** + * @brief Set the diameter of this anchor. + * + * @param diameter The diameter of this round anchor. + */ + void setDiameter(qreal diameter); + + /** + * @brief Set the position and diameter of this anchor. + * + * @param x The x position in the scene. + * @param y The y position in the scene. + * @param diameter The diameter of this anchor. + */ + void setPosDiameter(qreal x, qreal y, qreal diameter); + + /** + * @brief Set the reference to the selected anchor. + * + * @param anchor The pointer to the selection anchor. + */ + void setSelectionReference(ScmConstellationImageAnchor *&anchor); + + /** + * @brief Selects this anchor. + */ + void select(); + + /** + * @brief Deselects this anchor. + */ + void deselect(); + + /** + * @brief Set the selection changed callback function. + */ + void setSelectionChangedCallback(std::function func); + + /** + * @brief Set the position changed callback function. + */ + void setPositionChangedCallback(std::function func); + + /** + * @brief Set the bounds in which the anchors can be moved. + * + * @param bounds The bound the anchor can be moved in. + */ + void setMovementBounds(const QRectF &bounds); + + /** + * @brief set the bound star ID of this anchor. + */ + void setStarHip(StarId hip); + + /** + * @brief Tries to extract the hip from the star id. + * + * @param id The id to extract the hip from. + * @return true Success. + * @return false Failed. + */ + bool trySetStarHip(const QString &id); + + /** + * @brief Get the star hip id. + * + * @return const QString& + */ + const StarId &getStarHip() const; + + /** + * @brief Updates the color of the anchor based on its current state. + */ + void updateColor(); + + /** + * @brief Get the position of the anchor in the parent image. + * + * @return Vec2i The position of the anchor. + */ + Vec2i getPosition() const; + + /** + * @brief Set the position of the anchor in the parent image. + * + * @param position The position of the anchor. + */ + void setPosition(const Vec2i &position); + +protected: + void mousePressEvent(QGraphicsSceneMouseEvent *event) override; + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; + void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; + +private: + // Indicates if this anchor is selected. + bool isSelected = false; + // Holds the hip of the selected star + StarId hip = 0; // Hip start with 1, 0 = empty value + // Holds the default color of the anchor. + const Qt::GlobalColor color = Qt::GlobalColor::cyan; + // Holds the default color if no star is bound. + const Qt::GlobalColor colorNoStar = Qt::GlobalColor::darkCyan; + // Holds the selected color of the anchor. + const Qt::GlobalColor selectedColor = Qt::GlobalColor::green; + // Holds the selected color of the anchor if no star is bound. + const Qt::GlobalColor selectedColorNoStar = Qt::GlobalColor::darkGreen; + // Holds the selection group of this anchor. + ScmConstellationImageAnchor **selection = nullptr; + // Holds the set selection changed callback + std::function selectionChangedCallback = nullptr; + // Holds the set position changed callback + std::function positionChangedCallback = nullptr; + // Holds the bounds in which the anchor are moveable. + QRectF movementBound; +}; + +#endif // SCM_CONSTELLATION_IMAGE_ANCHOR_H diff --git a/plugins/SkyCultureMaker/src/gui/ScmConvertDialog.cpp b/plugins/SkyCultureMaker/src/gui/ScmConvertDialog.cpp new file mode 100644 index 0000000000000..f081cad49f3ae --- /dev/null +++ b/plugins/SkyCultureMaker/src/gui/ScmConvertDialog.cpp @@ -0,0 +1,455 @@ +/* + * Sky Culture Maker plug-in for Stellarium + * + * Copyright (C) 2025 Vincent Gerlach + * Copyright (C) 2025 Luca-Philipp Grumbach + * Copyright (C) 2025 Fabian Hofer + * Copyright (C) 2025 Mher Mnatsakanyan + * Copyright (C) 2025 Richard Hofmann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifdef SCM_CONVERTER_ENABLED_CPP + +# include "ScmConvertDialog.hpp" +# include "SkyCultureConverter.hpp" +# include "StelMainView.hpp" +# include "ui_scmConvertDialog.h" +# include + +ScmConvertDialog::ScmConvertDialog(SkyCultureMaker *maker) + : StelDialog("ScmConvertDialog") + , ui(new Ui_scmConvertDialog) + , watcher(new QFutureWatcher(this)) + , conversionCancelled(false) + , maker(maker) +{ + // The dialog widget is created in StelDialog::setVisible, not in the constructor. + // The ScmConvertDialog C++ instance is owned by ScmStartDialog. +} + +ScmConvertDialog::~ScmConvertDialog() +{ + // We must wait for the background thread to finish before this object is + // destroyed, otherwise the thread will be operating on a dangling 'this' + // pointer, which will lead to a crash. This also ensures that + // onConversionFinished() is called and temporary files are cleaned up. + // We send a cancel request to the watcher, which will stop the + // background task if it is still running, while finishing major steps + if (watcher->isRunning()) + { + conversionCancelled = true; // Signal the thread to cancel + watcher->waitForFinished(); + } + if (ui != nullptr) + { + delete ui; + } +} + +void ScmConvertDialog::retranslate() +{ + if (dialog) + { + ui->retranslateUi(dialog); + } +} + +void ScmConvertDialog::createDialogContent() +{ + ui->setupUi(dialog); + + // Connect signals + connect(ui->browseButton, &QPushButton::clicked, this, &ScmConvertDialog::browseFile); + connect(ui->titleBar, &TitleBar::closeClicked, this, &ScmConvertDialog::closeDialog); + connect(ui->titleBar, &TitleBar::movedTo, this, &StelDialog::handleMovedTo); + connect(ui->convertButton, &QPushButton::clicked, this, &ScmConvertDialog::convert); + connect(watcher, &QFutureWatcher::finished, this, &ScmConvertDialog::onConversionFinished); +} + +void ScmConvertDialog::closeDialog() +{ + StelDialog::close(); + maker->setToolbarButtonState(false); // Toggle the toolbar button to disabled +} + +void ScmConvertDialog::browseFile() +{ + const QString file = QFileDialog::getOpenFileName(&StelMainView::getInstance(), tr("Select an archive"), + QDir::homePath(), tr("Archives (*.zip *.rar *.7z *.tar)")); + if (!file.isEmpty()) + { + ui->filePathLineEdit->setText(file); + } +} + +void ScmConvertDialog::onConversionFinished() +{ + QString resultText = watcher->future().result(); + ui->convertResultLabel->setText(resultText); + if (!tempDirPath.isEmpty()) + { + QDir(tempDirPath).removeRecursively(); + } + if (!tempDestDirPath.isEmpty()) + { + QDir(tempDestDirPath).removeRecursively(); + } + ui->convertButton->setEnabled(true); +} + +bool ScmConvertDialog::chooseFallbackDirectory(QString &skyCulturesPath, QString &skyCultureId) +{ + // 10 is maximum number of tries the user have to select a fallback directory + for (size_t i = 0; i < 10; i++) + { + QString selectedDirectory = QFileDialog::getExistingDirectory(nullptr, tr("Open Directory")); + if (!QDir(selectedDirectory).exists()) + { + qDebug() << "Selected fallback directory does not exist!"; + continue; + } + + QDir newSkyCultureDir(selectedDirectory + QDir::separator() + skyCultureId); + if (newSkyCultureDir.mkpath(".")) + { + newSkyCultureDir.removeRecursively(); // clean up test directory + skyCulturesPath = selectedDirectory; + return true; + } + } + + qDebug() << "User exceeded maximum number (10) of attempts to set a fallback directory."; + return false; +} + +namespace +{ +// Code snippet from https://github.com/selmf/unarr/blob/master/test/main.c +ar_archive *ar_open_any_archive(ar_stream *stream, const char *fileext) +{ + ar_archive *ar = ar_open_rar_archive(stream); + if (!ar) + ar = ar_open_zip_archive(stream, + fileext && (strcmp(fileext, ".xps") == 0 || strcmp(fileext, ".epub") == 0)); + if (!ar) ar = ar_open_7z_archive(stream); + if (!ar) ar = ar_open_tar_archive(stream); + return ar; +} + +QString extractArchive(const QString &archivePath, const QString &destinationPath) +{ + ar_stream *stream = ar_open_file(archivePath.toUtf8().constData()); + if (!stream) + { + return QString("Failed to open archive: %1").arg(archivePath); + } + ar_archive *archive = ar_open_any_archive(stream, QFileInfo(archivePath).suffix().toUtf8().constData()); + if (!archive) + { + ar_close(stream); + return QString("Failed to open archive: %1").arg(archivePath); + } + + QString errorString; + // iterate entries and decompress each + while (ar_parse_entry(archive)) + { + QString name = QString::fromUtf8(ar_entry_get_name(archive)); + QString outPath = destinationPath + QDir::separator() + name; + QDir().mkpath(QFileInfo(outPath).path()); + QFile outFile(outPath); + if (outFile.open(QIODevice::WriteOnly)) + { + qint64 remaining = ar_entry_get_size(archive); + const qint64 bufSize = 8192; + QByteArray buffer; + while (remaining > 0) + { + qint64 chunk = qMin(remaining, bufSize); + buffer.resize(chunk); + if (!ar_entry_uncompress(archive, reinterpret_cast(buffer.data()), + chunk)) + { + errorString = QString("Failed to decompress entry: %1").arg(name); + break; + } + outFile.write(buffer); + remaining -= chunk; + } + outFile.close(); + } + else + { + errorString = QString("Failed to open file for writing: %1").arg(outPath); + } + + if (!errorString.isEmpty()) + { + break; + } + } + ar_close_archive(archive); + ar_close(stream); + + return errorString; +} + +QString validateArchivePath(const QString &path) +{ + QMimeDatabase db; + QMimeType mime = db.mimeTypeForFile(path, QMimeDatabase::MatchDefault); + + static const QStringList archiveTypes = {QStringLiteral("application/zip"), QStringLiteral("application/x-tar"), + QStringLiteral("application/x-7z-compressed"), + QStringLiteral("application/x-rar-compressed"), + QStringLiteral("application/vnd.rar")}; + + if (!archiveTypes.contains(mime.name())) + { + qWarning() << "SkyCultureMaker: Unsupported MIME type:" << mime.name() << "for file" << path; + return QStringLiteral("Please select a valid archive file " + "(zip, tar, rar or 7z)"); + } + return QString(); // No error +} + +QString extractAndDetermineSource(const QString &archivePath, const QString &tempDirPath, QString &outSourcePath) +{ + try + { + qDebug() << "SkyCultureMaker: Extracting archive:" << archivePath << "to" << tempDirPath; + + QString error = extractArchive(archivePath, tempDirPath); + if (!error.isEmpty()) + { + return error; + } + + qDebug() << "SkyCultureMaker: Archive extracted to:" << tempDirPath; + } + catch (const std::exception &e) + { + return QString("Error extracting archive: %1").arg(e.what()); + } + + QStringList extracted_files = QDir(tempDirPath).entryList(QDir::AllEntries | QDir::NoDotAndDotDot); + + qDebug() << "SkyCultureMaker: Extracted files:" << extracted_files.length(); + + if (extracted_files.isEmpty()) + { + return "No files found in the archive."; + } + + // set source as the folder that gets converted + // Archive can have a single folder with the skyculture files or + // an the skyculture files directly in the root + bool sourceFound = false; + if (extracted_files.contains("info.ini")) + { + outSourcePath = tempDirPath; + sourceFound = true; + } + else if (extracted_files.length() == 1) + { + const QString singleItemPath = QDir(tempDirPath).filePath(extracted_files.first()); + if (QFileInfo(singleItemPath).isDir() && QFile::exists(QDir(singleItemPath).filePath("info.ini"))) + { + outSourcePath = singleItemPath; + sourceFound = true; + } + } + + if (!sourceFound) + { + return "Invalid archive structure. Expected 'info.ini' " + "or a " + "single " + "subfolder."; + } + + return QString(); // No error +} + +QString performConversion(const QString &sourcePath, const QString &destPath) +{ + qDebug() << "SkyCultureMaker: Source for conversion:" << sourcePath; + qDebug() << "SkyCultureMaker: Destination for conversion:" << destPath; + + SkyCultureConverter::ReturnValue result; + + try + { + result = SkyCultureConverter::convert(sourcePath, destPath); + } + catch (const std::exception &e) + { + return QString("Error during conversion: %1").arg(e.what()); + } + + switch (result) + { + case SkyCultureConverter::ReturnValue::CONVERT_SUCCESS: return QString(); // Success + case SkyCultureConverter::ReturnValue::ERR_OUTPUT_DIR_EXISTS: + return "Output directory (convertion) already exists."; + case SkyCultureConverter::ReturnValue::ERR_INFO_INI_NOT_FOUND: return "info.ini not found in the archive."; + case SkyCultureConverter::ReturnValue::ERR_OUTPUT_DIR_CREATION_FAILED: + return "Failed to create output directory."; + case SkyCultureConverter::ReturnValue::ERR_OUTPUT_FILE_WRITE_FAILED: return "Failed to write output file."; + default: return "Unknown error."; + } +} + +QString moveConvertedFiles(const QString &tempDestDirPath, const QString &stem, const QString &skyCulturesPath) +{ + // move the converted files into skycultures folder inside programm directory + QString targetPath = QDir(skyCulturesPath).filePath(stem); + + QDir targetDir(targetPath); // QDir object for checking existence + const QString absoluteTargetPath = targetDir.absolutePath(); + const QString absoluteTempDestDirPath = QDir(tempDestDirPath).absolutePath(); + + qDebug() << "SkyCultureMaker: Target path for moved files:" << absoluteTargetPath; + + if (targetDir.exists()) + { + // Target folder already exists. Do not copy/move. + qDebug() << "SkyCultureMaker: Target folder" << absoluteTargetPath + << "already exists. No move operation " + "performed."; + return QString("Target folder already exists: %1").arg(absoluteTargetPath); + } + else if (QDir().rename(absoluteTempDestDirPath, absoluteTargetPath)) + { + qDebug() << "SkyCultureMaker: Successfully moved contents of" << absoluteTempDestDirPath << "to" << absoluteTargetPath; + return QString(); + } + else + { + return QString("Failed to move converted files to: %1").arg(absoluteTargetPath); + } +} + +} // namespace + +void ScmConvertDialog::convert() +{ + // Clear previous result message + ui->convertResultLabel->setText(tr("")); + + const QString path = ui->filePathLineEdit->text(); + if (path.isEmpty()) + { + ui->convertResultLabel->setText(tr("Please select a file first.")); + return; + } + + QString validationError = validateArchivePath(path); + if (!validationError.isEmpty()) + { + ui->convertResultLabel->setText(validationError); + return; + } + + qDebug() << "SkyCultureMaker: Selected file:" << path; + + // Create a temporary directory for extraction + QString stem = QFileInfo(path).baseName(); // e.g. "foo.tar.gz" -> "foo" + + QString skyCulturesPath = QDir(StelFileMgr::getInstallationDir()).filePath("skycultures"); + QDir skyCultureDirectory(skyCulturesPath + QDir::separator() + stem); + + // Since we are creating a test-directory to check for write permissions, + // we need to check if the directory already exists, because we would + // clean up the test directory after the test for permissions which would delete + // the existing skyculture directory if it exists. + if (skyCultureDirectory.exists()) + { + ui->convertResultLabel->setText( + tr("Target folder already exists: %1").arg(skyCulturesPath + QDir::separator() + stem)); + return; + } + + // Create a test directory to check for write permissions + if (!skyCultureDirectory.mkpath(".")) + { + bool fallbackSuccess = chooseFallbackDirectory(skyCulturesPath, stem); + if (!fallbackSuccess) + { + ui->convertResultLabel->setText(tr("Could not create destination directory.")); + return; + } + } + else + { + skyCultureDirectory.removeRecursively(); // clean up test directory + } + + tempDirPath = QDir::tempPath() + QDir::separator() + "skycultures" + QDir::separator() + stem; + QDir().mkpath(tempDirPath); + QDir tempFolder(tempDirPath); + + // Destination is where the converted files will be saved temporarily + // Important: the converter checks if the destination folder already exists + // and will not overwrite it, so we do not create it here. + // If the destination folder already exists, the converter will return an error. + tempDestDirPath = QDir::tempPath() + QDir::separator() + "skycultures" + QDir::separator() + "results" + + QDir::separator() + stem; + QDir tempDestFolder(tempDestDirPath); + + conversionCancelled = false; // Reset the flag before starting a new conversion + ui->convertButton->setEnabled(false); + + // Run conversion in a background thread. + QFuture future = QtConcurrent::run( + [this, path, stem, skyCulturesPath]() -> QString + { + QString sourcePath; + + // Validate the archive path (whether it is a valid archive file) + QString error = validateArchivePath(path); + if (!error.isEmpty()) return error; + + // Check for cancellation between major steps + if (conversionCancelled) return "Conversion cancelled."; + + // Extract the archive to a temporary directory + // Check if the skyculture files are in the root or in a subfolder in the archive + error = extractAndDetermineSource(path, tempDirPath, sourcePath); + if (!error.isEmpty()) return error; + + // Check for cancellation between major steps + if (conversionCancelled) return "Conversion cancelled."; + + // Call the actual converter + error = performConversion(sourcePath, tempDestDirPath); + if (!error.isEmpty()) return error; + + // Check for cancellation between major steps + if (conversionCancelled) return "Conversion cancelled."; + + error = moveConvertedFiles(tempDestDirPath, stem, skyCulturesPath); + if (!error.isEmpty()) return error; + + return QString("Conversion completed successfully. Files are in: %1").arg(skyCulturesPath); + }); + + watcher->setFuture(future); + + qDebug() << "SkyCultureMaker: Conversion started."; +} + +#endif // SCM_CONVERTER_ENABLED_CPP diff --git a/plugins/SkyCultureMaker/src/gui/ScmConvertDialog.hpp b/plugins/SkyCultureMaker/src/gui/ScmConvertDialog.hpp new file mode 100644 index 0000000000000..1246aaa09d087 --- /dev/null +++ b/plugins/SkyCultureMaker/src/gui/ScmConvertDialog.hpp @@ -0,0 +1,80 @@ +/* + * Sky Culture Maker plug-in for Stellarium + * + * Copyright (C) 2025 Vincent Gerlach + * Copyright (C) 2025 Luca-Philipp Grumbach + * Copyright (C) 2025 Fabian Hofer + * Copyright (C) 2025 Mher Mnatsakanyan + * Copyright (C) 2025 Richard Hofmann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SCMCONVERTDIALOG_HPP +#define SCMCONVERTDIALOG_HPP + +#include "StelDialog.hpp" + +#ifdef SCM_CONVERTER_ENABLED_CPP + +# include "SkyCultureMaker.hpp" +# include "StelFileMgr.hpp" +# include "ui_scmConvertDialog.h" +# include "unarr.h" +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include + +class Ui_scmConvertDialog; + +class ScmConvertDialog : public StelDialog +{ + Q_OBJECT + +public: + explicit ScmConvertDialog(SkyCultureMaker *maker); + ~ScmConvertDialog() override; + void retranslate() override; + +protected: + void createDialogContent() override; + +private slots: + void browseFile(); + void convert(); + void onConversionFinished(); + void closeDialog(); + bool chooseFallbackDirectory(QString &skyCulturesPath, QString &skyCultureId); + +private: + Ui_scmConvertDialog *ui; + QFutureWatcher *watcher; + QString tempDirPath; + QString tempDestDirPath; + std::atomic conversionCancelled; + SkyCultureMaker *maker = nullptr; +}; + +#endif // SCM_CONVERTER_ENABLED_CPP +#endif // SCMCONVERTDIALOG_HPP diff --git a/plugins/SkyCultureMaker/src/gui/ScmHideOrAbortMakerDialog.cpp b/plugins/SkyCultureMaker/src/gui/ScmHideOrAbortMakerDialog.cpp new file mode 100644 index 0000000000000..d4cd3cbc58574 --- /dev/null +++ b/plugins/SkyCultureMaker/src/gui/ScmHideOrAbortMakerDialog.cpp @@ -0,0 +1,103 @@ +/* + * Sky Culture Maker plug-in for Stellarium + * + * Copyright (C) 2025 Vincent Gerlach + * Copyright (C) 2025 Luca-Philipp Grumbach + * Copyright (C) 2025 Fabian Hofer + * Copyright (C) 2025 Mher Mnatsakanyan + * Copyright (C) 2025 Richard Hofmann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ScmHideOrAbortMakerDialog.hpp" +#include "ui_scmHideOrAbortMakerDialog.h" +#include +#include + +ScmHideOrAbortMakerDialog::ScmHideOrAbortMakerDialog(SkyCultureMaker *maker) + : StelDialogSeparate("ScmHideOrAbortMakerDialog") + , maker(maker) +{ + assert(maker != nullptr); + ui = new Ui_scmHideOrAbortMakerDialog; +} + +ScmHideOrAbortMakerDialog::~ScmHideOrAbortMakerDialog() +{ + if (ui != nullptr) + { + delete ui; + } + + qDebug() << "SkyCultureMaker: Unloaded the ScmHideOrAbortMakerDialog"; +} + +void ScmHideOrAbortMakerDialog::retranslate() +{ + if (dialog) + { + ui->retranslateUi(dialog); + } +} + +void ScmHideOrAbortMakerDialog::createDialogContent() +{ + ui->setupUi(dialog); + + // connect(&StelApp::getInstance(), SIGNAL(languageChanged()), this, SLOT(retranslate())); + connect(ui->titleBar, &TitleBar::closeClicked, this, &ScmHideOrAbortMakerDialog::cancelDialog); + connect(ui->titleBar, SIGNAL(movedTo(QPoint)), this, SLOT(handleMovedTo(QPoint))); + + connect(&StelApp::getInstance(), &StelApp::fontChanged, this, &ScmHideOrAbortMakerDialog::handleFontChanged); + connect(&StelApp::getInstance(), &StelApp::guiFontSizeChanged, this, + &ScmHideOrAbortMakerDialog::handleFontChanged); + handleFontChanged(); + + // Buttons + connect(ui->scmMakerAbortButton, &QPushButton::clicked, this, + &ScmHideOrAbortMakerDialog::abortScmCreationProcess); // Abort + connect(ui->scmMakerHideButton, &QPushButton::clicked, this, + &ScmHideOrAbortMakerDialog::hideScmCreationProcess); // Hide + connect(ui->scmMakerCancelButton, &QPushButton::clicked, this, + &ScmHideOrAbortMakerDialog::cancelDialog); // Cancel +} + +void ScmHideOrAbortMakerDialog::handleFontChanged() +{ + QFont questionFont = QApplication::font(); + questionFont.setPixelSize(questionFont.pixelSize() + 4); + ui->questionLbl->setFont(questionFont); +} + +// TODO: save state of the current sky culture +void ScmHideOrAbortMakerDialog::hideScmCreationProcess() +{ + maker->saveScmDialogVisibilityState(); + maker->hideAllDialogs(); + maker->setToolbarButtonState(false); // Turn OFF the toolbar button (image OFF) +} + +// TODO: clear the current sky culture +void ScmHideOrAbortMakerDialog::abortScmCreationProcess() +{ + maker->resetScmDialogs(); + maker->hideAllDialogs(); + maker->setToolbarButtonState(false); // Turn OFF the toolbar button (image OFF) +} + +void ScmHideOrAbortMakerDialog::cancelDialog() +{ + maker->setHideOrAbortMakerDialogVisibility(false); +} diff --git a/plugins/SkyCultureMaker/src/gui/ScmHideOrAbortMakerDialog.hpp b/plugins/SkyCultureMaker/src/gui/ScmHideOrAbortMakerDialog.hpp new file mode 100644 index 0000000000000..241123b131d9a --- /dev/null +++ b/plugins/SkyCultureMaker/src/gui/ScmHideOrAbortMakerDialog.hpp @@ -0,0 +1,58 @@ +/* + * Sky Culture Maker plug-in for Stellarium + * + * Copyright (C) 2025 Vincent Gerlach + * Copyright (C) 2025 Luca-Philipp Grumbach + * Copyright (C) 2025 Fabian Hofer + * Copyright (C) 2025 Mher Mnatsakanyan + * Copyright (C) 2025 Richard Hofmann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SCMHIDEORABORTMAKERDIALOG_HPP +#define SCMHIDEORABORTMAKERDIALOG_HPP + +#include "SkyCultureMaker.hpp" +#include "StelDialogSeparate.hpp" +#include + +class Ui_scmHideOrAbortMakerDialog; + +class ScmHideOrAbortMakerDialog : public StelDialogSeparate +{ +protected: + void createDialogContent() override; + +public: + ScmHideOrAbortMakerDialog(SkyCultureMaker *maker); + ~ScmHideOrAbortMakerDialog() override; + +public slots: + void retranslate() override; + +protected slots: + void handleFontChanged(); + +private slots: + void hideScmCreationProcess(); + void abortScmCreationProcess(); + void cancelDialog(); + +private: + Ui_scmHideOrAbortMakerDialog *ui = nullptr; + SkyCultureMaker *maker = nullptr; +}; + +#endif // SCMHIDEORABORTMAKERDIALOG_HPP diff --git a/plugins/SkyCultureMaker/src/gui/ScmSkyCultureDialog.cpp b/plugins/SkyCultureMaker/src/gui/ScmSkyCultureDialog.cpp new file mode 100644 index 0000000000000..a99cd654a01e3 --- /dev/null +++ b/plugins/SkyCultureMaker/src/gui/ScmSkyCultureDialog.cpp @@ -0,0 +1,351 @@ +/* + * Sky Culture Maker plug-in for Stellarium + * + * Copyright (C) 2025 Vincent Gerlach + * Copyright (C) 2025 Luca-Philipp Grumbach + * Copyright (C) 2025 Fabian Hofer + * Copyright (C) 2025 Mher Mnatsakanyan + * Copyright (C) 2025 Richard Hofmann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ScmSkyCultureDialog.hpp" +#include "ui_scmSkyCultureDialog.h" +#include +#include + +ScmSkyCultureDialog::ScmSkyCultureDialog(SkyCultureMaker *maker) + : StelDialogSeparate("ScmSkyCultureDialog") + , maker(maker) +{ + assert(maker != nullptr); + ui = new Ui_scmSkyCultureDialog; +} + +ScmSkyCultureDialog::~ScmSkyCultureDialog() +{ + delete ui; + + qDebug() << "SkyCultureMaker: Unloaded the ScmSkyCultureDialog"; +} + +void ScmSkyCultureDialog::setConstellations(std::vector *constellations) +{ + ScmSkyCultureDialog::constellations = constellations; + if (ui && dialog && constellations != nullptr) + { + ui->constellationsList->clear(); + for (const auto &constellation : *constellations) + { + // Add the constellation to the list widget + ui->constellationsList->addItem(getDisplayNameFromConstellation(constellation)); + } + } +} + +void ScmSkyCultureDialog::resetConstellations() +{ + if (ui && dialog) + { + ui->constellationsList->clear(); + constellations = nullptr; // Reset the constellations pointer + } +} + +void ScmSkyCultureDialog::retranslate() +{ + if (dialog) + { + ui->retranslateUi(dialog); + } +} + +void ScmSkyCultureDialog::close() +{ + maker->setHideOrAbortMakerDialogVisibility(true); +} + +void ScmSkyCultureDialog::createDialogContent() +{ + ui->setupUi(dialog); + + connect(&StelApp::getInstance(), SIGNAL(languageChanged()), this, SLOT(retranslate())); + connect(ui->titleBar, SIGNAL(movedTo(QPoint)), this, SLOT(handleMovedTo(QPoint))); + connect(ui->titleBar, &TitleBar::closeClicked, this, &ScmSkyCultureDialog::close); + + // Overview Tab + connect(ui->skyCultureNameLE, &QLineEdit::textChanged, this, + [this]() + { + name = ui->skyCultureNameLE->text(); + if (name.isEmpty()) + { + ui->ExportSkyCultureBtn->setEnabled(false); + } + else + { + ui->ExportSkyCultureBtn->setEnabled(true); + } + setIdFromName(name); + }); + + ui->ExportSkyCultureBtn->setEnabled(false); + ui->RemoveConstellationBtn->setEnabled(false); + ui->EditConstellationBtn->setEnabled(false); + + connect(ui->ExportSkyCultureBtn, &QPushButton::clicked, this, &ScmSkyCultureDialog::saveSkyCulture); + connect(ui->AddConstellationBtn, &QPushButton::clicked, this, [this]() { openConstellationDialog(false); }); + connect(ui->AddDarkConstellationBtn, &QPushButton::clicked, this, [this]() { openConstellationDialog(true); }); + + connect(ui->EditConstellationBtn, &QPushButton::clicked, this, &ScmSkyCultureDialog::editSelectedConstellation); + connect(ui->constellationsList, &QListWidget::itemSelectionChanged, this, + &ScmSkyCultureDialog::updateEditConstellationButton); + + connect(ui->RemoveConstellationBtn, &QPushButton::clicked, this, + &ScmSkyCultureDialog::removeSelectedConstellation); + connect(ui->constellationsList, &QListWidget::itemSelectionChanged, this, + &ScmSkyCultureDialog::updateRemoveConstellationButton); + + connect(&StelApp::getInstance(), &StelApp::fontChanged, this, &ScmSkyCultureDialog::handleFontChanged); + connect(&StelApp::getInstance(), &StelApp::guiFontSizeChanged, this, &ScmSkyCultureDialog::handleFontChanged); + handleFontChanged(); + + // Description Tab + + // add all licenses to the combo box + for (const auto &license : scm::LICENSES) + { + // add name, license type + ui->licenseCB->addItem(license.second.name, QVariant::fromValue(license.first)); + // set the license description as tooltip + int index = ui->licenseCB->count() - 1; + ui->licenseCB->setItemData(index, license.second.description, Qt::ToolTipRole); + // set NONE as the default license + if (license.first == scm::LicenseType::NONE) + { + ui->licenseCB->setCurrentIndex(index); + } + } + + // add all classifications to the combo box + for (const auto &classification : scm::CLASSIFICATIONS) + { + // add name, classification type + ui->classificationCB->addItem(classification.second.name, QVariant::fromValue(classification.first)); + // set the classification description as tooltip + int index = ui->classificationCB->count() - 1; + ui->classificationCB->setItemData(index, classification.second.description, Qt::ToolTipRole); + // set NONE as the default classification + if (classification.first == scm::ClassificationType::NONE) + { + ui->classificationCB->setCurrentIndex(index); + } + } +} + +void ScmSkyCultureDialog::handleFontChanged() +{ + QFont headlineFont = QApplication::font(); + headlineFont.setPixelSize(headlineFont.pixelSize() + 2); + ui->currentSCNameLbl->setFont(headlineFont); + ui->constellationsLbl->setFont(headlineFont); + ui->selectLicenseLbl->setFont(headlineFont); + + QFont descriptionTabLblFont = QApplication::font(); + descriptionTabLblFont.setPixelSize(descriptionTabLblFont.pixelSize() + 2); + descriptionTabLblFont.setBold(true); + ui->descriptionTabLbl->setFont(descriptionTabLblFont); +} + +void ScmSkyCultureDialog::saveSkyCulture() +{ + scm::Description desc = getDescriptionFromTextEdit(); + + // check if license is set + if (desc.license == scm::LicenseType::NONE) + { + maker->showUserWarningMessage(dialog, ui->titleBar->title(), q_("Please select a license for the sky culture.")); + return; + } + // check if description is complete + if (!desc.isComplete()) + { + maker->showUserWarningMessage(dialog, ui->titleBar->title(), q_("The sky culture description is not complete. Please fill in all required fields.")); + return; + } + + // If valid, set the sky culture description + maker->setSkyCultureDescription(desc); + + // open export dialog + maker->setSkyCultureExportDialogVisibility(true); +} + +void ScmSkyCultureDialog::editSelectedConstellation() +{ + auto selectedItems = ui->constellationsList->selectedItems(); + if (!selectedItems.isEmpty() && constellations != nullptr) + { + QListWidgetItem *item = selectedItems.first(); + QString constellationName = item->text(); + + // Get Id by comparing to the display name + // This will always work, even when the constellation id + // or name contains special characters + QString selectedConstellationId = ""; + for (const auto &constellation : *constellations) + { + if (constellationName == (getDisplayNameFromConstellation(constellation))) + { + selectedConstellationId = constellation.getId(); + break; + } + } + + maker->openConstellationDialog(selectedConstellationId); + } +} + +void ScmSkyCultureDialog::removeSelectedConstellation() +{ + auto selectedItems = ui->constellationsList->selectedItems(); + if (!selectedItems.isEmpty() && constellations != nullptr) + { + QListWidgetItem *item = selectedItems.first(); + QString constellationName = item->text(); + + // Get Id by comparing to the display name + // This will always work, even when the constellation id + // or name contains special characters + QString selectedConstellationId = ""; + for (const auto &constellation : *constellations) + { + if (constellationName == (getDisplayNameFromConstellation(constellation))) + { + selectedConstellationId = constellation.getId(); + break; + } + } + // Remove the constellation from the SC + maker->getCurrentSkyCulture()->removeConstellation(selectedConstellationId); + // Disable removal button + ui->RemoveConstellationBtn->setEnabled(false); + // The reason for not just removing the constellation in the UI here is that + // in case the constellation could not be removed from the SC, the UI + // and the SC would be out of sync + maker->updateSkyCultureDialog(); + } +} + +void ScmSkyCultureDialog::openConstellationDialog(bool isDarkConstellation) +{ + maker->setConstellationDialogVisibility(true); + maker->setConstellationDialogIsDarkConstellation(isDarkConstellation); +} + +void ScmSkyCultureDialog::setIdFromName(QString &name) +{ + QString id = name.toLower().replace(" ", "_"); + maker->getCurrentSkyCulture()->setId(id); +} + +void ScmSkyCultureDialog::updateAddConstellationButtons(bool enabled) +{ + ui->AddConstellationBtn->setEnabled(enabled); + ui->AddDarkConstellationBtn->setEnabled(enabled); +} + +void ScmSkyCultureDialog::updateEditConstellationButton() +{ + if (!ui->constellationsList->selectedItems().isEmpty()) + { + ui->EditConstellationBtn->setEnabled(true); + } + else + { + ui->EditConstellationBtn->setEnabled(false); + } +} + +void ScmSkyCultureDialog::updateRemoveConstellationButton() +{ + if (!ui->constellationsList->selectedItems().isEmpty()) + { + ui->RemoveConstellationBtn->setEnabled(true); + } + else + { + ui->RemoveConstellationBtn->setEnabled(false); + } +} + +QString ScmSkyCultureDialog::getDisplayNameFromConstellation(const scm::ScmConstellation &constellation) const +{ + return constellation.getEnglishName() + " (" + constellation.getId() + ")"; +} + +scm::Description ScmSkyCultureDialog::getDescriptionFromTextEdit() const +{ + scm::Description desc; + + desc.name = ui->skyCultureNameLE->text(); + desc.authors = ui->authorsTE->toPlainText(); + desc.license = ui->licenseCB->currentData().value(); + desc.cultureDescription = ui->cultureDescriptionTE->toPlainText(); + desc.about = ui->aboutTE->toPlainText(); + + desc.sky = ui->skyTE->toPlainText(); + desc.moonAndSun = ui->moonSunTE->toPlainText(); + desc.planets = ui->planetsTE->toPlainText(); + desc.zodiac = ui->zodiacTE->toPlainText(); + desc.milkyWay = ui->milkyWayTE->toPlainText(); + desc.otherObjects = ui->otherObjectsTE->toPlainText(); + + desc.constellations = ui->constellationsDescTE->toPlainText(); + desc.references = ui->referencesTE->toPlainText(); + desc.acknowledgements = ui->acknowledgementsTE->toPlainText(); + desc.classification = ui->classificationCB->currentData().value(); + + return desc; +} + +void ScmSkyCultureDialog::resetDialog() +{ + if (ui && dialog) + { + ui->skyCultureNameLE->clear(); + ui->authorsTE->clear(); + ui->cultureDescriptionTE->clear(); + ui->aboutTE->clear(); + ui->skyTE->clear(); + ui->moonSunTE->clear(); + ui->planetsTE->clear(); + ui->zodiacTE->clear(); + ui->milkyWayTE->clear(); + ui->otherObjectsTE->clear(); + ui->constellationsDescTE->clear(); + ui->referencesTE->clear(); + ui->acknowledgementsTE->clear(); + + ui->licenseCB->setCurrentIndex(0); + ui->classificationCB->setCurrentIndex(0); + + name.clear(); + setIdFromName(name); + resetConstellations(); + maker->setSkyCultureDescription(getDescriptionFromTextEdit()); + updateRemoveConstellationButton(); + } +} diff --git a/plugins/SkyCultureMaker/src/gui/ScmSkyCultureDialog.hpp b/plugins/SkyCultureMaker/src/gui/ScmSkyCultureDialog.hpp new file mode 100644 index 0000000000000..39d9a85b5cbcb --- /dev/null +++ b/plugins/SkyCultureMaker/src/gui/ScmSkyCultureDialog.hpp @@ -0,0 +1,123 @@ +/* + * Sky Culture Maker plug-in for Stellarium + * + * Copyright (C) 2025 Vincent Gerlach + * Copyright (C) 2025 Luca-Philipp Grumbach + * Copyright (C) 2025 Fabian Hofer + * Copyright (C) 2025 Mher Mnatsakanyan + * Copyright (C) 2025 Richard Hofmann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SCM_SKY_CULTURE_DIALOG_HPP +#define SCM_SKY_CULTURE_DIALOG_HPP + +#include "ScmConstellation.hpp" +#include "SkyCultureMaker.hpp" +#include "StelDialogSeparate.hpp" +#include "types/Classification.hpp" +#include "types/Description.hpp" +#include "types/License.hpp" +#include +#include +#include +#include +#include + +class Ui_scmSkyCultureDialog; + +class ScmSkyCultureDialog : public StelDialogSeparate +{ +protected: + void createDialogContent() override; + +public: + ScmSkyCultureDialog(SkyCultureMaker *maker); + ~ScmSkyCultureDialog() override; + + /** + * @brief Sets the constellations to be displayed in the dialog. + * + * @param constellations The vector of constellations to be set. + */ + void setConstellations(std::vector *constellations); + + /** + * @brief Resets the constellations to an empty vector. + */ + void resetConstellations(); + + /** + * @brief Resets all fields in the dialog to their default values. + */ + void resetDialog(); + + /** + * @brief Updates the add constellation button state. + * + * @param enabled Whether the button should be enabled or disabled. + */ + void updateAddConstellationButtons(bool enabled); + +public slots: + void retranslate() override; + void close() override; + +protected slots: + void handleFontChanged(); + +private slots: + void saveSkyCulture(); + void openConstellationDialog(bool isDarkConstellation); + void editSelectedConstellation(); + void removeSelectedConstellation(); + void updateEditConstellationButton(); + void updateRemoveConstellationButton(); + void saveLicense(); + +private: + Ui_scmSkyCultureDialog *ui = nullptr; + SkyCultureMaker *maker = nullptr; + + /// The name of the sky culture. + QString name = ""; + + /// The vector of constellations to be displayed in the dialog. + std::vector *constellations = nullptr; + + /** + * @brief Gets the display name from a constellation. + * + * @param constellation The constellation to get the display name from. + * @return The display name of the constellation. + */ + QString getDisplayNameFromConstellation(const scm::ScmConstellation &constellation) const; + + /** + * @brief Sets the id of the sky culture from the name. + * + * @param name The name to set the id from. + */ + void setIdFromName(QString &name); + + /** + * @brief Gets the description from the text edit. + * + * @return The description from the text edit. + */ + scm::Description getDescriptionFromTextEdit() const; +}; + +#endif // SCM_SKY_CULTURE_DIALOG_HPP diff --git a/plugins/SkyCultureMaker/src/gui/ScmSkyCultureExportDialog.cpp b/plugins/SkyCultureMaker/src/gui/ScmSkyCultureExportDialog.cpp new file mode 100644 index 0000000000000..254f9c6346f55 --- /dev/null +++ b/plugins/SkyCultureMaker/src/gui/ScmSkyCultureExportDialog.cpp @@ -0,0 +1,252 @@ +/* + * Sky Culture Maker plug-in for Stellarium + * + * Copyright (C) 2025 Vincent Gerlach + * Copyright (C) 2025 Luca-Philipp Grumbach + * Copyright (C) 2025 Fabian Hofer + * Copyright (C) 2025 Mher Mnatsakanyan + * Copyright (C) 2025 Richard Hofmann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ScmSkyCultureExportDialog.hpp" +#include "QDir" +#include "ScmSkyCulture.hpp" +#include "StelFileMgr.hpp" +#include "ui_scmSkyCultureExportDialog.h" +#include +#include +#include +#include + +ScmSkyCultureExportDialog::ScmSkyCultureExportDialog(SkyCultureMaker* maker) + : StelDialogSeparate("ScmSkyCultureExportDialog") + , maker(maker) +{ + assert(maker != nullptr); + ui = new Ui_scmSkyCultureExportDialog; + + QString appResourceBasePath = StelFileMgr::getUserDir(); + skyCulturesPath = QDir(appResourceBasePath).filePath("skycultures"); +} + +ScmSkyCultureExportDialog::~ScmSkyCultureExportDialog() +{ + if (ui != nullptr) + { + delete ui; + } +} + +void ScmSkyCultureExportDialog::retranslate() +{ + if (dialog) + { + ui->retranslateUi(dialog); + } +} + +void ScmSkyCultureExportDialog::createDialogContent() +{ + ui->setupUi(dialog); + + connect(&StelApp::getInstance(), &StelApp::fontChanged, this, &ScmSkyCultureExportDialog::handleFontChanged); + connect(&StelApp::getInstance(), &StelApp::guiFontSizeChanged, this, + &ScmSkyCultureExportDialog::handleFontChanged); + handleFontChanged(); + + connect(&StelApp::getInstance(), SIGNAL(languageChanged()), this, SLOT(retranslate())); + connect(ui->titleBar, SIGNAL(movedTo(QPoint)), this, SLOT(handleMovedTo(QPoint))); + connect(ui->titleBar, &TitleBar::closeClicked, this, &ScmSkyCultureExportDialog::close); + connect(ui->exportBtn, &QPushButton::clicked, this, &ScmSkyCultureExportDialog::exportSkyCulture); + connect(ui->exportAndExitBtn, &QPushButton::clicked, this, &ScmSkyCultureExportDialog::exportAndExitSkyCulture); + connect(ui->cancelBtn, &QPushButton::clicked, this, &ScmSkyCultureExportDialog::close); +} + +void ScmSkyCultureExportDialog::handleFontChanged() +{ + QFont titleLblFont = QApplication::font(); + titleLblFont.setPixelSize(titleLblFont.pixelSize() + 2); + titleLblFont.setBold(true); + ui->titleLbl->setFont(titleLblFont); +} + +void ScmSkyCultureExportDialog::exportSkyCulture() +{ + if (maker == nullptr) + { + qWarning() << "SkyCultureMaker: maker is nullptr. Cannot export sky culture."; + ScmSkyCultureExportDialog::close(); + return; + } + + scm::ScmSkyCulture* currentSkyCulture = maker->getCurrentSkyCulture(); + if (currentSkyCulture == nullptr) + { + qWarning() << "SkyCultureMaker: current sky culture is nullptr. Cannot export."; + maker->showUserErrorMessage(this->dialog, ui->titleBar->title(), q_("No sky culture is set.")); + ScmSkyCultureExportDialog::close(); + return; + } + + QString skyCultureId = currentSkyCulture->getId(); + + // Let the user choose the export directory with skyCulturesPath as default + QDir skyCultureDirectory; + bool exportDirectoryChosen = chooseExportDirectory(skyCultureId, skyCultureDirectory); + if (!exportDirectoryChosen) + { + qWarning() << "SkyCultureMaker: Could not export sky culture. User cancelled or failed to choose " + "directory."; + maker->showUserErrorMessage(this->dialog, ui->titleBar->title(), q_("Failed to choose export directory.")); + return; // User cancelled or failed to choose directory + } + + if (skyCultureDirectory.exists()) + { + qWarning() << "SkyCultureMaker: Sky culture with ID" << skyCultureId + << "already exists. Cannot export."; + maker->showUserErrorMessage(this->dialog, ui->titleBar->title(), q_("Sky culture with this ID already exists.")); + // dont close the dialog here, so the user can delete the folder first + return; + } + + // Create the sky culture directory + bool createdDirectorySuccessfully = skyCultureDirectory.mkpath("."); + if (!createdDirectorySuccessfully) + { + qWarning() << "SkyCultureMaker: Failed to create sky culture directory at" + << skyCultureDirectory.absolutePath(); + maker->showUserErrorMessage(this->dialog, ui->titleBar->title(), q_("Failed to create sky culture directory.")); + return; + } + + // save illustrations before json, because the relative illustrations path is required for the json export + bool savedIllustrationsSuccessfully = currentSkyCulture->saveIllustrations(skyCultureDirectory.absolutePath() + + QDir::separator() + "illustrations"); + if (!savedIllustrationsSuccessfully) + { + qWarning() << "SkyCultureMaker: Failed to export sky culture illustrations."; + maker->showUserErrorMessage(this->dialog, ui->titleBar->title(), q_("Failed to save the illustrations.")); + // delete the created directory + skyCultureDirectory.removeRecursively(); + ScmSkyCultureExportDialog::close(); + return; + } + + // Export the sky culture to the index.json file + qDebug() << "SkyCultureMaker: Exporting sky culture..."; + QJsonObject scJsonObject = currentSkyCulture->toJson(); + QJsonDocument scJsonDoc(scJsonObject); + if (scJsonDoc.isNull() || scJsonDoc.isEmpty()) + { + qWarning() << "SkyCultureMaker: Failed to create JSON document for sky culture."; + maker->showUserErrorMessage(this->dialog, ui->titleBar->title(), q_("Failed to create JSON document for sky culture.")); + skyCultureDirectory.removeRecursively(); + ScmSkyCultureExportDialog::close(); + return; + } + QFile scJsonFile(skyCultureDirectory.absoluteFilePath("index.json")); + if (!scJsonFile.open(QIODevice::WriteOnly | QIODevice::Text)) + { + qWarning() << "SkyCultureMaker: Failed to open index.json for writing."; + maker->showUserErrorMessage(this->dialog, ui->titleBar->title(), q_("Failed to open index.json for writing.")); + skyCultureDirectory.removeRecursively(); + ScmSkyCultureExportDialog::close(); + return; + } + scJsonFile.write(scJsonDoc.toJson(QJsonDocument::Indented)); + scJsonFile.close(); + + // Save the sky culture description + bool savedDescriptionSuccessfully = maker->saveSkyCultureDescription(skyCultureDirectory); + if (!savedDescriptionSuccessfully) + { + maker->showUserErrorMessage(this->dialog, ui->titleBar->title(), q_("Failed to export sky culture description.")); + qWarning() << "SkyCultureMaker: Failed to export sky culture description."; + skyCultureDirectory.removeRecursively(); + ScmSkyCultureExportDialog::close(); + return; + } + + // Save the CMakeLists.txt file + bool savedCMakeListsSuccessfully = saveSkyCultureCMakeListsFile(skyCultureDirectory); + if (!savedCMakeListsSuccessfully) + { + maker->showUserErrorMessage(this->dialog, ui->titleBar->title(), q_("Failed to export CMakeLists.txt.")); + qWarning() << "SkyCultureMaker: Failed to export CMakeLists.txt."; + skyCultureDirectory.removeRecursively(); + ScmSkyCultureExportDialog::close(); + return; + } + + maker->showUserInfoMessage(this->dialog, ui->titleBar->title(), + q_("Sky culture exported successfully to ") + + skyCultureDirectory.absolutePath()); + qInfo() << "SkyCultureMaker: Sky culture exported successfully to" << skyCultureDirectory.absolutePath(); + ScmSkyCultureExportDialog::close(); + + // Reload the sky cultures in Stellarium to make the new one available immediately + StelApp::getInstance().getSkyCultureMgr().reloadSkyCulture(); +} + +bool ScmSkyCultureExportDialog::chooseExportDirectory(const QString& skyCultureId, QDir& skyCultureDirectory) +{ + QString selectedDirectory = QFileDialog::getExistingDirectory(nullptr, q_("Choose Export Directory"), + skyCulturesPath); + if (selectedDirectory.isEmpty()) + { + // User cancelled the dialog + return false; + } + + if (!QDir(selectedDirectory).exists()) + { + maker->showUserErrorMessage(this->dialog, ui->titleBar->title(), q_("ERROR: The selected directory is not valid")); + qDebug() << "SkyCultureMaker: Selected non-existing export directory"; + return false; + } + + skyCultureDirectory = QDir(selectedDirectory + QDir::separator() + skyCultureId); + return true; +} + +void ScmSkyCultureExportDialog::exportAndExitSkyCulture() +{ + exportSkyCulture(); + maker->resetScmDialogs(); + maker->hideAllDialogs(); + maker->setIsScmEnabled(false); +} + +bool ScmSkyCultureExportDialog::saveSkyCultureCMakeListsFile(const QDir& directory) +{ + QFile cmakeListsFile(directory.absoluteFilePath("CMakeLists.txt")); + if (!cmakeListsFile.open(QIODevice::WriteOnly | QIODevice::Text)) + { + qWarning() << "SkyCultureMaker: Failed to open CMakeLists.txt for writing."; + return false; + } + + QTextStream out(&cmakeListsFile); + out << "get_filename_component(skyculturePath \"${CMAKE_CURRENT_SOURCE_DIR}\" REALPATH)\n"; + out << "get_filename_component(skyculture ${skyculturePath} NAME)\n"; + out << "install(DIRECTORY ./ DESTINATION ${SDATALOC}/skycultures/${skyculture}\n"; + out << " FILES_MATCHING PATTERN \"*\"\n"; + out << " PATTERN \"CMakeLists.txt\" EXCLUDE)\n"; + + cmakeListsFile.close(); + return true; +} diff --git a/plugins/SkyCultureMaker/src/gui/ScmSkyCultureExportDialog.hpp b/plugins/SkyCultureMaker/src/gui/ScmSkyCultureExportDialog.hpp new file mode 100644 index 0000000000000..e30efdb9de9cc --- /dev/null +++ b/plugins/SkyCultureMaker/src/gui/ScmSkyCultureExportDialog.hpp @@ -0,0 +1,59 @@ +/* + * Sky Culture Maker plug-in for Stellarium + * + * Copyright (C) 2025 Vincent Gerlach + * Copyright (C) 2025 Luca-Philipp Grumbach + * Copyright (C) 2025 Fabian Hofer + * Copyright (C) 2025 Mher Mnatsakanyan + * Copyright (C) 2025 Richard Hofmann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SCM_SKY_CULTURE_EXPORT_DIALOG_HPP +#define SCM_SKY_CULTURE_EXPORT_DIALOG_HPP + +#include "SkyCultureMaker.hpp" +#include "StelDialogSeparate.hpp" +#include + +class Ui_scmSkyCultureExportDialog; + +class ScmSkyCultureExportDialog : public StelDialogSeparate +{ +protected: + void createDialogContent() override; + +public: + ScmSkyCultureExportDialog(SkyCultureMaker *maker); + ~ScmSkyCultureExportDialog() override; + +public slots: + void retranslate() override; + +protected slots: + void handleFontChanged(); + +private: + Ui_scmSkyCultureExportDialog *ui = nullptr; + SkyCultureMaker *maker = nullptr; + QString skyCulturesPath; + + void exportSkyCulture(); + bool chooseExportDirectory(const QString &skyCultureId, QDir &skyCultureDirectory); + void exportAndExitSkyCulture(); + bool saveSkyCultureCMakeListsFile(const QDir &directory); +}; + +#endif // SCM_SKY_CULTURE_EXPORT_DIALOG_HPP diff --git a/plugins/SkyCultureMaker/src/gui/ScmStartDialog.cpp b/plugins/SkyCultureMaker/src/gui/ScmStartDialog.cpp new file mode 100644 index 0000000000000..81d489832af8a --- /dev/null +++ b/plugins/SkyCultureMaker/src/gui/ScmStartDialog.cpp @@ -0,0 +1,169 @@ +/* + * Sky Culture Maker plug-in for Stellarium + * + * Copyright (C) 2025 Vincent Gerlach + * Copyright (C) 2025 Luca-Philipp Grumbach + * Copyright (C) 2025 Fabian Hofer + * Copyright (C) 2025 Mher Mnatsakanyan + * Copyright (C) 2025 Richard Hofmann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ScmStartDialog.hpp" +#include "ui_scmStartDialog.h" +#include +#include + +#ifdef SCM_CONVERTER_ENABLED_CPP +# include "ScmConvertDialog.hpp" +#endif // SCM_CONVERTER_ENABLED_CPP + +#include +#include +#include +#include +#include + +ScmStartDialog::ScmStartDialog(SkyCultureMaker *maker) + : StelDialog("ScmStartDialog") + , maker(maker) +{ + assert(maker != nullptr); + ui = new Ui_scmStartDialog; +} + +ScmStartDialog::~ScmStartDialog() +{ + if (ui != nullptr) + { + delete ui; + } + + qDebug() << "SkyCultureMaker: Unloaded the ScmStartDialog"; +} + +void ScmStartDialog::retranslate() +{ + if (dialog) + { + // Issue #117 + // ui->retranslateUi(dialog); + } +} + +void ScmStartDialog::createDialogContent() +{ + ui->setupUi(dialog); + + // connect(&StelApp::getInstance(), SIGNAL(languageChanged()), this, SLOT(retranslate())); + connect(&StelApp::getInstance(), &StelApp::fontChanged, this, &ScmStartDialog::handleFontChanged); + connect(&StelApp::getInstance(), &StelApp::guiFontSizeChanged, this, &ScmStartDialog::handleFontChanged); + + // Buttons + connect(ui->scmStartCancelpushButton, &QPushButton::clicked, this, &ScmStartDialog::closeDialog); // Cancel + connect(ui->scmStartCreatepushButton, &QPushButton::clicked, this, + &ScmStartDialog::startScmCreationProcess); // Create + connect(ui->scmStartEditpushButton, &QPushButton::clicked, this, + &ScmStartDialog::closeDialog); // Edit - TODO: add logic (currently closing the window) + + connect(ui->titleBar, &TitleBar::closeClicked, this, &ScmStartDialog::closeDialog); + connect(ui->titleBar, SIGNAL(movedTo(QPoint)), this, SLOT(handleMovedTo(QPoint))); + // Init the correct font + handleFontChanged(); + +/* =============================================== SkyCultureConverter ============================================== */ +#ifdef SCM_CONVERTER_ENABLED_CPP + ui->scmStartConvertpushButton->setToolTip( + q_("Convert sky cultures from the legacy (fab) format to the new (json) format")); + connect(ui->scmStartConvertpushButton, &QPushButton::clicked, this, + [this]() + { + if (!converterDialog) + { + converterDialog = new ScmConvertDialog(maker); + } + maker->setStartDialogVisibility(false); // Hide the start dialog + converterDialog->setVisible(true); + }); +#else // SCM_CONVERTER_ENABLED_CPP is not defined + // Converter is disabled, so hide the button + ui->scmStartConvertpushButton->setVisible(false); +#endif // SCM_CONVERTER_ENABLED_CPP + /* ================================================================================================================== */ +} + +void ScmStartDialog::handleFontChanged() +{ + // Set welcome label font size + QFont welcomeLabelFont = QApplication::font(); + welcomeLabelFont.setPixelSize(welcomeLabelFont.pixelSize() + 4); + welcomeLabelFont.setBold(true); + ui->welcomeLabel->setFont(welcomeLabelFont); +} + +void ScmStartDialog::startScmCreationProcess() +{ + dialog->setVisible(false); // Close the dialog before starting the editor + maker->setSkyCultureDialogVisibility(true); // Start the editor dialog for creating a new Sky Culture + maker->setNewSkyCulture(); + + SkyCultureMaker::setActionToggle("actionShow_DateTime_Window_Global", true); + SkyCultureMaker::setActionToggle("actionShow_Location_Window_Global", true); + SkyCultureMaker::setActionToggle("actionShow_Ground", false); + SkyCultureMaker::setActionToggle("actionShow_Atmosphere", false); + SkyCultureMaker::setActionToggle("actionShow_MeteorShowers", false); + SkyCultureMaker::setActionToggle("actionShow_Satellite_Hints", false); +} + +void ScmStartDialog::closeDialog() +{ + maker->setIsScmEnabled(false); // Disable the Sky Culture Maker +} + +bool ScmStartDialog::isConverterDialogVisible() +{ +#ifdef SCM_CONVERTER_ENABLED_CPP + if (converterDialog != nullptr) + { + return converterDialog->visible(); + } + else + { + return false; + } +#else + return false; // Converter dialog is not available +#endif +} + +void ScmStartDialog::setConverterDialogVisibility(bool b) +{ +#ifdef SCM_CONVERTER_ENABLED_CPP + if (converterDialog != nullptr) + { + if (b != converterDialog->visible()) + { + converterDialog->setVisible(b); + } + } + else + { + qWarning() << "SkyCultureMaker: Converter dialog is not initialized!"; + } +#else + Q_UNUSED(b); + qWarning() << "SkyCultureMaker: Converter dialog is not available in this build!"; +#endif // SCM_CONVERTER_ENABLED_CPP +} diff --git a/plugins/SkyCultureMaker/src/gui/ScmStartDialog.hpp b/plugins/SkyCultureMaker/src/gui/ScmStartDialog.hpp new file mode 100644 index 0000000000000..bfaebd573d4a4 --- /dev/null +++ b/plugins/SkyCultureMaker/src/gui/ScmStartDialog.hpp @@ -0,0 +1,76 @@ +/* + * Sky Culture Maker plug-in for Stellarium + * + * Copyright (C) 2025 Vincent Gerlach + * Copyright (C) 2025 Luca-Philipp Grumbach + * Copyright (C) 2025 Fabian Hofer + * Copyright (C) 2025 Mher Mnatsakanyan + * Copyright (C) 2025 Richard Hofmann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SCMSTARTDIALOG_HPP +#define SCMSTARTDIALOG_HPP + +#include "SkyCultureMaker.hpp" +#include "StelDialog.hpp" +#include + +#ifdef SCM_CONVERTER_ENABLED_CPP +# include "ScmConvertDialog.hpp" +#endif + +class Ui_scmStartDialog; + +class ScmStartDialog : public StelDialog +{ +protected: + void createDialogContent() override; + +public: + ScmStartDialog(SkyCultureMaker *maker); + ~ScmStartDialog() override; + + /** + * @brief Check if the converter dialog is currently visible. + * @return true if the converter dialog is visible, false otherwise. + */ + bool isConverterDialogVisible(); + + /** + * @brief Set the visibility of the converter dialog. + * @param b The boolean value to be set. + */ + void setConverterDialogVisibility(bool b); + +public slots: + void retranslate() override; + +protected slots: + void handleFontChanged(); + +private slots: + void startScmCreationProcess(); + void closeDialog(); + +private: + Ui_scmStartDialog *ui = nullptr; + SkyCultureMaker *maker = nullptr; +#ifdef SCM_CONVERTER_ENABLED_CPP + ScmConvertDialog *converterDialog = nullptr; +#endif +}; + +#endif // SCMSTARTDIALOG_HPP diff --git a/plugins/SkyCultureMaker/src/gui/scmConstellationDialog.ui b/plugins/SkyCultureMaker/src/gui/scmConstellationDialog.ui new file mode 100644 index 0000000000000..f6cc9bf7e5f60 --- /dev/null +++ b/plugins/SkyCultureMaker/src/gui/scmConstellationDialog.ui @@ -0,0 +1,404 @@ + + + scmConstellationDialog + + + + 0 + 0 + 458 + 408 + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + SCM: Constellation Editor + + + + + + + false + + + 0 + + + + 32 + 32 + + + + + Drawing + + + + + + + + Pen + + + true + + + false + + + + + + + Revert the last drawn line. + + + Undo + + + + + + + Eraser + + + true + + + + + + + + + + + + + Constellation Name + + + + 15 + + + 10 + + + 15 + + + 10 + + + + + + + + English name + + + + + + + + + + + ID (Do not include 'CON' or the sky culture name) + + + + + + + + + + Native name (optional) + + + + + + + + + + Pronounciation (optional, European glyphs or Pinyin) + + + + + + + + + + IPA (optional) + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Artwork + + + + 5 + + + 5 + + + 5 + + + 5 + + + 5 + + + + + CrossCursor + + + true + + + QFrame::NoFrame + + + Qt::ScrollBarAsNeeded + + + Qt::ScrollBarAsNeeded + + + QAbstractScrollArea::AdjustIgnored + + + + + + + QLayout::SetDefaultConstraint + + + 0 + + + 0 + + + 0 + + + 0 + + + + + false + + + Bind Star + + + false + + + false + + + false + + + false + + + false + + + + + + + Upload Image + + + + + + + Remove Image + + + + + + + false + + + + 0 + 0 + + + + WhatsThisCursor + + + Qt::NoContextMenu + + + false + + + + + + false + + + false + + + true + + + + + + + + + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 10 + + + 20 + + + 10 + + + 20 + + + 10 + + + + + Save + + + + + + + Cancel + + + + + + + + + + titleBar + Content + tabs + + + + TitleBar + QFrame +
Dialog.hpp
+ 1 +
+
+ + + + +
diff --git a/plugins/SkyCultureMaker/src/gui/scmConvertDialog.ui b/plugins/SkyCultureMaker/src/gui/scmConvertDialog.ui new file mode 100644 index 0000000000000..4bd817f2d84df --- /dev/null +++ b/plugins/SkyCultureMaker/src/gui/scmConvertDialog.ui @@ -0,0 +1,122 @@ + + + scmConvertDialog + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Sky Culture Converter + + + + + + + QFrame::StyledPanel + + + + + + + 0 + 0 + + + + Select a file to convert. The file can be a zip, rar, tar or 7z archive containing the sky culture files. + + + true + + + + + + + + + true + + + Select a file… + + + + + + + Browse… + + + + + + + + + Convert + + + + + + + + 0 + 0 + + + + + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + TitleBar + QFrame +
Dialog.hpp
+ 1 +
+
+ + +
diff --git a/plugins/SkyCultureMaker/src/gui/scmHideOrAbortMakerDialog.ui b/plugins/SkyCultureMaker/src/gui/scmHideOrAbortMakerDialog.ui new file mode 100644 index 0000000000000..417a81a649736 --- /dev/null +++ b/plugins/SkyCultureMaker/src/gui/scmHideOrAbortMakerDialog.ui @@ -0,0 +1,160 @@ + + + scmHideOrAbortMakerDialog + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Sky Culture Maker + + + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + Hide the plugin to resume later or abort the process? + + + Qt::AlignCenter + + + 20 + + + + + + + 5 + + + 5 + + + 5 + + + 5 + + + 5 + + + + + Hide + + + + + + + Abort + + + + + + + Cancel + + + + + + + + + + + + + + + + + + TitleBar + QFrame +
Dialog.hpp
+ 1 +
+
+ + +
diff --git a/plugins/SkyCultureMaker/src/gui/scmSkyCultureDialog.ui b/plugins/SkyCultureMaker/src/gui/scmSkyCultureDialog.ui new file mode 100644 index 0000000000000..781cd083989ea --- /dev/null +++ b/plugins/SkyCultureMaker/src/gui/scmSkyCultureDialog.ui @@ -0,0 +1,609 @@ + + + scmSkyCultureDialog + + + + 0 + 0 + 372 + 543 + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Sky Culture Maker + + + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + + 32 + 32 + + + + + Overview + + + + + + Name of the Sky Culture + + + + + + + + + + Constellations + + + + + + + + QListWidget, QListWidget::viewport { + margin: 0px; + padding: 0px; + border: none; + outline: none; + } + QListWidget::item { + margin: 0px; + padding: 4px; + border: none; + outline: none; + } + + + + + + + + + + + 0 + 0 + + + + Add Constellation + + + + + + + + 0 + 0 + + + + Add Dark Constellation + + + + + + + + 0 + 0 + + + + Edit + + + + + + + + 0 + 0 + + + + Remove + + + + + + + + + + + + 0 + 0 + + + + License of the Sky Culture + + + + + + + + 0 + 0 + + + + + + + + Export Sky Culture + + + + + + + + + + Description + + + + 6 + + + 6 + + + 6 + + + 6 + + + 6 + + + + + true + + + + + 0 + 0 + 229 + 2930 + + + + + 12 + + + 10 + + + 10 + + + 10 + + + 10 + + + + + Please describe the Sky Culture + + + true + + + + + + + Mandatory fields are marked with an asterisk (*). + + + true + + + + + + + General Information + + + true + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Classification of the Sky Culture* + + + + + + + + + + Authors:* + + + + + + + false + + + Who are you and what is your background (native person, researcher, ...)? Ideally, please provide an email-address as well. + + + + + + + Culture Description:* + + + + + + + false + + + Please describe specifics of the source (book/clay tablet/parchment/...) or of the culture: e.g. sun-focussed, moon-focussed, climate of the region, geographic specifics. Did members of the culture build pyramids or any other well-known thing? What did they do with astronomy (e.g. time keeping, calendar making, weather predictions...)? Are there connections to other cultures? Is this version a didactic reduction or is it derived from a specific other source? + + + + + + + About:* + + + + + + + false + + + How do you know about this Sky Culture? Is this contribution based on your own or other people's research? Is it based on interviews with Elders or reading ancient text, etc.? + + + + + + + + + + Sky (What can the user see here?) + + + true + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + Sky Description + + + + + + + + + false + + + What special features did this culture use, what shall the user watch out for? Is it worthwhile to click a specific button rather than others - e.g. divisions of the ecliptic, super-constellations, dark constellations, all-sky-constellations, zoom into the Pleiades, etc.? Do the constellations change with the months or remain the same throughout the year? + + + + + + + Moon & Sun: + + + + + + + false + + + How did they determine their calendar? Did they observe phases? + + + + + + + Planets: + + + + + + + false + + + Were the planets deified? What deities connect, what meaning do they have? + + + + + + + Zodiac/ Lunar System: + + + + + + + false + + + Did they have the (Bab.) zodiac system or any other division of the "path of the Moon"? If so, was it a (equal or unequal) division of the ecliptic or of any other type? + + + + + + + Milky Way: + + + + + + + false + + + Was the Milky Way deified? What meaning did it have? + + + + + + + Other Celestial Objects: + + + + + + + false + + + What other celestial objects were significant? E.g what about shooting stars, aurorae? Did they observe stellar transients, e.g. supernovae, that were visible only in a specific year? + + + + + + + Constellations: + + + + + + + false + + + What is 'real' about your presentation? Did they really use stick figures? Did they paint images (or just use names of stars)? Did they use stars at all or prefer dark/bright clouds of the Milky Way? + + + + + + + + + + Credits and References + + + true + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + References:* + + + + + + + false + + + What references did you use? E.g. specific texts, images, or other media? + + + + + + + Acknowledgements: + + + + + + + false + + + What acknowledgements would you like to make? E.g. to interviewed people, organizations, or resources that helped you? + + + + + + + + + + Qt::Vertical + + + + 40 + 20 + + + + + + + + + + + + + + + + + + + + TitleBar + QFrame +
Dialog.hpp
+ 1 +
+
+ + + + +
diff --git a/plugins/SkyCultureMaker/src/gui/scmSkyCultureExportDialog.ui b/plugins/SkyCultureMaker/src/gui/scmSkyCultureExportDialog.ui new file mode 100644 index 0000000000000..0ea69fb250f39 --- /dev/null +++ b/plugins/SkyCultureMaker/src/gui/scmSkyCultureExportDialog.ui @@ -0,0 +1,181 @@ + + + scmSkyCultureExportDialog + + + + 0 + 0 + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + SCM: Export + + + + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + + 10 + + + 10 + + + 10 + + + 10 + + + 10 + + + + + Before exporting the Sky Culture, please make sure that: + + + Qt::AlignCenter + + + true + + + 0 + + + + + + + + 0 + 0 + + + + true + + + 1. You fully agree that the Sky Culture will be subject to the license you have selected. +2. You have described the Sky Culture as elaborately as possible. +3. You have the necessary rights to use and share all included content (e.g., artwork, descriptions). +4. All information provided is accurate to the best of your knowledge. +5. The content is shared in good faith, with respect for cultural significance and sensitivities. +6. You have provided proper attributions where required (e.g., sources of images, texts, or names). + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + On export, you will be prompted to select your personal skycultures directory. A new directory for the sky culture will be created inside. + + + true + + + + + + + + + + 5 + + + 10 + + + 10 + + + 10 + + + 10 + + + + + Export + + + + + + + Export and Exit + + + + + + + Cancel + + + + + + + + + + TitleBar + QFrame +
Dialog.hpp
+ 1 +
+
+ + + + +
diff --git a/plugins/SkyCultureMaker/src/gui/scmStartDialog.ui b/plugins/SkyCultureMaker/src/gui/scmStartDialog.ui new file mode 100644 index 0000000000000..76a53d7b84cd6 --- /dev/null +++ b/plugins/SkyCultureMaker/src/gui/scmStartDialog.ui @@ -0,0 +1,187 @@ + + + scmStartDialog + + + + 0 + 0 + 304 + 93 + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Sky Culture Maker + + + + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + Welcome to the Sky Culture Maker! + + + Qt::AlignCenter + + + 20 + + + + + + + 5 + + + 5 + + + 5 + + + 5 + + + 5 + + + + + Create a new sky culture + + + Create + + + + + + + false + + + Not implemented yet. + + + Edit + + + + + + + Convert + + + + + + + Cancel + + + + + + + + + + + + + + + + + + TitleBar + QFrame +
Dialog.hpp
+ 1 +
+
+ + +
diff --git a/plugins/SkyCultureMaker/src/types/Anchor.hpp b/plugins/SkyCultureMaker/src/types/Anchor.hpp new file mode 100644 index 0000000000000..090be941ffd64 --- /dev/null +++ b/plugins/SkyCultureMaker/src/types/Anchor.hpp @@ -0,0 +1,41 @@ +/* + * Sky Culture Maker plug-in for Stellarium + * + * Copyright (C) 2025 Vincent Gerlach + * Copyright (C) 2025 Luca-Philipp Grumbach + * Copyright (C) 2025 Fabian Hofer + * Copyright (C) 2025 Mher Mnatsakanyan + * Copyright (C) 2025 Richard Hofmann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ANCHOR_H +#define ANCHOR_H + +#include "VecMath.hpp" +#include + +namespace scm +{ +//! An anchor for a point in an artwork to the HIP id of a star +struct Anchor +{ + Vec2i position; + int hip; +}; + +} // namespace scm + +#endif // ANCHOR_H diff --git a/plugins/SkyCultureMaker/src/types/Classification.hpp b/plugins/SkyCultureMaker/src/types/Classification.hpp new file mode 100644 index 0000000000000..5a7c4cccd9078 --- /dev/null +++ b/plugins/SkyCultureMaker/src/types/Classification.hpp @@ -0,0 +1,126 @@ +/* + * Sky Culture Maker plug-in for Stellarium + * + * Copyright (C) 2025 Vincent Gerlach + * Copyright (C) 2025 Luca-Philipp Grumbach + * Copyright (C) 2025 Fabian Hofer + * Copyright (C) 2025 Mher Mnatsakanyan + * Copyright (C) 2025 Richard Hofmann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SCM_CLASSIFICATION_HPP +#define SCM_CLASSIFICATION_HPP + +#include +#include +#include +#include + +namespace scm +{ + +/** + * @brief The Classification struct represents a classification type for sky cultures. + * All information was taken from the Stellarium guide. + */ +struct Classification +{ + QString name; + QString description; + + Classification(const QString& name, const QString& description) + : name(name) + , description(description) + { + } +}; + +// Enum class to represent different types of classifications +enum class ClassificationType +{ + NONE = 0, + PERSONAL, + TRADITIONAL, + ETHNOGRAPHIC, + HISTORICAL, + SINGLE, + COMPARATIVE +}; + +inline QString classificationTypeToString(ClassificationType type) +{ + switch (type) + { + case ClassificationType::NONE: return "None"; + case ClassificationType::PERSONAL: return "Personal"; + case ClassificationType::TRADITIONAL: return "Traditional"; + case ClassificationType::ETHNOGRAPHIC: return "Ethnographic"; + case ClassificationType::HISTORICAL: return "Historical"; + case ClassificationType::SINGLE: return "Single"; + case ClassificationType::COMPARATIVE: return "Comparative"; + default: qDebug() << "Unknown ClassificationType: " << static_cast(type); return "Unkown"; + } +} + +const std::map CLASSIFICATIONS = { + {ClassificationType::NONE, Classification("None", "Please select a valid classification.")}, + { + ClassificationType::PERSONAL, + Classification("Personal", + "This is a privately developed sky culture, not based on published ethnographic or " + "historical research, and not supported by a noteworthy community. It may be included " + "in Stellarium when it is “pretty enough” without really approving its contents."), + }, + { + ClassificationType::TRADITIONAL, + Classification("Traditional", + "The content represents “common” knowledge by several members of an ethnic community, " + "and the sky culture has been developed by members of such community. Our “Modern” sky " + "culture is a key example: rooted in antiquity it has evolved for about 2500 years in " + "what is now commonly known as “western” world, and modern astronomers use it."), + }, + { + ClassificationType::ETHNOGRAPHIC, + Classification("Ethnographic", "The data of the sky culture is provided by ethnographic researchers " + "based on interviews of indigenous people."), + }, + { + ClassificationType::HISTORICAL, + Classification("Historical", "The sky culture is based on historical written sources from a (usually " + "short) period of the past."), + }, + { + ClassificationType::SINGLE, + Classification("Single", "The data of the sky culture is based on a single source like a historical " + "atlas, or related publications of a single author."), + }, + { + ClassificationType::COMPARATIVE, + Classification("Comparative", + "This sky culture is a special-purpose composition of e.g. artwork from one and stick " + "figures from another sky culture, and optionally asterisms as representations of a " + "third. Or comparison of two stick figure sets in constellations and asterisms. These " + "figures sometimes will appear not to fit together well. This may be intended, to " + "explain and highlight just those differences! The description text must clearly " + "explain and identify all sources and how these differences should be interpreted."), + }, +}; + +} // namespace scm + +Q_DECLARE_METATYPE(scm::ClassificationType); + +#endif // SCM_CLASSIFICATION_HPP diff --git a/plugins/SkyCultureMaker/src/types/CoordinateLine.hpp b/plugins/SkyCultureMaker/src/types/CoordinateLine.hpp new file mode 100644 index 0000000000000..34f2d88743afd --- /dev/null +++ b/plugins/SkyCultureMaker/src/types/CoordinateLine.hpp @@ -0,0 +1,80 @@ +/* + * Sky Culture Maker plug-in for Stellarium + * + * Copyright (C) 2025 Vincent Gerlach + * Copyright (C) 2025 Luca-Philipp Grumbach + * Copyright (C) 2025 Fabian Hofer + * Copyright (C) 2025 Mher Mnatsakanyan + * Copyright (C) 2025 Richard Hofmann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SCM_TYPES_COORDINATE_LINE_HPP +#define SCM_TYPES_COORDINATE_LINE_HPP + +#include "VecMath.hpp" +#include +#include "StelUtils.hpp" + +namespace scm +{ +//! The pair of start and end coordinate +struct CoordinateLine +{ + //! The start coordinate of the line. + Vec3d start; + + //! The end coordinate of the line. + Vec3d end; + + /** + * @brief Converts the ConstellationLine to a JSON array. + * + * @return QJsonArray The JSON representation of the coordinate line. + */ + QJsonArray toJson() const + { + QJsonArray json; + + // Only if both start and end points do not have names, we save the coordinates + QJsonArray startCoordinateArray; + double RA, DE; + convertToSphereCoords(RA, DE, start); + startCoordinateArray.append(RA); + startCoordinateArray.append(DE); + json.append(startCoordinateArray); + + QJsonArray endCoordinateArray; + convertToSphereCoords(RA, DE, end); + endCoordinateArray.append(RA); + endCoordinateArray.append(DE); + json.append(endCoordinateArray); + + return json; + } + +private: + static void convertToSphereCoords(double &RA, double &DE, const Vec3d &vec) + { + double longitude; + double latitude; + StelUtils::rectToSphe(&longitude, &latitude, vec); + RA = longitude * M_180_PI / 15.0; + DE = latitude * M_180_PI; + } +}; +} // namespace scm + +#endif \ No newline at end of file diff --git a/plugins/SkyCultureMaker/src/types/Description.hpp b/plugins/SkyCultureMaker/src/types/Description.hpp new file mode 100644 index 0000000000000..5324b83a71f90 --- /dev/null +++ b/plugins/SkyCultureMaker/src/types/Description.hpp @@ -0,0 +1,79 @@ +/* + * Sky Culture Maker plug-in for Stellarium + * + * Copyright (C) 2025 Vincent Gerlach + * Copyright (C) 2025 Luca-Philipp Grumbach + * Copyright (C) 2025 Fabian Hofer + * Copyright (C) 2025 Mher Mnatsakanyan + * Copyright (C) 2025 Richard Hofmann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SCM_DESCRIPTION_HPP +#define SCM_DESCRIPTION_HPP + +#include "Classification.hpp" +#include "License.hpp" +#include +#include +#include +#include + +namespace scm +{ + +/** + * @brief The Description struct represents a sky culture description. + */ +struct Description +{ + QString name; + QString authors; + scm::LicenseType license; + QString cultureDescription; + QString about; + + QString sky; + QString moonAndSun; + QString planets; + QString zodiac; + QString milkyWay; + QString otherObjects; + + QString constellations; + QString references; + QString acknowledgements; + scm::ClassificationType classification; + + /** + * @brief Check if the description is complete. + * @return true if all required fields are filled, false otherwise. + */ + bool isComplete() const + { + return !name.trimmed().isEmpty() && + !authors.trimmed().isEmpty() && + license != scm::LicenseType::NONE && + !cultureDescription.trimmed().isEmpty() && + !about.trimmed().isEmpty() && + !references.trimmed().isEmpty() && + classification != scm::ClassificationType::NONE; + } +}; +} // namespace scm + +Q_DECLARE_METATYPE(scm::Description); + +#endif // SCM_DESCRIPTION_HPP diff --git a/plugins/SkyCultureMaker/src/types/DialogID.hpp b/plugins/SkyCultureMaker/src/types/DialogID.hpp new file mode 100644 index 0000000000000..222db358612ee --- /dev/null +++ b/plugins/SkyCultureMaker/src/types/DialogID.hpp @@ -0,0 +1,40 @@ +/* + * Sky Culture Maker plug-in for Stellarium + * + * Copyright (C) 2025 Vincent Gerlach + * Copyright (C) 2025 Luca-Philipp Grumbach + * Copyright (C) 2025 Fabian Hofer + * Copyright (C) 2025 Mher Mnatsakanyan + * Copyright (C) 2025 Richard Hofmann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SCM_DIALOG_HPP +#define SCM_DIALOG_HPP + +namespace scm +{ +//! The possibles tools used for drawing. +enum class DialogID +{ + StartDialog, + SkyCultureDialog, + SkyCultureExportDialog, + HideOrAbortMakerDialog, + ConstellationDialog +}; +} // namespace scm + +#endif diff --git a/plugins/SkyCultureMaker/src/types/DrawTools.hpp b/plugins/SkyCultureMaker/src/types/DrawTools.hpp new file mode 100644 index 0000000000000..67b890a2d173c --- /dev/null +++ b/plugins/SkyCultureMaker/src/types/DrawTools.hpp @@ -0,0 +1,43 @@ +/* + * Sky Culture Maker plug-in for Stellarium + * + * Copyright (C) 2025 Vincent Gerlach + * Copyright (C) 2025 Luca-Philipp Grumbach + * Copyright (C) 2025 Fabian Hofer + * Copyright (C) 2025 Mher Mnatsakanyan + * Copyright (C) 2025 Richard Hofmann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SCM_TYPES_DRAWTOOLS_HPP +#define SCM_TYPES_DRAWTOOLS_HPP + +namespace scm +{ +//! The possibles tools used for drawing. +enum class DrawTools +{ + //! No tool is active. + None, + + //! The pen tool is selected. + Pen, + + //! The eraser tool is selected. + Eraser, +}; +} // namespace scm + +#endif diff --git a/plugins/SkyCultureMaker/src/types/Drawing.hpp b/plugins/SkyCultureMaker/src/types/Drawing.hpp new file mode 100644 index 0000000000000..909d8a5ebccad --- /dev/null +++ b/plugins/SkyCultureMaker/src/types/Drawing.hpp @@ -0,0 +1,49 @@ +/* + * Sky Culture Maker plug-in for Stellarium + * + * Copyright (C) 2025 Vincent Gerlach + * Copyright (C) 2025 Luca-Philipp Grumbach + * Copyright (C) 2025 Fabian Hofer + * Copyright (C) 2025 Mher Mnatsakanyan + * Copyright (C) 2025 Richard Hofmann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SCM_TYPES_DRAWING_HPP +#define SCM_TYPES_DRAWING_HPP + +namespace scm +{ +//! The possibles states during the drawing. +enum class Drawing +{ + //! No line is available. + None = 1, + + //! The line as a starting point. + hasStart = 2, + + //! The line has a not placed end that is attached to the cursor. + hasFloatingEnd = 4, + + //! The line is complete i.e. has start and end point. + hasEnd = 8, + + //! The end is an already existing point. + hasEndExistingPoint = 16, +}; +} // namespace scm + +#endif diff --git a/plugins/SkyCultureMaker/src/types/DrawingMode.hpp b/plugins/SkyCultureMaker/src/types/DrawingMode.hpp new file mode 100644 index 0000000000000..b0ef03e2d54f4 --- /dev/null +++ b/plugins/SkyCultureMaker/src/types/DrawingMode.hpp @@ -0,0 +1,41 @@ +/* + * Sky Culture Maker plug-in for Stellarium + * + * Copyright (C) 2025 Vincent Gerlach + * Copyright (C) 2025 Luca-Philipp Grumbach + * Copyright (C) 2025 Fabian Hofer + * Copyright (C) 2025 Mher Mnatsakanyan + * Copyright (C) 2025 Richard Hofmann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SCM_TYPES_DRAWINGMODE_HPP +#define SCM_TYPES_DRAWINGMODE_HPP + +namespace scm +{ +/** + * @brief Enum representing the drawing modes available in the Sky Culture Maker plugin. + * The modes determine how lines can be drawn in the constellation editor. + */ +enum class DrawingMode +{ + StarsAndDSO, + + Coordinates +}; +} // namespace scm + +#endif diff --git a/plugins/SkyCultureMaker/src/types/License.hpp b/plugins/SkyCultureMaker/src/types/License.hpp new file mode 100644 index 0000000000000..ee7f3146e38ce --- /dev/null +++ b/plugins/SkyCultureMaker/src/types/License.hpp @@ -0,0 +1,82 @@ +/* + * Sky Culture Maker plug-in for Stellarium + * + * Copyright (C) 2025 Vincent Gerlach + * Copyright (C) 2025 Luca-Philipp Grumbach + * Copyright (C) 2025 Fabian Hofer + * Copyright (C) 2025 Mher Mnatsakanyan + * Copyright (C) 2025 Richard Hofmann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SCM_LICENSE_HPP +#define SCM_LICENSE_HPP + +#include +#include +#include +#include + +namespace scm +{ + +//! Struct to represent a license +struct License +{ + QString name; + QString description; + + License(const QString& name, const QString& description) + : name(name) + , description(description) + { + } +}; + +//! Enum class to represent different types of licenses +enum class LicenseType +{ + NONE = 0, + CC0, + CC_BY, + CC_BY_NC, + CC_BY_ND, + CC_BY_NC_ND +}; + +//! Map of license types to their corresponding name and description +const std::map LICENSES = { + {LicenseType::NONE, License("None", "Please select a valid license.")}, + {LicenseType::CC0, License("CC0 1.0", + "This file is made available under the Creative Commons CC0 1.0 Universal Public Domain Dedication. " + "The person who associated a work with this deed has dedicated the work to the public domain by " + "waiving all of his or her rights to the work worldwide under copyright law, including all related " + "and neighboring rights, to the extent allowed by law. You can copy, modify, distribute and perform " + "the work, even for commercial purposes, all without asking permission.")}, + {LicenseType::CC_BY, License("CC BY 4.0", + "This work is licensed under the Creative Commons Attribution 4.0 License.")}, + {LicenseType::CC_BY_NC, License("CC BY-NC 4.0", + "This work is licensed under the Creative Commons Attribution-NonCommercial 4.0 License.")}, + {LicenseType::CC_BY_ND, License("CC BY-ND 4.0", + "This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 License.")}, + {LicenseType::CC_BY_NC_ND, License("CC BY-NC-ND 4.0", + "This work is licensed under the Creative Commons Attribution-NoDerivatives 4.0 License.")} +}; + +} // namespace scm + +Q_DECLARE_METATYPE(scm::LicenseType); + +#endif // SCM_LICENSE_HPP diff --git a/plugins/SkyCultureMaker/src/types/Lines.hpp b/plugins/SkyCultureMaker/src/types/Lines.hpp new file mode 100644 index 0000000000000..f807cd30b543b --- /dev/null +++ b/plugins/SkyCultureMaker/src/types/Lines.hpp @@ -0,0 +1,44 @@ +/* + * Sky Culture Maker plug-in for Stellarium + * + * Copyright (C) 2025 Vincent Gerlach + * Copyright (C) 2025 Luca-Philipp Grumbach + * Copyright (C) 2025 Fabian Hofer + * Copyright (C) 2025 Mher Mnatsakanyan + * Copyright (C) 2025 Richard Hofmann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SCM_TYPES_LINE_HPP +#define SCM_TYPES_LINE_HPP + +#include "CoordinateLine.hpp" +#include "StarLine.hpp" +#include + +namespace scm +{ +//! The lines of the current drawn constellation +struct Lines +{ + //! The coordinate pairs of a line. + std::vector coordinates; + + //! The optional available stars to the coordinates. + std::vector stars; +}; +} // namespace scm + +#endif diff --git a/plugins/SkyCultureMaker/src/types/SkyPoint.hpp b/plugins/SkyCultureMaker/src/types/SkyPoint.hpp new file mode 100644 index 0000000000000..75fc11eb5e571 --- /dev/null +++ b/plugins/SkyCultureMaker/src/types/SkyPoint.hpp @@ -0,0 +1,44 @@ +/* + * Sky Culture Maker plug-in for Stellarium + * + * Copyright (C) 2025 Vincent Gerlach + * Copyright (C) 2025 Luca-Philipp Grumbach + * Copyright (C) 2025 Fabian Hofer + * Copyright (C) 2025 Mher Mnatsakanyan + * Copyright (C) 2025 Richard Hofmann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SCM_TYPES_SKY_POINT_HPP +#define SCM_TYPES_SKY_POINT_HPP + +#include "VecMath.hpp" +#include +#include + +namespace scm +{ +//! A point in the sky with coordinates, optionally associated with a star ID. +struct SkyPoint +{ + //! The coordinate of the point. + Vec3d coordinate; + + //! The optional star at that coordinate. + std::optional star; +}; +} // namespace scm + +#endif diff --git a/plugins/SkyCultureMaker/src/types/StarLine.hpp b/plugins/SkyCultureMaker/src/types/StarLine.hpp new file mode 100644 index 0000000000000..c26daaf5d3903 --- /dev/null +++ b/plugins/SkyCultureMaker/src/types/StarLine.hpp @@ -0,0 +1,123 @@ +/* + * Sky Culture Maker plug-in for Stellarium + * + * Copyright (C) 2025 Vincent Gerlach + * Copyright (C) 2025 Luca-Philipp Grumbach + * Copyright (C) 2025 Fabian Hofer + * Copyright (C) 2025 Mher Mnatsakanyan + * Copyright (C) 2025 Richard Hofmann + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef SCM_TYPES_STAR_LINE_HPP +#define SCM_TYPES_STAR_LINE_HPP + +#include +#include +#include +#include + +namespace scm +{ +//! The pair of optional start and end stars +struct StarLine +{ + //! The start star of the line. + std::optional start; + + //! The end star of the line. + std::optional end; + + /** + * @brief Gets the formatted star Id from the name. + * + * @param starId The Id of the star or DSO, which may contain identifiers like "HIP" or "Gaia DR3". + * @return QString The formatted star Id, either as plain number for HIP or Gaia, or as "DSO:" for others. + * + */ + static QString getFormattedStarId(QString starId) + { + QRegularExpression hipExpression(R"(HIP\s+(\d+))"); + QRegularExpression gaiaExpression(R"(Gaia DR3\s+(\d+))"); + + QRegularExpressionMatch hipMatch = hipExpression.match(starId); + if (hipMatch.hasMatch()) + { + return hipMatch.captured(1); + } + QRegularExpressionMatch gaiaMatch = gaiaExpression.match(starId); + if (gaiaMatch.hasMatch()) + { + return gaiaMatch.captured(1); + } + // Neither HIP nor Gaia, return as DSO + return "DSO:" + starId.remove(' '); + } + + /** + * @brief Converts the StarLine to a JSON array. + * + * @return QJsonArray The JSON representation of the star line. + */ + QJsonArray toJson() const + { + QJsonArray json; + + if (start.has_value()) + { + QString formattedStarId = getFormattedStarId(start.value()); + + if (start.value().contains("HIP")) + { + // HIP are required as number + json.append(formattedStarId.toLongLong()); + } + else + { + // Other ids are required as string + json.append(formattedStarId); + } + } + else + { + json.append("-1"); + } + + if (end.has_value()) + { + QString formattedStarId = getFormattedStarId(end.value()); + + if (end.value().contains("HIP")) + { + // HIP are required as number + json.append(formattedStarId.toLongLong()); + } + else + { + // Other ids are required as string + json.append(formattedStarId); + } + } + else + { + json.append("-1"); + } + + return json; + } +}; +} // namespace scm + +#endif diff --git a/src/core/StelApp.cpp b/src/core/StelApp.cpp index a42f2a75a6826..ffe8ba232ff56 100644 --- a/src/core/StelApp.cpp +++ b/src/core/StelApp.cpp @@ -200,6 +200,10 @@ Q_IMPORT_PLUGIN(ObservabilityStelPluginInterface) Q_IMPORT_PLUGIN(Scenery3dStelPluginInterface) #endif +#ifdef USE_STATIC_PLUGIN_SKYCULTUREMAKER +Q_IMPORT_PLUGIN(SkyCultureMakerStelPluginInterface) +#endif + #ifdef USE_STATIC_PLUGIN_REMOTECONTROL Q_IMPORT_PLUGIN(RemoteControlStelPluginInterface) #endif