diff --git a/.clang-format b/.clang-format index 8608c4f2c17f0..a89623fbe2f0a 100644 --- a/.clang-format +++ b/.clang-format @@ -247,4 +247,4 @@ WhitespaceSensitiveMacros: - NS_SWIFT_NAME - PP_STRINGIZE - STRINGIZE -... +... \ No newline at end of file diff --git a/.clang-tidy b/.clang-tidy index 863bd8cc84de5..0eac1369eede2 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,37 +1,77 @@ --- -Checks: '*,-google-default-arguments,-fuchsia-*,-zircon-*,-abseil-*,-llvmlibc-*' -WarningsAsErrors: '' -HeaderFilterRegex: '' -AnalyzeTemporaryDtors: false -FormatStyle: file +Checks: > + -*, + readability-misleading-indentation, + readability-redundant-declaration, + readability-redundant-member-init, + readability-use-anyofallof, + readability-identifier-naming, + readability-braces-around-statements, + readability-function-cognitive-complexity, + readability-magic-numbers, + readability-else-after-return, + readability-avoid-const-params-in-decls, + readability-container-size-empty, + readability-redundant-string-cstr, + readability-redundant-control-flow, + readability-simplify-boolean-expr, + readability-uppercase-literal-suffix, + readability-static-accessed-through-instance, + readability-named-parameter, + modernize-use-auto, + modernize-use-nullptr, + modernize-use-override, + modernize-deprecated-headers, + modernize-use-using, + modernize-avoid-c-arrays, + modernize-make-unique, + modernize-make-shared, + cppcoreguidelines-avoid-magic-numbers, + cppcoreguidelines-pro-bounds-constant-array-index, + cppcoreguidelines-pro-type-static-cast-downcast, + cppcoreguidelines-pro-type-reinterpret-cast, + cppcoreguidelines-pro-type-cstyle-cast, + cppcoreguidelines-pro-type-member-init, + cppcoreguidelines-pro-bounds-pointer-arithmetic, + cppcoreguidelines-pro-type-vararg, + cppcoreguidelines-no-malloc, + cppcoreguidelines-special-member-functions, + cppcoreguidelines-avoid-non-const-global-variables, + cppcoreguidelines-init-variables, + hicpp-no-array-decay, + hicpp-signed-bitwise, + hicpp-uppercase-literal-suffix, + hicpp-use-auto, + hicpp-use-nullptr, + hicpp-use-override, + hicpp-deprecated-headers, + hicpp-explicit-conversions, + hicpp-multiway-paths-covered, + hicpp-no-malloc, + hicpp-special-member-functions, + hicpp-avoid-goto, + performance-for-range-copy, + performance-implicit-conversion-in-loop, + performance-inefficient-vector-operation, + performance-unnecessary-value-param +WarningsAsErrors: "*" +HeaderFilterRegex: ".*" +AnalyzeTemporaryDtors: true +FormatStyle: file CheckOptions: - - key: cert-dcl16-c.NewSuffixes - value: 'L;LL;LU;LLU' - - key: cppcoreguidelines-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic - value: '1' - - key: google-readability-braces-around-statements.ShortStatementLines - value: '1' - - key: google-readability-function-size.StatementThreshold - value: '800' - - key: google-readability-namespace-comments.ShortNamespaceLines - value: '10' - - key: google-readability-namespace-comments.SpacesBeforeComments - value: '2' - - key: modernize-loop-convert.MaxCopySize - value: '16' - - key: modernize-loop-convert.MinConfidence - value: reasonable - - key: modernize-loop-convert.NamingStyle - value: CamelCase - - key: modernize-pass-by-value.IncludeStyle - value: llvm - - key: modernize-replace-auto-ptr.IncludeStyle - value: llvm - - key: modernize-use-nullptr.NullMacros - value: 'NULL' - - key: misc-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic - value: '1' - - key: modernize-use-default-member-init.UseAssignment - value: '1' -... - + - key: readability-identifier-naming.ClassCase + value: CamelCase + - key: readability-identifier-naming.MethodCase + value: camelBack + - key: readability-identifier-naming.VariableCase + value: camelBack + - key: readability-identifier-naming.ConstantCase + value: UPPER_CASE + - key: readability-identifier-naming.MacroDefinitionCase + value: UPPER_CASE + - key: readability-identifier-naming.EnumCase + value: CamelCase + - key: readability-identifier-naming.EnumConstantCase + value: CamelCase + - key: readability-braces-around-statements.ShortStatementLines + value: 1 diff --git a/.github/workflows/bundle-build.yml b/.github/workflows/bundle-build.yml new file mode 100644 index 0000000000000..64cc324edcadd --- /dev/null +++ b/.github/workflows/bundle-build.yml @@ -0,0 +1,96 @@ +name: Bundle Build + +on: + push: + + pull_request: + branches: [ "main" ] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +env: + parallel_processes: 6 # A good default counts is: available Threads + 2 + +jobs: + build: + runs-on: windows-latest + strategy: + matrix: + build_type: ["MinSizeRel"] + + steps: + - uses: actions/checkout@v4 + + - name: Install Qt + uses: jurplel/install-qt-action@v4 + with: + aqtversion: '==3.1.*' + version: '6.5.1' + host: 'windows' + target: 'desktop' + cache: true + arch: 'win64_msvc2019_64' + modules: 'qt3d qtactiveqt qtcharts qtconnectivity qtdatavis3d qtgrpc qthttpserver qtimageformats qtlanguageserver qtlocation qtlottie qtmultimedia qtnetworkauth qtpdf qtpositioning qtquick3dphysics qtquickeffectmaker qtremoteobjects qtscxml qtsensors qtserialbus qtserialport qtspeech qtvirtualkeyboard qtwebchannel qtwebengine qtwebsockets qtwebview debug_info qt5compat qtquick3d qtquicktimeline qtshadertools' + + - name: Cache Inno Setup + id: cache-innosetup + uses: actions/cache@v4 + with: + path: innosetup.exe + key: innosetup-6.4.3 + + - name: Download Inno Setup + if: steps.cache-innosetup.outputs.cache-hit != 'true' + shell: powershell + run: | + wget -O innosetup.exe "https://jrsoftware.org/download.php/innosetup-6.4.3.exe" + + - name: Install Inno Setup + run: | + ./innosetup.exe /silent /dir=${{github.workspace}}/innoSetup /CURRENTUSER /NOICONS + set innoPath= + set PATH=%PATH%;${{github.workspace}}/innoSetup + + # Packages should be automatically downloaded using vcpkg through the toolchain + - name: Configure CMake + env: + CMAKE_PREFIX_PATH: ${{env.QT_ROOT_DIR}} + # Choose CMakeLists.text from a specific source directory with -S. + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: | + cmake -B build -S . -DCMAKE_BUILD_TYPE=${{matrix.build_type}} -DCMAKE_INSTALL_PREFIX=stellarium-bin -DSCM_SHOULD_ENABLE_CONVERTER=OFF + + - name: Build + # Build your program with the given configuration + run: | + cmake --build build --config ${{matrix.build_type}} -j ${{env.parallel_processes}} --target install + + - name: Build Installer + run: | + cmake --build build --config ${{matrix.build_type}} -j ${{env.parallel_processes}} --target stellarium-installer + + # We use the first find as it should only be one + - name: Install Stellarium for Bundeling + run: | + $installer = Get-ChildItem -Path installers -Recurse -Filter 'stellarium-*-qt6-win64.exe' | Select-Object -First 1 + if ($installer) { + New-Item -ItemType Directory -Path stellarium-installer -Force | Out-Null + Copy-Item $installer.FullName stellarium-installer\ + + $quickInstallScript = "stellarium-installer\quickInstall.bat" + $installerRelativePath = ".\" + $installer.Name + $quickInstallContent = "`"$installerRelativePath`" /silent /dir=stellarium /CURRENTUSER /NOICONS /LOG=`"installation.log`" /MERGETASKS=`"!desktopicon`"" + $quickInstallContent += "`r`nmklink stellarium.exe stellarium\stellarium.exe" + Set-Content -Path $quickInstallScript -Value $quickInstallContent + } else { + Write-Error "Installer not found." + } + + - name: Deploy Stellarium installer + uses: actions/upload-artifact@v4 + with: + name: stellarium-windows-installer + path: stellarium-installer + retention-days: 3 # Don't store to long as stellarium builds are large \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5ccf954f68d72..d8c5192f9c5aa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,13 +9,17 @@ on: pull_request: # The branches below must be a subset of the branches above branches: [master] + types: [opened, reopened, synchronize, ready_for_review] + +env: + parallel_processes: 6 # A good default counts is: available Threads + 2 jobs: # CI on Linux (Qt5) ci-linux-qt5: name: "Linux (amd64; qt5)" runs-on: ubuntu-latest - if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.actor, 'transifex')" + if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.actor, 'transifex') && github.event.pull_request.draft == false" steps: - name: Install dependencies @@ -42,7 +46,7 @@ jobs: - name: Compile working-directory: build - run: make -j3 + run: make -j ${{env.parallel_processes}} - name: Run unit tests uses: coactions/setup-xvfb@v1 @@ -54,8 +58,8 @@ jobs: ci-linux-qt6: name: "Linux (amd64; qt6)" runs-on: ubuntu-latest - if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.actor, 'transifex')" - + if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.actor, 'transifex') && github.event.pull_request.draft == false" + steps: - name: Install dependencies run: | @@ -68,7 +72,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 - name: Checkout repository uses: actions/checkout@v4 @@ -82,7 +86,7 @@ jobs: - name: Compile working-directory: build - run: make -j3 + run: make -j ${{env.parallel_processes}} - name: Run unit tests uses: coactions/setup-xvfb@v1 @@ -94,7 +98,7 @@ jobs: ci-linux-qt6-pch: name: "Linux (amd64; qt6; core)" runs-on: ubuntu-latest - if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.actor, 'transifex')" + if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.actor, 'transifex') && github.event.pull_request.draft == false" steps: - name: Install dependencies @@ -123,12 +127,12 @@ 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 working-directory: build - run: make -j3 + run: make -j ${{env.parallel_processes}} - name: Run unit tests uses: coactions/setup-xvfb@v1 @@ -140,7 +144,7 @@ jobs: ci-macos-qt5: name: "macOS (x86_64; qt5)" runs-on: macos-13 - if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.actor, 'transifex')" + if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.actor, 'transifex') && github.event.pull_request.draft == false" # @TODO Enable gpsd on macOS instance for CI testing # @BODY At the moment after installing gpsd (brew install gpsd) library can be found by cmake, but not headers! Apparently we should add some magic for environment variables or something else on macOS Catalina to make headers available for cmake/make @@ -167,7 +171,7 @@ jobs: - name: Compile working-directory: build - run: make -j3 + run: make -j ${{env.parallel_processes}} - name: Run unit tests uses: coactions/setup-xvfb@v1 @@ -179,7 +183,7 @@ jobs: ci-macos-intel-qt6: name: "macOS (x86_64; qt6)" runs-on: macos-13 - if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.actor, 'transifex')" + if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.actor, 'transifex') && github.event.pull_request.draft == false" # @TODO Enable gpsd on macOS instance for CI testing # @BODY At the moment after installing gpsd (brew install gpsd) library can be found by cmake, but not headers! Apparently we should add some magic for environment variables or something else on macOS Catalina to make headers available for cmake/make @@ -191,7 +195,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,11 +206,11 @@ 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/opt/qt@6;/usr/local/opt/tidy-html5" -DCMAKE_BUILD_TYPE=RelWithDebInfo -DSTELLARIUM_RELEASE_BUILD=Off -DENABLE_TESTING=On ${{ github.workspace }} - name: Compile working-directory: build - run: make -j3 + run: make -j ${{env.parallel_processes}} - name: Run unit tests uses: coactions/setup-xvfb@v1 @@ -218,7 +222,7 @@ jobs: ci-macos-silicon-qt6: name: "macOS (arm64; qt6)" runs-on: macos-14 - if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.actor, 'transifex')" + if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.actor, 'transifex') && github.event.pull_request.draft == false" # @TODO Enable gpsd on macOS instance for CI testing # @BODY At the moment after installing gpsd (brew install gpsd) library can be found by cmake, but not headers! Apparently we should add some magic for environment variables or something else on macOS Catalina to make headers available for cmake/make @@ -233,7 +237,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 @@ -248,7 +252,7 @@ jobs: - name: Compile working-directory: build - run: make -j3 + run: make -j ${{env.parallel_processes}} - name: Run unit tests uses: coactions/setup-xvfb@v1 @@ -260,7 +264,7 @@ jobs: ci-freebsd-qt6: name: "FreeBSD (x86_64; qt5)" runs-on: ubuntu-latest - if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.actor, 'transifex')" + if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.actor, 'transifex') && github.event.pull_request.draft == false" steps: - name: Checkout repository @@ -284,9 +288,9 @@ jobs: mkdir builds cd builds cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DSTELLARIUM_RELEASE_BUILD=Off -DENABLE_TESTING=On "${{ github.workspace }}" - make -j3 + make -j ${{env.parallel_processes}} Xvfb :0 -ac -screen 0 1024x768x24+32 >/dev/null 2>&1 & sleep 3 ctest --output-on-failure sleep 1 - pkill Xvfb + pkill Xvfb \ No newline at end of file diff --git a/.gitignore b/.gitignore index de9d5f0af4a99..0ff76878a6b43 100644 --- a/.gitignore +++ b/.gitignore @@ -75,3 +75,5 @@ qrc_*.cpp .kdev4 # Ignore Eclipse project files .project +# Ignore vscode project files +.vscode \ No newline at end of file diff --git a/BUILDING.md b/BUILDING.md index 67fb4e8e94780..357e738f26386 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 f04880bab0261..5905fd49549e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -299,12 +299,6 @@ 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 -SET(CMAKE_C_STANDARD 11) -SET(CMAKE_C_STANDARD_REQUIRED ON) -SET(CMAKE_C_EXTENSIONS OFF) IF(WIN32) # We don't need the extra Windows.h stuff, this may speed up compilation a tiny bit @@ -329,8 +323,10 @@ 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 requieres a C++17 compiler + # permissiv: requiered by Qt >6.9.0 + SET(STEL_MSVC_FLAGS "/wd4244 /wd4305 /wd4351 /wd4996 /wd5105 /utf-8 /Zc:__cplusplus /permissive-") # 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 @@ -567,6 +563,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) @@ -906,7 +903,11 @@ IF(WIN32) SET(ISS_AUTOGENERATED_WARNING "Do not edit this file! It has been automatically generated by CMake. Your changes will be lost the next time CMake is run.") GET_FILENAME_COMPONENT(_qt_bin_dir "${QMAKE_LOCATION}" DIRECTORY) - FIND_PROGRAM(WINDEPLOYQT_COMMAND windeployqt HINTS "${_qt_bin_dir}") + IF(Qt6_FOUND) + FIND_PROGRAM(WINDEPLOYQT_COMMAND windeployqt6 HINTS "${_qt_bin_dir}") + ELSE() + FIND_PROGRAM(WINDEPLOYQT_COMMAND windeployqt HINTS "${_qt_bin_dir}") + ENDIF() IF(WINDEPLOYQT_COMMAND) MESSAGE(STATUS "Found windeployqt: ${WINDEPLOYQT_COMMAND}") ELSE() diff --git a/plugins/SkyCultureMaker/CMakeLists.txt b/plugins/SkyCultureMaker/CMakeLists.txt new file mode 100644 index 0000000000000..58c992bbfbf1a --- /dev/null +++ b/plugins/SkyCultureMaker/CMakeLists.txt @@ -0,0 +1,58 @@ +# This is the cmake config file for the Sky Culture Maker plugin +SET(SCM_VERSION "0.1.0") + +# download CPM.cmake +file( + DOWNLOAD + https://github.com/cpm-cmake/CPM.cmake/releases/download/v0.40.8/CPM.cmake + ${CMAKE_CURRENT_BINARY_DIR}/cmake/CPM.cmake + EXPECTED_HASH SHA256=78ba32abdf798bc616bab7c73aac32a17bbd7b06ad9e26a6add69de8f3ae4791 +) +include(${CMAKE_CURRENT_BINARY_DIR}/cmake/CPM.cmake) + + +# Option to manually control converter, defaults to TRUE, but overridden by Qt version +option(SCM_SHOULD_ENABLE_CONVERTER "Attempt to enable Sky Culture Converter" TRUE) +set(SCM_CONVERTER_ENABLED FALSE) # Default to disabled + +if(SCM_SHOULD_ENABLE_CONVERTER AND NOT (QT_VERSION_MAJOR EQUAL "5")) + set(SCM_CONVERTER_ENABLED TRUE) + message(STATUS "Sky Culture Converter will be enabled.") + # download https://github.com/Stellarium/stellarium-skyculture-converter + CPMAddPackage( + NAME StellariumSkyCultureConverter + GITHUB_REPOSITORY Stellarium/stellarium-skyculture-converter + GIT_TAG master + OPTIONS + "SKYCULTURE_CONVERTER_BUILD_TESTS OFF" + ) + + # download https://github.com/selmf/unarr for archives + CPMAddPackage( + NAME unarr + GITHUB_REPOSITORY selmf/unarr + GIT_TAG v1.1.1 + ) + + 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 converter is enabled.") + endif() + endif(WIN32) + ADD_DEFINITIONS(-DSCM_CONVERTER_ENABLED_CPP) # Define for C++ +else() + message(STATUS "Sky Culture Converter is DISABLED (Qt version 5 or SCM_SHOULD_ENABLE_CONVERTER is OFF).") +endif() + +ADD_DEFINITIONS(-DSKYCULTUREMAKER_PLUGIN_VERSION="${SCM_VERSION}") +ADD_DEFINITIONS(-DSKYCULTUREMAKER_PLUGIN_LICENSE="GNU GPLv2 or later") + +ADD_SUBDIRECTORY( src ) \ No newline at end of file diff --git a/plugins/SkyCultureMaker/README.md b/plugins/SkyCultureMaker/README.md new file mode 100644 index 0000000000000..4b3e79ba5062b --- /dev/null +++ b/plugins/SkyCultureMaker/README.md @@ -0,0 +1,7 @@ +# Sky Culture Maker Plugin + +## Development Notes + +- makeCulturesList(): +- https://github.com/Integer-Ctrl/stellarium/blob/master/src/core/StelSkyCultureMgr.cpp#L163 +- läd ALLE skycultures in den Stellarium SC Manager 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..cf685cadc64dd --- /dev/null +++ b/plugins/SkyCultureMaker/resources/SkyCultureMaker.qrc @@ -0,0 +1,8 @@ + + + bt_LineDraw_Off.png + bt_LineDraw_On.png + bt_SCM_Off.png + bt_SCM_On.png + + diff --git a/plugins/SkyCultureMaker/resources/bt_LineDraw_Off.png b/plugins/SkyCultureMaker/resources/bt_LineDraw_Off.png new file mode 100644 index 0000000000000..9eea9b01e583c Binary files /dev/null and b/plugins/SkyCultureMaker/resources/bt_LineDraw_Off.png differ diff --git a/plugins/SkyCultureMaker/resources/bt_LineDraw_On.png b/plugins/SkyCultureMaker/resources/bt_LineDraw_On.png new file mode 100644 index 0000000000000..00e2308c82f41 Binary files /dev/null and b/plugins/SkyCultureMaker/resources/bt_LineDraw_On.png differ 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..b9d373e37ff24 --- /dev/null +++ b/plugins/SkyCultureMaker/src/CMakeLists.txt @@ -0,0 +1,69 @@ +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 + ScmAsterism.hpp + ScmAsterism.cpp + ScmCommonName.hpp + ScmCommonName.cpp + ScmConstellation.hpp + ScmConstellation.cpp + ScmSkyCulture.hpp + ScmSkyCulture.cpp + + gui/ScmStartDialog.hpp + gui/ScmStartDialog.cpp + gui/ScmConstellationDialog.hpp + gui/ScmConstellationDialog.cpp + gui/ScmSkyCultureDialog.hpp + gui/ScmSkyCultureDialog.cpp + + types/CoordinateLine.hpp + types/Drawing.hpp + types/DrawTools.hpp + types/Lines.hpp + types/StarLine.hpp + types/StarPoint.hpp +) + +SET( SCM_UIS + gui/scmConstellationDialog.ui + gui/scmSkyCultureDialog.ui + gui/scmStartDialog.ui +) + +################# 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 SKYMAKER_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 SKYMAKER_PLUGIN_LINK_LIBS skyculture_converter_lib unarr) +endif() + +TARGET_LINK_LIBRARIES(SkyCultureMaker-static ${SKYMAKER_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/ScmAsterism.cpp b/plugins/SkyCultureMaker/src/ScmAsterism.cpp new file mode 100644 index 0000000000000..7052144ec465d --- /dev/null +++ b/plugins/SkyCultureMaker/src/ScmAsterism.cpp @@ -0,0 +1,21 @@ +#include "ScmAsterism.hpp" + +void scm::ScmAsterism::setCommonName(const ScmCommonName &name) +{ + commonName = name; +} + +void scm::ScmAsterism::setId(const QString &id) +{ + ScmAsterism::id = id; +} + +scm::ScmCommonName scm::ScmAsterism::getCommonName() const +{ + return commonName; +} + +QString scm::ScmAsterism::getId() const +{ + return id; +} diff --git a/plugins/SkyCultureMaker/src/ScmAsterism.hpp b/plugins/SkyCultureMaker/src/ScmAsterism.hpp new file mode 100644 index 0000000000000..76a67da2baf08 --- /dev/null +++ b/plugins/SkyCultureMaker/src/ScmAsterism.hpp @@ -0,0 +1,60 @@ +/** + * @file ScmAsterism.hpp + * @author lgrumbach + * @brief Represents an asterism in a sky culture. + * @version 0.1 + * @date 2025-05-09 + * + */ + +#ifndef SCM_ASTERISM_HPP +#define SCM_ASTERISM_HPP + +#include "QString" +#include "ScmCommonName.hpp" + +namespace scm +{ + +class ScmAsterism +{ +public: + /** + * @brief Sets the id of the asterism + * + * @param id id + */ + void setId(const QString &id); + + /** + * @brief Gets the id of the asterism + * + * @return id + */ + QString getId() const; + + /** + * @brief Sets the common name of the asterism. + * + * @param name The common name of this asterim. + */ + void setCommonName(const ScmCommonName &name); + + /** + * @brief Returns the common name of the asterism. + * + * @return The common name of the this asterism. + */ + ScmCommonName getCommonName() const; + +private: + /// Id of the Asterism + QString id; + + /// Common name of the constellation + ScmCommonName commonName; +}; + +} // namespace scm + +#endif // SCM_ASTERISM_HPP diff --git a/plugins/SkyCultureMaker/src/ScmCommonName.cpp b/plugins/SkyCultureMaker/src/ScmCommonName.cpp new file mode 100644 index 0000000000000..99dcf6f420102 --- /dev/null +++ b/plugins/SkyCultureMaker/src/ScmCommonName.cpp @@ -0,0 +1,46 @@ +#include "ScmCommonName.hpp" + +scm::ScmCommonName::ScmCommonName(const QString &id) +{ + ScmCommonName::id = id; +} + +void scm::ScmCommonName::setEnglishName(const QString &name) +{ + englishName = name; +} + +void scm::ScmCommonName::setNativeName(const QString &name) +{ + nativeName = name; +} + +void scm::ScmCommonName::setPronounce(const QString &pronounce) +{ + ScmCommonName::pronounce = pronounce; +} + +void scm::ScmCommonName::setIpa(const QString &ipa) +{ + ScmCommonName::ipa = ipa; +} + +void scm::ScmCommonName::setReferences(const std::vector &refs) +{ + references = refs; +} + +QString scm::ScmCommonName::getEnglishName() const +{ + return englishName; +} + +std::optional scm::ScmCommonName::getIpa() const +{ + return ipa; +} + +std::optional> scm::ScmCommonName::getReferences() const +{ + return references; +} diff --git a/plugins/SkyCultureMaker/src/ScmCommonName.hpp b/plugins/SkyCultureMaker/src/ScmCommonName.hpp new file mode 100644 index 0000000000000..363aab117a32e --- /dev/null +++ b/plugins/SkyCultureMaker/src/ScmCommonName.hpp @@ -0,0 +1,85 @@ +/** + * @file ScmCommonName.hpp + * @author lgrumbach + * @brief Represents a common name of a star, planet or nonstellar object. Do not use for constellations. + * @version 0.1 + * @date 2025-05-09 + * + */ + +#ifndef SCM_COMMONNAME_HPP +#define SCM_COMMONNAME_HPP + +#include +#include +#include +#include + +namespace scm +{ + +class ScmCommonName +{ +public: + ScmCommonName(const QString &id); + + /// Sets the english name + void setEnglishName(const QString &name); + + /// Sets the native name + void setNativeName(const QString &name); + + /// Sets the native name in European glyphs or Pinyin for Chinese. + void setPronounce(const QString &pronounce); + + /// Sets the native name in IPA (International Phonetic Alphabet) + void setIpa(const QString &name); + + /// Sets the references to the sources of the name spellings + void setReferences(const std::vector &refs); + + /// Returns the english name + QString getEnglishName() const; + + /// Returns the native name + std::optional getNativeName() const; + + /// Returns the native name in European glyphs or Pinyin for Chinese. + std::optional getPronounce() const; + + /// Returns the native name in IPA (International Phonetic Alphabet) + std::optional getIpa() const; + + /// Returns the references to the sources of the name spellings + std::optional> getReferences() const; + + /// Returns the common name as a JSON object + QJsonObject toJson() const; + +private: + /*! Identifier of the common name + * Example: + * Star : "HIP " + * Planet: "NAME " + */ + 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; +}; + +} // namespace scm + +#endif // SCM_COMMONNAME_HPP diff --git a/plugins/SkyCultureMaker/src/ScmConstellation.cpp b/plugins/SkyCultureMaker/src/ScmConstellation.cpp new file mode 100644 index 0000000000000..e53ac70a78564 --- /dev/null +++ b/plugins/SkyCultureMaker/src/ScmConstellation.cpp @@ -0,0 +1,180 @@ +#include "ScmConstellation.hpp" + +scm::ScmConstellation::ScmConstellation(const std::vector &coordinates, + const std::vector &stars) + : constellationCoordinates(coordinates) + , constellationStars(stars) +{ + 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(); +} + +void scm::ScmConstellation::setId(const QString &id) +{ + ScmConstellation::id = id; +} + +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; +} + +void scm::ScmConstellation::setPronounce(const std::optional &pronounce) +{ + ScmConstellation::pronounce = pronounce; +} + +void scm::ScmConstellation::setIPA(const std::optional &ipa) +{ + ScmConstellation::ipa = ipa; +} + +void scm::ScmConstellation::setConstellation(const std::vector &coordinates, + const std::vector &stars) +{ + constellationCoordinates = coordinates; + constellationStars = stars; + + updateTextPosition(); +} + +void scm::ScmConstellation::drawConstellation(StelCore *core, const Vec3f &lineColor, const Vec3f &nameColor) const +{ + StelPainter painter(core->getProjection(drawFrame)); + painter.setBlending(true); + painter.setLineSmooth(true); + painter.setFont(constellationNameFont); + + bool alpha = 1.0f; + painter.setColor(lineColor, alpha); + + for (CoordinateLine p : constellationCoordinates) + { + painter.drawGreatCircleArc(p.start, p.end); + } + + drawNames(core, painter, nameColor); +} + +void scm::ScmConstellation::drawConstellation(StelCore *core) const +{ + drawConstellation(core, defaultConstellationLineColor, defaultConstellationNameColor); +} + +void scm::ScmConstellation::drawNames(StelCore *core, StelPainter &sPainter, const Vec3f &nameColor) const +{ + 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 +{ + drawNames(core, sPainter, defaultConstellationNameColor); +} + +QJsonObject scm::ScmConstellation::toJson(const QString &skyCultureName) const +{ + QJsonObject json; + + // Assemble lines object + QJsonArray linesArray; + + if (constellationStars.size() != 0) + { + // Stars are NOT empty + for (const auto &star : constellationStars) + { + linesArray.append(star.toJson()); + } + } + else + { + // Stars are empty, use the coorindates + for (const auto &coord : constellationCoordinates) + { + linesArray.append(coord.toJson()); + } + } + + json["id"] = "CON " + skyCultureName + " " + id; + json["lines"] = linesArray; + + // 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; +} + +void scm::ScmConstellation::updateTextPosition() +{ + XYZname.set(0., 0., 0.); + for (CoordinateLine p : constellationCoordinates) + { + XYZname += p.end; + XYZname += p.start; + } + XYZname.normalize(); +} diff --git a/plugins/SkyCultureMaker/src/ScmConstellation.hpp b/plugins/SkyCultureMaker/src/ScmConstellation.hpp new file mode 100644 index 0000000000000..9e1d6cf1a97a6 --- /dev/null +++ b/plugins/SkyCultureMaker/src/ScmConstellation.hpp @@ -0,0 +1,174 @@ +/** + * @file ScmConstellation.hpp + * @author lgrumbach + * @brief Represents a constellation in a sky culture. + * @version 0.1 + * @date 2025-05-09 + * + */ + +#ifndef SCM_CONSTELLATION_HPP +#define SCM_CONSTELLATION_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 std::vector &coordinates, const std::vector &stars); + + /// The frame that is used for calculation and is drawn on. + static const StelCore::FrameType drawFrame = StelCore::FrameJ2000; + + /** + * @brief Sets the id of the constellation + * + * @param id id + */ + void setId(const QString &id); + + /** + * @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 Sets the pronounciation of the constellation + * + * @param pronounce The pronounciation + */ + void setPronounce(const std::optional &pronounce); + + /** + * @brief Sets the IPA. + * + * @param ipa The optional ipa + */ + void setIPA(const std::optional &ipa); + + /** + * @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 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. + */ + QJsonObject toJson(const QString &skyCultureName) const; + +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 constellationCoordinates; + + /// List of stars forming the segments. Might be empty. + std::vector constellationStars; + + /// 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); + + /** + * @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/ScmDraw.cpp b/plugins/SkyCultureMaker/src/ScmDraw.cpp new file mode 100644 index 0000000000000..5cf6c590a6928 --- /dev/null +++ b/plugins/SkyCultureMaker/src/ScmDraw.cpp @@ -0,0 +1,465 @@ +#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) + { + 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() << "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) + , snapToStar(true) +{ + 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); + Vec3f color = {1.f, 0.5f, 0.5f}; + bool alpha = 1.0f; + painter.setColor(color, alpha); + + for (CoordinateLine p : drawnLines.coordinates) + { + painter.drawGreatCircleArc(p.start, p.end); + } + + if (hasFlag(drawState, Drawing::hasFloatingEnd)) + { + color = {1.f, 0.7f, 0.7f}; + painter.setColor(color, 0.5f); + 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(); + StelProjectorP prj = core->getProjection(drawFrame); + Vec3d point; + std::optional starID; + prj->unProject(x, y, point); + + // We want to combine any near start point to an existing point so that we don't create + // duplicates. + std::optional nearest = findNearestPoint(x, y, prj); + if (nearest.has_value()) + { + point = nearest.value().coordinate; + starID = nearest.value().star; + } + else if (snapToStar) + { + if (hasFlag(drawState, Drawing::hasEndExistingPoint)) + { + point = std::get(currentLine).end; + starID = std::get(currentLine).end; + } + else + { + StelObjectMgr &objectMgr = app.getStelObjectMgr(); + + if (objectMgr.getWasSelected()) + { + StelObjectP stelObj = objectMgr.getLastSelectedObject(); + Vec3d stelPos = stelObj->getJ2000EquatorialPos(core); + point = stelPos; + if (stelObj->getType() == "Star") + { + starID = stelObj->getID(); + } + } + } + } + + appendDrawPoint(point, starID); + + 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 && event->type() == QEvent::MouseButtonPress) + { + Vec2d currentPos(x, y); + lastEraserPos = currentPos; + } + else if (event->button() == Qt::RightButton && event->type() == QEvent::MouseButtonRelease) + { + // Reset + 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) + { + if (snapToStar) + { + StelObjectMgr &objectMgr = app.getStelObjectMgr(); + objectMgr.findAndSelect(core, x, y); + } + + if (hasFlag(drawState, (Drawing::hasStart | Drawing::hasFloatingEnd))) + { + StelProjectorP prj = core->getProjection(drawFrame); + Vec3d position; + prj->unProject(x, y, position); + if (snapToStar) + { + StelObjectMgr &objectMgr = app.getStelObjectMgr(); + if (objectMgr.getWasSelected()) + { + StelObjectP stelObj = objectMgr.getLastSelectedObject(); + Vec3d stelPos = stelObj->getJ2000EquatorialPos(core); + std::get(currentLine).end = stelPos; + } + else + { + std::get(currentLine).end = position; + } + } + else + { + 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_Control) + { + snapToStar = e->type() != QEvent::KeyPress; + + e->accept(); + } + + 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::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) + { + StarPoint point = {min->start, + drawnLines.stars.at(std::distance(drawnLines.coordinates.begin(), min)).start}; + return point; + } + else + { + StarPoint 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; + 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..231f5e5395ce4 --- /dev/null +++ b/plugins/SkyCultureMaker/src/ScmDraw.hpp @@ -0,0 +1,190 @@ +/** + * @file ScmDraw.hpp + * @author vgerlach, lgrumbach + * @brief Draws between stars objects and free coordinate points. + * @version 0.1 + * @date 2025-05-17 + */ + +#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/Lines.hpp" +#include "types/StarLine.hpp" +#include "types/StarPoint.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; + + /// 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; + + /// Indicates if a line start or end will snap to the nearest star. + bool snapToStar = false; + + /// 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 star 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 The found star point if available. + */ + 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 Set the active draw tool + * + * @param tool The tool to be used. + */ + void setTool(DrawTools tool); + + /** + * @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..1c6074a8229a9 --- /dev/null +++ b/plugins/SkyCultureMaker/src/ScmSkyCulture.cpp @@ -0,0 +1,142 @@ +#include "ScmSkyCulture.hpp" +#include "types/Classification.hpp" +#include +#include + +void scm::ScmSkyCulture::setId(const QString &id) +{ + ScmSkyCulture::id = id; +} + +void scm::ScmSkyCulture::setRegion(const QString ®ion) +{ + ScmSkyCulture::region = region; +} + +void scm::ScmSkyCulture::setClassificationType(ClassificationType classificationType) +{ + ScmSkyCulture::classificationType = classificationType; +} + +void scm::ScmSkyCulture::setFallbackToInternationalNames(bool fallback) +{ + ScmSkyCulture::fallbackToInternationalNames = fallback; +} + +void scm::ScmSkyCulture::addAsterism(const scm::ScmAsterism &asterism) +{ + asterisms.push_back(asterism); +} + +void scm::ScmSkyCulture::removeAsterism(const QString &id) +{ + asterisms.erase(remove_if(begin(asterisms), end(asterisms), + [id](scm::ScmAsterism const &a) { return a.getId() == id; }), + end(asterisms)); +} + +scm::ScmConstellation &scm::ScmSkyCulture::addConstellation(const QString &id, + const std::vector &coordinates, + const std::vector &stars) +{ + scm::ScmConstellation constellationObj(coordinates, stars); + constellationObj.setId(id); + 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; +} + +void scm::ScmSkyCulture::setLicense(scm::LicenseType license) +{ + ScmSkyCulture::license = license; +} + +scm::LicenseType scm::ScmSkyCulture::getLicense() const +{ + return ScmSkyCulture::license; +} + +void scm::ScmSkyCulture::setAuthors(const QString authors) +{ + ScmSkyCulture::authors = authors; +} + +QString scm::ScmSkyCulture::getAuthors() const +{ + return ScmSkyCulture::authors; +} + +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; + + QTextStream out(&file); + out << "# " << desc.name << "\n\n"; + out << "## Geographical Region\n" << desc.geoRegion << "\n\n"; + out << "## Classification\n " << classificationTypeToString(desc.classification) << "\n\n"; + + out << "## Sky\n" << desc.sky << "\n\n"; + out << "## Moon and Sun\n" << desc.moonAndSun << "\n\n"; + out << "## Zodiac\n" << desc.zodiac << "\n\n"; + out << "## Planets\n" << desc.planets << "\n\n"; + out << "## Constellations\n" << desc.constellations << "\n\n"; + out << "## Milky Way\n" << desc.milkyWay << "\n\n"; + out << "## Other Celestial Objects\n" << desc.otherObjects << "\n\n"; + + out << "## About\n" << desc.about << "\n\n"; + out << "## Authors\n" << desc.authors << "\n\n"; + out << "## Acknowledgements\n" << desc.acknowledgements << "\n\n"; + out << "## References\n" << desc.references << "\n"; + + try + { + file.close(); + return true; // successfully saved + } + catch (const std::exception &e) + { + qWarning("Error closing file: %s", e.what()); + return false; // error occurred while closing the file + } + } + else + { + qWarning("Could not open file for writing: %s", qPrintable(file.fileName())); + return false; // file could not be opened + } +} diff --git a/plugins/SkyCultureMaker/src/ScmSkyCulture.hpp b/plugins/SkyCultureMaker/src/ScmSkyCulture.hpp new file mode 100644 index 0000000000000..96d15714551f3 --- /dev/null +++ b/plugins/SkyCultureMaker/src/ScmSkyCulture.hpp @@ -0,0 +1,170 @@ +/** + * @file ScmSkyCulture.hpp + * @author fhofer, lgrumbach + * @brief Object holding all data of a sky culture during the creation process with the Sky Culture Maker. + * @version 0.1 + * @date 2025-05-09 + * + * The minimun information that the object should store can be found in any index.json file of an existing sky culture. + * For example: /{userpath}/stellarium/skycultures/maya/index.json + */ +#ifndef SCM_SKYCULTURE_HPP +#define SCM_SKYCULTURE_HPP + +#include +// #include +#include + +#include "ScmAsterism.hpp" +#include "ScmCommonName.hpp" +#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); + + /// Sets the region of the sky culture + void setRegion(const QString ®ion); + + /// Sets the classification of the sky culture + void setClassificationType(ClassificationType classificationType); + + /// Sets whether to show common names in addition to the culture-specific ones + void setFallbackToInternationalNames(bool fallback); + + /// Adds an asterism to the sky culture + void addAsterism(const ScmAsterism &asterism); + + /// Removes an asterism from the sky culture by its ID + void removeAsterism(const QString &id); + + /// Adds a constellation to the sky culture + ScmConstellation &addConstellation(const QString &id, const std::vector &coordinates, + const std::vector &stars); + + /// 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); + + /// Adds a common name to the sky culture + void addCommonName(ScmCommonName commonName); + + /// Removes a common name from the sky culture by its ID + void removeCommonName(const QString &id); + + /// Returns the asterisms of the sky culture + // TODO: QVector getAsterisms() const; + + /// Returns a pointer to the constellations of the sky culture + std::vector *getConstellations(); + + /// Returns the common names of the stars, planets and nonstellar objects + std::vector getCommonNames() const; + + /// Sets the license of the sky culture + void setLicense(scm::LicenseType license); + + /// Returns the license of the sky culture + scm::LicenseType getLicense() const; + + /// Sets the authors of the sky culture + void setAuthors(const QString authors); + + /// Returns the authors of the sky culture + QString getAuthors() const; + + /** + * @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); + +private: + /// Sky culture identifier + QString id; + + /*! The name of region following the United Nations geoscheme UN~M49 + * https://unstats.un.org/unsd/methodology/m49/ For skycultures of worldwide applicability (mostly those + * adhering to IAU constellation borders), use "World". + */ + QString region; + + /// Classification of the sky culture + ClassificationType classificationType = ClassificationType::NONE; + + /// Whether to show common names in addition to the culture-specific ones + bool fallbackToInternationalNames = false; + + /// The asterisms of the sky culture + std::vector asterisms; + + /// The constellations of the sky culture + std::vector constellations; + + /// The common names of the stars, planets and nonstellar objects + std::vector commonNames; + + /// The license of the sky culture + scm::LicenseType license = scm::LicenseType::NONE; + + /// The authors of the sky culture + QString authors; + + /// The description of the sky culture + scm::Description description; + + // TODO: edges: + /// Type of the edges. Can be one of "none", "iau" or "own". TODO: enum? + // std::optional edgeType; + + /// Source of the edges. + // std::optional edgeSource; + + /*! Describes the coordinate epoch. Allowed values: + * "J2000" (default) + * "B1875" used for the edge list defining the IAU borders. + * "Byyyy.y" (a number with B prepended) Arbitrary epoch as Besselian year. + * "Jyyyy.y" (a number with J prepended) Arbitrary epoch as Julian year. + * "JDddddddd.ddd" (a number with JD prepended) Arbitrary epoch as Julian Day number. + * "ddddddd.ddd" + */ + // std::optional edgeEpoch; + + /// Describes the edges of the constellations. + // std::optional> edges; +}; + +} // 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..55e76b4fc8f77 --- /dev/null +++ b/plugins/SkyCultureMaker/src/SkyCultureMaker.cpp @@ -0,0 +1,350 @@ +#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/ScmSkyCultureDialog.hpp" +#include "gui/ScmStartDialog.hpp" + +#include "ScmDraw.hpp" +#include +#include +#include +#include +#include +#include + +/** + * Managing the creation process of a new sky culture. + * 1. Navigate in stellarium (UI) to the location of interest (from where the culture should be created) + * 2. Starting creation process (click in UI) + * 3. Draw lines from start to star + * a) Only stars should be selectable + * b) Add functionality to draw separated/unconnected lines (e.g. cross constelation) + * c) Add functionality to delete a line + * I) Deleting a inner line of a stick figure should split the figure into two stick figures + * II) Connecting two stick figures should merge them into one stick figure + * 4. Add button to save sky culture + * 5. Click save button opens dialog to name: sky culture, lines, aliass, ... + * 6. Completing the dialog (check that all needed arguments are existing and valid) converts intern c++ object to json + */ + +/************************************************************************* + 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 = "Sky Culture Maker"; + info.authors = "Vincent Gerlach (RivinHD), Luca-Philipp Grumbach (xLPMG), Fabian Hofer (Integer-Ctrl), Richard " + "Hofmann (ZeyxRew), Mher Mnatsakanyan (MherMnatsakanyan03)"; + info.contact = "Contact us using our GitHub usernames, via an Issue or the Discussion tab in the Stellarium " + "repository."; + info.description = "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() << "SkyCulture Maker constructed"; + + setObjectName("SkyCultureMaker"); + font.setPixelSize(25); + + drawObj = new scm::ScmDraw(); + scmStartDialog = new ScmStartDialog(this); + scmSkyCultureDialog = new ScmSkyCultureDialog(this); + scmConstellationDialog = new ScmConstellationDialog(this); +} + +/************************************************************************* + Destructor +*************************************************************************/ +SkyCultureMaker::~SkyCultureMaker() +{ + // Initalized on start + delete drawObj; + delete scmStartDialog; + delete scmSkyCultureDialog; + delete scmConstellationDialog; + + if (currentSkyCulture != nullptr) + { + delete currentSkyCulture; + } +} + +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() << "Sky Culture Maker: 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() << "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() << (iconScmDisabled.isNull() ? "Failed to load image: bt_SCM_Off.png" + : "Loaded image: bt_SCM_Off.png"); + qDebug() << (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() << "Unable create toolbar button for SkyCultureMaker plugin: " << e.what(); + } +} + +/*********************** + Manage creation process +***********************/ + +void SkyCultureMaker::startScmProcess() +{ + if (true != isScmEnabled) + { + isScmEnabled = true; + emit eventIsScmEnabled(true); + } + + scmStartDialog->setVisible(true); +} + +void SkyCultureMaker::stopScmProcess() +{ + if (false != isScmEnabled) + { + isScmEnabled = false; + emit eventIsScmEnabled(false); + } + + // TODO: close or delete all dialogs related to the creation process + if (scmStartDialog->visible()) + { + scmStartDialog->setVisible(false); + } + + setSkyCultureDialogVisibility(false); + setConstellationDialogVisibility(false); +} + +void SkyCultureMaker::draw(StelCore *core) +{ + if (isLineDrawEnabled && drawObj != nullptr) + { + drawObj->drawLine(core); + } + + if (isScmEnabled && currentSkyCulture != nullptr) + { + currentSkyCulture->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::setSkyCultureDialogVisibility(bool b) +{ + if (b != scmSkyCultureDialog->visible()) + { + scmSkyCultureDialog->setVisible(b); + } +} + +void SkyCultureMaker::setConstellationDialogVisibility(bool b) +{ + if (b != scmConstellationDialog->visible()) + { + scmConstellationDialog->setVisible(b); + } + + setIsLineDrawEnabled(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); + } +} + +QFile SkyCultureMaker::getScmDescriptionFile() +{ + // TODO: Issue #85 + return QFile("description.md"); +} + +bool SkyCultureMaker::saveSkyCultureDescription() +{ + if (currentSkyCulture != nullptr) + { + return currentSkyCulture->saveDescriptionAsMarkdown(getScmDescriptionFile()); + } + + return false; +} diff --git a/plugins/SkyCultureMaker/src/SkyCultureMaker.hpp b/plugins/SkyCultureMaker/src/SkyCultureMaker.hpp new file mode 100644 index 0000000000000..35fb7c4af34a4 --- /dev/null +++ b/plugins/SkyCultureMaker/src/SkyCultureMaker.hpp @@ -0,0 +1,194 @@ +#ifndef SKYCULTUREMAKER_HPP +#define SKYCULTUREMAKER_HPP + +#include "ScmConstellation.hpp" +#include "ScmDraw.hpp" +#include "ScmSkyCulture.hpp" +#include "StelCore.hpp" +#include "StelModule.hpp" +#include "StelObjectModule.hpp" +#include "StelTranslator.hpp" +#include "VecMath.hpp" +#include + +#include + +class QPixmap; +class StelButton; +class ScmSkyCultureDialog; +class ScmConstellationDialog; +class ScmStartDialog; + +/// This is an example of a plug-in which can be dynamically loaded into stellarium +class SkyCultureMaker : public StelModule +{ + Q_OBJECT + 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); + + /////////////////////////////////////////////////////////////////////////// + // Methods defined in the StelModule class + 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 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 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. + * @return true if the description was saved successfully, false otherwise. + */ + bool saveSkyCultureDescription(); + + /** + * @brief Gets the saving file for the sky culture description. + * @return The file to save the description to. + */ + QFile getScmDescriptionFile(); + +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; + + /// The current sky culture + scm::ScmSkyCulture *currentSkyCulture = nullptr; +}; + +#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..cb6e4f4f81aef --- /dev/null +++ b/plugins/SkyCultureMaker/src/enumBitops.hpp @@ -0,0 +1,32 @@ +#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..4b5797b5d8907 --- /dev/null +++ b/plugins/SkyCultureMaker/src/gui/ScmConstellationDialog.cpp @@ -0,0 +1,221 @@ +#include "ScmConstellationDialog.hpp" +#include "StelGui.hpp" +#include "ui_scmConstellationDialog.h" +#include + +ScmConstellationDialog::ScmConstellationDialog(SkyCultureMaker *maker) + : StelDialogSeparate("ScmConstellationDialog") + , maker(maker) +{ + assert(maker != nullptr); + ui = new Ui_scmConstellationDialog; +} + +ScmConstellationDialog::~ScmConstellationDialog() +{ + delete ui; +} + +void ScmConstellationDialog::retranslate() +{ + if (dialog) + { + ui->retranslateUi(dialog); + } +} + +void ScmConstellationDialog::close() +{ + maker->setConstellationDialogVisibility(false); +} + +void ScmConstellationDialog::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, &ScmConstellationDialog::close); + + 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->saveBtn, &QPushButton::clicked, this, &ScmConstellationDialog::saveConstellation); + connect(ui->cancelBtn, &QPushButton::clicked, this, &ScmConstellationDialog::cancel); + + // LABELS TAB + connect(ui->enNameTE, &QTextEdit::textChanged, this, + [this]() + { + constellationEnglishName = ui->enNameTE->toPlainText(); + + QString newConstId = constellationEnglishName.toLower().replace(" ", "_"); + constellationPlaceholderId = newConstId; + ui->idTE->setPlaceholderText(newConstId); + }); + connect(ui->idTE, &QTextEdit::textChanged, this, [this]() { constellationId = ui->idTE->toPlainText(); }); + connect(ui->natNameTE, &QTextEdit::textChanged, this, + [this]() + { + constellationNativeName = ui->natNameTE->toPlainText(); + if (constellationNativeName->isEmpty()) + { + constellationNativeName = std::nullopt; + } + }); + connect(ui->pronounceTE, &QTextEdit::textChanged, this, + [this]() + { + constellationPronounce = ui->pronounceTE->toPlainText(); + if (constellationPronounce->isEmpty()) + { + constellationPronounce = std::nullopt; + } + }); + connect(ui->ipaTE, &QTextEdit::textChanged, this, + [this]() + { + constellationIPA = ui->ipaTE->toPlainText(); + if (constellationIPA->isEmpty()) + { + constellationIPA = std::nullopt; + } + }); +} + +void ScmConstellationDialog::togglePen(bool checked) +{ + if (checked) + { + ui->eraserBtn->setChecked(false); + activeTool = scm::DrawTools::Pen; + maker->setDrawTool(activeTool); + } + else + { + activeTool = scm::DrawTools::None; + maker->setDrawTool(activeTool); + } +} + +void ScmConstellationDialog::toggleEraser(bool checked) +{ + if (checked) + { + ui->penBtn->setChecked(false); + activeTool = scm::DrawTools::Eraser; + maker->setDrawTool(activeTool); + } + else + { + activeTool = scm::DrawTools::None; + maker->setDrawTool(activeTool); + } +} + +void ScmConstellationDialog::triggerUndo() +{ + maker->triggerDrawUndo(); + togglePen(true); +} + +bool ScmConstellationDialog::canConstellationBeSaved() const +{ + // shouldnt happen + if (maker->getCurrentSkyCulture() == nullptr) + { + ui->infoLbl->setText("WARNING: Could not save: Sky Culture is not set"); + return false; + } + + if (constellationEnglishName.isEmpty()) + { + ui->infoLbl->setText("WARNING: 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()) + { + ui->infoLbl->setText("WARNING: Could not save: Constellation ID is empty"); + return false; + } + + if (maker->getCurrentSkyCulture() != nullptr && + maker->getCurrentSkyCulture()->getConstellation(finalId) != nullptr) + { + ui->infoLbl->setText("WARNING: Could not save: Constellation with this ID already exists"); + return false; + } + + // Check if drawnStars is empty + auto drawnConstellation = maker->getScmDraw()->getCoordinates(); + if (drawnConstellation.empty()) + { + ui->infoLbl->setText("WARNING: Could not save: The constellation does not contain any drawings"); + return false; + } + + return true; +} + +void ScmConstellationDialog::cancel() +{ + 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 + + scm::ScmConstellation &constellation = culture->addConstellation(id, coordinates, stars); + + constellation.setEnglishName(constellationEnglishName); + constellation.setNativeName(constellationNativeName); + constellation.setPronounce(constellationPronounce); + constellation.setIPA(constellationIPA); + + maker->updateSkyCultureDialog(); + resetDialog(); + ScmConstellationDialog::close(); + } +} + +void ScmConstellationDialog::resetDialog() +{ + activeTool = scm::DrawTools::None; + ui->penBtn->setChecked(false); + ui->eraserBtn->setChecked(false); + maker->setDrawTool(activeTool); + + constellationId.clear(); + ui->idTE->clear(); + + constellationPlaceholderId.clear(); + ui->idTE->setPlaceholderText(""); + + constellationEnglishName.clear(); + ui->enNameTE->clear(); + + constellationNativeName = std::nullopt; + ui->natNameTE->clear(); + + constellationPronounce = std::nullopt; + ui->pronounceTE->clear(); + + constellationIPA = std::nullopt; + ui->ipaTE->clear(); + + // reset ScmDraw + maker->resetScmDraw(); +} diff --git a/plugins/SkyCultureMaker/src/gui/ScmConstellationDialog.hpp b/plugins/SkyCultureMaker/src/gui/ScmConstellationDialog.hpp new file mode 100644 index 0000000000000..0ae49c892620e --- /dev/null +++ b/plugins/SkyCultureMaker/src/gui/ScmConstellationDialog.hpp @@ -0,0 +1,70 @@ +#ifndef SCM_CONSTELLATION_DIALOG_HPP +#define SCM_CONSTELLATION_DIALOG_HPP + +#include "SkyCultureMaker.hpp" +#include "StelDialogSeparate.hpp" +#include "types/DrawTools.hpp" +#include +#include +#include + +class Ui_scmConstellationDialog; + +class ScmConstellationDialog : public StelDialogSeparate +{ +protected: + void createDialogContent() override; + +public: + ScmConstellationDialog(SkyCultureMaker *maker); + ~ScmConstellationDialog() override; + +public slots: + void retranslate() override; + void close() override; + +private slots: + void togglePen(bool checked); + void toggleEraser(bool checked); + void triggerUndo(); + +private: + Ui_scmConstellationDialog *ui = nullptr; + SkyCultureMaker *maker = nullptr; + scm::DrawTools activeTool = scm::DrawTools::None; + + /// 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; + /// Pronounciation of the constellation + std::optional constellationPronounce; + /// IPA representation of the constellation + std::optional constellationIPA; + + /** + * @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 Resets the constellation dialog data. + */ + void resetDialog(); +}; + +#endif // SCM_CONSTELLATION_DIALOG_HPP diff --git a/plugins/SkyCultureMaker/src/gui/ScmSkyCultureDialog.cpp b/plugins/SkyCultureMaker/src/gui/ScmSkyCultureDialog.cpp new file mode 100644 index 0000000000000..723bfadc3e026 --- /dev/null +++ b/plugins/SkyCultureMaker/src/gui/ScmSkyCultureDialog.cpp @@ -0,0 +1,266 @@ +#include "ScmSkyCultureDialog.hpp" +#include "ui_scmSkyCultureDialog.h" +#include + +ScmSkyCultureDialog::ScmSkyCultureDialog(SkyCultureMaker *maker) + : StelDialogSeparate("ScmSkyCultureDialog") + , maker(maker) +{ + assert(maker != nullptr); + ui = new Ui_scmSkyCultureDialog; +} + +ScmSkyCultureDialog::~ScmSkyCultureDialog() +{ + delete ui; +} + +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::retranslate() +{ + if (dialog) + { + ui->retranslateUi(dialog); + } +} + +void ScmSkyCultureDialog::close() +{ + maker->setSkyCultureDialogVisibility(false); +} + +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->skyCultureNameTE, &QTextEdit::textChanged, this, + [this]() + { + name = ui->skyCultureNameTE->toPlainText(); + if (name.isEmpty()) + { + ui->SaveSkyCultureBtn->setEnabled(false); + } + else + { + ui->SaveSkyCultureBtn->setEnabled(true); + } + setIdFromName(name); + }); + + ui->SaveSkyCultureBtn->setEnabled(false); + ui->RemoveConstellationBtn->setEnabled(false); + connect(ui->SaveSkyCultureBtn, &QPushButton::clicked, this, &ScmSkyCultureDialog::saveSkyCulture); + connect(ui->AddConstellationBtn, &QPushButton::clicked, this, &ScmSkyCultureDialog::constellationDialog); + connect(ui->RemoveConstellationBtn, &QPushButton::clicked, this, + &ScmSkyCultureDialog::removeSelectedConstellation); + connect(ui->RemoveConstellationBtn, &QPushButton::clicked, this, + &ScmSkyCultureDialog::removeSelectedConstellation); + connect(ui->constellationsList, &QListWidget::itemSelectionChanged, this, + &ScmSkyCultureDialog::updateRemoveConstellationButton); + + // License 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::saveSkyCulture() +{ + scm::Description desc = getDescriptionFromTextEdit(); + + // check if license is selected + int index = ui->licenseCB->currentIndex(); + if (index > 0 && index < ui->licenseCB->count()) + { + auto licenseType = ui->licenseCB->itemData(index).value(); + maker->getCurrentSkyCulture()->setLicense(licenseType); + } + else + { + ui->infoLbl->setText("ERROR: Please select a license for the sky culture."); + return; + } + + // check if description is complete + if (!desc.isComplete()) + { + ui->infoLbl->setText("ERROR: The sky culture description is not complete."); + return; + } + + // If valid, save the sky culture as markdown file + maker->setSkyCultureDescription(desc); + maker->saveSkyCultureDescription(); + + // only for debugging purposes + if (constellations != nullptr) + { + qDebug() << "[Constellations as JSON]:"; + for (const auto &constellation : *constellations) + { + QJsonObject obj = constellation.toJson(name); + QJsonDocument doc(obj); + qDebug().noquote() << doc.toJson(QJsonDocument::Compact); + } + } + bool success = maker->saveSkyCultureDescription(); + + if (success) + { + ui->infoLbl->setText("Sky culture saved successfully."); + } + else + { + ui->infoLbl->setText("ERROR: Could not save the sky culture."); + } +} + +void ScmSkyCultureDialog::saveLicense() +{ + if (maker->getCurrentSkyCulture() != nullptr) + { + // set license type + int index = ui->licenseCB->currentIndex(); + if (index >= 0 && index < ui->licenseCB->count()) + { + auto licenseType = ui->licenseCB->itemData(index).value(); + maker->getCurrentSkyCulture()->setLicense(licenseType); + } + // set authors + maker->getCurrentSkyCulture()->setAuthors(ui->authorsTE->toPlainText()); + // set classification type + index = ui->classificationCB->currentIndex(); + if (index >= 0 && index < ui->classificationCB->count()) + { + auto classificationType = ui->classificationCB->itemData(index).value(); + maker->getCurrentSkyCulture()->setClassificationType(classificationType); + } + } +} + +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::constellationDialog() +{ + maker->setConstellationDialogVisibility(true); +} + +void ScmSkyCultureDialog::setIdFromName(QString &name) +{ + QString id = name.toLower().replace(" ", "_"); + maker->getCurrentSkyCulture()->setId(id); +} + +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->skyCultureNameTE->toPlainText(); + desc.geoRegion = ui->geoRegionTE->toPlainText(); + desc.sky = ui->skyTE->toPlainText(); + desc.moonAndSun = ui->moonSunTE->toPlainText(); + desc.zodiac = ui->zodiacTE->toPlainText(); + desc.planets = ui->planetsTE->toPlainText(); + desc.constellations = ui->constellationsDescTE->toPlainText(); + desc.milkyWay = ui->milkyWayTE->toPlainText(); + desc.otherObjects = ui->otherObjectsTE->toPlainText(); + desc.about = ui->aboutTE->toPlainText(); + desc.authors = ui->authorsTE->toPlainText(); + desc.acknowledgements = ui->acknowledgementsTE->toPlainText(); + desc.references = ui->referencesTE->toPlainText(); + desc.classification = ui->classificationCB->currentData().value(); + + return desc; +} diff --git a/plugins/SkyCultureMaker/src/gui/ScmSkyCultureDialog.hpp b/plugins/SkyCultureMaker/src/gui/ScmSkyCultureDialog.hpp new file mode 100644 index 0000000000000..583b5bade4e63 --- /dev/null +++ b/plugins/SkyCultureMaker/src/gui/ScmSkyCultureDialog.hpp @@ -0,0 +1,82 @@ +#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 + +// debugging +#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); + +public slots: + void retranslate() override; + void close() override; + +private slots: + void saveSkyCulture(); + void constellationDialog(); + void removeSelectedConstellation(); + 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/ScmStartDialog.cpp b/plugins/SkyCultureMaker/src/gui/ScmStartDialog.cpp new file mode 100644 index 0000000000000..cba00305fb5b4 --- /dev/null +++ b/plugins/SkyCultureMaker/src/gui/ScmStartDialog.cpp @@ -0,0 +1,401 @@ +#include "ScmStartDialog.hpp" +#include "ui_scmStartDialog.h" +#include + +#ifdef SCM_CONVERTER_ENABLED_CPP +# include "SkyCultureConverter.hpp" +# include "StelFileMgr.hpp" +# include // For strcmp +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +#else // SCM_CONVERTER_ENABLED_CPP not defined +# include +# include +# include // For QT_VERSION_STR +#endif // SCM_CONVERTER_ENABLED_CPP + +#include +#include +#include +#include + +#ifdef SCM_CONVERTER_ENABLED_CPP +// Code snippert 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); + } + + // iterate entries and decompress each + while (ar_parse_entry(archive)) + { + QString name = QString::fromUtf8(ar_entry_get_name(archive)); + QString outPath = destinationPath + "/" + 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; + while (remaining > 0) + { + qint64 chunk = qMin(remaining, bufSize); + QByteArray buffer(chunk, 0); + if (!ar_entry_uncompress(archive, reinterpret_cast(buffer.data()), + chunk)) + break; + outFile.write(buffer); + remaining -= chunk; + } + outFile.close(); + } + } + ar_close_archive(archive); + ar_close(stream); + + return QString(); +} +#endif // SCM_CONVERTER_ENABLED_CPP + +ScmStartDialog::ScmStartDialog(SkyCultureMaker *maker) + : StelDialog("ScmStartDialog") + , maker(maker) +{ + assert(maker != nullptr); + ui = new Ui_scmStartDialog; +} + +ScmStartDialog::~ScmStartDialog() +{ + if (ui != nullptr) + { + delete ui; + } +} + +void ScmStartDialog::retranslate() +{ + if (dialog) + { + ui->retranslateUi(dialog); + } +} + +void ScmStartDialog::createDialogContent() +{ + ui->setupUi(dialog); + + // connect(&StelApp::getInstance(), SIGNAL(languageChanged()), this, SLOT(retranslate())); + connect(ui->titleBar, &TitleBar::closeClicked, this, &ScmStartDialog::closeDialog); + connect(ui->titleBar, SIGNAL(movedTo(QPoint)), this, SLOT(handleMovedTo(QPoint))); + + // 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) + +/* =============================================== SkyCultureConverter ============================================== */ +#ifdef SCM_CONVERTER_ENABLED_CPP + ui->scmStartConvertpushButton->setToolTip( + tr("Convert SkyCultures from the old (fib) format to the new (json) format")); + connect(ui->scmStartConvertpushButton, &QPushButton::clicked, this, + [this]() + { + // Create a new dialog for the conversion process + QDialog *converterDialog = new QDialog(nullptr); + converterDialog->setWindowTitle(tr("Convert Old Sky Culture Format")); + converterDialog->setAttribute(Qt::WA_DeleteOnClose); + + // Create widgets for the dialog + QVBoxLayout *layout = new QVBoxLayout(converterDialog); + QLabel *infoLabel = new QLabel(tr("Select an archive (.zip, .rar, .7z or .tar) with an old " + "format to convert to the new " + "format"), + converterDialog); + infoLabel->setWordWrap(true); + + QHBoxLayout *fileSelectLayout = new QHBoxLayout(); + QLineEdit *filePathLineEdit = new QLineEdit(converterDialog); + filePathLineEdit->setReadOnly(true); + filePathLineEdit->setPlaceholderText(tr("Select a file…")); + QPushButton *browseButton = new QPushButton(tr("Browse…"), converterDialog); + fileSelectLayout->addWidget(filePathLineEdit); + fileSelectLayout->addWidget(browseButton); + + QPushButton *convertButton = new QPushButton(tr("Convert"), converterDialog); + QLabel *convertResultLabel = new QLabel(converterDialog); + convertResultLabel->setWordWrap(true); + + QPushButton *closeButton = new QPushButton(tr("Close"), converterDialog); + + layout->addWidget(infoLabel); + layout->addLayout(fileSelectLayout); + layout->addWidget(convertButton); + layout->addWidget(convertResultLabel); + layout->addStretch(); + layout->addWidget(closeButton); + + // Connect signals for the new dialog's widgets + connect(browseButton, &QPushButton::clicked, this, + [filePathLineEdit]() + { + const QString file = + QFileDialog::getOpenFileName(nullptr, tr("Select an archive"), + QDir::homePath(), + tr("Archives (*.zip *.rar *.7z *.tar)")); + if (!file.isEmpty()) + { + filePathLineEdit->setText(file); + } + }); + + connect(closeButton, &QPushButton::clicked, converterDialog, &QDialog::close); + + connect(convertButton, &QPushButton::clicked, this, + [this, filePathLineEdit, convertResultLabel, convertButton]() + { + const QString path = filePathLineEdit->text(); + if (path.isEmpty()) + { + convertResultLabel->setText(tr("Please select a file.")); + return; + } + + qDebug() << "Selected file:" << path; + + // Create a temporary directory for extraction + QString baseName = QFileInfo(path).fileName(); // e.g. "foo.zip" + int dotPos = baseName.indexOf('.'); + QString stem = (dotPos == -1) + ? baseName + : baseName.left( + dotPos); // Extract the part before the first dot + + const QString tempDir = QDir::tempPath() + "/skycultures/" + stem; + QDir().mkpath(tempDir); + QDir tempFolder(tempDir); + + // Destination is where the converted files will be saved temporarily + const QString tempDestDir = QDir::tempPath() + "/skycultures/results/" + stem; + QDir tempDestFolder(tempDestDir); + + convertButton->setEnabled(false); + + // Run conversion in a background thread + QFuture future = QtConcurrent::run( + [path, tempDir, tempFolder, tempDestDir, tempDestFolder, + stem]() mutable -> QString + { + // Check if the file is a valid archive + QMimeDatabase db; + QMimeType mime = db.mimeTypeForFile(path, + QMimeDatabase::MatchContent); + + 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())) + { + return QStringLiteral( + "Please select a valid archive file " + "(zip, tar, rar or 7z)"); + } + + try + { + // Extract the archive to the temporary directory + qDebug() << "Extracting archive:" << path << "to" + << tempDir; + + QString error = extractArchive(path, tempDir); + if (!error.isEmpty()) + { + return error; + } + + qDebug() << "Archive extracted to:" << tempDir; + } + catch (const std::exception &e) + { + return QString("Error extracting archive: %1") + .arg(e.what()); + } + + QStringList extracted_files = tempFolder.entryList( + QDir::AllEntries | QDir::NoDotAndDotDot); + + qDebug() << "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 + QString source; + if (extracted_files.contains("info.ini")) + source = tempDir; + else if (extracted_files.length() == 1) + source = tempDir + "/" + extracted_files.first(); + else + return "Invalid archive structure. Expected 'info.ini' " + "or a " + "single " + "subfolder."; + + qDebug() << "Source for conversion:" << source; + qDebug() << "Destination for conversion:" << tempDestDir; + + SkyCultureConverter::ReturnValue result; + + try + { + result = SkyCultureConverter::convert(source, + tempDestDir); + } + catch (const std::exception &e) + { + return QString("Error during conversion: %1") + .arg(e.what()); + } + + switch (result) + { + case SkyCultureConverter::ReturnValue::CONVERT_SUCCESS: break; + 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."; + break; + default: return "Unknown error."; break; + } + + // move the converted files into skycultures folder inside programm directory + QString appResourceBasePath = StelFileMgr::getInstallationDir(); + + QString mainSkyCulturesPath = QDir(appResourceBasePath) + .filePath("skycultures"); + + QString targetPath = QDir(mainSkyCulturesPath).filePath(stem); + + qDebug() << "Target path for moved files:" << targetPath; + + QDir targetDir(targetPath); // QDir object for checking existence + if (targetDir.exists()) + { + // Target folder already exists. Do not copy/move. + qDebug() << "Target folder" << targetPath + << "already exists. No move operation " + "performed."; + return QString("Target folder already exists: %1") + .arg(targetPath); + } + else if (QDir().rename(tempDestFolder.path(), targetPath)) + { + qDebug() << "Successfully moved contents of" + << tempDestFolder.path() << "to" << targetPath; + return QString("Conversion completed successfully. " + "Files moved " + "to: %1") + .arg(targetPath); + } + else + { + qWarning() << "Failed to move" << tempDestFolder.path() + << "to" << targetPath; + return QString("Failed to move files to: %1") + .arg(targetPath); + } + }); + + // Watcher to re-enable the button & report result on UI thread + auto *watcher = new QFutureWatcher(this); + connect(watcher, &QFutureWatcher::finished, this, + [convertResultLabel, convertButton, watcher, tempFolder, + tempDestFolder]() mutable + { + QString resultText = watcher->future().result(); + convertResultLabel->setText(resultText); + tempFolder.removeRecursively(); + tempDestFolder.removeRecursively(); + convertButton->setEnabled(true); + watcher->deleteLater(); + }); + watcher->setFuture(future); + + qDebug() << "Conversion started."; + }); + + converterDialog->exec(); // Show the dialog modally + }); +#else // SCM_CONVERTER_ENABLED_CPP is not defined + // Converter is disabled, so disable the button + ui->scmStartConvertpushButton->setEnabled(false); + ui->scmStartConvertpushButton->setToolTip( + tr("Converter is only available from Qt6.5 onwards, currently build with version %1") + .arg(QT_VERSION_STR)); +#endif // SCM_CONVERTER_ENABLED_CPP +/* ================================================================================================================== */ +} + +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() +{ + StelDialog::close(); + maker->setIsScmEnabled(false); // Disable the Sky Culture Maker +} diff --git a/plugins/SkyCultureMaker/src/gui/ScmStartDialog.hpp b/plugins/SkyCultureMaker/src/gui/ScmStartDialog.hpp new file mode 100644 index 0000000000000..10dc91f877733 --- /dev/null +++ b/plugins/SkyCultureMaker/src/gui/ScmStartDialog.hpp @@ -0,0 +1,36 @@ +#ifndef SCMSTARTDIALOG_HPP +#define SCMSTARTDIALOG_HPP + +#include "SkyCultureMaker.hpp" +#include "StelDialog.hpp" +#include + +#ifdef SCM_CONVERTER_ENABLED_CPP +# include "SkyCultureConverter.hpp" +# include "unarr.h" +#endif + +class Ui_scmStartDialog; + +class ScmStartDialog : public StelDialog +{ +protected: + void createDialogContent() override; + +public: + ScmStartDialog(SkyCultureMaker *maker); + ~ScmStartDialog() override; + +public slots: + void retranslate() override; + +private slots: + void startScmCreationProcess(); + void closeDialog(); + +private: + Ui_scmStartDialog *ui = nullptr; + SkyCultureMaker *maker = nullptr; +}; + +#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..edef78660eef6 --- /dev/null +++ b/plugins/SkyCultureMaker/src/gui/scmConstellationDialog.ui @@ -0,0 +1,410 @@ + + + scmConstellationDialog + + + + 0 + 0 + 360 + 454 + + + + + 360 + 454 + + + + + 1000000 + 999999 + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + SCM: Constellation Editor + + + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 16777215 + 400 + + + + 1 + + + + 32 + 32 + + + + + Drawing + + + + + 20 + 71 + 311 + 271 + + + + + + + 20 + 20 + 311 + 41 + + + + + + + Undo + + + + + + + Eraser + + + true + + + + + + + Pen + + + true + + + false + + + + + + + + + Constellation Name + + + + 15 + + + 10 + + + 15 + + + 10 + + + + + + 20 + + + + Please name the Constellation + + + + + + + English name + + + + + + + + 16777215 + 32 + + + + + + + + ID (Do not include 'CON' or the sky culture name) + + + + + + + + 16777215 + 32 + + + + + + + + Native name (optional) + + + + + + + + 16777215 + 32 + + + + + + + + Pronounciation (optional, European glyphs or Pinyin) + + + + + + + + 16777215 + 32 + + + + + + + + IPA (optional) + + + + + + + + 16777215 + 32 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Artwork + + + + + 30 + 20 + 300 + 300 + + + + + + + 0 + 330 + 351 + 31 + + + + TextLabel + + + + + + 210 + 330 + 119 + 32 + + + + Upload + + + + + + + + + 10 + + + 20 + + + 20 + + + + + Save + + + + + + + Cancel + + + + + + + + + 20 + + + 5 + + + 20 + + + + + + 75 + true + + + + + + + true + + + + + + + + + Qt::Vertical + + + + 20 + 10 + + + + + + + + + + + + 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..1378c5f8cb0e5 --- /dev/null +++ b/plugins/SkyCultureMaker/src/gui/scmSkyCultureDialog.ui @@ -0,0 +1,565 @@ + + + scmSkyCultureDialog + + + + 0 + 0 + 400 + 340 + + + + + 0 + 0 + + + + + 1000000 + 999999 + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Sky Culture Maker + + + + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 16777215 + 800 + + + + 0 + + + + 32 + 32 + + + + + Overview + + + + + + + 14 + + + + Name of the Sky Culture + + + + + + + + 100 + 32 + + + + + 16777215 + 32 + + + + + 14 + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'.AppleSystemUIFont'; font-size:14pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> + + + + + + + + 14 + + + + Constellations: + + + + + + + + 100 + 64 + + + + + QListWidget, QListWidget::viewport { + margin: 0px; + padding: 0px; + border: none; + outline: none; + } + QListWidget::item { + margin: 0px; + padding: 4px; + border: none; + outline: none; + } + + + + + + + + + + + 150 + 0 + + + + + 13 + + + + Add Constellation + + + + + + + + 150 + 0 + + + + + 13 + + + + Remove Constellation + + + + + + + + + + + + 0 + 0 + + + + + 14 + + + + License of the Sky Culture + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 13 + + + + + 0 + 0 + + + + + + + + + 130 + 0 + + + + + 13 + + + + Save Sky Culture + + + + + + + + + + Boundaries + + + + + Common Names + + + + + Description + + + + + + true + + + + + 10 + + + 10 + + + 10 + + + 10 + + + 10 + + + + + + + Culture Description: + + + + + + + + + + + + + Geo-Region: + + + + + + + + + + + + Sky (what can the User see here): + + + + + + + + + + + + Moon & Sun: + + + + + + + + + + + + Zodiac/ Lunar System: + + + + + + + + + + + + Planets: + + + + + + + + + + + + Constellations: + + + + + + + + + + + + Milky Way: + + + + + + + + + + + + Other Celestial Objects: + + + + + + + + + + + + About: + + + + + + + + + + + + Authors: + + + + + + + + + + + + Acknowledgements: + + + + + + + + + + + + References: + + + + + + + + + + + + Classification of the Sky Culture + + + + + + + + 13 + + + + + + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 75 + true + + + + + + + Qt::AlignCenter + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + 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..a62b7c2134883 --- /dev/null +++ b/plugins/SkyCultureMaker/src/gui/scmStartDialog.ui @@ -0,0 +1,166 @@ + + + scmStartDialog + + + + 0 + 0 + 400 + 200 + + + + + + + + 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 + + + + + + + + 16 + false + + + + Sky Culture Maker + + + Qt::AlignCenter + + + + + + + + + Create + + + + + + + Edit + + + + + + + Convert + + + + + + + Cancel + + + + + + + + + + + + + + + + + + TitleBar + QFrame +
Dialog.hpp
+ 1 +
+
+ + +
diff --git a/plugins/SkyCultureMaker/src/types/Classification.hpp b/plugins/SkyCultureMaker/src/types/Classification.hpp new file mode 100644 index 0000000000000..057d72ab99cea --- /dev/null +++ b/plugins/SkyCultureMaker/src/types/Classification.hpp @@ -0,0 +1,111 @@ +#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, + INCOMPLETE, + PERSONAL, + TRADITIONAL, + ETHNOGRAPHIC, + HISTORICAL, + SINGLE, + COMPARATIVE +}; + +inline QString classificationTypeToString(ClassificationType type) +{ + switch (type) + { + case ClassificationType::NONE: return "None"; + case ClassificationType::INCOMPLETE: return "Incomplete"; + 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::INCOMPLETE, + Classification("Incomplete", "This is a personally developed sky culture which is not founded in " + "published historical or ethnological research. Stellarium may include it " + "when it is “pretty enough” without really approving its contents."), + }, + { + 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..e395cb148b003 --- /dev/null +++ b/plugins/SkyCultureMaker/src/types/CoordinateLine.hpp @@ -0,0 +1,64 @@ +/** + * @file CoordinateLine.hpp + * @author vgerlach, lgrumbach + * @brief Type describing a line between two coordinates. + * @version 0.1 + * @date 2025-06-02 + */ +#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..f4d45d6d02fdb --- /dev/null +++ b/plugins/SkyCultureMaker/src/types/Description.hpp @@ -0,0 +1,51 @@ +#ifndef SCM_DESCRIPTION_HPP +#define SCM_DESCRIPTION_HPP + +#include "Classification.hpp" +#include +#include +#include +#include + +namespace scm +{ + +/** + * @brief The Description struct represents a sky culture description. + */ +struct Description +{ + QString name; + QString geoRegion; + QString sky; + QString moonAndSun; + QString zodiac; + QString planets; + QString constellations; + QString milkyWay; + QString otherObjects; + QString about; + QString authors; + QString acknowledgements; + QString references; + 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() && !geoRegion.trimmed().isEmpty() && !sky.trimmed().isEmpty() && + !moonAndSun.trimmed().isEmpty() && !zodiac.trimmed().isEmpty() && !planets.trimmed().isEmpty() && + !constellations.trimmed().isEmpty() && !milkyWay.trimmed().isEmpty() && + !otherObjects.trimmed().isEmpty() && !about.trimmed().isEmpty() && + !authors.trimmed().isEmpty() && !acknowledgements.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/DrawTools.hpp b/plugins/SkyCultureMaker/src/types/DrawTools.hpp new file mode 100644 index 0000000000000..e243ef0cf8687 --- /dev/null +++ b/plugins/SkyCultureMaker/src/types/DrawTools.hpp @@ -0,0 +1,27 @@ +/** + * @file StarLine.hpp + * @author vgerlach, lgrumbach + * @brief Type describing the possible states of the draw tool. + * @version 0.1 + * @date 2025-06-02 + */ +#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..58cc1411f4a47 --- /dev/null +++ b/plugins/SkyCultureMaker/src/types/Drawing.hpp @@ -0,0 +1,33 @@ +/** + * @file Drawing.hpp + * @author vgerlach, lgrumbach + * @brief Type describing a the possible state during drawing a constellation. + * @version 0.1 + * @date 2025-06-02 + */ +#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/License.hpp b/plugins/SkyCultureMaker/src/types/License.hpp new file mode 100644 index 0000000000000..1826d21d37c0f --- /dev/null +++ b/plugins/SkyCultureMaker/src/types/License.hpp @@ -0,0 +1,49 @@ +#ifndef SCM_LICENSE_HPP +#define SCM_LICENSE_HPP + +#include +#include +#include +#include + +namespace scm +{ + +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 +}; + +const std::map LICENSES = { + {LicenseType::NONE, License("None", "Please select a valid license.")}, + {LicenseType::CC0, License("CC0", + "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", + "This work is licensed under the Creative Commons Attribution 4.0 License; Reusage allowed" + " - please mention author(s).")} +}; + +} // 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..2643394bf1c49 --- /dev/null +++ b/plugins/SkyCultureMaker/src/types/Lines.hpp @@ -0,0 +1,28 @@ +/** + * @file Lines.hpp + * @author vgerlach, lgrumbach + * @brief Type describing a lines in a constellation. + * @version 0.1 + * @date 2025-06-02 + */ +#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 too the coordinates. + std::vector stars; +}; +} // 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..556f9168c0afd --- /dev/null +++ b/plugins/SkyCultureMaker/src/types/StarLine.hpp @@ -0,0 +1,104 @@ +/** + * @file StarLine.hpp + * @author vgerlach, lgrumbach + * @brief Type describing a line between two stars. + * @version 0.1 + * @date 2025-06-02 + */ +#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 star ID from the name. + * + * @param starId The ID of the star, which may contain identifiers like "HIP" or "Gaia DR3". + */ + static QString getStarIdNumber(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); + } + return "-1"; + } + + /** + * @brief Converts the StartLine to a JSON array. + * + * @return QJsonArray The JSON representation of the start line. + */ + QJsonArray toJson() const + { + QJsonArray json; + + if (start.has_value()) + { + QString number = getStarIdNumber(start.value()); + + if (start.value().contains("HIP")) + { + // HIP are required as int + json.append(number.toInt()); + } + else + { + // Gaia is required as string + json.append(number); + } + } + else + { + json.append("-1"); + } + + if (end.has_value()) + { + QString number = getStarIdNumber(end.value()); + + if (end.value().contains("HIP")) + { + // HIP are required as int + json.append(number.toInt()); + } + else + { + // Gaia is required as string + json.append(number); + } + } + else + { + json.append("-1"); + } + + return json; + } +}; +} // namespace scm + +#endif diff --git a/plugins/SkyCultureMaker/src/types/StarPoint.hpp b/plugins/SkyCultureMaker/src/types/StarPoint.hpp new file mode 100644 index 0000000000000..0deee6581ad41 --- /dev/null +++ b/plugins/SkyCultureMaker/src/types/StarPoint.hpp @@ -0,0 +1,28 @@ +/** + * @file StarPoint.hpp + * @author vgerlach, lgrumbach + * @brief Type describing a single star point. + * @version 0.1 + * @date 2025-06-02 + */ +#ifndef SCM_TYPES_STAR_POINT_HPP +#define SCM_TYPES_STAR_POINT_HPP + +#include "VecMath.hpp" +#include +#include + +namespace scm +{ +//! The point of a single star with coordinates. +struct StarPoint +{ + //! The coordinate of a single point. + Vec3d coordinate; + + //! The optional star at that coordinate. + std::optional star; +}; +} // 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