diff options
-rw-r--r-- | .github/workflows/build.yml | 250 | ||||
-rw-r--r-- | CMakeLists.txt | 61 | ||||
-rw-r--r-- | launcher/Application.cpp | 62 | ||||
-rw-r--r-- | launcher/Application.h | 1 | ||||
-rw-r--r-- | launcher/CMakeLists.txt | 2 | ||||
-rwxr-xr-x | launcher/Launcher.in | 2 | ||||
-rw-r--r-- | launcher/modplatform/flame/FlameAPI.h | 11 | ||||
-rw-r--r-- | launcher/modplatform/flame/FlameModIndex.cpp | 6 | ||||
-rw-r--r-- | launcher/modplatform/modrinth/ModrinthAPI.h | 3 | ||||
-rw-r--r-- | launcher/ui/MainWindow.cpp | 465 | ||||
-rw-r--r-- | launcher/ui/MainWindow.h | 8 | ||||
-rw-r--r-- | launcher/ui/pages/global/LauncherPage.cpp | 10 | ||||
-rw-r--r-- | launcher/ui/pages/global/LauncherPage.ui | 37 | ||||
m--------- | libraries/libnbtplusplus | 0 | ||||
m--------- | libraries/quazip | 0 | ||||
-rw-r--r-- | program_info/CMakeLists.txt | 2 | ||||
-rw-r--r-- | program_info/portable.txt | 4 |
17 files changed, 594 insertions, 330 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0123c99a..196b6d79 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,14 +17,6 @@ jobs: - os: ubuntu-20.04 - - os: ubuntu-20.04 - portable: true - - - os: ubuntu-20.04 - qt_version: 5.15.2 - qt_host: linux - app_image: true - - os: windows-2022 name: "Windows-i686" msystem: mingw32 @@ -33,29 +25,22 @@ jobs: name: "Windows-x86_64" msystem: mingw64 - - os: windows-2022 - name: "Windows-i686-portable" - msystem: mingw32 - portable: true - - - os: windows-2022 - name: "Windows-x86_64-portable" - msystem: mingw64 - portable: true - - os: macos-11 - qt_version: 5.12.12 - qt_host: mac - macosx_deployment_target: 10.12 + macosx_deployment_target: 10.13 runs-on: ${{ matrix.os }} env: MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }} INSTALL_DIR: "install" + INSTALL_PORTABLE_DIR: "install-portable" + INSTALL_APPIMAGE_DIR: "install-appdir" BUILD_DIR: "build" steps: + ## + # PREPARE + ## - name: Checkout uses: actions/checkout@v3 with: @@ -87,26 +72,14 @@ jobs: distribution: 'temurin' java-version: '17' - - name: Cache Qt - if: runner.os != 'Windows' - id: cache-qt - uses: actions/cache@v3 - with: - path: "${{ github.workspace }}/Qt/" - key: ${{ runner.os }}-${{ matrix.qt_version }}-${{ matrix.qt_arch }}-qt_cache + - name: Install Qt (macOS) + if: runner.os == 'macOS' + run: | + brew update + brew install qt@5 - - name: Install Qt - if: runner.os != 'Linux' && runner.os != 'Windows' || matrix.app_image == true - uses: jurplel/install-qt-action@v2 - with: - version: ${{ matrix.qt_version }} - host: ${{ matrix.qt_host }} - arch: ${{ matrix.qt_arch }} - cached: ${{ steps.cache-qt.outputs.cache-hit }} - dir: "${{ github.workspace }}/Qt/" - - - name: Install System Qt on Linux - if: runner.os == 'Linux' && matrix.app_image != true + - name: Install Qt (Linux) + if: runner.os == 'Linux' run: | sudo apt-get -y update sudo apt-get -y install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5 @@ -115,174 +88,169 @@ jobs: if: runner.os != 'Windows' uses: urkle/action-get-ninja@v1 - - name: Download linuxdeploy family for AppImage on Linux - if: matrix.app_image == true + - name: Prepare AppImage (Linux) + if: runner.os == 'Linux' run: | wget "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage" wget "https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-x86_64.AppImage" wget "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage" - - name: Download JREs for AppImage on Linux - if: matrix.app_image == true - shell: bash - run: | ${{ github.workspace }}/.github/scripts/prepare_JREs.sh - - name: Configure CMake - if: runner.os != 'Linux' && runner.os != 'Windows' - run: | - cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -G Ninja + ## + # CONFIGURE + ## - - name: Configure CMake on Windows - if: runner.os == 'Windows' && matrix.portable != true - shell: msys2 {0} + - name: Configure CMake (macOS) + if: runner.os == 'macOS' run: | - cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_PORTABLE=OFF -G Ninja + cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DQt5_DIR=/usr/local/opt/qt@5 -DCMAKE_PREFIX_PATH=/usr/local/opt/qt@5 -G Ninja - - name: Configure CMake on Windows portable - if: runner.os == 'Windows' && matrix.portable == true + - name: Configure CMake (Windows) + if: runner.os == 'Windows' shell: msys2 {0} run: | - cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -G Ninja + cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -G Ninja - - name: Configure CMake on Linux - if: runner.os == 'Linux' && matrix.portable != true + - name: Configure CMake (Linux) + if: runner.os == 'Linux' run: | - cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DLauncher_PORTABLE=OFF -DENABLE_LTO=ON -G Ninja + cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -G Ninja - - name: Configure CMake on Linux Portable - if: runner.os == 'Linux' && matrix.portable == true - run: | - cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -G Ninja + ## + # BUILD + ## - name: Build if: runner.os != 'Windows' run: | cmake --build ${{ env.BUILD_DIR }} - - name: Build on Windows + - name: Build (Windows) if: runner.os == 'Windows' shell: msys2 {0} run: | cmake --build ${{ env.BUILD_DIR }} - - name: Install - if: runner.os != 'Linux' && runner.os != 'Windows' + ## + # PACKAGE BUILDS + ## + + - name: Package (macOS) + if: runner.os == 'macOS' run: | cmake --install ${{ env.BUILD_DIR }} - - name: Install on Windows + cd ${{ env.INSTALL_DIR }} + chmod +x "PolyMC.app/Contents/MacOS/polymc" + tar -czf ../PolyMC.tar.gz * + + - name: Package (Windows) if: runner.os == 'Windows' shell: msys2 {0} run: | cmake --install ${{ env.BUILD_DIR }} - - name: Install on Linux - if: runner.os == 'Linux' && matrix.portable != true + cd ${{ env.INSTALL_DIR }} + if [ "${{ matrix.msystem }}" == "mingw32" ]; then + cp /mingw32/bin/libcrypto-1_1.dll /mingw32/bin/libssl-1_1.dll ./ + elif [ "${{ matrix.msystem }}" == "mingw64" ]; then + cp /mingw64/bin/libcrypto-1_1-x64.dll /mingw64/bin/libssl-1_1-x64.dll ./ + fi + + - name: Package (Windows, portable) + if: runner.os == 'Windows' + shell: msys2 {0} run: | - DESTDIR=${{ env.INSTALL_DIR }} cmake --install ${{ env.BUILD_DIR }} + cp -r ${{ env.INSTALL_DIR }} ${{ env.INSTALL_PORTABLE_DIR }} # cmake install on Windows is slow, let's just copy instead + cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable - - name: Install on Linux portable - if: runner.os == 'Linux' && matrix.portable == true + - name: Package (Linux) + if: runner.os == 'Linux' run: | - cmake --install ${{ env.BUILD_DIR }} + cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_DIR }} + + cd ${{ env.INSTALL_DIR }} + tar -czf ../PolyMC.tar.gz * + + - name: Package (Linux, portable) + if: runner.os == 'Linux' + run: | + cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} + cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_PORTABLE_DIR }} --component portable + + cd ${{ env.INSTALL_PORTABLE_DIR }} + tar -czf ../PolyMC-portable.tar.gz * - - name: Bundle AppImage - if: matrix.app_image == true + - name: Package AppImage (Linux) + if: runner.os == 'Linux' shell: bash run: | + cmake --install ${{ env.BUILD_DIR }} --prefix ${{ env.INSTALL_APPIMAGE_DIR }}/usr + export OUTPUT="PolyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage" chmod +x linuxdeploy-*.AppImage - mkdir -p ${{ env.INSTALL_DIR }}/usr/lib/jvm/java-{8,17}-openjdk + mkdir -p ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-{8,17}-openjdk - cp -r ${{ github.workspace }}/JREs/jre8/* ${{ env.INSTALL_DIR }}/usr/lib/jvm/java-8-openjdk + cp -r ${{ github.workspace }}/JREs/jre8/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-8-openjdk - cp -r ${{ github.workspace }}/JREs/jre17/* ${{ env.INSTALL_DIR }}/usr/lib/jvm/java-17-openjdk + cp -r ${{ github.workspace }}/JREs/jre17/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-17-openjdk - export LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_DIR }}/usr/lib" - LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_DIR }}/usr/lib/jvm/java-8-openjdk/lib/amd64/server" - LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_DIR }}/usr/lib/jvm/java-8-openjdk/lib/amd64" - LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_DIR }}/usr/lib/jvm/java-17-openjdk/lib/server" - LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_DIR }}/usr/lib/jvm/java-17-openjdk/lib" + LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib" + LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-8-openjdk/lib/amd64/server" + LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-8-openjdk/lib/amd64" + LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-17-openjdk/lib/server" + LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-17-openjdk/lib" + export LD_LIBRARY_PATH - ./linuxdeploy-x86_64.AppImage --appdir ${{ env.INSTALL_DIR }} --output appimage --plugin qt -i ${{ env.INSTALL_DIR }}/usr/share/icons/hicolor/scalable/apps/org.polymc.PolyMC.svg + ./linuxdeploy-x86_64.AppImage --appdir ${{ env.INSTALL_APPIMAGE_DIR }} --output appimage --plugin qt -i ${{ env.INSTALL_APPIMAGE_DIR }}/usr/share/icons/hicolor/scalable/apps/org.polymc.PolyMC.svg - - name: Run macdeployqt - if: runner.os == 'macOS' - run: | - cd ${{ env.INSTALL_DIR }} - macdeployqt "PolyMC.app" -executable="PolyMC.app/Contents/MacOS/polymc" -always-overwrite - - - name: chmod binary on macOS - if: runner.os == 'macOS' - run: | - chmod +x "${{ github.workspace }}/${{ env.INSTALL_DIR }}/PolyMC.app/Contents/MacOS/polymc" + ## + # UPLOAD BUILDS + ## - - name: tar bundle on macOS + - name: Upload binary tarball (macOS) if: runner.os == 'macOS' - run: | - cd ${{ env.INSTALL_DIR }} - tar -czf ../PolyMC.tar.gz * + uses: actions/upload-artifact@v3 + with: + name: PolyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }} + path: PolyMC.tar.gz - - name: tar on Linux - if: runner.os == 'Linux' && matrix.app_image != true && matrix.portable != true - run: | - cd ${{ env.INSTALL_DIR }} - tar -czf ../PolyMC.tar.gz * + - name: Upload binary zip (Windows) + if: runner.os == 'Windows' + uses: actions/upload-artifact@v3 + with: + name: PolyMC-${{ matrix.name }}-${{ env.VERSION }}-${{ inputs.build_type }} + path: ${{ env.INSTALL_DIR }}/** - - name: tar on Linux portable - if: runner.os == 'Linux' && matrix.app_image != true && matrix.portable == true - run: | - cd ${{ env.INSTALL_DIR }} - tar -czf ../PolyMC-portable.tar.gz * + - name: Upload binary zip (Windows, portable) + if: runner.os == 'Windows' + uses: actions/upload-artifact@v3 + with: + name: PolyMC-${{ matrix.name }}-Portable-${{ env.VERSION }}-${{ inputs.build_type }} + path: ${{ env.INSTALL_PORTABLE_DIR }}/** - - name: Upload Linux tar.gz - if: runner.os == 'Linux' && matrix.app_image != true && matrix.portable != true + - name: Upload binary tarball (Linux) + if: runner.os == 'Linux' uses: actions/upload-artifact@v3 with: name: PolyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }} path: PolyMC.tar.gz - - name: Upload Linux Portable tar.gz - if: runner.os == 'Linux' && matrix.app_image != true && matrix.portable == true + - name: Upload binary tarball (Linux, portable) + if: runner.os == 'Linux' uses: actions/upload-artifact@v3 with: name: PolyMC-${{ runner.os }}-Portable-${{ env.VERSION }}-${{ inputs.build_type }} path: PolyMC-portable.tar.gz - - name: Upload AppImage for Linux - if: matrix.app_image == true + - name: Upload AppImage (Linux) + if: runner.os == 'Linux' uses: actions/upload-artifact@v3 with: name: PolyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage path: PolyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }}-x86_64.AppImage - - name: Copy OpenSSL libs on Windows x86 - if: runner.os == 'Windows' && matrix.msystem == 'mingw32' - shell: msys2 {0} - run: | - cp /mingw32/bin/libcrypto-1_1.dll ${{ env.INSTALL_DIR }}/ - cp /mingw32/bin/libssl-1_1.dll ${{ env.INSTALL_DIR }}/ - - name: Copy OpenSSL libs on Windows x86_64 - if: runner.os == 'Windows' && matrix.msystem == 'mingw64' - shell: msys2 {0} - run: | - cp /mingw64/bin/libcrypto-1_1-x64.dll ${{ env.INSTALL_DIR }}/ - cp /mingw64/bin/libssl-1_1-x64.dll ${{ env.INSTALL_DIR }}/ - - - name: Upload package for Windows - if: runner.os == 'Windows' - uses: actions/upload-artifact@v3 - with: - name: PolyMC-${{ matrix.name }}-${{ env.VERSION }}-${{ inputs.build_type }} - path: ${{ env.INSTALL_DIR }}/** - - - name: Upload package for macOS - if: runner.os == 'macOS' - uses: actions/upload-artifact@v3 - with: - name: PolyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }} - path: PolyMC.tar.gz diff --git a/CMakeLists.txt b/CMakeLists.txt index ee511a76..d726d9c8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -107,8 +107,7 @@ set(Launcher_DISCORD_URL "https://discord.gg/Z52pwxWCHP" CACHE STRING "URL for t set(Launcher_SUBREDDIT_URL "https://www.reddit.com/r/PolyMCLauncher/" CACHE STRING "URL for the subreddit.") # Builds -# TODO: Launcher_FORCE_BUNDLED_LIBS should be off in the future, but as of QuaZip 1.2, we can't do that yet. -set(Launcher_FORCE_BUNDLED_LIBS ON CACHE BOOL "Prevent using system libraries, if they are available as submodules") +set(Launcher_FORCE_BUNDLED_LIBS OFF CACHE BOOL "Prevent using system libraries, if they are available as submodules") set(Launcher_QT_VERSION_MAJOR "5" CACHE STRING "Major Qt version to build against") @@ -135,7 +134,7 @@ if(Launcher_QT_VERSION_MAJOR EQUAL 5) find_package(Qt5 REQUIRED COMPONENTS Core Widgets Concurrent Network Test Xml) if(NOT Launcher_FORCE_BUNDLED_LIBS) - find_package(QuaZip-Qt5 REQUIRED) + find_package(QuaZip-Qt5 1.3) endif() if (NOT QuaZip-Qt5_FOUND) set(QUAZIP_QT_MAJOR_VERSION ${QT_VERSION_MAJOR} CACHE STRING "Qt version to use (4, 5 or 6), defaults to ${QT_VERSION_MAJOR}" FORCE) @@ -165,12 +164,9 @@ add_subdirectory(program_info) ####################################### Install layout ####################################### -# Install the build results according to platform -set(Launcher_PORTABLE 1 CACHE BOOL "The type of installation (Portable or System)") - -if (Launcher_PORTABLE) - # launcher/Application.cpp will use this value - set(Launcher_APP_BINARY_DEFS "-DLAUNCHER_PORTABLE") +if(NOT (UNIX AND APPLE)) + # Install "portable.txt" if selected component is "portable" + install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_Portable_File}" DESTINATION "." COMPONENT portable EXCLUDE_FROM_ALL) endif() if(UNIX AND APPLE) @@ -180,8 +176,6 @@ if(UNIX AND APPLE) set(RESOURCES_DEST_DIR "${Launcher_Name}.app/Contents/Resources") set(JARS_DEST_DIR "${Launcher_Name}.app/Contents/MacOS/jars") - set(BUNDLE_DEST_DIR ".") - # Apps to bundle set(APPS "\${CMAKE_INSTALL_PREFIX}/${Launcher_Name}.app") @@ -206,30 +200,12 @@ if(UNIX AND APPLE) elseif(UNIX) set(BINARY_DEST_DIR "bin") - if(Launcher_PORTABLE) - set(LIBRARY_DEST_DIR "bin") - set(BUNDLE_DEST_DIR ".") - set(JARS_DEST_DIR "bin/jars") - - # Install basic runner script - configure_file(launcher/Launcher.in "${CMAKE_CURRENT_BINARY_DIR}/LauncherScript" @ONLY) - install(PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/LauncherScript" DESTINATION ${BUNDLE_DEST_DIR} RENAME ${Launcher_Name}) - else() - set(LIBRARY_DEST_DIR "lib${LIB_SUFFIX}") - set(JARS_DEST_DIR "share/jars") - set(LAUNCHER_DESKTOP_DEST_DIR "share/applications" CACHE STRING "Path to the desktop file directory") - set(LAUNCHER_METAINFO_DEST_DIR "share/metainfo" CACHE STRING "Path to the metainfo directory") - set(LAUNCHER_ICON_DEST_DIR "share/icons/hicolor/scalable/apps" CACHE STRING "Path to the scalable icon directory") - set(LAUNCHER_MAN_DEST_DIR "share/man/man6" CACHE STRING "Path to the man page directory") - - # jars path is determined on runtime, relative to "Application root path", generally /usr for Launcher_PORTABLE=0 - set(Launcher_APP_BINARY_DEFS "-DLAUNCHER_JARS_LOCATION=${JARS_DEST_DIR}") - - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_Desktop} DESTINATION ${LAUNCHER_DESKTOP_DEST_DIR}) - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_MetaInfo} DESTINATION ${LAUNCHER_METAINFO_DEST_DIR}) - install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_SVG} DESTINATION ${LAUNCHER_ICON_DEST_DIR}) - install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_ManPage} DESTINATION ${LAUNCHER_MAN_DEST_DIR} RENAME "${Launcher_APP_BINARY_NAME}.6") - endif() + set(LIBRARY_DEST_DIR "lib${LIB_SUFFIX}") + set(JARS_DEST_DIR "share/jars") + set(LAUNCHER_DESKTOP_DEST_DIR "share/applications" CACHE STRING "Path to the desktop file directory") + set(LAUNCHER_METAINFO_DEST_DIR "share/metainfo" CACHE STRING "Path to the metainfo directory") + set(LAUNCHER_ICON_DEST_DIR "share/icons/hicolor/scalable/apps" CACHE STRING "Path to the scalable icon directory") + set(LAUNCHER_MAN_DEST_DIR "share/man/man6" CACHE STRING "Path to the man page directory") # install as bundle with no dependencies included set(INSTALL_BUNDLE "nodeps") @@ -237,11 +213,22 @@ elseif(UNIX) # Set RPATH SET(Launcher_BINARY_RPATH "$ORIGIN/") + # jars path is determined on runtime, relative to "Application root path", generally /usr or the root of the portable bundle + set(Launcher_APP_BINARY_DEFS "-DLAUNCHER_JARS_LOCATION=${JARS_DEST_DIR}") + + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_Desktop} DESTINATION ${LAUNCHER_DESKTOP_DEST_DIR}) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${Launcher_MetaInfo} DESTINATION ${LAUNCHER_METAINFO_DEST_DIR}) + install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_SVG} DESTINATION ${LAUNCHER_ICON_DEST_DIR}) + install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${Launcher_ManPage} DESTINATION ${LAUNCHER_MAN_DEST_DIR} RENAME "${Launcher_APP_BINARY_NAME}.6") + + # Install basic runner script if component "portable" is selected + configure_file(launcher/Launcher.in "${CMAKE_CURRENT_BINARY_DIR}/LauncherScript" @ONLY) + install(PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/LauncherScript" DESTINATION "." RENAME ${Launcher_Name} COMPONENT portable EXCLUDE_FROM_ALL) + elseif(WIN32) set(BINARY_DEST_DIR ".") set(LIBRARY_DEST_DIR ".") set(PLUGIN_DEST_DIR ".") - set(BUNDLE_DEST_DIR ".") set(RESOURCES_DEST_DIR ".") set(JARS_DEST_DIR "jars") @@ -277,6 +264,8 @@ if (FORCE_BUNDLED_QUAZIP) set(BUILD_SHARED_LIBS 0) # link statically to avoid conflicts. set(QUAZIP_INSTALL 0) add_subdirectory(libraries/quazip) # zip manipulation library +else() + message(STATUS "Using system QuaZip") endif() add_subdirectory(libraries/rainbow) # Qt extension for colors add_subdirectory(libraries/iconfix) # fork of Qt's QIcon loader diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 9cca534c..8bd434f0 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -80,6 +80,7 @@ #include <QStringList> #include <QDebug> #include <QStyleFactory> +#include <QWindow> #include "InstanceList.h" @@ -316,6 +317,26 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) QString origcwdPath = QDir::currentPath(); QString binPath = applicationDirPath(); + + { + // Root path is used for updates and portable data +#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) + QDir foo(FS::PathCombine(binPath, "..")); // typically portable-root or /usr + m_rootPath = foo.absolutePath(); +#elif defined(Q_OS_WIN32) + m_rootPath = binPath; +#elif defined(Q_OS_MAC) + QDir foo(FS::PathCombine(binPath, "../..")); + m_rootPath = foo.absolutePath(); + // on macOS, touch the root to force Finder to reload the .app metadata (and fix any icon change issues) + FS::updateTimestamp(m_rootPath); +#endif + +#ifdef LAUNCHER_JARS_LOCATION + m_jarsPath = TOSTRING(LAUNCHER_JARS_LOCATION); +#endif + } + QString adjustedBy; QString dataPath; // change folder @@ -324,15 +345,14 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) { // the dir param. it makes multimc data path point to whatever the user specified // on command line - adjustedBy += "Command line " + dirParam; + adjustedBy = "Command line"; dataPath = dirParam; } else { -#if !defined(LAUNCHER_PORTABLE) || defined(Q_OS_MAC) QDir foo(FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), "..")); dataPath = foo.absolutePath(); - adjustedBy += dataPath; + adjustedBy = "Persistent data path"; #ifdef Q_OS_LINUX // TODO: this should be removed in a future version @@ -340,12 +360,15 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) QDir bar(FS::PathCombine(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation), "polymc")); if (bar.exists()) { dataPath = bar.absolutePath(); - adjustedBy += "Legacy data path " + dataPath; + adjustedBy = "Legacy data path"; } #endif -#else - dataPath = applicationDirPath(); - adjustedBy += "Fallback to binary path " + dataPath; + +#ifndef Q_OS_MACOS + if (QFile::exists(FS::PathCombine(m_rootPath, "portable.txt"))) { + dataPath = m_rootPath; + adjustedBy = "Portable data path"; + } #endif } @@ -535,24 +558,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) qDebug() << "<> Log initialized."; } - // Set up paths { - // Root path is used for updates. -#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) - QDir foo(FS::PathCombine(binPath, "..")); - m_rootPath = foo.absolutePath(); -#elif defined(Q_OS_WIN32) - m_rootPath = binPath; -#elif defined(Q_OS_MAC) - QDir foo(FS::PathCombine(binPath, "../..")); - m_rootPath = foo.absolutePath(); - // on macOS, touch the root to force Finder to reload the .app metadata (and fix any icon change issues) - FS::updateTimestamp(m_rootPath); -#endif - -#ifdef LAUNCHER_JARS_LOCATION - m_jarsPath = TOSTRING(LAUNCHER_JARS_LOCATION); -#endif qDebug() << BuildConfig.LAUNCHER_DISPLAYNAME << ", (c) 2013-2021 " << BuildConfig.LAUNCHER_COPYRIGHT; qDebug() << "Version : " << BuildConfig.printableVersionString(); @@ -613,6 +619,8 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) // Remembered state m_settings->registerSetting("LastUsedGroupForNewInstance", QString()); + m_settings->registerSetting("MenuBarInsteadOfToolBar", false); + QString defaultMonospace; int defaultSize = 11; #ifdef Q_OS_WIN32 @@ -1268,6 +1276,12 @@ bool Application::kill(InstancePtr instance) return true; } +void Application::closeCurrentWindow() +{ + if (focusWindow()) + focusWindow()->close(); +} + void Application::addRunningInstance() { m_runningInstances ++; diff --git a/launcher/Application.h b/launcher/Application.h index 54d9ba5f..172321c0 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -189,6 +189,7 @@ public slots: MinecraftAccountPtr accountToUse = nullptr ); bool kill(InstancePtr instance); + void closeCurrentWindow(); private slots: void on_windowClose(); diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 0ab398e8..6ed86726 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -991,7 +991,7 @@ if(DEFINED Launcher_APP_BINARY_DEFS) endif() install(TARGETS ${Launcher_Name} - BUNDLE DESTINATION ${BUNDLE_DEST_DIR} COMPONENT Runtime + BUNDLE DESTINATION "." COMPONENT Runtime LIBRARY DESTINATION ${LIBRARY_DEST_DIR} COMPONENT Runtime RUNTIME DESTINATION ${BINARY_DEST_DIR} COMPONENT Runtime ) diff --git a/launcher/Launcher.in b/launcher/Launcher.in index 5e5e2c2b..528e360e 100755 --- a/launcher/Launcher.in +++ b/launcher/Launcher.in @@ -21,7 +21,7 @@ echo "Launcher Dir: ${LAUNCHER_DIR}" # Set up env - filter out input LD_ variables but pass them in under different names export GAME_LIBRARY_PATH=${GAME_LIBRARY_PATH-${LD_LIBRARY_PATH}} export GAME_PRELOAD=${GAME_PRELOAD-${LD_PRELOAD}} -export LD_LIBRARY_PATH="${LAUNCHER_DIR}/bin":$LAUNCHER_LIBRARY_PATH +export LD_LIBRARY_PATH="${LAUNCHER_DIR}/lib@LIB_SUFFIX@":$LAUNCHER_LIBRARY_PATH export LD_PRELOAD=$LAUNCHER_PRELOAD export QT_PLUGIN_PATH="${LAUNCHER_DIR}/plugins" export QT_FONTPATH="${LAUNCHER_DIR}/fonts" diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index 9bcc357e..ce02df65 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -23,7 +23,7 @@ class FlameAPI : public NetworkModAPI { .arg(args.offset) .arg(args.search) .arg(args.sorting) - .arg(args.mod_loader) + .arg(getMappedModLoader(args.mod_loader)) .arg(gameVersionStr); }; @@ -31,4 +31,13 @@ class FlameAPI : public NetworkModAPI { { return QString("https://addons-ecs.forgesvc.net/api/v2/addon/%1/files").arg(args.addonId); }; + + public: + static auto getMappedModLoader(const ModLoaderType type) -> const ModLoaderType + { + // TODO: remove this once Quilt drops official Fabric support + if (type == Quilt) // NOTE: Most if not all Fabric mods should work *currently* + return Fabric; + return type; + } }; diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp index e86b64dd..c7b86b5c 100644 --- a/launcher/modplatform/flame/FlameModIndex.cpp +++ b/launcher/modplatform/flame/FlameModIndex.cpp @@ -3,6 +3,7 @@ #include "Json.h" #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" +#include "modplatform/flame/FlameAPI.h" #include "net/NetJob.h" void FlameMod::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) @@ -43,8 +44,9 @@ void FlameMod::loadIndexedPackVersions(ModPlatform::IndexedPack& pack, BaseInstance* inst) { QVector<ModPlatform::IndexedVersion> unsortedVersions; - bool hasFabric = !(dynamic_cast<MinecraftInstance*>(inst))->getPackProfile()->getComponentVersion("net.fabricmc.fabric-loader").isEmpty(); - QString mcVersion = (dynamic_cast<MinecraftInstance*>(inst))->getPackProfile()->getComponentVersion("net.minecraft"); + auto profile = (dynamic_cast<MinecraftInstance*>(inst))->getPackProfile(); + bool hasFabric = FlameAPI::getMappedModLoader(profile->getModLoader()) == ModAPI::Fabric; + QString mcVersion = profile->getComponentVersion("net.minecraft"); for (auto versionIter : arr) { auto obj = versionIter.toObject(); diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h index 6604d772..84dd7d03 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.h +++ b/launcher/modplatform/modrinth/ModrinthAPI.h @@ -54,6 +54,9 @@ class ModrinthAPI : public NetworkModAPI { { if (type == Unspecified) return "fabric, forge, quilt"; + // TODO: remove this once Quilt drops official Fabric support + if (type == Quilt) // NOTE: Most if not all Fabric mods should work *currently* + return "fabric, quilt"; return ModAPI::getModLoaderString(type); } diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 47c469e9..2b219aff 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -38,6 +38,7 @@ #include <QtWidgets/QToolBar> #include <QtWidgets/QWidget> #include <QtWidgets/QMenu> +#include <QtWidgets/QMenuBar> #include <QtWidgets/QMessageBox> #include <QtWidgets/QInputDialog> #include <QtWidgets/QLabel> @@ -243,6 +244,16 @@ public: QHBoxLayout *horizontalLayout = nullptr; QStatusBar *statusBar = nullptr; + QMenuBar *menuBar = nullptr; + QMenu *fileMenu; + QMenu *viewMenu; + QMenu *profileMenu; + + TranslatedAction actionCloseWindow; + + TranslatedAction actionOpenWiki; + TranslatedAction actionNewsMenuBar; + TranslatedToolbar mainToolBar; TranslatedToolbar instanceToolBar; TranslatedToolbar newsToolBar; @@ -253,12 +264,12 @@ public: { if(m_kill) { - actionLaunchInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Kill")); + actionLaunchInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "&Kill")); actionLaunchInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Kill the running instance")); } else { - actionLaunchInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Launch")); + actionLaunchInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "&Launch")); actionLaunchInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Launch the selected instance.")); } actionLaunchInstance.retranslate(); @@ -269,28 +280,16 @@ public: updateLaunchAction(); } - void createMainToolbar(QMainWindow *MainWindow) + void createMainToolbarActions(QMainWindow *MainWindow) { - mainToolBar = TranslatedToolbar(MainWindow); - mainToolBar->setObjectName(QStringLiteral("mainToolBar")); - mainToolBar->setMovable(true); - mainToolBar->setAllowedAreas(Qt::TopToolBarArea | Qt::BottomToolBarArea); - mainToolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); - mainToolBar->setFloatable(false); - mainToolBar.setWindowTitleId(QT_TRANSLATE_NOOP("MainWindow", "Main Toolbar")); - actionAddInstance = TranslatedAction(MainWindow); actionAddInstance->setObjectName(QStringLiteral("actionAddInstance")); actionAddInstance->setIcon(APPLICATION->getThemedIcon("new")); - actionAddInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Add Instance")); + actionAddInstance->setIconVisibleInMenu(false); + actionAddInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Add Instanc&e...")); actionAddInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Add a new instance.")); + actionAddInstance->setShortcut(QKeySequence::New); all_actions.append(&actionAddInstance); - mainToolBar->addAction(actionAddInstance); - - mainToolBar->addSeparator(); - - foldersMenu = new QMenu(MainWindow); - foldersMenu->setToolTipsVisible(true); actionViewInstanceFolder = TranslatedAction(MainWindow); actionViewInstanceFolder->setObjectName(QStringLiteral("actionViewInstanceFolder")); @@ -298,7 +297,6 @@ public: actionViewInstanceFolder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "View Instance Folder")); actionViewInstanceFolder.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the instance folder in a file browser.")); all_actions.append(&actionViewInstanceFolder); - foldersMenu->addAction(actionViewInstanceFolder); actionViewCentralModsFolder = TranslatedAction(MainWindow); actionViewCentralModsFolder->setObjectName(QStringLiteral("actionViewCentralModsFolder")); @@ -306,10 +304,16 @@ public: actionViewCentralModsFolder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "View Central Mods Folder")); actionViewCentralModsFolder.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the central mods folder in a file browser.")); all_actions.append(&actionViewCentralModsFolder); + + foldersMenu = new QMenu(MainWindow); + foldersMenu->setTitle(tr("F&olders")); + foldersMenu->setToolTipsVisible(true); + + foldersMenu->addAction(actionViewInstanceFolder); foldersMenu->addAction(actionViewCentralModsFolder); foldersMenuButton = TranslatedToolButton(MainWindow); - foldersMenuButton.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Folders")); + foldersMenuButton.setTextId(QT_TRANSLATE_NOOP("MainWindow", "F&olders")); foldersMenuButton.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open one of the folders shared between instances.")); foldersMenuButton->setMenu(foldersMenu); foldersMenuButton->setPopupMode(QToolButton::InstantPopup); @@ -317,120 +321,259 @@ public: foldersMenuButton->setIcon(APPLICATION->getThemedIcon("viewfolder")); foldersMenuButton->setFocusPolicy(Qt::NoFocus); all_toolbuttons.append(&foldersMenuButton); - QWidgetAction* foldersButtonAction = new QWidgetAction(MainWindow); - foldersButtonAction->setDefaultWidget(foldersMenuButton); - mainToolBar->addAction(foldersButtonAction); actionSettings = TranslatedAction(MainWindow); actionSettings->setObjectName(QStringLiteral("actionSettings")); actionSettings->setIcon(APPLICATION->getThemedIcon("settings")); actionSettings->setMenuRole(QAction::PreferencesRole); - actionSettings.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Settings")); + actionSettings.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Settings...")); actionSettings.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Change settings.")); + actionSettings->setShortcut(QKeySequence::Preferences); all_actions.append(&actionSettings); - mainToolBar->addAction(actionSettings); - - helpMenu = new QMenu(MainWindow); - helpMenu->setToolTipsVisible(true); if (!BuildConfig.BUG_TRACKER_URL.isEmpty()) { actionReportBug = TranslatedAction(MainWindow); actionReportBug->setObjectName(QStringLiteral("actionReportBug")); actionReportBug->setIcon(APPLICATION->getThemedIcon("bug")); - actionReportBug.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Report a Bug")); + actionReportBug.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Report a &Bug...")); actionReportBug.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the bug tracker to report a bug with %1.")); all_actions.append(&actionReportBug); - helpMenu->addAction(actionReportBug); } - + if(!BuildConfig.MATRIX_URL.isEmpty()) { actionMATRIX = TranslatedAction(MainWindow); actionMATRIX->setObjectName(QStringLiteral("actionMATRIX")); actionMATRIX->setIcon(APPLICATION->getThemedIcon("matrix")); - actionMATRIX.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Matrix space")); + actionMATRIX.setTextId(QT_TRANSLATE_NOOP("MainWindow", "&Matrix Space")); actionMATRIX.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open %1 Matrix space")); all_actions.append(&actionMATRIX); - helpMenu->addAction(actionMATRIX); } if (!BuildConfig.DISCORD_URL.isEmpty()) { actionDISCORD = TranslatedAction(MainWindow); actionDISCORD->setObjectName(QStringLiteral("actionDISCORD")); actionDISCORD->setIcon(APPLICATION->getThemedIcon("discord")); - actionDISCORD.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Discord guild")); + actionDISCORD.setTextId(QT_TRANSLATE_NOOP("MainWindow", "&Discord Guild")); actionDISCORD.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open %1 Discord guild.")); all_actions.append(&actionDISCORD); - helpMenu->addAction(actionDISCORD); } if (!BuildConfig.SUBREDDIT_URL.isEmpty()) { actionREDDIT = TranslatedAction(MainWindow); actionREDDIT->setObjectName(QStringLiteral("actionREDDIT")); actionREDDIT->setIcon(APPLICATION->getThemedIcon("reddit-alien")); - actionREDDIT.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Subreddit")); + actionREDDIT.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Sub&reddit")); actionREDDIT.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open %1 subreddit.")); all_actions.append(&actionREDDIT); - helpMenu->addAction(actionREDDIT); } actionAbout = TranslatedAction(MainWindow); actionAbout->setObjectName(QStringLiteral("actionAbout")); actionAbout->setIcon(APPLICATION->getThemedIcon("about")); actionAbout->setMenuRole(QAction::AboutRole); - actionAbout.setTextId(QT_TRANSLATE_NOOP("MainWindow", "About %1")); + actionAbout.setTextId(QT_TRANSLATE_NOOP("MainWindow", "&About %1")); actionAbout.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "View information about %1.")); all_actions.append(&actionAbout); - helpMenu->addAction(actionAbout); - - helpMenuButton = TranslatedToolButton(MainWindow); - helpMenuButton.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Help")); - helpMenuButton.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Get help with %1 or Minecraft.")); - helpMenuButton->setMenu(helpMenu); - helpMenuButton->setPopupMode(QToolButton::InstantPopup); - helpMenuButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); - helpMenuButton->setIcon(APPLICATION->getThemedIcon("help")); - helpMenuButton->setFocusPolicy(Qt::NoFocus); - all_toolbuttons.append(&helpMenuButton); - QWidgetAction* helpButtonAction = new QWidgetAction(MainWindow); - helpButtonAction->setDefaultWidget(helpMenuButton); - mainToolBar->addAction(helpButtonAction); if(BuildConfig.UPDATER_ENABLED) { actionCheckUpdate = TranslatedAction(MainWindow); actionCheckUpdate->setObjectName(QStringLiteral("actionCheckUpdate")); actionCheckUpdate->setIcon(APPLICATION->getThemedIcon("checkupdate")); - actionCheckUpdate.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Update")); + actionCheckUpdate.setTextId(QT_TRANSLATE_NOOP("MainWindow", "&Update...")); actionCheckUpdate.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Check for new updates for %1.")); + actionCheckUpdate->setMenuRole(QAction::ApplicationSpecificRole); all_actions.append(&actionCheckUpdate); - mainToolBar->addAction(actionCheckUpdate); } - mainToolBar->addSeparator(); - actionCAT = TranslatedAction(MainWindow); actionCAT->setObjectName(QStringLiteral("actionCAT")); actionCAT->setCheckable(true); actionCAT->setIcon(APPLICATION->getThemedIcon("cat")); - actionCAT.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Meow")); + actionCAT.setTextId(QT_TRANSLATE_NOOP("MainWindow", "&Meow")); actionCAT.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "It's a fluffy kitty :3")); actionCAT->setPriority(QAction::LowPriority); all_actions.append(&actionCAT); - mainToolBar->addAction(actionCAT); // profile menu and its actions actionManageAccounts = TranslatedAction(MainWindow); actionManageAccounts->setObjectName(QStringLiteral("actionManageAccounts")); - actionManageAccounts.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Manage Accounts")); + actionManageAccounts.setTextId(QT_TRANSLATE_NOOP("MainWindow", "&Manage Accounts...")); // FIXME: no tooltip! actionManageAccounts->setCheckable(false); actionManageAccounts->setIcon(APPLICATION->getThemedIcon("accounts")); all_actions.append(&actionManageAccounts); + } + + void createMainToolbar(QMainWindow *MainWindow) + { + mainToolBar = TranslatedToolbar(MainWindow); + mainToolBar->setVisible(menuBar->isNativeMenuBar() || !APPLICATION->settings()->get("MenuBarInsteadOfToolBar").toBool()); + mainToolBar->setObjectName(QStringLiteral("mainToolBar")); + mainToolBar->setMovable(true); + mainToolBar->setAllowedAreas(Qt::TopToolBarArea | Qt::BottomToolBarArea); + mainToolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + mainToolBar->setFloatable(false); + mainToolBar.setWindowTitleId(QT_TRANSLATE_NOOP("MainWindow", "Main Toolbar")); + + mainToolBar->addAction(actionAddInstance); + + mainToolBar->addSeparator(); + + QWidgetAction* foldersButtonAction = new QWidgetAction(MainWindow); + foldersButtonAction->setDefaultWidget(foldersMenuButton); + mainToolBar->addAction(foldersButtonAction); + + mainToolBar->addAction(actionSettings); + + helpMenu = new QMenu(MainWindow); + helpMenu->setToolTipsVisible(true); + + if (!BuildConfig.BUG_TRACKER_URL.isEmpty()) { + helpMenu->addAction(actionReportBug); + } + + if(!BuildConfig.MATRIX_URL.isEmpty()) { + helpMenu->addAction(actionMATRIX); + } + + if (!BuildConfig.DISCORD_URL.isEmpty()) { + helpMenu->addAction(actionDISCORD); + } + + if (!BuildConfig.SUBREDDIT_URL.isEmpty()) { + helpMenu->addAction(actionREDDIT); + } + + helpMenu->addAction(actionAbout); + + helpMenuButton = TranslatedToolButton(MainWindow); + helpMenuButton.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Help")); + helpMenuButton.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Get help with %1 or Minecraft.")); + helpMenuButton->setMenu(helpMenu); + helpMenuButton->setPopupMode(QToolButton::InstantPopup); + helpMenuButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + helpMenuButton->setIcon(APPLICATION->getThemedIcon("help")); + helpMenuButton->setFocusPolicy(Qt::NoFocus); + all_toolbuttons.append(&helpMenuButton); + QWidgetAction* helpButtonAction = new QWidgetAction(MainWindow); + helpButtonAction->setDefaultWidget(helpMenuButton); + mainToolBar->addAction(helpButtonAction); + + if(BuildConfig.UPDATER_ENABLED) + { + mainToolBar->addAction(actionCheckUpdate); + } + + mainToolBar->addSeparator(); + + mainToolBar->addAction(actionCAT); all_toolbars.append(&mainToolBar); MainWindow->addToolBar(Qt::TopToolBarArea, mainToolBar); } + void createMenuBar(QMainWindow *MainWindow) + { + menuBar = new QMenuBar(MainWindow); + menuBar->setVisible(APPLICATION->settings()->get("MenuBarInsteadOfToolBar").toBool()); + + fileMenu = menuBar->addMenu(tr("&File")); + fileMenu->addAction(actionAddInstance); + fileMenu->addAction(actionLaunchInstance); + fileMenu->addAction(actionLaunchInstanceOffline); + fileMenu->addAction(actionCloseWindow); + fileMenu->addSeparator(); + fileMenu->addAction(actionEditInstance); + fileMenu->addAction(actionEditInstNotes); + fileMenu->addAction(actionMods); + fileMenu->addAction(actionWorlds); + fileMenu->addAction(actionScreenshots); + fileMenu->addAction(actionChangeInstGroup); + fileMenu->addSeparator(); + fileMenu->addAction(actionViewSelectedMCFolder); + fileMenu->addAction(actionConfig_Folder); + fileMenu->addAction(actionViewSelectedInstFolder); + fileMenu->addSeparator(); + fileMenu->addAction(actionExportInstance); + fileMenu->addAction(actionDeleteInstance); + fileMenu->addAction(actionCopyInstance); + fileMenu->addSeparator(); + fileMenu->addAction(actionSettings); + + viewMenu = menuBar->addMenu(tr("&View")); + viewMenu->addAction(actionCAT); + viewMenu->addSeparator(); + + menuBar->addMenu(foldersMenu); + + profileMenu = menuBar->addMenu(tr("&Profiles")); + profileMenu->addAction(actionManageAccounts); + + helpMenu = menuBar->addMenu(tr("&Help")); + helpMenu->addAction(actionAbout); + helpMenu->addAction(actionOpenWiki); + helpMenu->addAction(actionNewsMenuBar); + helpMenu->addSeparator(); + if (!BuildConfig.BUG_TRACKER_URL.isEmpty()) + helpMenu->addAction(actionReportBug); + if (!BuildConfig.MATRIX_URL.isEmpty()) + helpMenu->addAction(actionMATRIX); + if (!BuildConfig.DISCORD_URL.isEmpty()) + helpMenu->addAction(actionDISCORD); + if (!BuildConfig.SUBREDDIT_URL.isEmpty()) + helpMenu->addAction(actionREDDIT); + helpMenu->addSeparator(); + if(BuildConfig.UPDATER_ENABLED) + helpMenu->addAction(actionCheckUpdate); + + MainWindow->setMenuBar(menuBar); + } + + void createMenuActions(MainWindow *MainWindow) + { + actionCloseWindow = TranslatedAction(MainWindow); + actionCloseWindow->setObjectName(QStringLiteral("actionCloseWindow")); + actionCloseWindow.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Close &Window")); + actionCloseWindow.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Close the current window")); + actionCloseWindow->setShortcut(QKeySequence::Close); + connect(actionCloseWindow, &QAction::triggered, APPLICATION, &Application::closeCurrentWindow); + all_actions.append(&actionCloseWindow); + + actionOpenWiki = TranslatedAction(MainWindow); + actionOpenWiki->setObjectName(QStringLiteral("actionOpenWiki")); + actionOpenWiki.setTextId(QT_TRANSLATE_NOOP("MainWindow", "%1 He&lp")); + actionOpenWiki.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the %1 wiki")); + connect(actionOpenWiki, &QAction::triggered, MainWindow, &MainWindow::on_actionOpenWiki_triggered); + all_actions.append(&actionOpenWiki); + + actionNewsMenuBar = TranslatedAction(MainWindow); + actionNewsMenuBar->setObjectName(QStringLiteral("actionNewsMenuBar")); + actionNewsMenuBar.setTextId(QT_TRANSLATE_NOOP("MainWindow", "%1 &News")); + actionNewsMenuBar.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the %1 wiki")); + connect(actionNewsMenuBar, &QAction::triggered, MainWindow, &MainWindow::on_actionMoreNews_triggered); + all_actions.append(&actionNewsMenuBar); + } + + // "Instance actions" are actions that require an instance to be selected (i.e. "new instance" is not here) + void setInstanceActionsEnabled(bool enabled) + { + actionLaunchInstance->setEnabled(enabled); + actionLaunchInstanceOffline->setEnabled(enabled); + actionEditInstance->setEnabled(enabled); + actionEditInstNotes->setEnabled(enabled); + actionMods->setEnabled(enabled); + actionWorlds->setEnabled(enabled); + actionScreenshots->setEnabled(enabled); + actionChangeInstGroup->setEnabled(enabled); + actionViewSelectedMCFolder->setEnabled(enabled); + actionConfig_Folder->setEnabled(enabled); + actionViewSelectedInstFolder->setEnabled(enabled); + actionExportInstance->setEnabled(enabled); + actionDeleteInstance->setEnabled(enabled); + actionCopyInstance->setEnabled(enabled); + } + void createStatusBar(QMainWindow *MainWindow) { statusBar = new QStatusBar(MainWindow); @@ -461,18 +604,8 @@ public: MainWindow->addToolBar(Qt::BottomToolBarArea, newsToolBar); } - void createInstanceToolbar(QMainWindow *MainWindow) + void createInstanceActions(QMainWindow *MainWindow) { - instanceToolBar = TranslatedToolbar(MainWindow); - instanceToolBar->setObjectName(QStringLiteral("instanceToolBar")); - // disabled until we have an instance selected - instanceToolBar->setEnabled(false); - instanceToolBar->setMovable(true); - instanceToolBar->setAllowedAreas(Qt::LeftToolBarArea | Qt::RightToolBarArea); - instanceToolBar->setToolButtonStyle(Qt::ToolButtonTextOnly); - instanceToolBar->setFloatable(false); - instanceToolBar->setWindowTitle(QT_TRANSLATE_NOOP("MainWindow", "Instance Toolbar")); - // NOTE: not added to toolbar, but used for instance context menu (right click) actionChangeInstIcon = TranslatedAction(MainWindow); actionChangeInstIcon->setObjectName(QStringLiteral("actionChangeInstIcon")); @@ -487,7 +620,6 @@ public: changeIconButton->setIcon(APPLICATION->getThemedIcon("news")); changeIconButton->setToolTip(actionChangeInstIcon->toolTip()); changeIconButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - instanceToolBar->addWidget(changeIconButton); // NOTE: not added to toolbar, but used for instance context menu (right click) actionRenameInstance = TranslatedAction(MainWindow); @@ -501,74 +633,63 @@ public: renameButton->setObjectName(QStringLiteral("renameButton")); renameButton->setToolTip(actionRenameInstance->toolTip()); renameButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - instanceToolBar->addWidget(renameButton); - - instanceToolBar->addSeparator(); actionLaunchInstance = TranslatedAction(MainWindow); actionLaunchInstance->setObjectName(QStringLiteral("actionLaunchInstance")); + actionLaunchInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "&Launch")); + actionLaunchInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Launch the selected instance.")); all_actions.append(&actionLaunchInstance); - instanceToolBar->addAction(actionLaunchInstance); actionLaunchInstanceOffline = TranslatedAction(MainWindow); actionLaunchInstanceOffline->setObjectName(QStringLiteral("actionLaunchInstanceOffline")); - actionLaunchInstanceOffline.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Launch Offline")); + actionLaunchInstanceOffline.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Launch &Offline")); actionLaunchInstanceOffline.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Launch the selected instance in offline mode.")); all_actions.append(&actionLaunchInstanceOffline); - instanceToolBar->addAction(actionLaunchInstanceOffline); - - instanceToolBar->addSeparator(); actionEditInstance = TranslatedAction(MainWindow); actionEditInstance->setObjectName(QStringLiteral("actionEditInstance")); - actionEditInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Edit Instance")); + actionEditInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Edit Inst&ance...")); actionEditInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Change the instance settings, mods and versions.")); + actionEditInstance->setShortcut(QKeySequence(tr("Ctrl+I"))); all_actions.append(&actionEditInstance); - instanceToolBar->addAction(actionEditInstance); actionEditInstNotes = TranslatedAction(MainWindow); actionEditInstNotes->setObjectName(QStringLiteral("actionEditInstNotes")); - actionEditInstNotes.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Edit Notes")); + actionEditInstNotes.setTextId(QT_TRANSLATE_NOOP("MainWindow", "E&dit Notes...")); actionEditInstNotes.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Edit the notes for the selected instance.")); all_actions.append(&actionEditInstNotes); - instanceToolBar->addAction(actionEditInstNotes); actionMods = TranslatedAction(MainWindow); actionMods->setObjectName(QStringLiteral("actionMods")); - actionMods.setTextId(QT_TRANSLATE_NOOP("MainWindow", "View Mods")); + actionMods.setTextId(QT_TRANSLATE_NOOP("MainWindow", "View &Mods")); actionMods.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "View the mods of this instance.")); all_actions.append(&actionMods); - instanceToolBar->addAction(actionMods); actionWorlds = TranslatedAction(MainWindow); actionWorlds->setObjectName(QStringLiteral("actionWorlds")); - actionWorlds.setTextId(QT_TRANSLATE_NOOP("MainWindow", "View Worlds")); + actionWorlds.setTextId(QT_TRANSLATE_NOOP("MainWindow", "&View Worlds")); actionWorlds.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "View the worlds of this instance.")); all_actions.append(&actionWorlds); - instanceToolBar->addAction(actionWorlds); actionScreenshots = TranslatedAction(MainWindow); actionScreenshots->setObjectName(QStringLiteral("actionScreenshots")); - actionScreenshots.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Manage Screenshots")); + actionScreenshots.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Manage &Screenshots")); actionScreenshots.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "View and upload screenshots for this instance.")); all_actions.append(&actionScreenshots); - instanceToolBar->addAction(actionScreenshots); actionChangeInstGroup = TranslatedAction(MainWindow); actionChangeInstGroup->setObjectName(QStringLiteral("actionChangeInstGroup")); - actionChangeInstGroup.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Change Group")); + actionChangeInstGroup.setTextId(QT_TRANSLATE_NOOP("MainWindow", "&Change Group...")); actionChangeInstGroup.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Change the selected instance's group.")); + actionChangeInstGroup->setShortcut(QKeySequence(tr("Ctrl+G"))); all_actions.append(&actionChangeInstGroup); - instanceToolBar->addAction(actionChangeInstGroup); - - instanceToolBar->addSeparator(); actionViewSelectedMCFolder = TranslatedAction(MainWindow); actionViewSelectedMCFolder->setObjectName(QStringLiteral("actionViewSelectedMCFolder")); - actionViewSelectedMCFolder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Minecraft Folder")); + actionViewSelectedMCFolder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Minec&raft Folder")); actionViewSelectedMCFolder.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the selected instance's Minecraft folder in a file browser.")); + actionViewSelectedMCFolder->setShortcut(QKeySequence(tr("Ctrl+M"))); all_actions.append(&actionViewSelectedMCFolder); - instanceToolBar->addAction(actionViewSelectedMCFolder); /* actionViewSelectedModsFolder = TranslatedAction(MainWindow); @@ -576,52 +697,97 @@ public: actionViewSelectedModsFolder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Mods Folder")); actionViewSelectedModsFolder.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the selected instance's mods folder in a file browser.")); all_actions.append(&actionViewSelectedModsFolder); - instanceToolBar->addAction(actionViewSelectedModsFolder); */ actionConfig_Folder = TranslatedAction(MainWindow); actionConfig_Folder->setObjectName(QStringLiteral("actionConfig_Folder")); - actionConfig_Folder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Config Folder")); + actionConfig_Folder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Confi&g Folder")); actionConfig_Folder.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the instance's config folder.")); + // Qt on macOS is "smart" and will eat up this action when added to the menu bar because it starts with the word "config"... + // Docs: https://doc.qt.io/qt-5/qmenubar.html#qmenubar-as-a-global-menu-bar + actionConfig_Folder->setMenuRole(QAction::NoRole); all_actions.append(&actionConfig_Folder); - instanceToolBar->addAction(actionConfig_Folder); actionViewSelectedInstFolder = TranslatedAction(MainWindow); actionViewSelectedInstFolder->setObjectName(QStringLiteral("actionViewSelectedInstFolder")); - actionViewSelectedInstFolder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Instance Folder")); + actionViewSelectedInstFolder.setTextId(QT_TRANSLATE_NOOP("MainWindow", "&Instance Folder")); actionViewSelectedInstFolder.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Open the selected instance's root folder in a file browser.")); all_actions.append(&actionViewSelectedInstFolder); - instanceToolBar->addAction(actionViewSelectedInstFolder); - - instanceToolBar->addSeparator(); actionExportInstance = TranslatedAction(MainWindow); actionExportInstance->setObjectName(QStringLiteral("actionExportInstance")); - actionExportInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Export Instance")); + actionExportInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "E&xport Instance...")); actionExportInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Export the selected instance as a zip file.")); + actionExportInstance->setShortcut(QKeySequence(tr("Ctrl+E"))); all_actions.append(&actionExportInstance); - instanceToolBar->addAction(actionExportInstance); actionDeleteInstance = TranslatedAction(MainWindow); actionDeleteInstance->setObjectName(QStringLiteral("actionDeleteInstance")); - actionDeleteInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Delete Instance")); + actionDeleteInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Dele&te Instance...")); actionDeleteInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Delete the selected instance.")); + actionDeleteInstance->setShortcuts({QKeySequence(tr("Backspace")), QKeySequence::Delete}); all_actions.append(&actionDeleteInstance); - instanceToolBar->addAction(actionDeleteInstance); actionCopyInstance = TranslatedAction(MainWindow); actionCopyInstance->setObjectName(QStringLiteral("actionCopyInstance")); actionCopyInstance->setIcon(APPLICATION->getThemedIcon("copy")); - actionCopyInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Copy Instance")); + actionCopyInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Cop&y Instance...")); actionCopyInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Copy the selected instance.")); + actionCopyInstance->setShortcut(QKeySequence(tr("Ctrl+D"))); all_actions.append(&actionCopyInstance); + + setInstanceActionsEnabled(false); + } + + void createInstanceToolbar(QMainWindow *MainWindow) + { + instanceToolBar = TranslatedToolbar(MainWindow); + instanceToolBar->setObjectName(QStringLiteral("instanceToolBar")); + // disabled until we have an instance selected + instanceToolBar->setEnabled(false); + instanceToolBar->setMovable(true); + instanceToolBar->setAllowedAreas(Qt::LeftToolBarArea | Qt::RightToolBarArea); + instanceToolBar->setToolButtonStyle(Qt::ToolButtonTextOnly); + instanceToolBar->setFloatable(false); + instanceToolBar->setWindowTitle(QT_TRANSLATE_NOOP("MainWindow", "Instance Toolbar")); + + instanceToolBar->addWidget(changeIconButton); + instanceToolBar->addWidget(renameButton); + + instanceToolBar->addSeparator(); + + instanceToolBar->addAction(actionLaunchInstance); + instanceToolBar->addAction(actionLaunchInstanceOffline); + + instanceToolBar->addSeparator(); + + instanceToolBar->addAction(actionEditInstance); + instanceToolBar->addAction(actionEditInstNotes); + instanceToolBar->addAction(actionMods); + instanceToolBar->addAction(actionWorlds); + instanceToolBar->addAction(actionScreenshots); + instanceToolBar->addAction(actionChangeInstGroup); + + instanceToolBar->addSeparator(); + + instanceToolBar->addAction(actionViewSelectedMCFolder); + /* + instanceToolBar->addAction(actionViewSelectedModsFolder); + */ + instanceToolBar->addAction(actionConfig_Folder); + instanceToolBar->addAction(actionViewSelectedInstFolder); + + instanceToolBar->addSeparator(); + + instanceToolBar->addAction(actionExportInstance); + instanceToolBar->addAction(actionDeleteInstance); instanceToolBar->addAction(actionCopyInstance); all_toolbars.append(&instanceToolBar); MainWindow->addToolBar(Qt::RightToolBarArea, instanceToolBar); } - void setupUi(QMainWindow *MainWindow) + void setupUi(MainWindow *MainWindow) { if (MainWindow->objectName().isEmpty()) { @@ -634,6 +800,12 @@ public: MainWindow->setAccessibleName(BuildConfig.LAUNCHER_NAME); #endif + createMainToolbarActions(MainWindow); + createMenuActions(MainWindow); + createInstanceActions(MainWindow); + + createMenuBar(MainWindow); + createMainToolbar(MainWindow); centralWidget = new QWidget(MainWindow); @@ -649,6 +821,8 @@ public: createNewsToolbar(MainWindow); createInstanceToolbar(MainWindow); + MainWindow->updateToolsMenu(); + retranslateUi(MainWindow); QMetaObject::connectSlotsByName(MainWindow); @@ -853,6 +1027,18 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow retranslateUi(); } +// macOS always has a native menu bar, so these fixes are not applicable +// Other systems may or may not have a native menu bar (most do not - it seems like only Ubuntu Unity does) +#ifndef Q_OS_MAC +void MainWindow::keyReleaseEvent(QKeyEvent *event) +{ + if(event->key()==Qt::Key_Alt && !APPLICATION->settings()->get("MenuBarInsteadOfToolBar").toBool()) + ui->menuBar->setVisible(!ui->menuBar->isVisible()); + else + QMainWindow::keyReleaseEvent(event); +} +#endif + void MainWindow::retranslateUi() { auto accounts = APPLICATION->accounts(); @@ -954,12 +1140,18 @@ void MainWindow::showInstanceContextMenu(const QPoint &pos) myMenu.exec(view->mapToGlobal(pos)); } +void MainWindow::updateMainToolBar() +{ + ui->menuBar->setVisible(APPLICATION->settings()->get("MenuBarInsteadOfToolBar").toBool()); + ui->mainToolBar->setVisible(ui->menuBar->isNativeMenuBar() || !APPLICATION->settings()->get("MenuBarInsteadOfToolBar").toBool()); +} + void MainWindow::updateToolsMenu() { QToolButton *launchButton = dynamic_cast<QToolButton*>(ui->instanceToolBar->widgetForAction(ui->actionLaunchInstance)); QToolButton *launchOfflineButton = dynamic_cast<QToolButton*>(ui->instanceToolBar->widgetForAction(ui->actionLaunchInstanceOffline)); - if(!m_selectedInstance || m_selectedInstance->isRunning()) + if(m_selectedInstance && m_selectedInstance->isRunning()) { ui->actionLaunchInstance->setMenu(nullptr); ui->actionLaunchInstanceOffline->setMenu(nullptr); @@ -989,15 +1181,23 @@ void MainWindow::updateToolsMenu() } QAction *normalLaunch = launchMenu->addAction(tr("Launch")); + normalLaunch->setShortcut(QKeySequence::Open); QAction *normalLaunchOffline = launchOfflineMenu->addAction(tr("Launch Offline")); - connect(normalLaunch, &QAction::triggered, [this]() - { - APPLICATION->launch(m_selectedInstance, true); - }); - connect(normalLaunchOffline, &QAction::triggered, [this]() - { - APPLICATION->launch(m_selectedInstance, false); - }); + normalLaunchOffline->setShortcut(QKeySequence(tr("Ctrl+Shift+O"))); + if (m_selectedInstance) + { + connect(normalLaunch, &QAction::triggered, [this]() { + APPLICATION->launch(m_selectedInstance, true); + }); + connect(normalLaunchOffline, &QAction::triggered, [this]() { + APPLICATION->launch(m_selectedInstance, false); + }); + } + else + { + normalLaunch->setDisabled(true); + normalLaunchOffline->setDisabled(true); + } QString profilersTitle = tr("Profilers"); launchMenu->addSeparator()->setText(profilersTitle); launchOfflineMenu->addSeparator()->setText(profilersTitle); @@ -1014,7 +1214,7 @@ void MainWindow::updateToolsMenu() profilerAction->setToolTip(profilerToolTip); profilerOfflineAction->setToolTip(profilerToolTip); } - else + else if (m_selectedInstance) { connect(profilerAction, &QAction::triggered, [this, profiler]() { @@ -1025,6 +1225,11 @@ void MainWindow::updateToolsMenu() APPLICATION->launch(m_selectedInstance, false, profiler.get()); }); } + else + { + profilerAction->setDisabled(true); + profilerOfflineAction->setDisabled(true); + } } ui->actionLaunchInstance->setMenu(launchMenu); ui->actionLaunchInstanceOffline->setMenu(launchOfflineMenu); @@ -1033,6 +1238,7 @@ void MainWindow::updateToolsMenu() void MainWindow::repopulateAccountsMenu() { accountMenu->clear(); + ui->profileMenu->clear(); auto accounts = APPLICATION->accounts(); MinecraftAccountPtr defaultAccount = accounts->defaultAccount(); @@ -1053,6 +1259,7 @@ void MainWindow::repopulateAccountsMenu() QAction *action = new QAction(tr("No accounts added!"), this); action->setEnabled(false); accountMenu->addAction(action); + ui->profileMenu->addAction(action); } else { @@ -1076,26 +1283,39 @@ void MainWindow::repopulateAccountsMenu() else { action->setIcon(APPLICATION->getThemedIcon("noaccount")); } + + const int highestNumberKey = 9; + if(i<highestNumberKey) + { + action->setShortcut(QKeySequence(tr("Ctrl+%1").arg(i + 1))); + } + accountMenu->addAction(action); + ui->profileMenu->addAction(action); connect(action, SIGNAL(triggered(bool)), SLOT(changeActiveAccount())); } } accountMenu->addSeparator(); + ui->profileMenu->addSeparator(); QAction *action = new QAction(tr("No Default Account"), this); action->setCheckable(true); action->setIcon(APPLICATION->getThemedIcon("noaccount")); action->setData(-1); + action->setShortcut(QKeySequence(tr("Ctrl+0"))); if (!defaultAccount) { action->setChecked(true); } accountMenu->addAction(action); + ui->profileMenu->addAction(action); connect(action, SIGNAL(triggered(bool)), SLOT(changeActiveAccount())); accountMenu->addSeparator(); + ui->profileMenu->addSeparator(); accountMenu->addAction(ui->actionManageAccounts); + ui->profileMenu->addAction(ui->actionManageAccounts); } void MainWindow::updatesAllowedChanged(bool allowed) @@ -1626,6 +1846,7 @@ void MainWindow::globalSettingsClosed() APPLICATION->instances()->loadList(); proxymodel->invalidate(); proxymodel->sort(0); + updateMainToolBar(); updateToolsMenu(); updateStatusCenter(); update(); @@ -1671,6 +1892,11 @@ void MainWindow::on_actionReportBug_triggered() DesktopServices::openUrl(QUrl(BuildConfig.BUG_TRACKER_URL)); } +void MainWindow::on_actionOpenWiki_triggered() +{ + DesktopServices::openUrl(QUrl(BuildConfig.HELP_URL.arg(""))); +} + void MainWindow::on_actionMoreNews_triggered() { DesktopServices::openUrl(QUrl(BuildConfig.NEWS_OPEN_URL)); @@ -1858,6 +2084,7 @@ void MainWindow::instanceChanged(const QModelIndex ¤t, const QModelIndex & if (m_selectedInstance) { ui->instanceToolBar->setEnabled(true); + ui->setInstanceActionsEnabled(true); if(m_selectedInstance->isRunning()) { ui->actionLaunchInstance->setEnabled(true); @@ -1882,6 +2109,7 @@ void MainWindow::instanceChanged(const QModelIndex ¤t, const QModelIndex & else { ui->instanceToolBar->setEnabled(false); + ui->setInstanceActionsEnabled(false); APPLICATION->settings()->set("SelectedInstance", QString()); selectionBad(); return; @@ -1910,6 +2138,7 @@ void MainWindow::selectionBad() statusBar()->clearMessage(); ui->instanceToolBar->setEnabled(false); + ui->setInstanceActionsEnabled(false); ui->renameButton->setText(tr("Rename Instance")); updateInstanceToolIcon("grass"); diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index f2852d78..2032acba 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -110,6 +110,8 @@ private slots: void on_actionReportBug_triggered(); + void on_actionOpenWiki_triggered(); + void on_actionMoreNews_triggered(); void newsButtonClicked(); @@ -149,6 +151,8 @@ private slots: void showInstanceContextMenu(const QPoint &); + void updateMainToolBar(); + void updateToolsMenu(); void instanceActivated(QModelIndex); @@ -184,6 +188,10 @@ private slots: void globalSettingsClosed(); +#ifndef Q_OS_MAC + void keyReleaseEvent(QKeyEvent *event) override; +#endif + private: void retranslateUi(); diff --git a/launcher/ui/pages/global/LauncherPage.cpp b/launcher/ui/pages/global/LauncherPage.cpp index 42ad5ae3..097a2bfa 100644 --- a/launcher/ui/pages/global/LauncherPage.cpp +++ b/launcher/ui/pages/global/LauncherPage.cpp @@ -41,6 +41,7 @@ #include <QMessageBox> #include <QDir> #include <QTextCharFormat> +#include <QMenuBar> #include "updater/UpdateChecker.h" @@ -322,6 +323,8 @@ void LauncherPage::applySettings() APPLICATION->setApplicationTheme(newAppTheme, false); } + s->set("MenuBarInsteadOfToolBar", ui->preferMenuBarCheckBox->isChecked()); + // Console settings s->set("ShowConsole", ui->showConsoleCheck->isChecked()); s->set("AutoCloseConsole", ui->autoCloseConsoleCheck->isChecked()); @@ -410,6 +413,13 @@ void LauncherPage::loadSettings() } } + // Toolbar/menu bar settings (not applicable if native menu bar is present) + ui->toolsBox->setEnabled(!QMenuBar().isNativeMenuBar()); +#ifdef Q_OS_MACOS + ui->toolsBox->setVisible(!QMenuBar().isNativeMenuBar()); +#endif + ui->preferMenuBarCheckBox->setChecked(s->get("MenuBarInsteadOfToolBar").toBool()); + // Console settings ui->showConsoleCheck->setChecked(s->get("ShowConsole").toBool()); ui->autoCloseConsoleCheck->setChecked(s->get("AutoCloseConsole").toBool()); diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index c110dd09..4cc2a113 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -290,6 +290,16 @@ </item> </widget> </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Colors</string> + </property> + <property name="buddy"> + <cstring>themeComboBoxColors</cstring> + </property> + </widget> + </item> <item row="1" column="1"> <widget class="QComboBox" name="themeComboBoxColors"> <property name="sizePolicy"> @@ -303,13 +313,28 @@ </property> </widget> </item> - <item row="1" column="0"> - <widget class="QLabel" name="label_4"> - <property name="text"> - <string>Colors</string> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="toolsBox"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Tools</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QCheckBox" name="preferMenuBarCheckBox"> + <property name="toolTip"> + <string>The menubar is more friendly for keyboard-driven interaction.</string> </property> - <property name="buddy"> - <cstring>themeComboBoxColors</cstring> + <property name="text"> + <string>Replace toolbar with menubar</string> </property> </widget> </item> diff --git a/libraries/libnbtplusplus b/libraries/libnbtplusplus -Subproject 129be45a7f91920e76673af104534d215c497d8 +Subproject 2203af7eeb48c45398139b583615134efd8d407 diff --git a/libraries/quazip b/libraries/quazip -Subproject 09ec1d10c6d627f895109b21728dda000cbfa7d +Subproject 6117161af08e366c37499895b00ef62f93adc34 diff --git a/program_info/CMakeLists.txt b/program_info/CMakeLists.txt index 9c243826..60549d8d 100644 --- a/program_info/CMakeLists.txt +++ b/program_info/CMakeLists.txt @@ -17,5 +17,7 @@ set(Launcher_Branding_ICNS "program_info/polymc.icns" PARENT_SCOPE) set(Launcher_Branding_WindowsRC "program_info/polymc.rc" PARENT_SCOPE) set(Launcher_Branding_LogoQRC "program_info/polymc.qrc" PARENT_SCOPE) +set(Launcher_Portable_File "program_info/portable.txt" PARENT_SCOPE) + configure_file(org.polymc.PolyMC.desktop.in org.polymc.PolyMC.desktop) configure_file(org.polymc.PolyMC.metainfo.xml.in org.polymc.PolyMC.metainfo.xml) diff --git a/program_info/portable.txt b/program_info/portable.txt new file mode 100644 index 00000000..b7e256a2 --- /dev/null +++ b/program_info/portable.txt @@ -0,0 +1,4 @@ +This file enables the portable mode for the launcher. + +If this file is present in the root directory of the launcher, it will store all data here. Otherwise it will store your data in your appdata directory. +You can safely delete this file, if you don't want the launcher to store your data here. |