aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/build.yml232
-rw-r--r--CMakeLists.txt54
-rw-r--r--launcher/Application.cpp53
-rw-r--r--launcher/CMakeLists.txt6
-rwxr-xr-xlauncher/Launcher.in2
-rw-r--r--launcher/minecraft/MinecraftInstance.cpp4
-rw-r--r--launcher/modplatform/ModAPI.h17
-rw-r--r--launcher/modplatform/flame/FlameAPI.h6
-rw-r--r--launcher/modplatform/modrinth/ModrinthAPI.h15
-rw-r--r--launcher/ui/dialogs/ModDownloadDialog.cpp6
-rw-r--r--launcher/ui/pages/modplatform/ModModel.cpp27
-rw-r--r--launcher/ui/pages/modplatform/ModModel.h6
-rw-r--r--launcher/ui/pages/modplatform/ModPage.cpp58
-rw-r--r--launcher/ui/pages/modplatform/ModPage.h9
-rw-r--r--launcher/ui/pages/modplatform/ModPage.ui24
-rw-r--r--launcher/ui/widgets/ModFilterWidget.cpp109
-rw-r--r--launcher/ui/widgets/ModFilterWidget.h69
-rw-r--r--launcher/ui/widgets/ModFilterWidget.ui54
-rw-r--r--program_info/CMakeLists.txt2
-rw-r--r--program_info/portable.txt4
20 files changed, 532 insertions, 225 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 0123c99a..4b1195ee 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,16 +25,6 @@ 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
@@ -53,9 +35,14 @@ jobs:
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,16 +74,16 @@ jobs:
distribution: 'temurin'
java-version: '17'
- - name: Cache Qt
- if: runner.os != 'Windows'
+ - name: Cache Qt (macOS)
+ if: runner.os == 'macOS'
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
- if: runner.os != 'Linux' && runner.os != 'Windows' || matrix.app_image == true
+ - name: Install Qt (macOS)
+ if: runner.os == 'macOS'
uses: jurplel/install-qt-action@v2
with:
version: ${{ matrix.qt_version }}
@@ -105,8 +92,8 @@ jobs:
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 +102,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 -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
-
- cp -r ${{ github.workspace }}/JREs/jre8/* ${{ env.INSTALL_DIR }}/usr/lib/jvm/java-8-openjdk
+ mkdir -p ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-{8,17}-openjdk
- cp -r ${{ github.workspace }}/JREs/jre17/* ${{ env.INSTALL_DIR }}/usr/lib/jvm/java-17-openjdk
+ cp -r ${{ github.workspace }}/JREs/jre8/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-8-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"
+ cp -r ${{ github.workspace }}/JREs/jre17/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-17-openjdk
- ./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
+ 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
- - name: Run macdeployqt
- if: runner.os == 'macOS'
- run: |
- cd ${{ env.INSTALL_DIR }}
- macdeployqt "PolyMC.app" -executable="PolyMC.app/Contents/MacOS/polymc" -always-overwrite
+ ./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: 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 9530ff10..722b68da 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -164,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)
@@ -179,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")
@@ -205,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")
@@ -236,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")
diff --git a/launcher/Application.cpp b/launcher/Application.cpp
index 9cca534c..6e934fa4 100644
--- a/launcher/Application.cpp
+++ b/launcher/Application.cpp
@@ -316,6 +316,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 +344,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 +359,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 +557,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();
diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt
index 42348792..6ed86726 100644
--- a/launcher/CMakeLists.txt
+++ b/launcher/CMakeLists.txt
@@ -825,7 +825,6 @@ SET(LAUNCHER_SOURCES
ui/dialogs/ModDownloadDialog.cpp
ui/dialogs/ModDownloadDialog.h
-
# GUI - widgets
ui/widgets/Common.cpp
ui/widgets/Common.h
@@ -849,6 +848,8 @@ SET(LAUNCHER_SOURCES
ui/widgets/LogView.h
ui/widgets/MCModInfoFrame.cpp
ui/widgets/MCModInfoFrame.h
+ ui/widgets/ModFilterWidget.cpp
+ ui/widgets/ModFilterWidget.h
ui/widgets/ModListView.cpp
ui/widgets/ModListView.h
ui/widgets/PageContainer.cpp
@@ -907,6 +908,7 @@ qt5_wrap_ui(LAUNCHER_UI
ui/widgets/InstanceCardWidget.ui
ui/widgets/CustomCommands.ui
ui/widgets/MCModInfoFrame.ui
+ ui/widgets/ModFilterWidget.ui
ui/dialogs/CopyInstanceDialog.ui
ui/dialogs/ProfileSetupDialog.ui
ui/dialogs/ProgressDialog.ui
@@ -989,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/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp
index 3ba79178..503b34d9 100644
--- a/launcher/minecraft/MinecraftInstance.cpp
+++ b/launcher/minecraft/MinecraftInstance.cpp
@@ -213,8 +213,12 @@ QString MinecraftInstance::binRoot() const
QString MinecraftInstance::getNativePath() const
{
+#ifdef Q_OS_FREEBSD
+ QDir natives_dir("/usr/local/lib/lwjgl/");
+#else
QDir natives_dir(FS::PathCombine(instanceRoot(), "natives/"));
return natives_dir.absolutePath();
+#endif
}
QString MinecraftInstance::getLocalLibraryPath() const
diff --git a/launcher/modplatform/ModAPI.h b/launcher/modplatform/ModAPI.h
index 1a562172..8e6cd45c 100644
--- a/launcher/modplatform/ModAPI.h
+++ b/launcher/modplatform/ModAPI.h
@@ -3,6 +3,8 @@
#include <QString>
#include <QList>
+#include "Version.h"
+
namespace ModPlatform {
class ListModel;
}
@@ -22,7 +24,7 @@ class ModAPI {
QString search;
QString sorting;
ModLoaderType mod_loader;
- QString version;
+ std::list<Version> versions;
};
virtual void searchMods(CallerType* caller, SearchArgs&& args) const = 0;
@@ -30,7 +32,7 @@ class ModAPI {
struct VersionSearchArgs {
QString addonId;
- QList<QString> mcVersions;
+ std::list<Version> mcVersions;
ModLoaderType loader;
};
@@ -53,4 +55,15 @@ class ModAPI {
}
return "";
}
+
+ protected:
+ inline auto getGameVersionsString(std::list<Version> mcVersions) const -> QString
+ {
+ QString s;
+ for(auto& ver : mcVersions){
+ s += QString("%1,").arg(ver.toString());
+ }
+ s.remove(s.length() - 1, 1); //remove last comma
+ return s;
+ }
};
diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h
index 8654a693..9bcc357e 100644
--- a/launcher/modplatform/flame/FlameAPI.h
+++ b/launcher/modplatform/flame/FlameAPI.h
@@ -6,6 +6,8 @@ class FlameAPI : public NetworkModAPI {
private:
inline auto getModSearchURL(SearchArgs& args) const -> QString override
{
+ auto gameVersionStr = args.versions.size() != 0 ? QString("gameVersion=%1").arg(args.versions.front().toString()) : QString();
+
return QString(
"https://addons-ecs.forgesvc.net/api/v2/addon/search?"
"gameId=432&"
@@ -17,12 +19,12 @@ class FlameAPI : public NetworkModAPI {
"searchFilter=%2&"
"sort=%3&"
"modLoaderType=%4&"
- "gameVersion=%5")
+ "%5")
.arg(args.offset)
.arg(args.search)
.arg(args.sorting)
.arg(args.mod_loader)
- .arg(args.version);
+ .arg(gameVersionStr);
};
inline auto getVersionsURL(VersionSearchArgs& args) const -> QString override
diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h
index eefa4a85..6604d772 100644
--- a/launcher/modplatform/modrinth/ModrinthAPI.h
+++ b/launcher/modplatform/modrinth/ModrinthAPI.h
@@ -22,12 +22,12 @@ class ModrinthAPI : public NetworkModAPI {
"limit=25&"
"query=%2&"
"index=%3&"
- "facets=[[\"categories:%4\"],[\"versions:%5\"],[\"project_type:mod\"]]")
+ "facets=[[\"categories:%4\"],%5[\"project_type:mod\"]]")
.arg(args.offset)
.arg(args.search)
.arg(args.sorting)
.arg(getModLoaderString(args.mod_loader))
- .arg(args.version);
+ .arg(getGameVersionsArray(args.versions));
};
inline auto getVersionsURL(VersionSearchArgs& args) const -> QString override
@@ -40,15 +40,14 @@ class ModrinthAPI : public NetworkModAPI {
.arg(getModLoaderString(args.loader));
};
- inline auto getGameVersionsString(QList<QString> mcVersions) const -> QString
+ auto getGameVersionsArray(std::list<Version> mcVersions) const -> QString
{
QString s;
- for(int i = 0; i < mcVersions.count(); i++){
- s += mcVersions.at(i);
- if(i < mcVersions.count() - 1)
- s += ",";
+ for(auto& ver : mcVersions){
+ s += QString("\"versions:%1\",").arg(ver.toString());
}
- return s;
+ s.remove(s.length() - 1, 1); //remove last comma
+ return s.isEmpty() ? QString() : QString("[%1],").arg(s);
}
static auto getModLoaderString(ModLoaderType type) -> const QString
diff --git a/launcher/ui/dialogs/ModDownloadDialog.cpp b/launcher/ui/dialogs/ModDownloadDialog.cpp
index a53f93e8..d02ea476 100644
--- a/launcher/ui/dialogs/ModDownloadDialog.cpp
+++ b/launcher/ui/dialogs/ModDownloadDialog.cpp
@@ -22,7 +22,9 @@ ModDownloadDialog::ModDownloadDialog(const std::shared_ptr<ModFolderModel> &mods
: QDialog(parent), mods(mods), m_instance(instance)
{
setObjectName(QStringLiteral("ModDownloadDialog"));
- resize(400, 347);
+
+ resize(std::max(0.5*parent->width(), 400.0), std::max(0.75*parent->height(), 400.0));
+
m_verticalLayout = new QVBoxLayout(this);
m_verticalLayout->setObjectName(QStringLiteral("verticalLayout"));
@@ -80,7 +82,7 @@ void ModDownloadDialog::confirm()
tr("Confirm mods to download")
);
- for(auto task : keys){
+ for(auto& task : keys){
confirm_dialog->appendMod(task, modTask.find(task).value()->getFilename());
}
diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp
index f75d2847..da0331b5 100644
--- a/launcher/ui/pages/modplatform/ModModel.cpp
+++ b/launcher/ui/pages/modplatform/ModModel.cpp
@@ -72,14 +72,11 @@ void ListModel::performPaginatedSearch()
auto profile = (dynamic_cast<MinecraftInstance*>((dynamic_cast<ModPage*>(parent()))->m_instance))->getPackProfile();
m_parent->apiProvider()->searchMods(this,
- { nextSearchOffset, currentSearchTerm, getSorts()[currentSort], profile->getModLoader(), getMineVersions().at(0) });
+ { nextSearchOffset, currentSearchTerm, getSorts()[currentSort], profile->getModLoader(), getMineVersions() });
}
-void ListModel::searchWithTerm(const QString& term, const int sort)
+void ListModel::refresh()
{
- if (currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull() && currentSort == sort) { return; }
- currentSearchTerm = term;
- currentSort = sort;
if (jobPtr) {
jobPtr->abort();
searchState = ResetRequested;
@@ -94,6 +91,20 @@ void ListModel::searchWithTerm(const QString& term, const int sort)
performPaginatedSearch();
}
+void ListModel::searchWithTerm(const QString& term, const int sort, const bool filter_changed)
+{
+ if (currentSearchTerm == term
+ && currentSearchTerm.isNull() == term.isNull()
+ && currentSort == sort
+ && !filter_changed)
+ { return; }
+
+ currentSearchTerm = term;
+ currentSort = sort;
+
+ refresh();
+}
+
void ListModel::getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback)
{
if (m_logoMap.contains(logo)) {
@@ -223,9 +234,7 @@ void ListModel::versionRequestSucceeded(QJsonDocument doc, QString addonId)
/******** Helpers ********/
-auto ModPlatform::ListModel::getMineVersions() const -> QList<QString>
+auto ModPlatform::ListModel::getMineVersions() const -> std::list<Version>
{
- return { (dynamic_cast<MinecraftInstance*>((dynamic_cast<ModPage*>(parent()))->m_instance))
- ->getPackProfile()
- ->getComponentVersion("net.minecraft") };
+ return m_parent->getFilter()->versions;
}
diff --git a/launcher/ui/pages/modplatform/ModModel.h b/launcher/ui/pages/modplatform/ModModel.h
index dbadbeee..d460cff2 100644
--- a/launcher/ui/pages/modplatform/ModModel.h
+++ b/launcher/ui/pages/modplatform/ModModel.h
@@ -7,6 +7,7 @@
#include "net/NetJob.h"
class ModPage;
+class Version;
namespace ModPlatform {
@@ -33,7 +34,8 @@ class ListModel : public QAbstractListModel {
/* Ask the API for more information */
void fetchMore(const QModelIndex& parent) override;
- void searchWithTerm(const QString& term, const int sort);
+ void refresh();
+ void searchWithTerm(const QString& term, const int sort, const bool filter_changed);
void requestModVersions(const ModPlatform::IndexedPack& current);
virtual void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) = 0;
@@ -62,7 +64,7 @@ class ListModel : public QAbstractListModel {
void requestLogo(QString file, QString url);
- inline auto getMineVersions() const -> QList<QString>;
+ inline auto getMineVersions() const -> std::list<Version>;
protected:
ModPage* m_parent;
diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp
index eabd8379..29f6b601 100644
--- a/launcher/ui/pages/modplatform/ModPage.cpp
+++ b/launcher/ui/pages/modplatform/ModPage.cpp
@@ -2,21 +2,39 @@
#include "ui_ModPage.h"
#include <QKeyEvent>
+#include <memory>
#include "minecraft/MinecraftInstance.h"
#include "minecraft/PackProfile.h"
#include "ui/dialogs/ModDownloadDialog.h"
ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance* instance, ModAPI* api)
- : QWidget(dialog), m_instance(instance), ui(new Ui::ModPage), dialog(dialog), api(api)
+ : QWidget(dialog)
+ , m_instance(instance)
+ , ui(new Ui::ModPage)
+ , dialog(dialog)
+ , filter_widget(static_cast<MinecraftInstance*>(instance)->getPackProfile()->getComponentVersion("net.minecraft"), this)
+ , api(api)
{
ui->setupUi(this);
connect(ui->searchButton, &QPushButton::clicked, this, &ModPage::triggerSearch);
+ connect(ui->modFilterButton, &QPushButton::clicked, this, &ModPage::filterMods);
ui->searchEdit->installEventFilter(this);
ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300);
+ ui->gridLayout_3->addWidget(&filter_widget, 0, 0, 1, ui->gridLayout_3->columnCount());
+
+ filter_widget.setInstance(static_cast<MinecraftInstance*>(m_instance));
+ m_filter = filter_widget.getFilter();
+
+ connect(&filter_widget, &ModFilterWidget::filterChanged, this, [&]{
+ ui->searchButton->setStyleSheet("text-decoration: underline");
+ });
+ connect(&filter_widget, &ModFilterWidget::filterUnchanged, this, [&]{
+ ui->searchButton->setStyleSheet("text-decoration: none");
+ });
}
ModPage::~ModPage()
@@ -49,9 +67,24 @@ auto ModPage::eventFilter(QObject* watched, QEvent* event) -> bool
/******** Callbacks to events in the UI (set up in the derived classes) ********/
+void ModPage::filterMods()
+{
+ filter_widget.setHidden(!filter_widget.isHidden());
+}
+
void ModPage::triggerSearch()
{
- listModel->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex());
+ auto changed = filter_widget.changed();
+ m_filter = filter_widget.getFilter();
+
+ if(changed){
+ ui->packView->clearSelection();
+ ui->packDescription->clear();
+ ui->versionSelectionBox->clear();
+ updateSelectionButton();
+ }
+
+ listModel->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex(), changed);
}
void ModPage::onSelectionChanged(QModelIndex first, QModelIndex second)
@@ -131,7 +164,7 @@ void ModPage::retranslate()
ui->retranslateUi(this);
}
-void ModPage::updateModVersions()
+void ModPage::updateModVersions(int prev_count)
{
auto packProfile = (dynamic_cast<MinecraftInstance*>(m_instance))->getPackProfile();
@@ -141,15 +174,22 @@ void ModPage::updateModVersions()
for (int i = 0; i < current.versions.size(); i++) {
auto version = current.versions[i];
- //NOTE: Flame doesn't care about loaderString, so passing it changes nothing.
- if (!validateVersion(version, mcVersion, loaderString)) {
- continue;
+ bool valid = false;
+ for(auto& mcVer : m_filter->versions){
+ //NOTE: Flame doesn't care about loaderString, so passing it changes nothing.
+ if (validateVersion(version, mcVer.toString(), loaderString)) {
+ valid = true;
+ break;
+ }
}
- ui->versionSelectionBox->addItem(version.version, QVariant(i));
+ if(valid || m_filter->versions.size() == 0)
+ ui->versionSelectionBox->addItem(version.version, QVariant(i));
+ }
+ if (ui->versionSelectionBox->count() == 0 && prev_count != 0) {
+ ui->versionSelectionBox->addItem(tr("No valid version found!"), QVariant(-1));
+ ui->modSelectionButton->setText(tr("Cannot select invalid version :("));
}
- if (ui->versionSelectionBox->count() == 0) { ui->versionSelectionBox->addItem(tr("No valid version found!"), QVariant(-1)); }
- ui->modSelectionButton->setText(tr("Cannot select invalid version :("));
updateSelectionButton();
}
diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h
index 0cd13f37..85aaede9 100644
--- a/launcher/ui/pages/modplatform/ModPage.h
+++ b/launcher/ui/pages/modplatform/ModPage.h
@@ -7,6 +7,7 @@
#include "modplatform/ModIndex.h"
#include "ui/pages/BasePage.h"
#include "ui/pages/modplatform/ModModel.h"
+#include "ui/widgets/ModFilterWidget.h"
class ModDownloadDialog;
@@ -39,9 +40,10 @@ class ModPage : public QWidget, public BasePage {
virtual auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, QString loaderVer = "") const -> bool = 0;
auto apiProvider() const -> const ModAPI* { return api.get(); };
+ auto getFilter() const -> const std::shared_ptr<ModFilterWidget::Filter> { return m_filter; }
auto getCurrent() -> ModPlatform::IndexedPack& { return current; }
- void updateModVersions();
+ void updateModVersions(int prev_count = -1);
void openedImpl() override;
auto eventFilter(QObject* watched, QEvent* event) -> bool override;
@@ -52,6 +54,7 @@ class ModPage : public QWidget, public BasePage {
void updateSelectionButton();
protected slots:
+ virtual void filterMods();
void triggerSearch();
void onSelectionChanged(QModelIndex first, QModelIndex second);
void onVersionSelectionChanged(QString data);
@@ -60,6 +63,10 @@ class ModPage : public QWidget, public BasePage {
protected:
Ui::ModPage* ui = nullptr;
ModDownloadDialog* dialog = nullptr;
+
+ ModFilterWidget filter_widget;
+ std::shared_ptr<ModFilterWidget::Filter> m_filter;
+
ModPlatform::ListModel* listModel = nullptr;
ModPlatform::IndexedPack current;
diff --git a/launcher/ui/pages/modplatform/ModPage.ui b/launcher/ui/pages/modplatform/ModPage.ui
index 508f1bac..afcd9bb7 100644
--- a/launcher/ui/pages/modplatform/ModPage.ui
+++ b/launcher/ui/pages/modplatform/ModPage.ui
@@ -11,7 +11,7 @@
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
- <item row="1" column="0" colspan="2">
+ <item row="1" column="0" colspan="4">
<layout class="QGridLayout" name="gridLayout_3">
<item row="1" column="2">
<widget class="QTextBrowser" name="packDescription">
@@ -41,7 +41,7 @@
</item>
</layout>
</item>
- <item row="0" column="1">
+ <item row="0" column="3">
<widget class="QPushButton" name="searchButton">
<property name="text">
<string>Search</string>
@@ -51,12 +51,12 @@
<item row="0" column="0">
<widget class="QLineEdit" name="searchEdit">
<property name="placeholderText">
- <string>Search and filter...</string>
+ <string>Search for mods...</string>
</property>
</widget>
</item>
- <item row="2" column="0" colspan="2">
- <layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0,0">
+ <item row="2" column="0" colspan="4">
+ <layout class="QGridLayout" name="gridLayout_4" columnstretch="0,0,0">
<item row="0" column="2">
<widget class="QComboBox" name="versionSelectionBox"/>
</item>
@@ -82,6 +82,20 @@
</item>
</layout>
</item>
+ <item row="0" column="1">
+ <widget class="QPushButton" name="modFilterButton">
+ <property name="text">
+ <string>Filter options</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="2">
+ <widget class="Line" name="line">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
<tabstops>
diff --git a/launcher/ui/widgets/ModFilterWidget.cpp b/launcher/ui/widgets/ModFilterWidget.cpp
new file mode 100644
index 00000000..ffc8d05d
--- /dev/null
+++ b/launcher/ui/widgets/ModFilterWidget.cpp
@@ -0,0 +1,109 @@
+#include "ModFilterWidget.h"
+#include "ui_ModFilterWidget.h"
+
+ModFilterWidget::ModFilterWidget(Version def, QWidget* parent)
+ : QTabWidget(parent), m_filter(new Filter()), ui(new Ui::ModFilterWidget)
+{
+ ui->setupUi(this);
+
+ m_mcVersion_buttons.addButton(ui->strictVersionButton, VersionButtonID::Strict);
+ ui->strictVersionButton->click();
+ m_mcVersion_buttons.addButton(ui->majorVersionButton, VersionButtonID::Major);
+ m_mcVersion_buttons.addButton(ui->allVersionsButton, VersionButtonID::All);
+ //m_mcVersion_buttons.addButton(ui->betweenVersionsButton, VersionButtonID::Between);
+
+ connect(&m_mcVersion_buttons, SIGNAL(idClicked(int)), this, SLOT(onVersionFilterChanged(int)));
+
+ m_filter->versions.push_front(def);
+
+ setHidden(true);
+}
+
+void ModFilterWidget::setInstance(MinecraftInstance* instance)
+{
+ m_instance = instance;
+
+ auto mcVersionSplit = mcVersionStr().split(".");
+
+ ui->strictVersionButton->setText(
+ tr("Strict match (= %1)").arg(mcVersionStr()));
+ ui->majorVersionButton->setText(
+ tr("Major version match (= %1.%2.x)").arg(mcVersionSplit[0], mcVersionSplit[1]));
+ ui->allVersionsButton->setText(
+ tr("Any version"));
+ //ui->betweenVersionsButton->setText(
+ // tr("Between two versions"));
+}
+
+auto ModFilterWidget::getFilter() -> std::shared_ptr<Filter>
+{
+ m_last_version_id = m_version_id;
+ emit filterUnchanged();
+ return m_filter;
+}
+
+void ModFilterWidget::disableVersionButton(VersionButtonID id)
+{
+ switch(id){
+ case(VersionButtonID::Strict):
+ ui->strictVersionButton->setEnabled(false);
+ break;
+ case(VersionButtonID::Major):
+ ui->majorVersionButton->setEnabled(false);
+ break;
+ case(VersionButtonID::All):
+ ui->allVersionsButton->setEnabled(false);
+ break;
+ case(VersionButtonID::Between):
+ // ui->betweenVersionsButton->setEnabled(false);
+ break;
+ default:
+ break;
+ }
+}
+
+void ModFilterWidget::onVersionFilterChanged(int id)
+{
+ //ui->lowerVersionComboBox->setEnabled(id == VersionButtonID::Between);
+ //ui->upperVersionComboBox->setEnabled(id == VersionButtonID::Between);
+
+ auto versionSplit = mcVersionStr().split(".");
+ int index = 0;
+
+ auto cast_id = (VersionButtonID) id;
+ if (cast_id != m_version_id) {
+ m_version_id = cast_id;
+ } else {
+ return;
+ }
+
+ m_filter->versions.clear();
+
+ switch(cast_id){
+ case(VersionButtonID::Strict):
+ m_filter->versions.push_front(mcVersion());
+ break;
+ case(VersionButtonID::Major):
+ for(auto i = Version(QString("%1.%2").arg(versionSplit[0], versionSplit[1])); i <= mcVersion(); index++){
+ m_filter->versions.push_front(i);
+ i = Version(QString("%1.%2.%3").arg(versionSplit[0], versionSplit[1], QString("%1").arg(index)));
+ }
+ break;
+ case(VersionButtonID::All):
+ // Empty list to avoid enumerating all versions :P
+ break;
+ case(VersionButtonID::Between):
+ // TODO
+ break;
+ }
+
+ if(changed())
+ emit filterChanged();
+ else
+ emit filterUnchanged();
+}
+
+ModFilterWidget::~ModFilterWidget()
+{
+ delete ui;
+}
diff --git a/launcher/ui/widgets/ModFilterWidget.h b/launcher/ui/widgets/ModFilterWidget.h
new file mode 100644
index 00000000..334fc672
--- /dev/null
+++ b/launcher/ui/widgets/ModFilterWidget.h
@@ -0,0 +1,69 @@
+#pragma once
+
+#include <QTabWidget>
+#include <QButtonGroup>
+
+#include "Version.h"
+#include "minecraft/MinecraftInstance.h"
+#include "minecraft/PackProfile.h"
+
+class MinecraftInstance;
+
+namespace Ui {
+class ModFilterWidget;
+}
+
+class ModFilterWidget : public QTabWidget
+{
+ Q_OBJECT
+public:
+ enum VersionButtonID {
+ Strict = 0,
+ Major = 1,
+ All = 2,
+ Between = 3
+ };
+
+ struct Filter {
+ std::list<Version> versions;
+
+ bool operator==(const Filter& other) const { return versions == other.versions; }
+ bool operator!=(const Filter& other) const { return !(*this == other); }
+ };
+
+ std::shared_ptr<Filter> m_filter;
+
+public:
+ explicit ModFilterWidget(Version def, QWidget* parent = nullptr);
+ ~ModFilterWidget();
+
+ void setInstance(MinecraftInstance* instance);
+
+ /// By default all buttons are enabled
+ void disableVersionButton(VersionButtonID);
+
+ auto getFilter() -> std::shared_ptr<Filter>;
+ auto changed() const -> bool { return m_last_version_id != m_version_id; }
+
+private:
+ inline auto mcVersionStr() const -> QString { return m_instance ? m_instance->getPackProfile()->getComponentVersion("net.minecraft") : ""; }
+ inline auto mcVersion() const -> Version { return { mcVersionStr() }; }
+
+private slots:
+ void onVersionFilterChanged(int id);
+
+public: signals:
+ void filterChanged();
+ void filterUnchanged();
+
+private:
+ Ui::ModFilterWidget* ui;
+
+ MinecraftInstance* m_instance = nullptr;
+
+ QButtonGroup m_mcVersion_buttons;
+
+ /* Used to tell if the filter was changed since the last getFilter() call */
+ VersionButtonID m_last_version_id = VersionButtonID::Strict;
+ VersionButtonID m_version_id = VersionButtonID::Strict;
+};
diff --git a/launcher/ui/widgets/ModFilterWidget.ui b/launcher/ui/widgets/ModFilterWidget.ui
new file mode 100644
index 00000000..ad1090e2
--- /dev/null
+++ b/launcher/ui/widgets/ModFilterWidget.ui
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ModFilterWidget</class>
+ <widget class="QTabWidget" name="ModFilterWidget">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <widget class="QWidget" name="VersionPage">
+ <attribute name="title">
+ <string>Minecraft versions</string>
+ </attribute>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <layout class="QFormLayout" name="formLayout">
+ <item row="2" column="0">
+ <widget class="QRadioButton" name="allVersionsButton">
+ <property name="text">
+ <string>allVersions</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QRadioButton" name="strictVersionButton">
+ <property name="text">
+ <string>strictVersion</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QRadioButton" name="majorVersionButton">
+ <property name="text">
+ <string>majorVersion</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
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.