From 6410929e17d642618f284d5c97d457f1ac653e6e Mon Sep 17 00:00:00 2001 From: SulfurNitride Date: Sat, 14 Feb 2026 15:40:33 -0600 Subject: Migrate data paths to ~/.local/share/fluorine/, add native build, fix %command% and system Proton scanning - All data paths migrated from ~/.var/app/com.fluorine.manager/ to ~/.local/share/fluorine/ so native and Flatpak builds share the same instances, plugins, and configs - New fluorinepaths.h/.cpp with fluorineDataDir() helper and one-time migration from old path (writes MOVED.txt breadcrumb) - New libs/nak/src/paths.rs as Rust equivalent (data_dir()) - Strip %command% tokens from launch wrapper in protonlauncher.cpp - Always scan /usr/share/steam/compatibilitytools.d/ for system Proton packages (Arch installs Proton there); add Flatpak filesystem permission - Native build (build-native.sh) installs to ~/.local/share/fluorine/ with desktop entry and ~/.local/bin symlink instead of portable zip - build-flatpak.sh wrapper for Flatpak builds - Fix container locale (LANG=C.UTF-8) for AutoUic warnings - Fix prefixExists() to handle both compatdata and pfx directory layouts - Fix globalInstancesRootPath() to use fluorineDataDir() on Linux - Fix umu-run Flatpak lookup to use fluorineDataDir() - Update README with build instructions and Arch dependency list Co-Authored-By: Claude Opus 4.6 --- CMakeLists.txt | 7 ++ README.md | 48 +++++++- build-flatpak.sh | 9 ++ build-native.sh | 78 +++++++++++++ build.sh | 27 ----- docker/Dockerfile | 1 + docker/build-inner.sh | 25 ++-- flatpak/com.fluorine.manager.yml | 3 +- flatpak/fluorine-manager-wrapper.sh | 5 +- libs/7zip/CMakeLists.txt | 2 +- libs/basic_games/basic_game.py | 8 +- libs/bsa_ffi/src/archive/mod.rs | 2 + libs/bsapacker/src/qlibbsarch/QLibbsarch.h | 12 +- libs/bsatk/src/CMakeLists.txt | 3 + libs/bsatk/src/bsaarchive.cpp | 12 -- libs/bsatk/src/bsafile.cpp | 1 - libs/game_bethesda/src/gamebryo/dummybsa.cpp | 6 +- .../src/gamebryo/gamebryosavegame.cpp | 3 +- libs/game_bethesda/src/gamebryo/gamegamebryo.cpp | 6 +- .../src/games/enderal/enderalsavegame.cpp | 3 +- .../src/games/enderalse/enderalsesavegame.cpp | 3 +- .../src/games/fallout4/fallout4savegame.cpp | 3 +- .../src/games/fallout4london/fo4londonsavegame.cpp | 3 +- .../src/games/fallout4vr/fallout4vrsavegame.cpp | 3 +- .../src/games/fallout76/fallout76savegame.cpp | 3 +- .../src/games/skyrim/skyrimsavegame.cpp | 3 +- .../src/games/skyrimse/skyrimsesavegame.cpp | 3 +- .../src/games/skyrimvr/skyrimvrsavegame.cpp | 3 +- .../src/games/starfield/starfieldsavegame.cpp | 3 +- libs/installer_fomod/src/fomodinstallerdialog.cpp | 27 +++-- libs/installer_fomod_plus/installer/CMakeLists.txt | 2 +- .../patchwizard/CMakeLists.txt | 2 +- libs/installer_fomod_plus/scanner/CMakeLists.txt | 2 +- libs/nak/src/deps/tools.rs | 9 +- libs/nak/src/dxvk.rs | 6 +- libs/nak/src/lib.rs | 1 + libs/nak/src/logging.rs | 5 +- libs/nak/src/paths.rs | 12 ++ libs/nak/src/steam/proton.rs | 9 +- libs/plugin_python/src/mobase/CMakeLists.txt | 7 +- .../src/mobase/wrappers/basic_classes.cpp | 5 +- libs/plugin_python/src/pybind11-qt/CMakeLists.txt | 3 + .../src/pybind11-utils/CMakeLists.txt | 5 + libs/plugin_python/src/runner/CMakeLists.txt | 3 + libs/uibase/include/uibase/filterwidget.h | 6 +- libs/uibase/src/utility.cpp | 18 ++- src/src/CMakeLists.txt | 2 + src/src/bbcode.cpp | 2 +- src/src/categoriesdialog.cpp | 56 ++++----- src/src/commandline.cpp | 17 +-- src/src/copyeventfilter.cpp | 2 +- src/src/createinstancedialog.cpp | 54 ++++++++- src/src/createinstancedialog.h | 39 +------ src/src/datatab.cpp | 2 +- src/src/downloadlistview.cpp | 46 ++++---- src/src/downloadmanager.cpp | 117 ++++++++++++------- src/src/envmodule.cpp | 2 +- src/src/fluorineconfig.cpp | 5 +- src/src/fluorinepaths.cpp | 91 +++++++++++++++ src/src/fluorinepaths.h | 15 +++ src/src/icondelegate.cpp | 2 +- src/src/instancemanager.cpp | 7 ++ src/src/instancemanagerdialog.cpp | 52 +++++---- src/src/loglist.cpp | 16 +-- src/src/main.cpp | 3 + src/src/mainwindow.cpp | 23 ++-- src/src/modinfo.cpp | 3 +- src/src/modinforegular.cpp | 87 +++++++------- src/src/modlist.cpp | 120 +++++++++---------- src/src/modlistcontextmenu.cpp | 130 +++++++++++---------- src/src/modlistsortproxy.cpp | 74 +++++++----- src/src/modlistsortproxy.h | 5 +- src/src/modlistview.cpp | 60 +++++----- src/src/modlistviewactions.cpp | 16 +-- src/src/nxmaccessmanager.cpp | 8 +- src/src/organizercore.cpp | 4 +- src/src/plugincontainer.cpp | 19 ++- src/src/pluginlist.cpp | 7 +- src/src/pluginlistcontextmenu.cpp | 22 ++-- src/src/pluginlistsortproxy.cpp | 36 +++--- src/src/pluginlistsortproxy.h | 5 +- src/src/pluginlistview.cpp | 16 +-- src/src/profile.cpp | 8 +- src/src/protonlauncher.cpp | 8 +- src/src/qtgroupingproxy.cpp | 6 +- src/src/selfupdater.cpp | 11 +- src/src/settings.cpp | 2 +- src/src/settingsdialog.ui | 4 +- src/src/settingsdialogmodlist.cpp | 4 +- src/src/settingsdialogproton.cpp | 7 +- src/src/spawn.cpp | 4 +- src/src/texteditor.cpp | 18 +-- src/src/uilocker.cpp | 4 +- 93 files changed, 1008 insertions(+), 643 deletions(-) create mode 100755 build-flatpak.sh create mode 100755 build-native.sh delete mode 100755 build.sh create mode 100644 libs/nak/src/paths.rs create mode 100644 src/src/fluorinepaths.cpp create mode 100644 src/src/fluorinepaths.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 2cf6d19..482d16c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,13 @@ cmake_minimum_required(VERSION 3.16) project(ModOrganizer2 VERSION 2.5.3 LANGUAGES C CXX) +if(POLICY CMP0135) + cmake_policy(SET CMP0135 NEW) +endif() +if(POLICY CMP0167) + cmake_policy(SET CMP0167 NEW) +endif() + set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_POSITION_INDEPENDENT_CODE ON) diff --git a/README.md b/README.md index 83139ff..73f9ace 100644 --- a/README.md +++ b/README.md @@ -32,17 +32,56 @@ You can then get started with: `flatpak run com.fluorine.manager` or you should More information can be found in the [FAQ](https://github.com/SulfurNitride/Fluorine-Manager/blob/main/docs/FAQ.md). -You can find me in the [NaK Discord](https://discord.gg/9JWQzSeUWt) +You can find me in the [NaK Discord](https://discord.gg/9JWQzSeUWt) If you want to support the things I put out, I do have a [Ko-Fi](https://ko-fi.com/sulfurnitride) I will never charge money for any of my content. -## Build +## Building + +Both builds install to `~/.local/share/fluorine/` — the same location, so Flatpak and native share instances, plugins, and configs. + +### Flatpak (recommended for end users) ```bash -cmake -B build -cmake --build build -j"$(nproc)" +./build-flatpak.sh bundle +# Produces a .flatpak file you can install with: +# flatpak install --user fluorine-manager.flatpak ``` +### Native (container build) + +Requires podman (or docker). The container handles all dependencies automatically. + +```bash +./build-native.sh +# Builds inside a container, then installs to ~/.local/share/fluorine/ +# Creates a desktop entry and symlinks fluorine-manager into ~/.local/bin/ +``` + +### Native (building from source on host) + +If you want to build directly on your system without a container, you need: + +**Build tools:** GCC/Clang, CMake, Ninja, Rust toolchain, pkg-config, patchelf + +**Libraries:** +- Qt 6 (base, webengine, websockets, wayland) +- Boost (program_options, thread) +- Python 3 dev headers, pybind11, PyQt6 +- spdlog, toml++, tinyxml2, sqlite3, fontconfig +- libfuse3, lz4, zlib, zstd, bzip2, lzma +- OpenSSL, libcurl + +**Python packages:** `sip` (build tools), `psutil`, `vdf` + +Then build: +```bash +cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_PLUGIN_PYTHON=ON +cmake --build build --parallel +``` + +Note: `python-sip` (the build tools package providing `sipbuild`) is required in addition to `python-pyqt6-sip` (the runtime module). If you see `ModuleNotFoundError: No module named 'sipbuild'`, install `python-sip`. + ## Known Limitations - Some third-party MO2 plugins are Windows-only and will fail on Linux (for example DLL/ctypes `windll` assumptions). @@ -55,4 +94,3 @@ libs/ MO2 sub-libraries src/ Main organizer source docs/ Notes and tracking ``` - diff --git a/build-flatpak.sh b/build-flatpak.sh new file mode 100755 index 0000000..68e58a5 --- /dev/null +++ b/build-flatpak.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +# build-flatpak.sh — Build the Flatpak package. +# Usage: ./build-flatpak.sh [install|bundle] (default: install) +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +MODE="${1:-install}" + +exec bash "${SCRIPT_DIR}/flatpak/flatpak-install.sh" "${MODE}" diff --git a/build-native.sh b/build-native.sh new file mode 100755 index 0000000..32dccab --- /dev/null +++ b/build-native.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash +# build-native.sh — Build and install Fluorine Manager natively (non-Flatpak). +# Uses a container to compile, then installs to ~/.local/share/fluorine/. +# Override container engine with CONTAINER_ENGINE=docker. +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +IMAGE_NAME="fluorine-builder" +CONTAINER_ENGINE="${CONTAINER_ENGINE:-podman}" +INSTALL_DIR="${HOME}/.local/share/fluorine" +STAGING="${SCRIPT_DIR}/build-container/staging" + +# ── Build the container image if it doesn't exist ── +if ! ${CONTAINER_ENGINE} image exists "${IMAGE_NAME}" 2>/dev/null; then + echo "Building ${IMAGE_NAME} image (one-time)..." + ${CONTAINER_ENGINE} build -t "${IMAGE_NAME}" -f "${SCRIPT_DIR}/docker/Dockerfile" "${SCRIPT_DIR}/docker" +fi + +# ── Ensure build dir exists (Podman needs it before mounting) ── +mkdir -p "${SCRIPT_DIR}/build-container" + +# ── Run the build inside the container ── +echo "Building Fluorine Manager inside container..." +${CONTAINER_ENGINE} run --rm \ + -v "${SCRIPT_DIR}:/src:Z" \ + -v "${SCRIPT_DIR}/build-container:/src/build:Z" \ + -w /src \ + "${IMAGE_NAME}" \ + bash docker/build-inner.sh + +if [ ! -d "${STAGING}" ]; then + echo "ERROR: Staging directory not found at ${STAGING}" + exit 1 +fi + +# ── Install to ~/.local/share/fluorine/ ── +echo "" +echo "Installing to ${INSTALL_DIR}/ ..." +mkdir -p "${INSTALL_DIR}" + +# Remove dangling symlinks left by the Flatpak wrapper (they point to /app/ +# which doesn't exist outside the sandbox) and stale symlinks that conflict +# with directories from the staging area. +find "${INSTALL_DIR}" -maxdepth 3 -type l ! -exec test -e {} \; -delete 2>/dev/null || true +for d in plugins/libs plugins/dlls plugins/data; do + [ -L "${INSTALL_DIR}/${d}" ] && rm -f "${INSTALL_DIR}/${d}" +done + +# Copy all files, preserving structure. Existing user data (Prefix/, logs/, +# config/, instances) won't be touched because they're in subdirs that the +# staging area doesn't contain. +cp -af "${STAGING}/." "${INSTALL_DIR}/" + +# ── Desktop entry ── +DESKTOP_DIR="${HOME}/.local/share/applications" +mkdir -p "${DESKTOP_DIR}" +cat > "${DESKTOP_DIR}/fluorine-manager.desktop" </dev/null | sort -rh +echo "" +echo "Fluorine Manager installed to: ${INSTALL_DIR}/" +echo "Run with: fluorine-manager (or find it in your app launcher)" diff --git a/build.sh b/build.sh deleted file mode 100755 index 2d0d4d0..0000000 --- a/build.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -IMAGE_NAME="fluorine-builder" -CONTAINER_ENGINE="${CONTAINER_ENGINE:-podman}" - -# ── Build the container image if it doesn't exist ── -if ! ${CONTAINER_ENGINE} image exists "${IMAGE_NAME}" 2>/dev/null; then - echo "Building ${IMAGE_NAME} image (one-time)..." - ${CONTAINER_ENGINE} build -t "${IMAGE_NAME}" -f "${SCRIPT_DIR}/docker/Dockerfile" "${SCRIPT_DIR}/docker" -fi - -# ── Ensure build dir exists (Podman needs it before mounting) ── -mkdir -p "${SCRIPT_DIR}/build-container" - -# ── Run the build inside the container ── -echo "Building ModOrganizer inside container..." -${CONTAINER_ENGINE} run --rm \ - -v "${SCRIPT_DIR}:/src:Z" \ - -v "${SCRIPT_DIR}/build-container:/src/build:Z" \ - -w /src \ - "${IMAGE_NAME}" \ - bash docker/build-inner.sh - -echo "" -echo "Portable ZIP built: ${SCRIPT_DIR}/build-container/ModOrganizer-linux-x86_64.zip" diff --git a/docker/Dockerfile b/docker/Dockerfile index fc62dc8..d6b38ce 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,6 +1,7 @@ FROM ubuntu:25.10 ENV DEBIAN_FRONTEND=noninteractive +ENV LANG=C.UTF-8 ENV RUSTUP_HOME=/opt/rust/rustup ENV CARGO_HOME=/opt/rust/cargo ENV PATH="/opt/rust/cargo/bin:${PATH}" diff --git a/docker/build-inner.sh b/docker/build-inner.sh index a96cf98..962898b 100755 --- a/docker/build-inner.sh +++ b/docker/build-inner.sh @@ -18,10 +18,9 @@ RUNDIR="build/src/src" PY_MM="$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')" -# ── Output layout ── -OUT_DIR="/src/build/ModOrganizer-portable" -ZIP_OUT="/src/build/ModOrganizer-linux-x86_64.zip" -rm -rf "${OUT_DIR}" "${ZIP_OUT}" +# ── Output layout (staging area — installed to ~/.local/share/fluorine by build-native.sh) ── +OUT_DIR="/src/build/staging" +rm -rf "${OUT_DIR}" mkdir -p "${OUT_DIR}/plugins" "${OUT_DIR}/dlls" "${OUT_DIR}/lib" # ── Main binary + helpers ── @@ -238,10 +237,11 @@ if ! PYTHONHOME="${OUT_DIR}/python" \ fi # ── Launcher script ── -cat > "${OUT_DIR}/ModOrganizer" <<'LAUNCH' +cat > "${OUT_DIR}/fluorine-manager" <<'LAUNCH' #!/usr/bin/env bash set -euo pipefail -HERE="$(cd "$(dirname "$0")" && pwd)" +SELF="$(readlink -f "$0")" +HERE="$(cd "$(dirname "$SELF")" && pwd)" export PATH="${HERE}:${PATH}" export LD_LIBRARY_PATH="${HERE}/lib:${HERE}/python/lib:${LD_LIBRARY_PATH:-}" export MO2_BASE_DIR="${HERE}" @@ -284,18 +284,11 @@ fi cd "${HERE}" exec env PYTHONHOME="${MO2_PYTHONHOME}" "${HERE}/ModOrganizer-core" "$@" LAUNCH -chmod +x "${OUT_DIR}/ModOrganizer" - -# ── ZIP ── -( - cd "${OUT_DIR}" - zip -r -9 "${ZIP_OUT}" . -) +chmod +x "${OUT_DIR}/fluorine-manager" # ── Summary ── echo "" -echo "=== Package Summary ===" +echo "=== Build Summary ===" du -sh "${OUT_DIR}"/*/ "${OUT_DIR}"/ModOrganizer-core 2>/dev/null | sort -rh echo "" -ZIP_SIZE="$(du -sh "${ZIP_OUT}" | cut -f1)" -echo "Done! Portable ZIP at: build/ModOrganizer-linux-x86_64.zip (${ZIP_SIZE})" +echo "Staging complete." diff --git a/flatpak/com.fluorine.manager.yml b/flatpak/com.fluorine.manager.yml index 834d8e9..a8ad61c 100644 --- a/flatpak/com.fluorine.manager.yml +++ b/flatpak/com.fluorine.manager.yml @@ -19,6 +19,7 @@ finish-args: - --filesystem=~/.steam:ro - --filesystem=~/.local/share/Steam - --filesystem=~/.var/app/com.valvesoftware.Steam:ro + - --filesystem=/usr/share/steam:ro - --talk-name=org.freedesktop.Flatpak cleanup: @@ -228,8 +229,6 @@ modules: cmake -S . -B _build -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_PREFIX_PATH=/app \ - -DPython3_ROOT_DIR=/app/lib/fluorine/python \ - -DPython3_EXECUTABLE=/app/lib/fluorine/python/bin/python3 \ -DBUILD_PLUGIN_PYTHON=ON - cmake --build _build --parallel diff --git a/flatpak/fluorine-manager-wrapper.sh b/flatpak/fluorine-manager-wrapper.sh index fabe535..2e05fcb 100755 --- a/flatpak/fluorine-manager-wrapper.sh +++ b/flatpak/fluorine-manager-wrapper.sh @@ -27,7 +27,8 @@ if [ -n "$INSTANCE_DIR" ] && [ -d "$INSTANCE_DIR" ]; then USER_DIR="$INSTANCE_DIR" else # Global instance: shared overlay at ~/.local/share/fluorine/ - USER_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/fluorine" + # Use $HOME directly to bypass Flatpak's XDG_DATA_HOME remapping. + USER_DIR="$HOME/.local/share/fluorine" fi # ── Create writable overlay with symlinks to bundled files ── @@ -78,7 +79,7 @@ setup_overlay() { # VFS helper must be a real binary copy (not a symlink to /app/) because # it runs on the host via flatpak-spawn --host, where /app/ doesn't exist. # libfuse3 is statically linked, so no extra .so files needed. - VFS_HELPER_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/fluorine/bin" + VFS_HELPER_DIR="$HOME/.local/share/fluorine/bin" if [ -e "${BUNDLED}/mo2-vfs-helper" ]; then mkdir -p "${VFS_HELPER_DIR}" cp -f "${BUNDLED}/mo2-vfs-helper" "${VFS_HELPER_DIR}/mo2-vfs-helper" diff --git a/libs/7zip/CMakeLists.txt b/libs/7zip/CMakeLists.txt index 915be26..5db6f0a 100644 --- a/libs/7zip/CMakeLists.txt +++ b/libs/7zip/CMakeLists.txt @@ -443,7 +443,7 @@ target_compile_options(7z PRIVATE -Wno-unused-parameter -Wno-sign-compare -Wno-missing-field-initializers - -Wno-reorder + $<$:-Wno-reorder> ) target_link_libraries(7z PRIVATE pthread dl) diff --git a/libs/basic_games/basic_game.py b/libs/basic_games/basic_game.py index b3ff73d..1047bbf 100644 --- a/libs/basic_games/basic_game.py +++ b/libs/basic_games/basic_game.py @@ -31,11 +31,11 @@ def _find_wine_userprofile() -> str | None: candidates: list[str] = [] - # 1. Flatpak data prefix (most common for Fluorine) - flatpak_pfx = os.path.expanduser( - "~/.var/app/com.fluorine.manager/Prefix/pfx" + # 1. Fluorine data prefix (shared by native and Flatpak builds) + fluorine_pfx = os.path.expanduser( + "~/.local/share/fluorine/Prefix/pfx" ) - candidates.append(flatpak_pfx) + candidates.append(fluorine_pfx) # 2. Fluorine config prefix_path try: diff --git a/libs/bsa_ffi/src/archive/mod.rs b/libs/bsa_ffi/src/archive/mod.rs index 247789e..27b1db1 100644 --- a/libs/bsa_ffi/src/archive/mod.rs +++ b/libs/bsa_ffi/src/archive/mod.rs @@ -201,6 +201,7 @@ pub enum GameVersion { StarfieldV3, } +#[allow(dead_code)] impl GameVersion { /// Get display name for this game version pub fn display_name(&self) -> &'static str { @@ -353,6 +354,7 @@ impl GameVersion { } /// Detect game version from archive format +#[allow(dead_code)] pub fn detect_game_version(archive_path: &Path) -> Option { match detect_format(archive_path) { Some(ArchiveFormat::Tes3Bsa) => Some(GameVersion::Morrowind), diff --git a/libs/bsapacker/src/qlibbsarch/QLibbsarch.h b/libs/bsapacker/src/qlibbsarch/QLibbsarch.h index 7af53f4..9e37e05 100644 --- a/libs/bsapacker/src/qlibbsarch/QLibbsarch.h +++ b/libs/bsapacker/src/qlibbsarch/QLibbsarch.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -23,7 +24,9 @@ namespace QLibBsarch { if (result.code == BSA_RESULT_EXCEPTION) { - const std::string &error = QLibBsarch::wcharToString(result.text); + wchar_t aligned_text[1024]; + std::memcpy(aligned_text, result.text, sizeof(aligned_text)); + const std::string error = QLibBsarch::wcharToString(aligned_text); LOG_LIBBSARCH << QString::fromStdString(error); throw std::runtime_error(error); } @@ -31,12 +34,7 @@ namespace QLibBsarch inline void checkResult(const bsa_result_message_buffer_s &result) { - if (result.message.code == BSA_RESULT_EXCEPTION) - { - const std::string &error = QLibBsarch::wcharToString(result.message.text); - LOG_LIBBSARCH << QString::fromStdString(error); - throw std::runtime_error(error); - } + checkResult(result.message); } } // namespace QLibBsarch diff --git a/libs/bsatk/src/CMakeLists.txt b/libs/bsatk/src/CMakeLists.txt index 99a0f26..d9b7be1 100644 --- a/libs/bsatk/src/CMakeLists.txt +++ b/libs/bsatk/src/CMakeLists.txt @@ -1,4 +1,7 @@ cmake_minimum_required(VERSION 3.16) +if(POLICY CMP0167) + cmake_policy(SET CMP0167 NEW) +endif() find_package(Boost REQUIRED COMPONENTS thread) find_package(ZLIB REQUIRED) diff --git a/libs/bsatk/src/bsaarchive.cpp b/libs/bsatk/src/bsaarchive.cpp index e1655c1..4ae94a6 100644 --- a/libs/bsatk/src/bsaarchive.cpp +++ b/libs/bsatk/src/bsaarchive.cpp @@ -446,11 +446,9 @@ EErrorCode Archive::write(const char* fileName) writeHeader(outfile, determineFileFlags(fileNames), static_cast(folderNames.size()), folderNamesLength, fileNamesLength); -#pragma message("folders (and files?) need to be sorted by hash!") // dummy-pass: before we can store the actual folder data // prepare folder and file headers -#pragma message("it's unnecessary to write actual data, placeholders are sufficient") for (std::vector::const_iterator folderIter = folders.begin(); folderIter != folders.end(); ++folderIter) { (*folderIter)->writeHeader(outfile); @@ -673,7 +671,6 @@ EErrorCode Archive::extractDirect(File::Ptr file, std::ofstream& outFile) const if (namePrefixed()) { std::string fullName = readBString(m_File); if (size <= fullName.length()) { -#pragma message("report error!") return result; } size -= fullName.length() + 1; @@ -735,7 +732,6 @@ std::shared_ptr Archive::decompress(unsigned char* inBuffer, stream.next_out = reinterpret_cast(outBuffer.get()); zlibRet = inflate(&stream, Z_NO_FLUSH); if ((zlibRet != Z_OK) && (zlibRet != Z_STREAM_END) && (zlibRet != Z_BUF_ERROR)) { -#pragma message("pass result code to caller") throw std::runtime_error("invalid data"); } } while (stream.avail_out == 0); @@ -824,7 +820,6 @@ EErrorCode Archive::extractCompressed(File::Ptr file, std::ofstream& outFile) co if (namePrefixed()) { std::string fullName = readBString(m_File); if (inSize <= fullName.length()) { -#pragma message("report error!") return result; } inSize -= fullName.length() + 1; @@ -850,7 +845,6 @@ EErrorCode Archive::extractCompressed(File::Ptr file, std::ofstream& outFile) co if (namePrefixed()) { std::string fullName = readBString(m_File); if (inSize <= fullName.length()) { -#pragma message("report error!") return result; } inSize -= fullName.length() + 1; @@ -907,7 +901,6 @@ void Archive::readFiles(std::queue& queue, boost::mutex& mutex, if (namePrefixed()) { std::string fullName = readBString(m_File); if (size <= fullName.length()) { -#pragma message("report error!") continue; } size -= fullName.length() + 1; @@ -954,7 +947,6 @@ void Archive::readFiles(std::queue& queue, boost::mutex& mutex, reinterpret_cast(unpackedChunk.get()), length); unpackedChunk.reset(); } catch (const std::exception&) { -#pragma message("report error!") continue; } } else { @@ -1029,7 +1021,6 @@ void Archive::extractFiles(const std::string& targetDirectory, fstream::out | fstream::binary | fstream::trunc); if (!outputFile.is_open()) { -#pragma message("report error!") continue; // return ERROR_ACCESSFAILED; } @@ -1052,7 +1043,6 @@ void Archive::extractFiles(const std::string& targetDirectory, buffer.reset(); } } catch (const std::exception&) { -#pragma message("report error!") dataBuffer.first.reset(); fileInfo.data.first.reset(); continue; @@ -1121,7 +1111,6 @@ void Archive::extractFiles(const std::string& targetDirectory, dataBuffer.second); } } catch (const std::exception&) { -#pragma message("report error!") dataBuffer.first.reset(); fileInfo.data.first.reset(); continue; @@ -1151,7 +1140,6 @@ EErrorCode Archive::extractAll( const std::function& progress, bool overwrite) { -#pragma message("report errors") createFolders(outputDirectory, m_RootFolder); std::vector fileList; diff --git a/libs/bsatk/src/bsafile.cpp b/libs/bsatk/src/bsafile.cpp index db4e4e6..3179c63 100644 --- a/libs/bsatk/src/bsafile.cpp +++ b/libs/bsatk/src/bsafile.cpp @@ -104,7 +104,6 @@ EErrorCode File::writeData(fstream& sourceArchive, fstream& targetArchive) const if (m_SourceFile.length() == 0) { // copy from source archive -#pragma message("we may have to compress/decompress!") sourceArchive.seekg(m_DataOffset, fstream::beg); try { diff --git a/libs/game_bethesda/src/gamebryo/dummybsa.cpp b/libs/game_bethesda/src/gamebryo/dummybsa.cpp index 7b0f877..3fbd2ef 100644 --- a/libs/game_bethesda/src/gamebryo/dummybsa.cpp +++ b/libs/game_bethesda/src/gamebryo/dummybsa.cpp @@ -19,6 +19,7 @@ along with Mod Organizer. If not, see . #include "dummybsa.h" #include +#include #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include @@ -184,7 +185,10 @@ void DummyBSA::writeFileRecordBlocks(QFile& file, const std::string& folderName) void DummyBSA::write(const QString& fileName) { QFile file(fileName); - file.open(QIODevice::WriteOnly); + if (!file.open(QIODevice::WriteOnly)) { + throw std::runtime_error( + QString("failed to open dummy BSA for writing: %1").arg(fileName).toStdString()); + } m_TotalFileNameLength = static_cast(m_FileName.length() + 1); diff --git a/libs/game_bethesda/src/gamebryo/gamebryosavegame.cpp b/libs/game_bethesda/src/gamebryo/gamebryosavegame.cpp index 523df0d..7625af3 100644 --- a/libs/game_bethesda/src/gamebryo/gamebryosavegame.cpp +++ b/libs/game_bethesda/src/gamebryo/gamebryosavegame.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #ifdef _WIN32 #include @@ -90,7 +91,7 @@ void GamebryoSaveGame::setCreationTime(_SYSTEMTIME const& ctime) QTime time; time.setHMS(ctime.wHour, ctime.wMinute, ctime.wSecond, ctime.wMilliseconds); - m_CreationTime = QDateTime(date, time, Qt::UTC); + m_CreationTime = QDateTime(date, time, QTimeZone::UTC); } GamebryoSaveGame::FileWrapper::FileWrapper(QString const& filepath, diff --git a/libs/game_bethesda/src/gamebryo/gamegamebryo.cpp b/libs/game_bethesda/src/gamebryo/gamegamebryo.cpp index caeeed0..1bcc2c0 100644 --- a/libs/game_bethesda/src/gamebryo/gamegamebryo.cpp +++ b/libs/game_bethesda/src/gamebryo/gamegamebryo.cpp @@ -495,7 +495,11 @@ void GameGamebryo::copyToProfile(QString const& sourcePath, sourceFile = resolveFileCaseInsensitive(sourceFile); if (!MOBase::shellCopy(sourceFile, filePath)) { // if copy file fails, create the file empty - QFile(filePath).open(QIODevice::WriteOnly); + QFile outputFile(filePath); + if (!outputFile.open(QIODevice::WriteOnly)) { + MOBase::log::warn("Failed to create fallback file '{}': {}", filePath, + outputFile.errorString()); + } } } } diff --git a/libs/game_bethesda/src/games/enderal/enderalsavegame.cpp b/libs/game_bethesda/src/games/enderal/enderalsavegame.cpp index cd9beb3..235bf1c 100644 --- a/libs/game_bethesda/src/games/enderal/enderalsavegame.cpp +++ b/libs/game_bethesda/src/games/enderal/enderalsavegame.cpp @@ -4,6 +4,7 @@ #include #else #include +#include using SYSTEMTIME = _SYSTEMTIME; @@ -14,7 +15,7 @@ static void FileTimeToSystemTime(const FILETIME* ft, SYSTEMTIME* st) const int64_t unixSecs = static_cast(ticks / 10000000ULL) - 11644473600LL; const uint64_t remainderHns = ticks % 10000000ULL; - const QDateTime dt = QDateTime::fromSecsSinceEpoch(unixSecs, Qt::UTC); + const QDateTime dt = QDateTime::fromSecsSinceEpoch(unixSecs, QTimeZone::UTC); const QDate d = dt.date(); const QTime t = dt.time(); diff --git a/libs/game_bethesda/src/games/enderalse/enderalsesavegame.cpp b/libs/game_bethesda/src/games/enderalse/enderalsesavegame.cpp index 2662505..365ca37 100644 --- a/libs/game_bethesda/src/games/enderalse/enderalsesavegame.cpp +++ b/libs/game_bethesda/src/games/enderalse/enderalsesavegame.cpp @@ -4,6 +4,7 @@ #include #else #include +#include union _ULARGE_INTEGER { struct { @@ -22,7 +23,7 @@ static void FileTimeToSystemTime(const FILETIME* ft, SYSTEMTIME* st) const int64_t unixSecs = static_cast(ticks / 10000000ULL) - 11644473600LL; const uint64_t remainderHns = ticks % 10000000ULL; - const QDateTime dt = QDateTime::fromSecsSinceEpoch(unixSecs, Qt::UTC); + const QDateTime dt = QDateTime::fromSecsSinceEpoch(unixSecs, QTimeZone::UTC); const QDate d = dt.date(); const QTime t = dt.time(); diff --git a/libs/game_bethesda/src/games/fallout4/fallout4savegame.cpp b/libs/game_bethesda/src/games/fallout4/fallout4savegame.cpp index 2f88d1b..17863ad 100644 --- a/libs/game_bethesda/src/games/fallout4/fallout4savegame.cpp +++ b/libs/game_bethesda/src/games/fallout4/fallout4savegame.cpp @@ -4,6 +4,7 @@ #include #else #include +#include using SYSTEMTIME = _SYSTEMTIME; @@ -14,7 +15,7 @@ static void FileTimeToSystemTime(const FILETIME* ft, SYSTEMTIME* st) const int64_t unixSecs = static_cast(ticks / 10000000ULL) - 11644473600LL; const uint64_t remainderHns = ticks % 10000000ULL; - const QDateTime dt = QDateTime::fromSecsSinceEpoch(unixSecs, Qt::UTC); + const QDateTime dt = QDateTime::fromSecsSinceEpoch(unixSecs, QTimeZone::UTC); const QDate d = dt.date(); const QTime t = dt.time(); diff --git a/libs/game_bethesda/src/games/fallout4london/fo4londonsavegame.cpp b/libs/game_bethesda/src/games/fallout4london/fo4londonsavegame.cpp index ead2d1a..c87d107 100644 --- a/libs/game_bethesda/src/games/fallout4london/fo4londonsavegame.cpp +++ b/libs/game_bethesda/src/games/fallout4london/fo4londonsavegame.cpp @@ -4,6 +4,7 @@ #include #else #include +#include using SYSTEMTIME = _SYSTEMTIME; @@ -14,7 +15,7 @@ static void FileTimeToSystemTime(const FILETIME* ft, SYSTEMTIME* st) const int64_t unixSecs = static_cast(ticks / 10000000ULL) - 11644473600LL; const uint64_t remainderHns = ticks % 10000000ULL; - const QDateTime dt = QDateTime::fromSecsSinceEpoch(unixSecs, Qt::UTC); + const QDateTime dt = QDateTime::fromSecsSinceEpoch(unixSecs, QTimeZone::UTC); const QDate d = dt.date(); const QTime t = dt.time(); diff --git a/libs/game_bethesda/src/games/fallout4vr/fallout4vrsavegame.cpp b/libs/game_bethesda/src/games/fallout4vr/fallout4vrsavegame.cpp index cf29aae..d29c4fa 100644 --- a/libs/game_bethesda/src/games/fallout4vr/fallout4vrsavegame.cpp +++ b/libs/game_bethesda/src/games/fallout4vr/fallout4vrsavegame.cpp @@ -4,6 +4,7 @@ #include #else #include +#include using SYSTEMTIME = _SYSTEMTIME; @@ -14,7 +15,7 @@ static void FileTimeToSystemTime(const FILETIME* ft, SYSTEMTIME* st) const int64_t unixSecs = static_cast(ticks / 10000000ULL) - 11644473600LL; const uint64_t remainderHns = ticks % 10000000ULL; - const QDateTime dt = QDateTime::fromSecsSinceEpoch(unixSecs, Qt::UTC); + const QDateTime dt = QDateTime::fromSecsSinceEpoch(unixSecs, QTimeZone::UTC); const QDate d = dt.date(); const QTime t = dt.time(); diff --git a/libs/game_bethesda/src/games/fallout76/fallout76savegame.cpp b/libs/game_bethesda/src/games/fallout76/fallout76savegame.cpp index 66e8a00..c070429 100644 --- a/libs/game_bethesda/src/games/fallout76/fallout76savegame.cpp +++ b/libs/game_bethesda/src/games/fallout76/fallout76savegame.cpp @@ -4,6 +4,7 @@ #include #else #include +#include using SYSTEMTIME = _SYSTEMTIME; @@ -14,7 +15,7 @@ static void FileTimeToSystemTime(const FILETIME* ft, SYSTEMTIME* st) const int64_t unixSecs = static_cast(ticks / 10000000ULL) - 11644473600LL; const uint64_t remainderHns = ticks % 10000000ULL; - const QDateTime dt = QDateTime::fromSecsSinceEpoch(unixSecs, Qt::UTC); + const QDateTime dt = QDateTime::fromSecsSinceEpoch(unixSecs, QTimeZone::UTC); const QDate d = dt.date(); const QTime t = dt.time(); diff --git a/libs/game_bethesda/src/games/skyrim/skyrimsavegame.cpp b/libs/game_bethesda/src/games/skyrim/skyrimsavegame.cpp index bd16036..621692e 100644 --- a/libs/game_bethesda/src/games/skyrim/skyrimsavegame.cpp +++ b/libs/game_bethesda/src/games/skyrim/skyrimsavegame.cpp @@ -4,6 +4,7 @@ #include #else #include +#include // SYSTEMTIME is _SYSTEMTIME (defined in gamebryosavegame.h for Linux) using SYSTEMTIME = _SYSTEMTIME; @@ -16,7 +17,7 @@ static void FileTimeToSystemTime(const FILETIME* ft, SYSTEMTIME* st) const int64_t unixSecs = static_cast(ticks / 10000000ULL) - 11644473600LL; const uint64_t remainderHns = ticks % 10000000ULL; // leftover 100ns units - const QDateTime dt = QDateTime::fromSecsSinceEpoch(unixSecs, Qt::UTC); + const QDateTime dt = QDateTime::fromSecsSinceEpoch(unixSecs, QTimeZone::UTC); const QDate d = dt.date(); const QTime t = dt.time(); diff --git a/libs/game_bethesda/src/games/skyrimse/skyrimsesavegame.cpp b/libs/game_bethesda/src/games/skyrimse/skyrimsesavegame.cpp index d68ba19..59b9b6e 100644 --- a/libs/game_bethesda/src/games/skyrimse/skyrimsesavegame.cpp +++ b/libs/game_bethesda/src/games/skyrimse/skyrimsesavegame.cpp @@ -4,6 +4,7 @@ #include #else #include +#include // Windows ULARGE_INTEGER union for 64-bit FILETIME math union _ULARGE_INTEGER { @@ -29,7 +30,7 @@ static void FileTimeToSystemTime(const FILETIME* ft, SYSTEMTIME* st) int64_t unixSecs = static_cast(ull.QuadPart / 10000000ULL) - 11644473600LL; uint64_t remainderHns = ull.QuadPart % 10000000ULL; // leftover 100ns units - QDateTime dt = QDateTime::fromSecsSinceEpoch(unixSecs, Qt::UTC); + QDateTime dt = QDateTime::fromSecsSinceEpoch(unixSecs, QTimeZone::UTC); QDate d = dt.date(); QTime t = dt.time(); diff --git a/libs/game_bethesda/src/games/skyrimvr/skyrimvrsavegame.cpp b/libs/game_bethesda/src/games/skyrimvr/skyrimvrsavegame.cpp index 1670dc8..75b3683 100644 --- a/libs/game_bethesda/src/games/skyrimvr/skyrimvrsavegame.cpp +++ b/libs/game_bethesda/src/games/skyrimvr/skyrimvrsavegame.cpp @@ -4,6 +4,7 @@ #include #else #include +#include // Windows ULARGE_INTEGER union for 64-bit FILETIME math union _ULARGE_INTEGER { @@ -25,7 +26,7 @@ static void FileTimeToSystemTime(const FILETIME* ft, SYSTEMTIME* st) const int64_t unixSecs = static_cast(ticks / 10000000ULL) - 11644473600LL; const uint64_t remainderHns = ticks % 10000000ULL; // leftover 100ns units - const QDateTime dt = QDateTime::fromSecsSinceEpoch(unixSecs, Qt::UTC); + const QDateTime dt = QDateTime::fromSecsSinceEpoch(unixSecs, QTimeZone::UTC); const QDate d = dt.date(); const QTime t = dt.time(); diff --git a/libs/game_bethesda/src/games/starfield/starfieldsavegame.cpp b/libs/game_bethesda/src/games/starfield/starfieldsavegame.cpp index 49653cb..9640b75 100644 --- a/libs/game_bethesda/src/games/starfield/starfieldsavegame.cpp +++ b/libs/game_bethesda/src/games/starfield/starfieldsavegame.cpp @@ -4,6 +4,7 @@ #include #else #include +#include using SYSTEMTIME = _SYSTEMTIME; @@ -14,7 +15,7 @@ static void FileTimeToSystemTime(const FILETIME* ft, SYSTEMTIME* st) const int64_t unixSecs = static_cast(ticks / 10000000ULL) - 11644473600LL; const uint64_t remainderHns = ticks % 10000000ULL; - const QDateTime dt = QDateTime::fromSecsSinceEpoch(unixSecs, Qt::UTC); + const QDateTime dt = QDateTime::fromSecsSinceEpoch(unixSecs, QTimeZone::UTC); const QDate d = dt.date(); const QTime t = dt.time(); diff --git a/libs/installer_fomod/src/fomodinstallerdialog.cpp b/libs/installer_fomod/src/fomodinstallerdialog.cpp index 0fa6bfc..f675e3d 100644 --- a/libs/installer_fomod/src/fomodinstallerdialog.cpp +++ b/libs/installer_fomod/src/fomodinstallerdialog.cpp @@ -127,11 +127,15 @@ int FomodInstallerDialog::bomOffset(const QByteArray& buffer) static const unsigned char BOM_UTF8[] = {0xEF, 0xBB, 0xBF}; static const unsigned char BOM_UTF16BE[] = {0xFE, 0xFF}; static const unsigned char BOM_UTF16LE[] = {0xFF, 0xFE}; + auto startsWithBytes = [&buffer](const unsigned char* bytes, qsizetype size) { + return buffer.startsWith( + QByteArrayView(reinterpret_cast(bytes), size)); + }; - if (buffer.startsWith(reinterpret_cast(BOM_UTF8))) + if (startsWithBytes(BOM_UTF8, sizeof(BOM_UTF8))) return 3; - if (buffer.startsWith(reinterpret_cast(BOM_UTF16BE)) || - buffer.startsWith(reinterpret_cast(BOM_UTF16LE))) + if (startsWithBytes(BOM_UTF16BE, sizeof(BOM_UTF16BE)) || + startsWithBytes(BOM_UTF16LE, sizeof(BOM_UTF16LE))) return 2; return 0; @@ -150,25 +154,30 @@ QByteArray skipXmlHeader(QIODevice& file) static const unsigned char UTF16LE[] = {0x3C, 0x00, 0x3F, 0x00}; static const unsigned char UTF16BE[] = {0x00, 0x3C, 0x00, 0x3F}; static const unsigned char UTF8[] = {0x3C, 0x3F, 0x78, 0x6D}; + auto startsWithBytes = [](const QByteArray& bytes, const unsigned char* prefix, + qsizetype size) { + return bytes.startsWith( + QByteArrayView(reinterpret_cast(prefix), size)); + }; file.seek(0); QByteArray rawBytes = file.read(4); QTextStream stream(&file); int bom = 0; - if (rawBytes.startsWith((const char*)UTF16LE_BOM)) { + if (startsWithBytes(rawBytes, UTF16LE_BOM, sizeof(UTF16LE_BOM))) { stream.setEncoding(QStringConverter::Encoding::Utf16LE); bom = 2; - } else if (rawBytes.startsWith((const char*)UTF16BE_BOM)) { + } else if (startsWithBytes(rawBytes, UTF16BE_BOM, sizeof(UTF16BE_BOM))) { stream.setEncoding(QStringConverter::Encoding::Utf16BE); bom = 2; - } else if (rawBytes.startsWith((const char*)UTF8_BOM)) { + } else if (startsWithBytes(rawBytes, UTF8_BOM, sizeof(UTF8_BOM))) { stream.setEncoding(QStringConverter::Encoding::Utf8); bom = 3; - } else if (rawBytes.startsWith(QByteArray((const char*)UTF16LE, 4))) { + } else if (startsWithBytes(rawBytes, UTF16LE, sizeof(UTF16LE))) { stream.setEncoding(QStringConverter::Encoding::Utf16LE); - } else if (rawBytes.startsWith(QByteArray((const char*)UTF16BE, 4))) { + } else if (startsWithBytes(rawBytes, UTF16BE, sizeof(UTF16BE))) { stream.setEncoding(QStringConverter::Encoding::Utf16BE); - } else if (rawBytes.startsWith(QByteArray((const char*)UTF8, 4))) { + } else if (startsWithBytes(rawBytes, UTF8, sizeof(UTF8))) { stream.setEncoding(QStringConverter::Encoding::Utf8); } // otherwise maybe the textstream knows the encoding? diff --git a/libs/installer_fomod_plus/installer/CMakeLists.txt b/libs/installer_fomod_plus/installer/CMakeLists.txt index 70018ff..07a670b 100644 --- a/libs/installer_fomod_plus/installer/CMakeLists.txt +++ b/libs/installer_fomod_plus/installer/CMakeLists.txt @@ -29,7 +29,7 @@ FetchContent_Declare( -P ${CMAKE_CURRENT_LIST_DIR}/../cmake/patch_pugixml.cmake ) -FetchContent_Declare(json URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz) +FetchContent_Declare(json URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz DOWNLOAD_EXTRACT_TIMESTAMP TRUE) FetchContent_MakeAvailable(pugixml json) set(project_type plugin) diff --git a/libs/installer_fomod_plus/patchwizard/CMakeLists.txt b/libs/installer_fomod_plus/patchwizard/CMakeLists.txt index be46b32..2cb8ebb 100644 --- a/libs/installer_fomod_plus/patchwizard/CMakeLists.txt +++ b/libs/installer_fomod_plus/patchwizard/CMakeLists.txt @@ -14,7 +14,7 @@ file(GLOB SHARE_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/../share/**/*.cpp") add_library(fomod_plus_patch_wizard SHARED ${PATCHWIZARD_SOURCES} ${SHARE_SOURCES}) -FetchContent_Declare(json URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz) +FetchContent_Declare(json URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz DOWNLOAD_EXTRACT_TIMESTAMP TRUE) FetchContent_Declare(pugixml GIT_REPOSITORY https://github.com/zeux/pugixml GIT_TAG v1.14) FetchContent_MakeAvailable(pugixml json) diff --git a/libs/installer_fomod_plus/scanner/CMakeLists.txt b/libs/installer_fomod_plus/scanner/CMakeLists.txt index daea477..f50dea2 100644 --- a/libs/installer_fomod_plus/scanner/CMakeLists.txt +++ b/libs/installer_fomod_plus/scanner/CMakeLists.txt @@ -13,7 +13,7 @@ file(GLOB_RECURSE SCANNER_SOURCES CONFIGURE_DEPENDS add_library(fomod_plus_scanner SHARED ${SCANNER_SOURCES}) -FetchContent_Declare(json URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz) +FetchContent_Declare(json URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz DOWNLOAD_EXTRACT_TIMESTAMP TRUE) FetchContent_Declare(pugixml GIT_REPOSITORY https://github.com/zeux/pugixml GIT_TAG v1.14) FetchContent_MakeAvailable(pugixml json) diff --git a/libs/nak/src/deps/tools.rs b/libs/nak/src/deps/tools.rs index c97247c..0e3e356 100644 --- a/libs/nak/src/deps/tools.rs +++ b/libs/nak/src/deps/tools.rs @@ -1,7 +1,7 @@ //! Linux tool management (winetricks, cabextract) //! //! Handles downloading and managing Linux CLI tools. -//! Tools are stored in ~/.var/app/com.fluorine.manager/bin/ for Fluorine Manager. +//! Tools are stored in ~/.local/share/fluorine/bin/ for Fluorine Manager. use std::error::Error; use std::fs; @@ -13,14 +13,13 @@ use std::process::Command; use crate::logging::{log_error, log_info, log_warning}; // ============================================================================ -// NaK Bin Directory (~/.var/app/com.fluorine.manager/bin/) +// NaK Bin Directory (~/.local/share/fluorine/bin/) // ============================================================================ -/// Get the tool bin directory path (~/.var/app/com.fluorine.manager/bin/) +/// Get the tool bin directory path (~/.local/share/fluorine/bin/) /// This is accessible from both native and Flatpak environments. pub fn get_nak_bin_path() -> PathBuf { - let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string()); - PathBuf::from(home).join(".var/app/com.fluorine.manager/bin") + crate::paths::data_dir().join("bin") } /// Check if a command exists (either in system PATH or tool bin) diff --git a/libs/nak/src/dxvk.rs b/libs/nak/src/dxvk.rs index 97e4222..b335773 100644 --- a/libs/nak/src/dxvk.rs +++ b/libs/nak/src/dxvk.rs @@ -1,7 +1,7 @@ //! DXVK configuration management for Fluorine Manager. //! //! Downloads dxvk.conf from upstream, appends Fluorine-specific settings, -//! and stores at `~/.var/app/com.fluorine.manager/config/dxvk.conf`. +//! and stores at `~/.local/share/fluorine/config/dxvk.conf`. use std::error::Error; use std::fs; @@ -20,9 +20,7 @@ dxvk.enableGraphicsPipelineLibrary = False /// Get the path where the DXVK config will be stored. pub fn get_dxvk_conf_path() -> PathBuf { - let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string()); - PathBuf::from(home) - .join(".var/app/com.fluorine.manager/config/dxvk.conf") + crate::paths::data_dir().join("config/dxvk.conf") } /// Ensure the dxvk.conf file exists, downloading if necessary. diff --git a/libs/nak/src/lib.rs b/libs/nak/src/lib.rs index c93f07b..59f41c6 100644 --- a/libs/nak/src/lib.rs +++ b/libs/nak/src/lib.rs @@ -7,6 +7,7 @@ pub mod config; pub mod dxvk; pub mod game_finder; pub mod logging; +pub mod paths; pub mod runtime_wrap; pub mod steam; pub mod utils; diff --git a/libs/nak/src/logging.rs b/libs/nak/src/logging.rs index 8c0f91a..e506bf8 100644 --- a/libs/nak/src/logging.rs +++ b/libs/nak/src/logging.rs @@ -1,7 +1,7 @@ //! Logging for Fluorine Manager. //! //! All log messages are: -//! 1. Written to `~/.var/app/com.fluorine.manager/logs/nak.log` (always) +//! 1. Written to `~/.local/share/fluorine/logs/nak.log` (always) //! 2. Forwarded to an optional callback set via `set_log_callback()` (for MOBase::log) use std::fs; @@ -25,8 +25,7 @@ pub fn set_log_callback(cb: impl Fn(&str, &str) + Send + Sync + 'static) { /// Get the log directory path. fn log_dir() -> PathBuf { - let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string()); - PathBuf::from(home).join(".var/app/com.fluorine.manager/logs") + crate::paths::data_dir().join("logs") } /// Get the log file path. diff --git a/libs/nak/src/paths.rs b/libs/nak/src/paths.rs new file mode 100644 index 0000000..4b212b9 --- /dev/null +++ b/libs/nak/src/paths.rs @@ -0,0 +1,12 @@ +//! Shared data directory for Fluorine Manager. +//! +//! All data lives under `~/.local/share/fluorine/` — accessible from both +//! native and Flatpak builds (the Flatpak has `--filesystem=home`). + +use std::path::PathBuf; + +/// Returns the Fluorine data directory (`~/.local/share/fluorine`). +pub fn data_dir() -> PathBuf { + let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string()); + PathBuf::from(home).join(".local/share/fluorine") +} diff --git a/libs/nak/src/steam/proton.rs b/libs/nak/src/steam/proton.rs index 064fd96..4059dc4 100644 --- a/libs/nak/src/steam/proton.rs +++ b/libs/nak/src/steam/proton.rs @@ -56,8 +56,6 @@ pub fn find_steam_protons() -> Vec { return protons; }; - let is_flatpak = steam_path.to_string_lossy().contains(".var/app/com.valvesoftware.Steam"); - // 1. Steam's built-in Protons (steamapps/common/Proton*) protons.extend(find_builtin_protons(&steam_path)); @@ -65,11 +63,8 @@ pub fn find_steam_protons() -> Vec { protons.extend(find_custom_protons(&steam_path)); // 3. System-level Protons in /usr/share/steam/compatibilitytools.d/ - if is_flatpak { - crate::logging::log_info("Flatpak Steam detected - skipping system protons in /usr/share/steam/compatibilitytools.d/"); - } else { - protons.extend(find_system_protons()); - } + // (Arch packages Proton here; Flatpak has --filesystem=/usr/share/steam:ro) + protons.extend(find_system_protons()); // Filter to only include Proton 10+ (required for Steam-native integration) protons.retain(is_proton_10_or_newer); diff --git a/libs/plugin_python/src/mobase/CMakeLists.txt b/libs/plugin_python/src/mobase/CMakeLists.txt index 2fac52f..3e765dd 100644 --- a/libs/plugin_python/src/mobase/CMakeLists.txt +++ b/libs/plugin_python/src/mobase/CMakeLists.txt @@ -5,7 +5,7 @@ if(NOT TARGET mo2::uibase) find_package(mo2-uibase CONFIG REQUIRED) endif() -pybind11_add_module(mobase MODULE) +pybind11_add_module(mobase MODULE NO_EXTRAS) mo2_default_source_group() mo2_configure_target(mobase NO_SOURCES @@ -38,3 +38,8 @@ mo2_target_sources(mobase ./wrappers/wrappers.h ) target_link_libraries(mobase PRIVATE pybind11::qt pybind11::utils mo2::uibase Qt6::Core) +set_target_properties(mobase PROPERTIES INTERPROCEDURAL_OPTIMIZATION OFF) +if(NOT MSVC) + target_compile_options(mobase PRIVATE -fno-lto) + target_link_options(mobase PRIVATE -fno-lto) +endif() diff --git a/libs/plugin_python/src/mobase/wrappers/basic_classes.cpp b/libs/plugin_python/src/mobase/wrappers/basic_classes.cpp index ea69a46..f782099 100644 --- a/libs/plugin_python/src/mobase/wrappers/basic_classes.cpp +++ b/libs/plugin_python/src/mobase/wrappers/basic_classes.cpp @@ -575,10 +575,7 @@ namespace mo2::python { mo2::python::show_deprecation_warning( "appVersion", "IOrganizer::appVersion() is deprecated, use " "IOrganizer::version() instead."); -#pragma warning(push) -#pragma warning(disable : 4996) - return o.appVersion(); -#pragma warning(pop) + return o.version(); }) .def("version", &IOrganizer::version) .def("createMod", &IOrganizer::createMod, diff --git a/libs/plugin_python/src/pybind11-qt/CMakeLists.txt b/libs/plugin_python/src/pybind11-qt/CMakeLists.txt index cb742ad..82793cb 100644 --- a/libs/plugin_python/src/pybind11-qt/CMakeLists.txt +++ b/libs/plugin_python/src/pybind11-qt/CMakeLists.txt @@ -56,5 +56,8 @@ add_custom_command( add_dependencies(pybind11-qt PyQt6-siph) target_include_directories(pybind11-qt PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) +if(NOT MSVC) + target_compile_options(pybind11-qt PRIVATE -Wno-attributes) +endif() add_library(pybind11::qt ALIAS pybind11-qt) diff --git a/libs/plugin_python/src/pybind11-utils/CMakeLists.txt b/libs/plugin_python/src/pybind11-utils/CMakeLists.txt index 77895b6..72b0707 100644 --- a/libs/plugin_python/src/pybind11-utils/CMakeLists.txt +++ b/libs/plugin_python/src/pybind11-utils/CMakeLists.txt @@ -17,6 +17,11 @@ mo2_configure_target(pybind11-utils TRANSLATIONS OFF ) mo2_default_source_group() +set_target_properties(pybind11-utils PROPERTIES + AUTOMOC OFF + AUTOUIC OFF + AUTORCC OFF +) target_link_libraries(pybind11-utils PUBLIC pybind11::pybind11) target_include_directories(pybind11-utils PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) diff --git a/libs/plugin_python/src/runner/CMakeLists.txt b/libs/plugin_python/src/runner/CMakeLists.txt index d164460..9c90314 100644 --- a/libs/plugin_python/src/runner/CMakeLists.txt +++ b/libs/plugin_python/src/runner/CMakeLists.txt @@ -22,6 +22,9 @@ mo2_default_source_group() target_link_libraries(runner PUBLIC mo2::uibase PRIVATE pybind11::embed pybind11::qt) target_include_directories(runner PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_compile_definitions(runner PRIVATE RUNNER_BUILD) +if(NOT MSVC) + target_compile_options(runner PRIVATE -Wno-attributes) +endif() if(NOT WIN32 AND Python_SHARED_LIBRARY) get_filename_component(_pylib_name "${Python_SHARED_LIBRARY}" NAME) target_compile_definitions(runner PRIVATE diff --git a/libs/uibase/include/uibase/filterwidget.h b/libs/uibase/include/uibase/filterwidget.h index b6f09cd..eb1d00f 100644 --- a/libs/uibase/include/uibase/filterwidget.h +++ b/libs/uibase/include/uibase/filterwidget.h @@ -24,10 +24,12 @@ class QDLLEXPORT FilterWidgetProxyModel : public QSortFilterProxyModel public: FilterWidgetProxyModel(FilterWidget& fw, QWidget* parent = nullptr); void refreshFilter() { -#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) +#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0) beginFilterChange(); -#endif + endFilterChange(QSortFilterProxyModel::Direction::Rows); +#else invalidateFilter(); +#endif } protected: diff --git a/libs/uibase/src/utility.cpp b/libs/uibase/src/utility.cpp index 0ad64ad..e6f53b8 100644 --- a/libs/uibase/src/utility.cpp +++ b/libs/uibase/src/utility.cpp @@ -64,7 +64,17 @@ bool removeDir(const QString& dirName) dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | QDir::AllDirs | QDir::Files, QDir::DirsFirst)) { - if (info.isDir()) { + // Never recurse into symlinked directories: in Flatpak they can point to + // read-only runtime locations (e.g. /app), and we only want to remove the link. + if (info.isSymLink()) { + QFile file(info.absoluteFilePath()); + if (!file.remove()) { + reportError(QObject::tr("removal of \"%1\" failed: %2") + .arg(info.absoluteFilePath()) + .arg(file.errorString())); + return false; + } + } else if (info.isDir()) { if (!removeDir(info.absoluteFilePath())) { return false; } @@ -209,7 +219,11 @@ static bool shellOpDelete(const QStringList& fileNames, bool recycle) // On Linux, "recycle" moves to trash using Qt; otherwise just delete for (const auto& fileName : fileNames) { QFileInfo fi(fileName); - if (fi.isDir()) { + if (fi.isSymLink()) { + if (!QFile::remove(fileName)) { + return false; + } + } else if (fi.isDir()) { if (!removeDir(fileName)) { return false; } diff --git a/src/src/CMakeLists.txt b/src/src/CMakeLists.txt index 2b3d9cc..664b236 100644 --- a/src/src/CMakeLists.txt +++ b/src/src/CMakeLists.txt @@ -31,11 +31,13 @@ file(GLOB ORGANIZER_QRC # Keep explicitly listed sources tracked by CMake even when using GLOB. list(APPEND ORGANIZER_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/fluorineconfig.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/fluorinepaths.cpp ${CMAKE_CURRENT_SOURCE_DIR}/protonlauncher.cpp ${CMAKE_CURRENT_SOURCE_DIR}/settingsdialogproton.cpp ${CMAKE_CURRENT_SOURCE_DIR}/wineprefix.cpp) list(APPEND ORGANIZER_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/fluorineconfig.h + ${CMAKE_CURRENT_SOURCE_DIR}/fluorinepaths.h ${CMAKE_CURRENT_SOURCE_DIR}/protonlauncher.h ${CMAKE_CURRENT_SOURCE_DIR}/settingsdialogproton.h ${CMAKE_CURRENT_SOURCE_DIR}/wineprefix.h) diff --git a/src/src/bbcode.cpp b/src/src/bbcode.cpp index 4d8148e..5567fb0 100644 --- a/src/src/bbcode.cpp +++ b/src/src/bbcode.cpp @@ -43,7 +43,7 @@ public: { // extract the tag name auto match = m_TagNameExp.match(input, 1, QRegularExpression::NormalMatch, - QRegularExpression::AnchoredMatchOption); + QRegularExpression::AnchorAtOffsetMatchOption); QString tagName = match.captured(0).toLower(); TagMap::iterator tagIter = m_TagMap.find(tagName); if (tagIter != m_TagMap.end()) { diff --git a/src/src/categoriesdialog.cpp b/src/src/categoriesdialog.cpp index 09b89ba..d575e17 100644 --- a/src/src/categoriesdialog.cpp +++ b/src/src/categoriesdialog.cpp @@ -29,6 +29,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include class NewIDValidator : public QIntValidator { @@ -223,25 +224,25 @@ void CategoriesDialog::fillTable() ++row; table->insertRow(row); - QScopedPointer idItem(new QTableWidgetItem()); + auto idItem = std::make_unique(); idItem->setData(Qt::DisplayRole, category.ID()); - QScopedPointer nameItem(new QTableWidgetItem(category.name())); - QScopedPointer parentIDItem(new QTableWidgetItem()); + auto nameItem = std::make_unique(category.name()); + auto parentIDItem = std::make_unique(); parentIDItem->setData(Qt::DisplayRole, category.parentID()); - QScopedPointer nexusCatItem(new QTableWidgetItem()); + auto nexusCatItem = std::make_unique(); - table->setItem(row, 0, idItem.take()); - table->setItem(row, 1, nameItem.take()); - table->setItem(row, 2, parentIDItem.take()); - table->setItem(row, 3, nexusCatItem.take()); + table->setItem(row, 0, idItem.release()); + table->setItem(row, 1, nameItem.release()); + table->setItem(row, 2, parentIDItem.release()); + table->setItem(row, 3, nexusCatItem.release()); } for (const auto& nexusCat : categories.m_NexusMap) { - QScopedPointer nexusItem(new QListWidgetItem()); + auto nexusItem = std::make_unique(); nexusItem->setData(Qt::DisplayRole, nexusCat.second.name()); nexusItem->setData(Qt::UserRole, nexusCat.second.ID()); - list->addItem(nexusItem.take()); + list->addItem(nexusItem.release()); auto item = table->item(categories.resolveNexusID(nexusCat.first) - 1, 3); if (item != nullptr) { auto itemData = item->data(Qt::UserRole).toList(); @@ -268,14 +269,14 @@ void CategoriesDialog::addCategory_clicked() ui->categoriesTable->setSortingEnabled(false); ui->categoriesTable->insertRow(row); - QScopedPointer idItem(new QTableWidgetItem()); + auto idItem = std::make_unique(); idItem->setData(Qt::DisplayRole, ++m_HighestID); - QScopedPointer parentIDItem(new QTableWidgetItem()); + auto parentIDItem = std::make_unique(); parentIDItem->setData(Qt::DisplayRole, 0); - ui->categoriesTable->setItem(row, 0, idItem.take()); + ui->categoriesTable->setItem(row, 0, idItem.release()); ui->categoriesTable->setItem(row, 1, new QTableWidgetItem("new")); - ui->categoriesTable->setItem(row, 2, parentIDItem.take()); + ui->categoriesTable->setItem(row, 2, parentIDItem.release()); ui->categoriesTable->setItem(row, 3, new QTableWidgetItem("")); ui->categoriesTable->setSortingEnabled(true); } @@ -322,35 +323,34 @@ void CategoriesDialog::nexusImport_clicked() data.append(QVariant(name)); data.append(QVariant(nexusID)); nexusData.insert(nexusData.size(), data); - QScopedPointer nexusCatItem( - new QTableWidgetItem(nexusLabel.join(", "))); + auto nexusCatItem = std::make_unique(nexusLabel.join(", ")); nexusCatItem->setData(Qt::UserRole, nexusData); if (!table->findItems(name, Qt::MatchExactly).size()) { row = table->rowCount(); table->insertRow(table->rowCount()); - QScopedPointer idItem(new QTableWidgetItem()); + auto idItem = std::make_unique(); idItem->setData(Qt::DisplayRole, ++m_HighestID); - QScopedPointer nameItem(new QTableWidgetItem(name)); - QScopedPointer parentIDItem(new QTableWidgetItem()); + auto nameItem = std::make_unique(name); + auto parentIDItem = std::make_unique(); parentIDItem->setData(Qt::DisplayRole, 0); // No parent - table->setItem(row, 0, idItem.take()); - table->setItem(row, 1, nameItem.take()); - table->setItem(row, 2, parentIDItem.take()); + table->setItem(row, 0, idItem.release()); + table->setItem(row, 1, nameItem.release()); + table->setItem(row, 2, parentIDItem.release()); if (importDialog.assign()) { - table->setItem(row, 3, nexusCatItem.take()); + table->setItem(row, 3, nexusCatItem.release()); } } else { for (auto item : table->findItems(name, Qt::MatchContains | Qt::MatchWrap)) { if (item->column() == 1 && item->text() == name && importDialog.remap()) { - table->setItem(item->row(), 3, nexusCatItem.take()); + table->setItem(item->row(), 3, nexusCatItem.release()); } else if (importDialog.remap()) { - QScopedPointer blankItem(new QTableWidgetItem()); + auto blankItem = std::make_unique(); blankItem->setData(Qt::UserRole, QVariantList()); - table->setItem(item->row(), 3, blankItem.get()); + table->setItem(item->row(), 3, blankItem.release()); } } } @@ -370,10 +370,10 @@ void CategoriesDialog::nxmGameInfoAvailable(QString gameName, QVariant, list->clear(); for (const auto& category : categories) { auto catMap = category.toMap(); - QScopedPointer nexusItem(new QListWidgetItem()); + auto nexusItem = std::make_unique(); nexusItem->setData(Qt::DisplayRole, catMap["name"].toString()); nexusItem->setData(Qt::UserRole, catMap["category_id"].toInt()); - list->addItem(nexusItem.take()); + list->addItem(nexusItem.release()); } } diff --git a/src/src/commandline.cpp b/src/src/commandline.cpp index 1582923..9c1fa85 100644 --- a/src/src/commandline.cpp +++ b/src/src/commandline.cpp @@ -984,13 +984,16 @@ std::optional CreatePortableCommand::runEarly() } } - // Create empty profile files - const QStringList profileFiles = {"modlist.txt", "plugins.txt", "loadorder.txt", "initweaks.ini"}; - for (const auto& f : profileFiles) { - QFile file(QDir(instanceDir).filePath("profiles/Default/" + f)); - file.open(QIODevice::WriteOnly); - file.close(); - } + // Create empty profile files + const QStringList profileFiles = {"modlist.txt", "plugins.txt", "loadorder.txt", "initweaks.ini"}; + for (const auto& f : profileFiles) { + QFile file(QDir(instanceDir).filePath("profiles/Default/" + f)); + if (!file.open(QIODevice::WriteOnly)) { + std::cerr << "Error: failed to create file: " << f.toStdString() << "\n"; + return 1; + } + file.close(); + } // Generate ModOrganizer.ini { diff --git a/src/src/copyeventfilter.cpp b/src/src/copyeventfilter.cpp index 901e797..a5fc5f9 100644 --- a/src/src/copyeventfilter.cpp +++ b/src/src/copyeventfilter.cpp @@ -24,7 +24,7 @@ void CopyEventFilter::copySelection() const // sort to reflect the visual order QModelIndexList selectedRows = m_view->selectionModel()->selectedRows(); std::sort(selectedRows.begin(), selectedRows.end(), - [=](const auto& lidx, const auto& ridx) { + [=, this](const auto& lidx, const auto& ridx) { return m_view->visualRect(lidx).top() < m_view->visualRect(ridx).top(); }); diff --git a/src/src/createinstancedialog.cpp b/src/src/createinstancedialog.cpp index 10bcce5..89506e6 100644 --- a/src/src/createinstancedialog.cpp +++ b/src/src/createinstancedialog.cpp @@ -7,6 +7,7 @@ #include "ui_createinstancedialog.h" #include #include +#include using namespace MOBase; @@ -133,13 +134,13 @@ CreateInstanceDialog::CreateInstanceDialog(const PluginContainer& pc, Settings* addShortcutAction(QKeySequence::Find, Actions::Find); - addShortcut(Qt::ALT + Qt::Key_Left, [&] { + addShortcut(Qt::ALT | Qt::Key_Left, [&] { back(); }); - addShortcut(Qt::ALT + Qt::Key_Right, [&] { + addShortcut(Qt::ALT | Qt::Key_Right, [&] { next(false); }); - addShortcut(Qt::CTRL + Qt::Key_Return, [&] { + addShortcut(Qt::CTRL | Qt::Key_Return, [&] { next(); }); @@ -494,6 +495,22 @@ bool CreateInstanceDialog::switching() const return m_switching; } +template +auto CreateInstanceDialog::getSelected(MF mf, Args&&... args) const +{ + // return type + using T = decltype((std::declval().*mf)(std::forward(args)...)); + + for (auto&& p : m_pages) { + const auto t = (p.get()->*mf)(std::forward(args)...); + if (t != T()) { + return t; + } + } + + return T(); +} + CreateInstanceDialog::CreationInfo CreateInstanceDialog::rawCreationInfo() const { const auto iniFilename = QString::fromStdWString(AppConfig::iniFileName()); @@ -562,3 +579,34 @@ CreateInstanceDialog::CreationInfo CreateInstanceDialog::creationInfo() const return ci; } + +template +void CreateInstanceDialog::setSinglePage(const QString& instanceName) +{ + for (auto&& p : m_pages) { + if (auto* tp = dynamic_cast(p.get())) { + tp->setSkip(false); + } else { + p->setSkip(true); + } + } + + setSinglePageImpl(instanceName); +} + +template +Page* CreateInstanceDialog::getPage() +{ + for (auto&& p : m_pages) { + if (auto* tp = dynamic_cast(p.get())) { + return tp; + } + } + + return nullptr; +} + +// explicit instantiations for types used by instancemanager.cpp +template void CreateInstanceDialog::setSinglePage(const QString&); +template void CreateInstanceDialog::setSinglePage(const QString&); +template cid::GamePage* CreateInstanceDialog::getPage(); diff --git a/src/src/createinstancedialog.h b/src/src/createinstancedialog.h index d1f5656..bacdcf9 100644 --- a/src/src/createinstancedialog.h +++ b/src/src/createinstancedialog.h @@ -102,32 +102,12 @@ public: // disables all the pages except for the given one, used on startup when some // specific info is missing template - void setSinglePage(const QString& instanceName) - { - for (auto&& p : m_pages) { - if (auto* tp = dynamic_cast(p.get())) { - tp->setSkip(false); - } else { - p->setSkip(true); - } - } - - setSinglePageImpl(instanceName); - } + void setSinglePage(const QString& instanceName); // returns the page having the give path, or null // template - Page* getPage() - { - for (auto&& p : m_pages) { - if (auto* tp = dynamic_cast(p.get())) { - return tp; - } - } - - return nullptr; - } + Page* getPage(); // moves to the next page; if `allowFinish` is true, calls finish() if // currently on the last page @@ -218,20 +198,7 @@ private: // that's not empty; used by gatherInfo() // template - auto getSelected(MF mf, Args&&... args) const - { - // return type - using T = decltype((std::declval().*mf)(std::forward(args)...)); - - for (auto&& p : m_pages) { - const auto t = (p.get()->*mf)(std::forward(args)...); - if (t != T()) { - return t; - } - } - - return T(); - } + auto getSelected(MF mf, Args&&... args) const; }; #endif // MODORGANIZER_CREATEINSTANCEDIALOG_INCLUDED diff --git a/src/src/datatab.cpp b/src/src/datatab.cpp index c36c7c1..ea1430b 100644 --- a/src/src/datatab.cpp +++ b/src/src/datatab.cpp @@ -58,7 +58,7 @@ DataTab::DataTab(OrganizerCore& core, PluginContainer& pc, QWidget* parent, onHiddenFiles(); }); - connect(ui.tree->selectionModel(), &QItemSelectionModel::selectionChanged, [=] { + connect(ui.tree->selectionModel(), &QItemSelectionModel::selectionChanged, [=, this] { const auto* fileTreeModel = m_filetree->model(); const auto& selectedIndexList = MOShared::indexViewToModel( ui.tree->selectionModel()->selectedRows(), fileTreeModel); diff --git a/src/src/downloadlistview.cpp b/src/src/downloadlistview.cpp index 5b9b9e4..8928e6c 100644 --- a/src/src/downloadlistview.cpp +++ b/src/src/downloadlistview.cpp @@ -228,64 +228,64 @@ void DownloadListView::onCustomContextMenu(const QPoint& point) hidden = m_Manager->isHidden(row); if (state >= DownloadManager::STATE_READY) { - menu.addAction(tr("Install"), [=] { + menu.addAction(tr("Install"), [=, this] { issueInstall(row); }); if (m_Manager->isInfoIncomplete(row)) { - menu.addAction(tr("Query Info"), [=] { + menu.addAction(tr("Query Info"), [=, this] { issueQueryInfoMd5(row); }); } else { - menu.addAction(tr("Visit on Nexus"), [=] { + menu.addAction(tr("Visit on Nexus"), [=, this] { issueVisitOnNexus(row); }); - menu.addAction(tr("Visit the uploader's profile"), [=] { + menu.addAction(tr("Visit the uploader's profile"), [=, this] { issueVisitUploaderProfile(row); }); } - menu.addAction(tr("Open File"), [=] { + menu.addAction(tr("Open File"), [=, this] { issueOpenFile(row); }); - menu.addAction(tr("Open Meta File"), [=] { + menu.addAction(tr("Open Meta File"), [=, this] { issueOpenMetaFile(row); }); - menu.addAction(tr("Reveal in Explorer"), [=] { + menu.addAction(tr("Reveal in Explorer"), [=, this] { issueOpenInDownloadsFolder(row); }); menu.addSeparator(); - menu.addAction(tr("Delete..."), [=] { + menu.addAction(tr("Delete..."), [=, this] { issueDelete(row); }); if (hidden) - menu.addAction(tr("Un-Hide"), [=] { + menu.addAction(tr("Un-Hide"), [=, this] { issueRestoreToView(row); }); else - menu.addAction(tr("Hide"), [=] { + menu.addAction(tr("Hide"), [=, this] { issueRemoveFromView(row); }); } else if (state == DownloadManager::STATE_DOWNLOADING) { - menu.addAction(tr("Cancel"), [=] { + menu.addAction(tr("Cancel"), [=, this] { issueCancel(row); }); - menu.addAction(tr("Pause"), [=] { + menu.addAction(tr("Pause"), [=, this] { issuePause(row); }); - menu.addAction(tr("Reveal in Explorer"), [=] { + menu.addAction(tr("Reveal in Explorer"), [=, this] { issueOpenInDownloadsFolder(row); }); } else if ((state == DownloadManager::STATE_PAUSED) || (state == DownloadManager::STATE_ERROR) || (state == DownloadManager::STATE_PAUSING)) { - menu.addAction(tr("Delete..."), [=] { + menu.addAction(tr("Delete..."), [=, this] { issueDelete(row); }); - menu.addAction(tr("Resume"), [=] { + menu.addAction(tr("Resume"), [=, this] { issueResume(row); }); - menu.addAction(tr("Reveal in Explorer"), [=] { + menu.addAction(tr("Reveal in Explorer"), [=, this] { issueOpenInDownloadsFolder(row); }); } @@ -297,29 +297,29 @@ void DownloadListView::onCustomContextMenu(const QPoint& point) // display download-specific actions } - menu.addAction(tr("Delete Installed Downloads..."), [=] { + menu.addAction(tr("Delete Installed Downloads..."), [=, this] { issueDeleteCompleted(); }); - menu.addAction(tr("Delete Uninstalled Downloads..."), [=] { + menu.addAction(tr("Delete Uninstalled Downloads..."), [=, this] { issueDeleteUninstalled(); }); - menu.addAction(tr("Delete All Downloads..."), [=] { + menu.addAction(tr("Delete All Downloads..."), [=, this] { issueDeleteAll(); }); menu.addSeparator(); if (!hidden) { - menu.addAction(tr("Hide Installed..."), [=] { + menu.addAction(tr("Hide Installed..."), [=, this] { issueRemoveFromViewCompleted(); }); - menu.addAction(tr("Hide Uninstalled..."), [=] { + menu.addAction(tr("Hide Uninstalled..."), [=, this] { issueRemoveFromViewUninstalled(); }); - menu.addAction(tr("Hide All..."), [=] { + menu.addAction(tr("Hide All..."), [=, this] { issueRemoveFromViewAll(); }); } else { - menu.addAction(tr("Un-Hide All..."), [=] { + menu.addAction(tr("Un-Hide All..."), [=, this] { issueRestoreToViewAll(); }); } diff --git a/src/src/downloadmanager.cpp b/src/src/downloadmanager.cpp index 875bcd9..ae3b94e 100644 --- a/src/src/downloadmanager.cpp +++ b/src/src/downloadmanager.cpp @@ -229,12 +229,13 @@ DownloadManager::DownloadManager(NexusInterface* nexusInterface, QObject* parent m_ParentWidget(nullptr) { m_OrganizerCore = dynamic_cast(parent); - connect(&m_DirWatcher, SIGNAL(directoryChanged(QString)), this, - SLOT(directoryChanged(QString))); - m_TimeoutTimer.setSingleShot(false); - // connect(&m_TimeoutTimer, SIGNAL(timeout()), this, SLOT(checkDownloadTimeout())); - m_TimeoutTimer.start(5 * 1000); -} + connect(&m_DirWatcher, SIGNAL(directoryChanged(QString)), this, + SLOT(directoryChanged(QString))); + m_TimeoutTimer.setSingleShot(false); + connect(&m_TimeoutTimer, &QTimer::timeout, this, + &DownloadManager::checkDownloadTimeout); + m_TimeoutTimer.start(5 * 1000); +} DownloadManager::~DownloadManager() { @@ -952,17 +953,19 @@ void DownloadManager::removeDownload(int index, bool deleteFile) refreshList(); } -void DownloadManager::cancelDownload(int index) -{ - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - reportError(tr("cancel: invalid download index %1").arg(index)); - return; - } - - if (m_ActiveDownloads.at(index)->m_State == STATE_DOWNLOADING) { - setState(m_ActiveDownloads.at(index), STATE_CANCELING); - } -} +void DownloadManager::cancelDownload(int index) +{ + if ((index < 0) || (index >= m_ActiveDownloads.size())) { + reportError(tr("cancel: invalid download index %1").arg(index)); + return; + } + + DownloadInfo* info = m_ActiveDownloads.at(index); + if (info->m_State == STATE_DOWNLOADING || info->m_State == STATE_STARTED || + info->m_State == STATE_PAUSING) { + setState(info, STATE_CANCELING); + } +} void DownloadManager::pauseDownload(int index) { @@ -1610,14 +1613,20 @@ void DownloadManager::setState(DownloadManager::DownloadInfo* info, row = i; break; } - } - info->m_State = state; - switch (state) { - case STATE_PAUSED: { - info->m_Reply->abort(); - info->m_Output.close(); - m_DownloadPaused(row); - } break; + } + info->m_State = state; + switch (state) { + case STATE_CANCELING: { + // Force termination so the download transitions through finished(). + if (info->m_Reply != nullptr && info->m_Reply->isRunning()) { + info->m_Reply->abort(); + } + } break; + case STATE_PAUSED: { + info->m_Reply->abort(); + info->m_Output.close(); + m_DownloadPaused(row); + } break; case STATE_ERROR: { info->m_Reply->abort(); info->m_Output.close(); @@ -2411,24 +2420,46 @@ void DownloadManager::managedGameChanged(MOBase::IPluginGame const* managedGame) m_ManagedGame = managedGame; } -void DownloadManager::checkDownloadTimeout() -{ - for (int i = 0; i < m_ActiveDownloads.size(); ++i) { - if (m_ActiveDownloads[i]->m_StartTime.elapsed() - - m_ActiveDownloads[i]->m_DownloadTimeLast > - 5 * 1000 && - m_ActiveDownloads[i]->m_State == STATE_DOWNLOADING && - m_ActiveDownloads[i]->m_Reply != nullptr && - m_ActiveDownloads[i]->m_Reply->isOpen()) { - pauseDownload(i); - downloadFinished(i); - resumeDownload(i); - } - } -} - -void DownloadManager::writeData(DownloadInfo* info) -{ +void DownloadManager::checkDownloadTimeout() +{ + for (int i = 0; i < m_ActiveDownloads.size(); ++i) { + DownloadInfo* info = m_ActiveDownloads[i]; + if (info->m_State != STATE_DOWNLOADING || info->m_Reply == nullptr || + !info->m_Reply->isOpen()) { + continue; + } + + const bool stalled = + (info->m_StartTime.elapsed() - info->m_DownloadTimeLast) > (5 * 1000); + if (!stalled) { + continue; + } + + pauseDownload(i); + downloadFinished(i); + + // downloadFinished() can remove the download entry, so find it again. + const int index = indexByInfo(info); + if (index < 0) { + continue; + } + + if (info->m_Tries <= 0) { + emit showMessage(tr("Download stalled repeatedly and was paused. " + "Please retry manually.")); + continue; + } + + --info->m_Tries; + log::warn("download '{}' stalled, retrying ({} retries left)", info->m_FileName, + info->m_Tries); + resumeDownloadInt(index); + emit update(index); + } +} + +void DownloadManager::writeData(DownloadInfo* info) +{ if (info != nullptr) { qint64 ret = info->m_Output.write(info->m_Reply->readAll()); if (ret < info->m_Reply->size()) { diff --git a/src/src/envmodule.cpp b/src/src/envmodule.cpp index 2c8d5be..fd5651a 100644 --- a/src/src/envmodule.cpp +++ b/src/src/envmodule.cpp @@ -529,7 +529,7 @@ HandlePtr Process::openHandleForWait() const // On Linux, just wrap the pid. The caller can use waitpid() or // kill(pid, 0) to check on the process. if (kill(m_pid, 0) == 0) { - return HandlePtr(HandleCloser::pointer(m_pid)); + return HandlePtr(HandleCloser::pointer(static_cast(m_pid))); } return {}; } diff --git a/src/src/fluorineconfig.cpp b/src/src/fluorineconfig.cpp index 34cc5cd..7ca320e 100644 --- a/src/src/fluorineconfig.cpp +++ b/src/src/fluorineconfig.cpp @@ -98,7 +98,10 @@ bool FluorineConfig::prefixExists() const return false; } - return QDir(QDir(prefix_path).filePath("drive_c")).exists(); + // prefix_path may point to the compatdata dir (containing pfx/) or + // directly to the pfx dir (containing drive_c). + const QDir dir(prefix_path); + return dir.exists("drive_c") || dir.exists("pfx/drive_c"); } QString FluorineConfig::compatDataPath() const diff --git a/src/src/fluorinepaths.cpp b/src/src/fluorinepaths.cpp new file mode 100644 index 0000000..815f5aa --- /dev/null +++ b/src/src/fluorinepaths.cpp @@ -0,0 +1,91 @@ +#include "fluorinepaths.h" +#include "fluorineconfig.h" + +#include +#include +#include + +#include + +static const QString OldRoot = + QDir::homePath() + "/.var/app/com.fluorine.manager"; + +QString fluorineDataDir() +{ + // Use $HOME directly so this resolves the same path in both native + // and Flatpak builds (the Flatpak has --filesystem=home). + return QDir::homePath() + "/.local/share/fluorine"; +} + +void fluorineMigrateDataDir() +{ +#ifdef _WIN32 + return; +#endif + + const QString oldRoot = OldRoot; + const QString newRoot = fluorineDataDir(); + + // Already migrated or old path never existed + if (QFile::exists(oldRoot + "/MOVED.txt")) { + return; + } + if (!QDir(oldRoot).exists()) { + return; + } + + // Check if there is actually data to migrate + const QStringList subdirs = {"logs", "bin", "config", "Prefix"}; + bool hasData = false; + for (const QString& sub : subdirs) { + if (QDir(oldRoot + "/" + sub).exists()) { + hasData = true; + break; + } + } + if (!hasData) { + return; + } + + fprintf(stderr, "[fluorine] Migrating data from %s to %s\n", + qUtf8Printable(oldRoot), qUtf8Printable(newRoot)); + + QDir().mkpath(newRoot); + + for (const QString& sub : subdirs) { + const QString src = oldRoot + "/" + sub; + const QString dst = newRoot + "/" + sub; + if (!QDir(src).exists()) { + continue; + } + if (QDir(dst).exists()) { + fprintf(stderr, "[fluorine] skip %s (destination already exists)\n", + qUtf8Printable(sub)); + continue; + } + if (QDir().rename(src, dst)) { + fprintf(stderr, "[fluorine] moved %s\n", qUtf8Printable(sub)); + } else { + fprintf(stderr, "[fluorine] FAILED to move %s\n", qUtf8Printable(sub)); + } + } + + // Update FluorineConfig's prefix_path if it references the old root + if (auto cfg = FluorineConfig::load()) { + if (cfg->prefix_path.startsWith(oldRoot)) { + cfg->prefix_path.replace(oldRoot, newRoot); + cfg->save(); + fprintf(stderr, "[fluorine] updated config prefix_path\n"); + } + } + + // Write breadcrumb so we don't attempt migration again + QFile marker(oldRoot + "/MOVED.txt"); + if (marker.open(QIODevice::WriteOnly)) { + QTextStream ts(&marker); + ts << "Data migrated to " << newRoot << "\n"; + marker.close(); + } + + fprintf(stderr, "[fluorine] Migration complete.\n"); +} diff --git a/src/src/fluorinepaths.h b/src/src/fluorinepaths.h new file mode 100644 index 0000000..a32f6ec --- /dev/null +++ b/src/src/fluorinepaths.h @@ -0,0 +1,15 @@ +#ifndef FLUORINEPATHS_H +#define FLUORINEPATHS_H + +#include + +/// Returns the shared Fluorine data directory: ~/.local/share/fluorine +/// Uses $HOME directly to bypass Flatpak's XDG_DATA_HOME remapping +/// (the Flatpak has --filesystem=home). +QString fluorineDataDir(); + +/// One-time migration from the old ~/.var/app/com.fluorine.manager/ path +/// to ~/.local/share/fluorine/. Call before initLogging(). +void fluorineMigrateDataDir(); + +#endif // FLUORINEPATHS_H diff --git a/src/src/icondelegate.cpp b/src/src/icondelegate.cpp index b3c23b0..5a26730 100644 --- a/src/src/icondelegate.cpp +++ b/src/src/icondelegate.cpp @@ -33,7 +33,7 @@ IconDelegate::IconDelegate(QTreeView* view, int column, int compactSize) { if (view) { connect(view->header(), &QHeaderView::sectionResized, - [=](int column, int, int size) { + [=, this](int column, int, int size) { if (column == m_column) { m_compact = size < m_compactSize; } diff --git a/src/src/instancemanager.cpp b/src/src/instancemanager.cpp index 1b9eff1..3aa4db5 100644 --- a/src/src/instancemanager.cpp +++ b/src/src/instancemanager.cpp @@ -21,6 +21,7 @@ along with Mod Organizer. If not, see . #include "createinstancedialog.h" #include "createinstancedialogpages.h" #include "filesystemutilities.h" +#include "fluorinepaths.h" #include "instancemanagerdialog.h" #include "nexusinterface.h" #include "plugincontainer.h" @@ -625,8 +626,14 @@ QString InstanceManager::instancePath(const QString& instanceName) const QString InstanceManager::globalInstancesRootPath() const { +#ifndef _WIN32 + // Use the shared Fluorine data dir so the path is the same in native and + // Flatpak builds (QStandardPaths is remapped inside a Flatpak sandbox). + return QDir::fromNativeSeparators(fluorineDataDir()); +#else return QDir::fromNativeSeparators( QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation)); +#endif } QString InstanceManager::iniPath(const QString& instanceDir) const diff --git a/src/src/instancemanagerdialog.cpp b/src/src/instancemanagerdialog.cpp index 24ce3bd..d9a4a74 100644 --- a/src/src/instancemanagerdialog.cpp +++ b/src/src/instancemanagerdialog.cpp @@ -690,27 +690,37 @@ void InstanceManagerDialog::createNew() select(dlg.creationInfo().instanceName); } -std::size_t InstanceManagerDialog::singleSelectionIndex() const -{ - const auto sel = - m_filter.mapSelectionToSource(ui->list->selectionModel()->selection()); - - if (sel.size() != 1) { - return NoSelection; - } - - return static_cast(sel.indexes()[0].row()); -} - -const Instance* InstanceManagerDialog::singleSelection() const -{ - const auto i = singleSelectionIndex(); - if (i == NoSelection) { - return nullptr; - } - - return m_instances[i].get(); -} +std::size_t InstanceManagerDialog::singleSelectionIndex() const +{ + const auto sel = + m_filter.mapSelectionToSource(ui->list->selectionModel()->selection()); + + if (sel.size() != 1) { + return NoSelection; + } + + const auto indexes = sel.indexes(); + if (indexes.size() != 1 || !indexes[0].isValid()) { + return NoSelection; + } + + const int row = indexes[0].row(); + if (row < 0 || static_cast(row) >= m_instances.size()) { + return NoSelection; + } + + return static_cast(row); +} + +const Instance* InstanceManagerDialog::singleSelection() const +{ + const auto i = singleSelectionIndex(); + if (i == NoSelection || i >= m_instances.size()) { + return nullptr; + } + + return m_instances[i].get(); +} void InstanceManagerDialog::fillData(const Instance& ii) { diff --git a/src/src/loglist.cpp b/src/src/loglist.cpp index 55ace68..15299f5 100644 --- a/src/src/loglist.cpp +++ b/src/src/loglist.cpp @@ -20,6 +20,7 @@ along with Mod Organizer. If not, see . #include "loglist.h" #include "copyeventfilter.h" #include "env.h" +#include "fluorinepaths.h" #include "organizercore.h" #include @@ -28,15 +29,6 @@ using namespace MOBase; static LogModel* g_instance = nullptr; const std::size_t MaxLines = 1000; -static QString fluorineLogDir() -{ -#ifndef _WIN32 - return QDir::homePath() + "/.var/app/com.fluorine.manager/logs"; -#else - return {}; -#endif -} - static std::unique_ptr m_console; static bool m_stdout = false; static std::mutex m_stdoutMutex; @@ -249,7 +241,7 @@ void LogList::clear() void LogList::openLogsFolder() { #ifndef _WIN32 - const QString logsPath = fluorineLogDir(); + const QString logsPath = fluorineDataDir() + "/logs"; #else const QString logsPath = qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::logPath()); @@ -414,8 +406,8 @@ bool createAndMakeWritable(const std::wstring& subPath) bool setLogDirectory(const QString& dir) { #ifndef _WIN32 - // On Linux, all logs go to ~/.var/app/com.fluorine.manager/logs/ - const QString logDir = fluorineLogDir(); + // On Linux, all logs go to ~/.local/share/fluorine/logs/ + const QString logDir = fluorineDataDir() + "/logs"; QDir().mkpath(logDir); const auto logFile = logDir + "/" + QString::fromStdWString(AppConfig::logFileName()); diff --git a/src/src/main.cpp b/src/src/main.cpp index b82b1b8..b12ac7e 100644 --- a/src/src/main.cpp +++ b/src/src/main.cpp @@ -1,5 +1,6 @@ #include "commandline.h" #include "env.h" +#include "fluorinepaths.h" #include "instancemanager.h" #include "loglist.h" #include "moapplication.h" @@ -82,6 +83,8 @@ int run(int argc, char* argv[]) } #endif + fluorineMigrateDataDir(); + initLogging(); // Route NaK (Rust) log messages through MOBase::log diff --git a/src/src/mainwindow.cpp b/src/src/mainwindow.cpp index 8a219a7..008d3b6 100644 --- a/src/src/mainwindow.cpp +++ b/src/src/mainwindow.cpp @@ -145,6 +145,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include #include #include #include @@ -492,31 +493,31 @@ MainWindow::MainWindow(Settings& settings, OrganizerCore& organizerCore, m_Tutorial.expose("espList", m_OrganizerCore.pluginList()); m_OrganizerCore.setUserInterface(this); - m_OrganizerCore.onFinishedRun([=](const QString, unsigned int) { + m_OrganizerCore.onFinishedRun([=, this](const QString, unsigned int) { if (isHidden()) { m_SystemTrayManager->restoreFromSystemTray(); } }); - connect(m_OrganizerCore.modList(), &ModList::showMessage, [=](auto&& message) { + connect(m_OrganizerCore.modList(), &ModList::showMessage, [=, this](auto&& message) { showMessage(message); }); connect(m_OrganizerCore.modList(), &ModList::modRenamed, - [=](auto&& oldName, auto&& newName) { + [=, this](auto&& oldName, auto&& newName) { modRenamed(oldName, newName); }); - connect(m_OrganizerCore.modList(), &ModList::modUninstalled, [=](auto&& name) { + connect(m_OrganizerCore.modList(), &ModList::modUninstalled, [=, this](auto&& name) { modRemoved(name); }); - connect(m_OrganizerCore.modList(), &ModList::fileMoved, [=](auto&&... args) { + connect(m_OrganizerCore.modList(), &ModList::fileMoved, [=, this](auto&&... args) { fileMoved(args...); }); connect(m_OrganizerCore.installationManager(), &InstallationManager::modReplaced, - [=](auto&& name) { + [=, this](auto&& name) { modRemoved(name); }); connect(m_OrganizerCore.downloadManager(), &DownloadManager::showMessage, - [=](auto&& message) { + [=, this](auto&& message) { showMessage(message); }); for (const QString& fileName : m_PluginContainer.pluginFileNames()) { @@ -555,7 +556,7 @@ void MainWindow::setupModList() { ui->modList->setup(m_OrganizerCore, m_CategoryFactory, this, ui); - connect(&ui->modList->actions(), &ModListViewActions::overwriteCleared, [=]() { + connect(&ui->modList->actions(), &ModListViewActions::overwriteCleared, [=, this]() { scheduleCheckForProblems(); }); connect(&ui->modList->actions(), &ModListViewActions::originModified, this, @@ -2711,7 +2712,7 @@ QMenu* MainWindow::openFolderMenu() FolderMenu->addAction(tr("Open MO2 Plugins folder"), this, SLOT(openPluginsFolder())); FolderMenu->addAction(tr("Open MO2 Stylesheets folder"), this, SLOT(openStylesheetsFolder())); - FolderMenu->addAction(tr("Open MO2 Logs folder"), [=] { + FolderMenu->addAction(tr("Open MO2 Logs folder"), [=, this] { ui->logList->openLogsFolder(); }); @@ -3431,7 +3432,7 @@ void MainWindow::nxmModInfoAvailable(QString gameName, int modID, QVariant userD mod->setLastNexusQuery(QDateTime::currentDateTimeUtc()); mod->setNexusLastModified( - QDateTime::fromSecsSinceEpoch(result["updated_timestamp"].toInt(), Qt::UTC)); + QDateTime::fromSecsSinceEpoch(result["updated_timestamp"].toInt(), QTimeZone::UTC)); m_OrganizerCore.modList()->notifyChange(ModInfo::getIndex(mod->name())); } @@ -3703,7 +3704,7 @@ void MainWindow::extractBSATriggered(QTreeWidgetItem* item) void MainWindow::on_bsaList_customContextMenuRequested(const QPoint& pos) { QMenu menu; - menu.addAction(tr("Extract..."), [=, item = ui->bsaList->itemAt(pos)]() { + menu.addAction(tr("Extract..."), [=, this, item = ui->bsaList->itemAt(pos)]() { extractBSATriggered(item); }); diff --git a/src/src/modinfo.cpp b/src/src/modinfo.cpp index f12ceda..d10abfd 100644 --- a/src/src/modinfo.cpp +++ b/src/src/modinfo.cpp @@ -44,6 +44,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include using namespace MOBase; using namespace MOShared; @@ -412,7 +413,7 @@ std::set> ModInfo::filteredMods(QString gameName, info->gameName().compare(gameName, Qt::CaseInsensitive) == 0) if (info->getLastNexusUpdate().addSecs(-3600) < QDateTime::fromSecsSinceEpoch( - update["latest_file_update"].toInt(), Qt::UTC)) + update["latest_file_update"].toInt(), QTimeZone::UTC)) return true; return false; }); diff --git a/src/src/modinforegular.cpp b/src/src/modinforegular.cpp index 808b572..562f3e5 100644 --- a/src/src/modinforegular.cpp +++ b/src/src/modinforegular.cpp @@ -5,55 +5,56 @@ #include "moddatacontent.h" #include "organizercore.h" #include "plugincontainer.h" -#include "report.h" -#include "settings.h" -#include -#include +#include "report.h" +#include "settings.h" +#include +#include #include #include #include +#include #include using namespace MOBase; using namespace MOShared; -namespace -{ -// Arguably this should be a class static or we should be using FileString rather -// than QString for the names. Or both. -static bool ByName(const ModInfo::Ptr& LHS, const ModInfo::Ptr& RHS) -{ - return QString::compare(LHS->name(), RHS->name(), Qt::CaseInsensitive) < 0; -} - -QString loadMetaPath(const QString& value) -{ - if (value.isEmpty()) { - return value; - } - - if (MOBase::isWindowsDrivePath(value) || QDir::isAbsolutePath(value)) { - return MOBase::normalizePathForHost(value); - } - - return QDir::fromNativeSeparators(value); -} - -QString storeMetaPath(const QString& value) -{ - if (value.isEmpty()) { - return value; - } - - if (MOBase::isWindowsDrivePath(value) || QDir::isAbsolutePath(value)) { - return MOBase::normalizePathForWine(value); - } - - return QDir::fromNativeSeparators(value); -} -} // namespace +namespace +{ +// Arguably this should be a class static or we should be using FileString rather +// than QString for the names. Or both. +static bool ByName(const ModInfo::Ptr& LHS, const ModInfo::Ptr& RHS) +{ + return QString::compare(LHS->name(), RHS->name(), Qt::CaseInsensitive) < 0; +} + +QString loadMetaPath(const QString& value) +{ + if (value.isEmpty()) { + return value; + } + + if (MOBase::isWindowsDrivePath(value) || QDir::isAbsolutePath(value)) { + return MOBase::normalizePathForHost(value); + } + + return QDir::fromNativeSeparators(value); +} + +QString storeMetaPath(const QString& value) +{ + if (value.isEmpty()) { + return value; + } + + if (MOBase::isWindowsDrivePath(value) || QDir::isAbsolutePath(value)) { + return MOBase::normalizePathForWine(value); + } + + return QDir::fromNativeSeparators(value); +} +} // namespace ModInfoRegular::ModInfoRegular(const QDir& path, OrganizerCore& core) : ModInfoWithConflictInfo(core), m_Name(path.dirName()), @@ -121,8 +122,8 @@ void ModInfoRegular::readMeta() m_Version.parse(metaFile.value("version", "").toString()); m_NewestVersion = metaFile.value("newestVersion", "").toString(); m_IgnoredVersion = metaFile.value("ignoredVersion", "").toString(); - m_InstallationFile = - loadMetaPath(metaFile.value("installationFile", "").toString()); + m_InstallationFile = + loadMetaPath(metaFile.value("installationFile", "").toString()); m_NexusDescription = metaFile.value("nexusDescription", "").toString(); m_NexusFileStatus = metaFile.value("nexusFileStatus", "1").toInt(); m_NexusCategory = metaFile.value("nexusCategory", 0).toInt(); @@ -287,7 +288,7 @@ void ModInfoRegular::saveMeta() metaFile.setValue("newestVersion", m_NewestVersion.canonicalString()); metaFile.setValue("ignoredVersion", m_IgnoredVersion.canonicalString()); metaFile.setValue("version", m_Version.canonicalString()); - metaFile.setValue("installationFile", storeMetaPath(m_InstallationFile)); + metaFile.setValue("installationFile", storeMetaPath(m_InstallationFile)); metaFile.setValue("repository", m_Repository); metaFile.setValue("gameName", m_GameName); metaFile.setValue("modid", m_NexusID); @@ -393,7 +394,7 @@ void ModInfoRegular::nxmDescriptionAvailable(QString, int, QVariant, } m_LastNexusQuery = QDateTime::currentDateTimeUtc(); m_NexusLastModified = - QDateTime::fromSecsSinceEpoch(result["updated_timestamp"].toInt(), Qt::UTC); + QDateTime::fromSecsSinceEpoch(result["updated_timestamp"].toInt(), QTimeZone::UTC); m_MetaInfoChanged = true; saveMeta(); disconnect(sender(), SIGNAL(descriptionAvailable(QString, int, QVariant, QVariant))); diff --git a/src/src/modlist.cpp b/src/src/modlist.cpp index 9f695bd..35f4a94 100644 --- a/src/src/modlist.cpp +++ b/src/src/modlist.cpp @@ -659,33 +659,33 @@ QMimeData* ModList::mimeData(const QModelIndexList& indexes) const return result; } -void ModList::changeModPriority(std::vector sourceIndices, int newPriority) -{ - if (m_Profile == nullptr) - return; - - sourceIndices.erase( - std::remove_if(sourceIndices.begin(), sourceIndices.end(), [](int index) { - return index < 0 || static_cast(index) >= ModInfo::getNumMods(); - }), - sourceIndices.end()); - - if (sourceIndices.empty()) { - return; - } - - const bool anyNeedsMove = std::any_of( - sourceIndices.begin(), sourceIndices.end(), - [this, newPriority](int index) { return m_Profile->getModPriority(index) != newPriority; }); - if (!anyNeedsMove) { - return; - } - - emit layoutAboutToBeChanged(); +void ModList::changeModPriority(std::vector sourceIndices, int newPriority) +{ + if (m_Profile == nullptr) + return; + + sourceIndices.erase( + std::remove_if(sourceIndices.begin(), sourceIndices.end(), [](int index) { + return index < 0 || static_cast(index) >= ModInfo::getNumMods(); + }), + sourceIndices.end()); + + if (sourceIndices.empty()) { + return; + } + + const bool anyNeedsMove = std::any_of( + sourceIndices.begin(), sourceIndices.end(), + [this, newPriority](int index) { return m_Profile->getModPriority(index) != newPriority; }); + if (!anyNeedsMove) { + return; + } + + emit layoutAboutToBeChanged(); // sort the moving mods by ascending priorities std::sort(sourceIndices.begin(), sourceIndices.end(), - [=](const int& LHS, const int& RHS) { + [=, this](const int& LHS, const int& RHS) { return m_Profile->getModPriority(LHS) > m_Profile->getModPriority(RHS); }); @@ -701,7 +701,7 @@ void ModList::changeModPriority(std::vector sourceIndices, int newPriority) // sort the moving mods by descending priorities std::sort(sourceIndices.begin(), sourceIndices.end(), - [=](const int& LHS, const int& RHS) { + [=, this](const int& LHS, const int& RHS) { return m_Profile->getModPriority(LHS) < m_Profile->getModPriority(RHS); }); @@ -1147,8 +1147,8 @@ bool ModList::canDropMimeData(const QMimeData* mimeData, Qt::DropAction action, return false; } -bool ModList::dropMimeData(const QMimeData* mimeData, Qt::DropAction action, int row, - int, const QModelIndex& parent) +bool ModList::dropMimeData(const QMimeData* mimeData, Qt::DropAction action, int row, + int, const QModelIndex& parent) { if (action == Qt::IgnoreAction) { return true; @@ -1165,38 +1165,38 @@ bool ModList::dropMimeData(const QMimeData* mimeData, Qt::DropAction action, int return false; } - if (dropInfo.isLocalFileDrop()) { - return dropLocalFiles(dropInfo, row, parent); - } else { - if (dropInfo.isModDrop()) { - std::vector sourceRows = dropInfo.rows(); - sourceRows.erase( - std::remove_if(sourceRows.begin(), sourceRows.end(), [](int index) { - return index < 0 || static_cast(index) >= ModInfo::getNumMods(); - }), - sourceRows.end()); - - if (sourceRows.empty()) { - return true; - } - - changeModPriority(sourceRows, dropPriority); - return true; - } else if (dropInfo.isDownloadDrop()) { - emit downloadArchiveDropped(dropInfo.download(), dropPriority); - return true; - } else if (dropInfo.isExternalArchiveDrop()) { - emit externalArchiveDropped(dropInfo.externalUrl(), dropPriority); - return true; - } else if (dropInfo.isExternalFolderDrop()) { - emit externalFolderDropped(dropInfo.externalUrl(), dropPriority); - return true; - } else { - return false; - } - } - return true; -} + if (dropInfo.isLocalFileDrop()) { + return dropLocalFiles(dropInfo, row, parent); + } else { + if (dropInfo.isModDrop()) { + std::vector sourceRows = dropInfo.rows(); + sourceRows.erase( + std::remove_if(sourceRows.begin(), sourceRows.end(), [](int index) { + return index < 0 || static_cast(index) >= ModInfo::getNumMods(); + }), + sourceRows.end()); + + if (sourceRows.empty()) { + return true; + } + + changeModPriority(sourceRows, dropPriority); + return true; + } else if (dropInfo.isDownloadDrop()) { + emit downloadArchiveDropped(dropInfo.download(), dropPriority); + return true; + } else if (dropInfo.isExternalArchiveDrop()) { + emit externalArchiveDropped(dropInfo.externalUrl(), dropPriority); + return true; + } else if (dropInfo.isExternalFolderDrop()) { + emit externalFolderDropped(dropInfo.externalUrl(), dropPriority); + return true; + } else { + return false; + } + } + return true; +} void ModList::removeRowForce(int row, const QModelIndex& parent) { @@ -1427,7 +1427,7 @@ void ModList::shiftModsPriority(const QModelIndexList& indices, int offset) auto index = idx.data(IndexRole).toInt(); allIndex.push_back(index); } - std::sort(allIndex.begin(), allIndex.end(), [=](int lhs, int rhs) { + std::sort(allIndex.begin(), allIndex.end(), [=, this](int lhs, int rhs) { bool cmp = m_Profile->getModPriority(lhs) < m_Profile->getModPriority(rhs); return offset > 0 ? !cmp : cmp; }); diff --git a/src/src/modlistcontextmenu.cpp b/src/src/modlistcontextmenu.cpp index d32d6cd..7c78b1f 100644 --- a/src/src/modlistcontextmenu.cpp +++ b/src/src/modlistcontextmenu.cpp @@ -7,6 +7,8 @@ #include "modlistviewactions.h" #include "organizercore.h" +#include + using namespace MOBase; ModListGlobalContextMenu::ModListGlobalContextMenu(OrganizerCore& core, @@ -20,7 +22,7 @@ ModListGlobalContextMenu::ModListGlobalContextMenu(OrganizerCore& core, QWidget* parent) : QMenu(parent) { - connect(this, &QMenu::aboutToShow, [=, &core] { + connect(this, &QMenu::aboutToShow, [=, this, &core] { populate(core, view, index); }); } @@ -47,24 +49,24 @@ void ModListGlobalContextMenu::populate(OrganizerCore& core, ModListView* view, createText = tr("Create empty mod below"); } - addAction(installText, [=]() { + addAction(installText, [=, this]() { view->actions().installMod("", index); }); - addAction(createText, [=]() { + addAction(createText, [=, this]() { view->actions().createEmptyMod(index); }); - addAction(tr("Create separator above"), [=]() { + addAction(tr("Create separator above"), [=, this]() { view->actions().createSeparator(index); }); } } else { - addAction(tr("Install mod..."), [=]() { + addAction(tr("Install mod..."), [=, this]() { view->actions().installMod(); }); - addAction(tr("Create empty mod"), [=]() { + addAction(tr("Create empty mod"), [=, this]() { view->actions().createEmptyMod(); }); - addAction(tr("Create separator"), [=]() { + addAction(tr("Create separator"), [=, this]() { view->actions().createSeparator(); }); } @@ -84,21 +86,21 @@ void ModListGlobalContextMenu::populate(OrganizerCore& core, ModListView* view, disableTxt = tr("Disable all matching mods"); } - addAction(enableTxt, [=] { + addAction(enableTxt, [=, this] { view->actions().setAllMatchingModsEnabled(true); }); - addAction(disableTxt, [=] { + addAction(disableTxt, [=, this] { view->actions().setAllMatchingModsEnabled(false); }); - addAction(tr("Check for updates"), [=]() { + addAction(tr("Check for updates"), [=, this]() { view->actions().checkModsForUpdates(); }); - addAction(tr("Auto assign categories"), [=]() { + addAction(tr("Auto assign categories"), [=, this]() { view->actions().assignCategories(); }); addAction(tr("Refresh"), &core, &OrganizerCore::refresh); - addAction(tr("Export to csv..."), [=]() { + addAction(tr("Export to csv..."), [=, this]() { view->actions().exportModListCSV(); }); } @@ -149,7 +151,7 @@ bool ModListChangeCategoryMenu::populate(QMenu* menu, CategoryFactory* factory, } int id = factory->getCategoryID(i); - QScopedPointer checkBox(new QCheckBox(targetMenu)); + auto checkBox = std::make_unique(targetMenu); bool enabled = categories.find(id) != categories.end(); checkBox->setText(factory->getCategoryName(i).replace('&', "&&")); if (enabled) { @@ -157,10 +159,10 @@ bool ModListChangeCategoryMenu::populate(QMenu* menu, CategoryFactory* factory, } checkBox->setChecked(enabled ? Qt::Checked : Qt::Unchecked); - QScopedPointer checkableAction(new QWidgetAction(targetMenu)); - checkableAction->setDefaultWidget(checkBox.take()); + auto checkableAction = std::make_unique(targetMenu); + checkableAction->setDefaultWidget(checkBox.release()); checkableAction->setData(id); - targetMenu->addAction(checkableAction.take()); + targetMenu->addAction(checkableAction.release()); if (factory->hasChildren(i)) { if (populate(targetMenu, factory, mod, factory->getCategoryID(i)) || enabled) { @@ -176,7 +178,7 @@ ModListPrimaryCategoryMenu::ModListPrimaryCategoryMenu(CategoryFactory* categori ModInfo::Ptr mod, QMenu* parent) : QMenu(tr("Primary Category"), parent) { - connect(this, &QMenu::aboutToShow, [=]() { + connect(this, &QMenu::aboutToShow, [=, this]() { populate(categories, mod); }); } @@ -242,7 +244,7 @@ ModListContextMenu::ModListContextMenu(const QModelIndex& index, OrganizerCore& bool expanded = view->isExpanded(viewIndex); addSeparator(); addAction(tr("Collapse all"), view, &QTreeView::collapseAll); - addAction(tr("Collapse others"), [=]() { + addAction(tr("Collapse others"), [=, this]() { m_view->collapseAll(); m_view->setExpanded(viewIndex, expanded); }); @@ -266,7 +268,7 @@ ModListContextMenu::ModListContextMenu(const QModelIndex& index, OrganizerCore& // add information for all except foreign if (!info->isForeign()) { - QAction* infoAction = addAction(tr("Information..."), [=]() { + QAction* infoAction = addAction(tr("Information..."), [=, this]() { view->actions().displayModInformation(m_index.data(ModList::IndexRole).toInt()); }); setDefaultAction(infoAction); @@ -341,14 +343,14 @@ void ModListContextMenu::addCategoryContextMenus(ModInfo::Ptr mod) { ModListChangeCategoryMenu* categoriesMenu = new ModListChangeCategoryMenu(m_categories, mod, this); - connect(categoriesMenu, &QMenu::aboutToHide, [=]() { + connect(categoriesMenu, &QMenu::aboutToHide, [=, this]() { m_actions.setCategories(m_selected, m_index, categoriesMenu->categories()); }); addMenuAsPushButton(categoriesMenu); ModListPrimaryCategoryMenu* primaryCategoryMenu = new ModListPrimaryCategoryMenu(m_categories, mod, this); - connect(primaryCategoryMenu, &QMenu::aboutToHide, [=]() { + connect(primaryCategoryMenu, &QMenu::aboutToHide, [=, this]() { int category = primaryCategoryMenu->primaryCategory(); if (category != -1) { m_actions.setPrimaryCategory(m_selected, category); @@ -360,20 +362,20 @@ void ModListContextMenu::addCategoryContextMenus(ModInfo::Ptr mod) void ModListContextMenu::addOverwriteActions(ModInfo::Ptr mod) { if (QDir(mod->absolutePath()).count() > 2) { - addAction(tr("Sync to Mods..."), [=]() { + addAction(tr("Sync to Mods..."), [=, this]() { m_core.syncOverwrite(); }); - addAction(tr("Create Mod..."), [=]() { + addAction(tr("Create Mod..."), [=, this]() { m_actions.createModFromOverwrite(); }); - addAction(tr("Move content to Mod..."), [=]() { + addAction(tr("Move content to Mod..."), [=, this]() { m_actions.moveOverwriteContentToExistingMod(); }); - addAction(tr("Clear Overwrite..."), [=]() { + addAction(tr("Clear Overwrite..."), [=, this]() { m_actions.clearOverwrite(); }); } - addAction(tr("Open in Explorer"), [=]() { + addAction(tr("Open in Explorer"), [=, this]() { m_actions.openExplorer(m_selected); }); } @@ -383,10 +385,10 @@ void ModListContextMenu::addSeparatorActions(ModInfo::Ptr mod) addCategoryContextMenus(mod); addSeparator(); - addAction(tr("Rename Separator..."), [=]() { + addAction(tr("Rename Separator..."), [=, this]() { m_actions.renameMod(m_index); }); - addAction(tr("Remove Separator..."), [=]() { + addAction(tr("Remove Separator..."), [=, this]() { m_actions.removeMods(m_selected); }); addSeparator(); @@ -395,12 +397,12 @@ void ModListContextMenu::addSeparatorActions(ModInfo::Ptr mod) addSendToContextMenu(); addSeparator(); } - addAction(tr("Select Color..."), [=]() { + addAction(tr("Select Color..."), [=, this]() { m_actions.setColor(m_selected, m_index); }); if (mod->color().isValid()) { - addAction(tr("Reset Color"), [=]() { + addAction(tr("Reset Color"), [=, this]() { m_actions.resetColor(m_selected); }); } @@ -418,45 +420,45 @@ void ModListContextMenu::addForeignActions(ModInfo::Ptr mod) void ModListContextMenu::addBackupActions(ModInfo::Ptr mod) { auto flags = mod->getFlags(); - addAction(tr("Restore Backup"), [=]() { + addAction(tr("Restore Backup"), [=, this]() { m_actions.restoreBackup(m_index); }); - addAction(tr("Remove Backup..."), [=]() { + addAction(tr("Remove Backup..."), [=, this]() { m_actions.removeMods(m_selected); }); addSeparator(); if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_INVALID) != flags.end()) { - addAction(tr("Ignore missing data"), [=]() { + addAction(tr("Ignore missing data"), [=, this]() { m_actions.ignoreMissingData(m_selected); }); } if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_ALTERNATE_GAME) != flags.end()) { - addAction(tr("Mark as converted/working"), [=]() { + addAction(tr("Mark as converted/working"), [=, this]() { m_actions.markConverted(m_selected); }); } addSeparator(); if (mod->nexusId() > 0) { - addAction(tr("Visit on Nexus"), [=]() { + addAction(tr("Visit on Nexus"), [=, this]() { m_actions.visitOnNexus(m_selected); }); } if (!mod->uploaderUrl().isEmpty()) { - addAction(tr("Visit the uploader's profile"), [=]() { + addAction(tr("Visit the uploader's profile"), [=, this]() { m_actions.visitUploaderProfile(m_selected); }); } const auto url = mod->parseCustomURL(); if (url.isValid()) { - addAction(tr("Visit on %1").arg(url.host()), [=]() { + addAction(tr("Visit on %1").arg(url.host()), [=, this]() { m_actions.visitWebPage(m_selected); }); } - addAction(tr("Open in Explorer"), [=]() { + addAction(tr("Open in Explorer"), [=, this]() { m_actions.openExplorer(m_selected); }); } @@ -469,32 +471,32 @@ void ModListContextMenu::addRegularActions(ModInfo::Ptr mod) addSeparator(); if (mod->downgradeAvailable()) { - addAction(tr("Change versioning scheme"), [=]() { + addAction(tr("Change versioning scheme"), [=, this]() { m_actions.changeVersioningScheme(m_index); }); } if (mod->nexusId() > 0) - addAction(tr("Force-check updates"), [=]() { + addAction(tr("Force-check updates"), [=, this]() { m_actions.checkModsForUpdates(m_selected); }); if (mod->updateIgnored()) { - addAction(tr("Un-ignore update"), [=]() { + addAction(tr("Un-ignore update"), [=, this]() { m_actions.setIgnoreUpdate(m_selected, false); }); } else { if (mod->updateAvailable() || mod->downgradeAvailable()) { - addAction(tr("Ignore update"), [=]() { + addAction(tr("Ignore update"), [=, this]() { m_actions.setIgnoreUpdate(m_selected, true); }); } } addSeparator(); - addAction(tr("Enable selected"), [=]() { + addAction(tr("Enable selected"), [=, this]() { m_core.modList()->setActive(m_selected, true); }); - addAction(tr("Disable selected"), [=]() { + addAction(tr("Disable selected"), [=, this]() { m_core.modList()->setActive(m_selected, false); }); @@ -505,22 +507,22 @@ void ModListContextMenu::addRegularActions(ModInfo::Ptr mod) addSeparator(); } - addAction(tr("Rename Mod..."), [=]() { + addAction(tr("Rename Mod..."), [=, this]() { m_actions.renameMod(m_index); }); - addAction(tr("Reinstall Mod"), [=]() { + addAction(tr("Reinstall Mod"), [=, this]() { m_actions.reinstallMod(m_index); }); - addAction(tr("Remove Mod..."), [=]() { + addAction(tr("Remove Mod..."), [=, this]() { m_actions.removeMods(m_selected); }); - addAction(tr("Create Backup"), [=]() { + addAction(tr("Create Backup"), [=, this]() { m_actions.createBackup(m_index); }); if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_HIDDEN_FILES) != flags.end()) { - addAction(tr("Restore hidden files"), [=]() { + addAction(tr("Restore hidden files"), [=, this]() { m_actions.restoreHiddenFiles(m_selected); }); } @@ -528,11 +530,11 @@ void ModListContextMenu::addRegularActions(ModInfo::Ptr mod) addSeparator(); if (m_index.column() == ModList::COL_NOTES) { - addAction(tr("Select Color..."), [=]() { + addAction(tr("Select Color..."), [=, this]() { m_actions.setColor(m_selected, m_index); }); if (mod->color().isValid()) { - addAction(tr("Reset Color"), [=]() { + addAction(tr("Reset Color"), [=, this]() { m_actions.resetColor(m_selected); }); } @@ -542,20 +544,20 @@ void ModListContextMenu::addRegularActions(ModInfo::Ptr mod) if (mod->nexusId() > 0 && Settings::instance().nexus().endorsementIntegration()) { switch (mod->endorsedState()) { case EndorsedState::ENDORSED_TRUE: { - addAction(tr("Un-Endorse"), [=]() { + addAction(tr("Un-Endorse"), [=, this]() { m_actions.setEndorsed(m_selected, false); }); } break; case EndorsedState::ENDORSED_FALSE: { - addAction(tr("Endorse"), [=]() { + addAction(tr("Endorse"), [=, this]() { m_actions.setEndorsed(m_selected, true); }); - addAction(tr("Won't endorse"), [=]() { + addAction(tr("Won't endorse"), [=, this]() { m_actions.willNotEndorsed(m_selected); }); } break; case EndorsedState::ENDORSED_NEVER: { - addAction(tr("Endorse"), [=]() { + addAction(tr("Endorse"), [=, this]() { m_actions.setEndorsed(m_selected, true); }); } break; @@ -570,7 +572,7 @@ void ModListContextMenu::addRegularActions(ModInfo::Ptr mod) if (mod->nexusId() > 0 && (mod->getNexusCategory() > 0 || !mod->installationFile().isEmpty()) && !mod->isSeparator()) { - addAction(tr("Remap Category (From Nexus)"), [=]() { + addAction(tr("Remap Category (From Nexus)"), [=, this]() { m_actions.remapCategory(m_selected); }); } @@ -578,12 +580,12 @@ void ModListContextMenu::addRegularActions(ModInfo::Ptr mod) if (mod->nexusId() > 0 && Settings::instance().nexus().trackedIntegration()) { switch (mod->trackedState()) { case TrackedState::TRACKED_FALSE: { - addAction(tr("Start tracking"), [=]() { + addAction(tr("Start tracking"), [=, this]() { m_actions.setTracked(m_selected, true); }); } break; case TrackedState::TRACKED_TRUE: { - addAction(tr("Stop tracking"), [=]() { + addAction(tr("Stop tracking"), [=, this]() { m_actions.setTracked(m_selected, false); }); } break; @@ -598,14 +600,14 @@ void ModListContextMenu::addRegularActions(ModInfo::Ptr mod) addSeparator(); if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_INVALID) != flags.end()) { - addAction(tr("Ignore missing data"), [=]() { + addAction(tr("Ignore missing data"), [=, this]() { m_actions.ignoreMissingData(m_selected); }); } if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_ALTERNATE_GAME) != flags.end()) { - addAction(tr("Mark as converted/working"), [=]() { + addAction(tr("Mark as converted/working"), [=, this]() { m_actions.markConverted(m_selected); }); } @@ -613,25 +615,25 @@ void ModListContextMenu::addRegularActions(ModInfo::Ptr mod) addSeparator(); if (mod->nexusId() > 0) { - addAction(tr("Visit on Nexus"), [=]() { + addAction(tr("Visit on Nexus"), [=, this]() { m_actions.visitOnNexus(m_selected); }); } if (!mod->uploaderUrl().isEmpty()) { - addAction(tr("Visit the uploader's profile"), [=]() { + addAction(tr("Visit the uploader's profile"), [=, this]() { m_actions.visitUploaderProfile(m_selected); }); } const auto url = mod->parseCustomURL(); if (url.isValid()) { - addAction(tr("Visit on %1").arg(url.host()), [=]() { + addAction(tr("Visit on %1").arg(url.host()), [=, this]() { m_actions.visitWebPage(m_selected); }); } - addAction(tr("Open in Explorer"), [=]() { + addAction(tr("Open in Explorer"), [=, this]() { m_actions.openExplorer(m_selected); }); } diff --git a/src/src/modlistsortproxy.cpp b/src/src/modlistsortproxy.cpp index 4ffeec9..81b9a17 100644 --- a/src/src/modlistsortproxy.cpp +++ b/src/src/modlistsortproxy.cpp @@ -51,26 +51,36 @@ void ModListSortProxy::setProfile(Profile* profile) m_Profile = profile; } -void ModListSortProxy::updateFilterActive() -{ - m_FilterActive = (!m_Criteria.empty() || !m_Filter.isEmpty()); - emit filterActive(m_FilterActive); -} - -void ModListSortProxy::setCriteria(const std::vector& criteria) -{ +void ModListSortProxy::updateFilterActive() +{ + m_FilterActive = (!m_Criteria.empty() || !m_Filter.isEmpty()); + emit filterActive(m_FilterActive); +} + +void ModListSortProxy::refreshFilter() +{ +#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0) + beginFilterChange(); + endFilterChange(QSortFilterProxyModel::Direction::Rows); +#else + invalidateFilter(); +#endif +} + +void ModListSortProxy::setCriteria(const std::vector& criteria) +{ // avoid refreshing the filter unless we are checking all mods for update. const bool changed = (criteria != m_Criteria); const bool isForUpdates = (!criteria.empty() && criteria[0].id == CategoryFactory::UpdateAvailable); - if (changed || isForUpdates) { - m_Criteria = criteria; - updateFilterActive(); - invalidateFilter(); - emit filterInvalidated(); - } -} + if (changed || isForUpdates) { + m_Criteria = criteria; + updateFilterActive(); + refreshFilter(); + emit filterInvalidated(); + } +} unsigned long ModListSortProxy::flagsId(const std::vector& flags) const { @@ -254,13 +264,13 @@ bool ModListSortProxy::lessThan(const QModelIndex& left, const QModelIndex& righ return lt; } -void ModListSortProxy::updateFilter(const QString& filter) -{ - m_Filter = filter; - updateFilterActive(); - invalidateFilter(); - emit filterInvalidated(); -} +void ModListSortProxy::updateFilter(const QString& filter) +{ + m_Filter = filter; + updateFilterActive(); + refreshFilter(); + emit filterInvalidated(); +} bool ModListSortProxy::hasConflictFlag( const std::vector& flags) const @@ -560,14 +570,14 @@ void ModListSortProxy::setColumnVisible(int column, bool visible) void ModListSortProxy::setOptions(ModListSortProxy::FilterMode mode, SeparatorsMode separators) -{ - if (m_FilterMode != mode || separators != m_FilterSeparators) { - m_FilterMode = mode; - m_FilterSeparators = separators; - invalidateFilter(); - emit filterInvalidated(); - } -} +{ + if (m_FilterMode != mode || separators != m_FilterSeparators) { + m_FilterMode = mode; + m_FilterSeparators = separators; + refreshFilter(); + emit filterInvalidated(); + } +} bool ModListSortProxy::filterAcceptsRow(int source_row, const QModelIndex& parent) const { @@ -586,12 +596,12 @@ bool ModListSortProxy::filterAcceptsRow(int source_row, const QModelIndex& paren return false; } - unsigned int index = ULONG_MAX; + unsigned int index = UINT_MAX; { bool ok = false; index = idx.data(ModList::IndexRole).toInt(&ok); if (!ok) { - index = ULONG_MAX; + index = UINT_MAX; } } diff --git a/src/src/modlistsortproxy.h b/src/src/modlistsortproxy.h index 030dc76..b15606d 100644 --- a/src/src/modlistsortproxy.h +++ b/src/src/modlistsortproxy.h @@ -127,8 +127,9 @@ protected: virtual bool lessThan(const QModelIndex& left, const QModelIndex& right) const; virtual bool filterAcceptsRow(int row, const QModelIndex& parent) const; -private: - unsigned long flagsId(const std::vector& flags) const; +private: + void refreshFilter(); + unsigned long flagsId(const std::vector& flags) const; unsigned long conflictFlagsId(const std::vector& flags) const; bool hasConflictFlag(const std::vector& flags) const; void updateFilterActive(); diff --git a/src/src/modlistview.cpp b/src/src/modlistview.cpp index ae69ac4..108baf3 100644 --- a/src/src/modlistview.cpp +++ b/src/src/modlistview.cpp @@ -154,11 +154,11 @@ ModListView::ModListView(QWidget* parent) // time window m_refreshMarkersTimer.setInterval(50); m_refreshMarkersTimer.setSingleShot(true); - connect(&m_refreshMarkersTimer, &QTimer::timeout, [=] { + connect(&m_refreshMarkersTimer, &QTimer::timeout, [=, this] { refreshMarkersAndPlugins(); }); - installEventFilter(new CopyEventFilter(this, [=](auto& index) { + installEventFilter(new CopyEventFilter(this, [=, this](auto& index) { QVariant mIndex = index.data(ModList::IndexRole); QString name = index.data(Qt::DisplayRole).toString(); if (mIndex.isValid() && hasCollapsibleSeparators()) { @@ -430,7 +430,7 @@ void ModListView::scrollToAndSelect(const QModelIndexList& indexes, } selectionModel()->select(selection, QItemSelectionModel::Select | QItemSelectionModel::Rows); - QTimer::singleShot(50, [=] { + QTimer::singleShot(50, [=, this] { setFocus(); }); } @@ -716,21 +716,21 @@ void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, MainWindo mwui->filtersSeparators, mwui->espList}; - connect(m_core, &OrganizerCore::modInstalled, [=](auto&& name) { + connect(m_core, &OrganizerCore::modInstalled, [=, this](auto&& name) { onModInstalled(name); }); connect(m_core, &OrganizerCore::profileChanged, this, &ModListView::onProfileChanged); - connect(core.modList(), &ModList::modPrioritiesChanged, [=](auto&& indices) { + connect(core.modList(), &ModList::modPrioritiesChanged, [=, this](auto&& indices) { onModPrioritiesChanged(indices); }); - connect(core.modList(), &ModList::clearOverwrite, [=] { + connect(core.modList(), &ModList::clearOverwrite, [=, this] { m_actions->clearOverwrite(); }); - connect(core.modList(), &ModList::modStatesChanged, [=] { + connect(core.modList(), &ModList::modStatesChanged, [=, this] { updateModCount(); setOverwriteMarkers(selectionModel()->selectedRows()); }); - connect(core.modList(), &ModList::modelReset, [=] { + connect(core.modList(), &ModList::modelReset, [=, this] { clearOverwriteMarkers(); }); @@ -746,14 +746,14 @@ void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, MainWindo // we need to store the expanded/collapsed state of all items and restore them 1) when // switching proxies, 2) when filtering and 3) when reseting the mod list. - connect(this, &QTreeView::expanded, [=](const QModelIndex& index) { + connect(this, &QTreeView::expanded, [=, this](const QModelIndex& index) { auto it = m_collapsed[m_sortProxy->sourceModel()].find( index.data(Qt::DisplayRole).toString()); if (it != m_collapsed[m_sortProxy->sourceModel()].end()) { m_collapsed[m_sortProxy->sourceModel()].erase(it); } }); - connect(this, &QTreeView::collapsed, [=](const QModelIndex& index) { + connect(this, &QTreeView::collapsed, [=, this](const QModelIndex& index) { m_collapsed[m_sortProxy->sourceModel()].insert( index.data(Qt::DisplayRole).toString()); }); @@ -761,7 +761,7 @@ void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, MainWindo // the top-level proxy m_sortProxy = new ModListSortProxy(core.currentProfile().get(), &core); setModel(m_sortProxy); - connect(m_sortProxy, &ModList::modelReset, [=] { + connect(m_sortProxy, &ModList::modelReset, [=, this] { refreshExpandedItems(); }); @@ -773,7 +773,7 @@ void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, MainWindo } }); connect(ui.groupBy, QOverload::of(&QComboBox::currentIndexChanged), - [=](int index) { + [=, this](int index) { updateGroupByProxy(); onModFilterActive(m_sortProxy->isFilterActive()); }); @@ -788,11 +788,11 @@ void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, MainWindo connect(m_sortProxy, &ModListSortProxy::filterInvalidated, this, &ModListView::updateModCount); - connect(header(), &QHeaderView::sortIndicatorChanged, [=](int, Qt::SortOrder) { + connect(header(), &QHeaderView::sortIndicatorChanged, [=, this](int, Qt::SortOrder) { verticalScrollBar()->repaint(); }); connect(header(), &QHeaderView::sectionResized, - [=](int logicalIndex, int oldSize, int newSize) { + [=, this](int logicalIndex, int oldSize, int newSize) { m_sortProxy->setColumnVisible(logicalIndex, newSize != 0); }); @@ -839,13 +839,13 @@ void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, MainWindo // in the manual installer connect( m_core->modList(), &ModList::downloadArchiveDropped, this, - [=](int row, int priority) { + [=, this](int row, int priority) { m_core->installDownload(row, priority); }, Qt::QueuedConnection); connect( m_core->modList(), &ModList::externalArchiveDropped, this, - [=](const QUrl& url, int priority) { + [=, this](const QUrl& url, int priority) { setWindowState(Qt::WindowActive); m_core->installArchive(url.toLocalFile(), priority, false, nullptr); }, @@ -853,32 +853,32 @@ void ModListView::setup(OrganizerCore& core, CategoryFactory& factory, MainWindo connect(m_core->modList(), &ModList::externalFolderDropped, this, &ModListView::onExternalFolderDropped); - connect(selectionModel(), &QItemSelectionModel::selectionChanged, [=] { + connect(selectionModel(), &QItemSelectionModel::selectionChanged, [=, this] { m_refreshMarkersTimer.start(); }); - connect(this, &QTreeView::collapsed, [=] { + connect(this, &QTreeView::collapsed, [=, this] { m_refreshMarkersTimer.start(); }); - connect(this, &QTreeView::expanded, [=] { + connect(this, &QTreeView::expanded, [=, this] { m_refreshMarkersTimer.start(); }); // filters connect(m_sortProxy, &ModListSortProxy::filterActive, this, &ModListView::onModFilterActive); - connect(m_filters.get(), &FilterList::criteriaChanged, [=](auto&& v) { + connect(m_filters.get(), &FilterList::criteriaChanged, [=, this](auto&& v) { onFiltersCriteria(v); }); - connect(m_filters.get(), &FilterList::optionsChanged, [=](auto&& mode, auto&& sep) { + connect(m_filters.get(), &FilterList::optionsChanged, [=, this](auto&& mode, auto&& sep) { setFilterOptions(mode, sep); }); connect(ui.filter, &QLineEdit::textChanged, m_sortProxy, &ModListSortProxy::updateFilter); - connect(ui.clearFilters, &QPushButton::clicked, [=]() { + connect(ui.clearFilters, &QPushButton::clicked, [=, this]() { ui.filter->clear(); m_filters->clearSelection(); }); - connect(m_sortProxy, &ModListSortProxy::filterInvalidated, [=]() { + connect(m_sortProxy, &ModListSortProxy::filterInvalidated, [=, this]() { if (hasCollapsibleSeparators()) { refreshExpandedItems(); } @@ -1383,9 +1383,9 @@ void ModListView::dropEvent(QDropEvent* event) { // from Qt source QModelIndex index; - if (viewport()->rect().contains(event->pos())) { - index = indexAt(event->pos()); - if (!index.isValid() || !visualRect(index).contains(event->pos())) + if (viewport()->rect().contains(event->position().toPoint())) { + index = indexAt(event->position().toPoint()); + if (!index.isValid() || !visualRect(index).contains(event->position().toPoint())) index = QModelIndex(); } @@ -1439,10 +1439,10 @@ void ModListView::mousePressEvent(QMouseEvent* event) // restore triggers setEditTriggers(triggers); - const auto index = indexAt(event->pos()); + const auto index = indexAt(event->position().toPoint()); if (event->isAccepted() && hasCollapsibleSeparators() && index.isValid() && - model()->hasChildren(indexAt(event->pos())) && + model()->hasChildren(indexAt(event->position().toPoint())) && (event->modifiers() & Qt::AltModifier)) { const auto flag = selectionModel()->isSelected(index) @@ -1472,13 +1472,13 @@ void ModListView::mouseReleaseEvent(QMouseEvent* event) // selection state of the item after QTreeView::mouseReleaseEvent(event); - const auto index = indexAt(event->pos()); + const auto index = indexAt(event->position().toPoint()); // restore triggers setEditTriggers(triggers); if (event->isAccepted() && hasCollapsibleSeparators() && index.isValid() && - model()->hasChildren(indexAt(event->pos())) && + model()->hasChildren(indexAt(event->position().toPoint())) && (event->modifiers() & Qt::AltModifier)) { const auto flag = selectionModel()->isSelected(index) diff --git a/src/src/modlistviewactions.cpp b/src/src/modlistviewactions.cpp index 6b2daad..ace0045 100644 --- a/src/src/modlistviewactions.cpp +++ b/src/src/modlistviewactions.cpp @@ -232,7 +232,7 @@ void ModListViewActions::checkModsForUpdates() const } else { QString apiKey; if (GlobalSettings::nexusApiKey(apiKey)) { - m_core.doAfterLogin([=]() { + m_core.doAfterLogin([=, this]() { checkModsForUpdates(); }); NexusInterface::instance().getAccessManager()->apiCheck(apiKey); @@ -312,7 +312,7 @@ void ModListViewActions::checkModsForUpdates( } else { QString apiKey; if (GlobalSettings::nexusApiKey(apiKey)) { - m_core.doAfterLogin([=]() { + m_core.doAfterLogin([=, this]() { checkModsForUpdates(IDs); }); NexusInterface::instance().getAccessManager()->apiCheck(apiKey); @@ -578,7 +578,7 @@ void ModListViewActions::displayModInformation(ModInfo::Ptr modInfo, dialog->show(); dialog->raise(); dialog->activateWindow(); - connect(dialog, &QDialog::finished, [=]() { + connect(dialog, &QDialog::finished, [=, this]() { m_core.modList()->modInfoChanged(modInfo); dialog->deleteLater(); m_core.refreshDirectoryStructure(); @@ -592,7 +592,7 @@ void ModListViewActions::displayModInformation(ModInfo::Ptr modInfo, ModInfoDialog dialog(m_core, m_core.pluginContainer(), modInfo, m_view, m_parent); connect(&dialog, &ModInfoDialog::originModified, this, &ModListViewActions::originModified); - connect(&dialog, &ModInfoDialog::modChanged, [=](unsigned int index) { + connect(&dialog, &ModInfoDialog::modChanged, [=, this](unsigned int index) { auto idx = m_view->indexModelToView(m_core.modList()->index(index, 0)); m_view->selectionModel()->select(idx, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); @@ -740,7 +740,7 @@ void ModListViewActions::sendModsToFirstConflict(const QModelIndexList& indexes) std::set priorities; std::transform(conflicts.begin(), conflicts.end(), - std::inserter(priorities, priorities.end()), [=](auto index) { + std::inserter(priorities, priorities.end()), [=, this](auto index) { return m_core.currentProfile()->getModPriority(index); }); @@ -764,7 +764,7 @@ void ModListViewActions::sendModsToLastConflict(const QModelIndexList& indexes) std::set priorities; std::transform(conflicts.begin(), conflicts.end(), - std::inserter(priorities, priorities.end()), [=](auto index) { + std::inserter(priorities, priorities.end()), [=, this](auto index) { return m_core.currentProfile()->getModPriority(index); }); @@ -1136,7 +1136,7 @@ void ModListViewActions::restoreHiddenFiles(const QModelIndexList& indices) cons void ModListViewActions::setTracked(const QModelIndexList& indices, bool tracked) const { - m_core.loggedInAction(m_parent, [=] { + m_core.loggedInAction(m_parent, [=, this] { for (auto& idx : indices) { ModInfo::getByIndex(idx.data(ModList::IndexRole).toInt())->track(tracked); } @@ -1146,7 +1146,7 @@ void ModListViewActions::setTracked(const QModelIndexList& indices, bool tracked void ModListViewActions::setEndorsed(const QModelIndexList& indices, bool endorsed) const { - m_core.loggedInAction(m_parent, [=] { + m_core.loggedInAction(m_parent, [=, this] { if (indices.size() > 1) { MessageDialog::showMessage( tr("Endorsing multiple mods will take a while. Please wait..."), m_parent); diff --git a/src/src/nxmaccessmanager.cpp b/src/src/nxmaccessmanager.cpp index 3a68b62..ad9b0bd 100644 --- a/src/src/nxmaccessmanager.cpp +++ b/src/src/nxmaccessmanager.cpp @@ -162,11 +162,9 @@ NexusSSOLogin::NexusSSOLogin() : m_keyReceived(false), m_active(false) onConnected(); }); - QObject::connect(&m_socket, - qOverload(&QWebSocket::error), - [&](auto&& e) { - onError(e); - }); + QObject::connect(&m_socket, &QWebSocket::errorOccurred, [&](auto&& e) { + onError(e); + }); QObject::connect(&m_socket, &QWebSocket::sslErrors, [&](auto&& errors) { onSslErrors(errors); diff --git a/src/src/organizercore.cpp b/src/src/organizercore.cpp index 5a3d21d..e0e32cb 100644 --- a/src/src/organizercore.cpp +++ b/src/src/organizercore.cpp @@ -213,7 +213,7 @@ OrganizerCore::OrganizerCore(Settings& settings) &OrganizerCore::onDirectoryRefreshed); connect(&m_ModList, SIGNAL(removeOrigin(QString)), this, SLOT(removeOrigin(QString))); - connect(&m_ModList, &ModList::modStatesChanged, [=] { + connect(&m_ModList, &ModList::modStatesChanged, [=, this] { currentProfile()->writeModlist(); }); connect(&m_ModList, &ModList::modPrioritiesChanged, [this](auto&& indexes) { @@ -908,7 +908,7 @@ OrganizerCore::doInstall(const QString& archivePath, GuessedValue modNa // connect( this, &OrganizerCore::directoryStructureReady, this, - [=] { + [=, this] { const int modIndex = ModInfo::getIndex(modName); if (modIndex != UINT_MAX) { const auto modInfo = ModInfo::getByIndex(modIndex); diff --git a/src/src/plugincontainer.cpp b/src/src/plugincontainer.cpp index 1397aad..98665fb 100644 --- a/src/src/plugincontainer.cpp +++ b/src/src/plugincontainer.cpp @@ -1175,7 +1175,10 @@ void PluginContainer::loadPlugins() loadCheck.close(); } - loadCheck.open(QIODevice::WriteOnly); + if (!loadCheck.open(QIODevice::WriteOnly)) { + log::warn("failed to open loadcheck file for writing '{}'", + QDir::toNativeSeparators(loadCheck.fileName())); + } } QString pluginPath = @@ -1259,11 +1262,15 @@ void PluginContainer::loadPlugins() } log::warn("user skipped plugin '{}', remembering in loadcheck", skipPlugin); - loadCheck.open(QIODevice::WriteOnly); - loadCheck.write(skipPlugin.toUtf8()); - loadCheck.write("\n"); - loadCheck.flush(); - } + if (loadCheck.open(QIODevice::WriteOnly)) { + loadCheck.write(skipPlugin.toUtf8()); + loadCheck.write("\n"); + loadCheck.flush(); + } else { + log::warn("failed to persist skipped plugin to '{}'", + QDir::toNativeSeparators(loadCheck.fileName())); + } + } bf::at_key(m_Plugins).push_back(this); diff --git a/src/src/pluginlist.cpp b/src/src/pluginlist.cpp index aa44e0a..8fac338 100644 --- a/src/src/pluginlist.cpp +++ b/src/src/pluginlist.cpp @@ -567,7 +567,7 @@ void PluginList::shiftPluginsPriority(const QModelIndexList& indices, int offset for (auto& idx : indices) { allIndex.push_back(idx.row()); } - std::sort(allIndex.begin(), allIndex.end(), [=](int lhs, int rhs) { + std::sort(allIndex.begin(), allIndex.end(), [=, this](int lhs, int rhs) { bool cmp = m_ESPs[lhs].priority < m_ESPs[rhs].priority; return offset > 0 ? !cmp : cmp; }); @@ -662,7 +662,10 @@ void PluginList::readLockedOrderFrom(const QString& fileName) return; } - file.open(QIODevice::ReadOnly); + if (!file.open(QIODevice::ReadOnly)) { + log::error("failed to open locked order file '{}': {}", fileName, file.errorString()); + return; + } int lineNumber = 0; while (!file.atEnd()) { QByteArray line = file.readLine(); diff --git a/src/src/pluginlistcontextmenu.cpp b/src/src/pluginlistcontextmenu.cpp index 39f2065..d989c0f 100644 --- a/src/src/pluginlistcontextmenu.cpp +++ b/src/src/pluginlistcontextmenu.cpp @@ -21,24 +21,24 @@ PluginListContextMenu::PluginListContextMenu(const QModelIndex& index, } if (!m_selected.isEmpty()) { - addAction(tr("Enable selected"), [=]() { + addAction(tr("Enable selected"), [=, this]() { m_core.pluginList()->setEnabled(m_selected, true); }); - addAction(tr("Disable selected"), [=]() { + addAction(tr("Disable selected"), [=, this]() { m_core.pluginList()->setEnabled(m_selected, false); }); addSeparator(); } - addAction(tr("Enable all"), [=]() { + addAction(tr("Enable all"), [=, this]() { if (QMessageBox::question(m_view->topLevelWidget(), tr("Confirm"), tr("Really enable all plugins?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { m_core.pluginList()->setEnabledAll(true); } }); - addAction(tr("Disable all"), [=]() { + addAction(tr("Disable all"), [=, this]() { if (QMessageBox::question(m_view->topLevelWidget(), tr("Confirm"), tr("Really disable all plugins?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { @@ -65,12 +65,12 @@ PluginListContextMenu::PluginListContextMenu(const QModelIndex& index, } if (hasLocked) { - addAction(tr("Unlock load order"), [=]() { + addAction(tr("Unlock load order"), [=, this]() { setESPLock(m_selected, false); }); } if (hasUnlocked) { - addAction(tr("Lock load order"), [=]() { + addAction(tr("Lock load order"), [=, this]() { setESPLock(m_selected, true); }); } @@ -83,14 +83,14 @@ PluginListContextMenu::PluginListContextMenu(const QModelIndex& index, ModInfo::getIndex(m_core.pluginList()->origin(m_index.data().toString())); // this is to avoid showing the option on game files like skyrim.esm if (modInfoIndex != UINT_MAX) { - addAction(tr("Open Origin in Explorer"), [=]() { + addAction(tr("Open Origin in Explorer"), [=, this]() { openOriginExplorer(m_selected); }); ModInfo::Ptr modInfo = ModInfo::getByIndex(modInfoIndex); std::vector flags = modInfo->getFlags(); if (!modInfo->isForeign() && m_selected.size() == 1) { - QAction* infoAction = addAction(tr("Open Origin Info..."), [=]() { + QAction* infoAction = addAction(tr("Open Origin Info..."), [=, this]() { openOriginInformation(index); }); setDefaultAction(infoAction); @@ -103,13 +103,13 @@ QMenu* PluginListContextMenu::createSendToContextMenu() { QMenu* menu = new QMenu(m_view); menu->setTitle(tr("Send to... ")); - menu->addAction(tr("Top"), [=]() { + menu->addAction(tr("Top"), [=, this]() { m_core.pluginList()->sendToPriority(m_selected, 0); }); - menu->addAction(tr("Bottom"), [=]() { + menu->addAction(tr("Bottom"), [=, this]() { m_core.pluginList()->sendToPriority(m_selected, INT_MAX); }); - menu->addAction(tr("Priority..."), [=]() { + menu->addAction(tr("Priority..."), [=, this]() { sendPluginsToPriority(m_selected); }); return menu; diff --git a/src/src/pluginlistsortproxy.cpp b/src/src/pluginlistsortproxy.cpp index 0d42ce9..b7fb797 100644 --- a/src/src/pluginlistsortproxy.cpp +++ b/src/src/pluginlistsortproxy.cpp @@ -25,17 +25,27 @@ along with Mod Organizer. If not, see . #include #include -PluginListSortProxy::PluginListSortProxy(QObject* parent) - : QSortFilterProxyModel(parent), m_SortIndex(0), m_SortOrder(Qt::AscendingOrder) -{ +PluginListSortProxy::PluginListSortProxy(QObject* parent) + : QSortFilterProxyModel(parent), m_SortIndex(0), m_SortOrder(Qt::AscendingOrder) +{ m_EnabledColumns.set(PluginList::COL_NAME); m_EnabledColumns.set(PluginList::COL_PRIORITY); m_EnabledColumns.set(PluginList::COL_MODINDEX); - this->setDynamicSortFilter(true); -} - -void PluginListSortProxy::setEnabledColumns(unsigned int columns) -{ + this->setDynamicSortFilter(true); +} + +void PluginListSortProxy::refreshFilter() +{ +#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0) + beginFilterChange(); + endFilterChange(QSortFilterProxyModel::Direction::Rows); +#else + invalidateFilter(); +#endif +} + +void PluginListSortProxy::setEnabledColumns(unsigned int columns) +{ emit layoutAboutToBeChanged(); for (int i = 0; i <= PluginList::COL_LASTCOLUMN; ++i) { m_EnabledColumns.set(i, (columns & (1 << i)) != 0); @@ -43,11 +53,11 @@ void PluginListSortProxy::setEnabledColumns(unsigned int columns) emit layoutChanged(); } -void PluginListSortProxy::updateFilter(const QString& filter) -{ - m_CurrentFilter = filter; - invalidateFilter(); -} +void PluginListSortProxy::updateFilter(const QString& filter) +{ + m_CurrentFilter = filter; + refreshFilter(); +} bool PluginListSortProxy::filterAcceptsRow(int row, const QModelIndex&) const { diff --git a/src/src/pluginlistsortproxy.h b/src/src/pluginlistsortproxy.h index 370dd87..73d439d 100644 --- a/src/src/pluginlistsortproxy.h +++ b/src/src/pluginlistsortproxy.h @@ -52,8 +52,9 @@ protected: virtual bool filterAcceptsRow(int row, const QModelIndex& parent) const; virtual bool lessThan(const QModelIndex& left, const QModelIndex& right) const; -private: - int m_SortIndex; +private: + void refreshFilter(); + int m_SortIndex; Qt::SortOrder m_SortOrder; std::bitset m_EnabledColumns; diff --git a/src/src/pluginlistview.cpp b/src/src/pluginlistview.cpp index e90b8b8..58b0548 100644 --- a/src/src/pluginlistview.cpp +++ b/src/src/pluginlistview.cpp @@ -203,7 +203,7 @@ void PluginListView::onSortButtonClicked() m_core->savePluginList(); topLevelWidget()->setEnabled(false); - Guard g([=]() { + Guard g([=, this]() { topLevelWidget()->setEnabled(true); }); @@ -251,15 +251,15 @@ void PluginListView::setup(OrganizerCore& core, MainWindow* mw, Ui::MainWindow* setItemDelegateForColumn(PluginList::COL_FLAGS, new GenericIconDelegate(this)); // counter - connect(core.pluginList(), &PluginList::writePluginsList, [=] { + connect(core.pluginList(), &PluginList::writePluginsList, [=, this] { updatePluginCount(); }); - connect(core.pluginList(), &PluginList::esplist_changed, [=] { + connect(core.pluginList(), &PluginList::esplist_changed, [=, this] { updatePluginCount(); }); // sort - connect(mwui->sortButton, &QPushButton::clicked, [=] { + connect(mwui->sortButton, &QPushButton::clicked, [=, this] { onSortButtonClicked(); }); @@ -269,7 +269,7 @@ void PluginListView::setup(OrganizerCore& core, MainWindow* mw, Ui::MainWindow* connect(ui.filter, &QLineEdit::textChanged, this, &PluginListView::onFilterChanged); // highlight mod list when selected - connect(selectionModel(), &QItemSelectionModel::selectionChanged, [=] { + connect(selectionModel(), &QItemSelectionModel::selectionChanged, [=, this] { std::set mods; auto& directoryEntry = *m_core->directoryStructure(); auto pluginIndices = indexViewToModel(selectionModel()->selectedRows()); @@ -291,10 +291,10 @@ void PluginListView::setup(OrganizerCore& core, MainWindow* mw, Ui::MainWindow* }); // using a lambda here to avoid storing the mod list actions - connect(this, &QTreeView::customContextMenuRequested, [=](auto&& pos) { + connect(this, &QTreeView::customContextMenuRequested, [=, this](auto&& pos) { onCustomContextMenuRequested(pos); }); - connect(this, &QTreeView::doubleClicked, [=](auto&& index) { + connect(this, &QTreeView::doubleClicked, [=, this](auto&& index) { onDoubleClicked(index); }); } @@ -303,7 +303,7 @@ void PluginListView::onCustomContextMenuRequested(const QPoint& pos) { try { PluginListContextMenu menu(indexViewToModel(indexAt(pos)), *m_core, this); - connect(&menu, &PluginListContextMenu::openModInformation, [=](auto&& modIndex) { + connect(&menu, &PluginListContextMenu::openModInformation, [=, this](auto&& modIndex) { m_modActions->displayModInformation(modIndex); }); menu.exec(viewport()->mapToGlobal(pos)); diff --git a/src/src/profile.cpp b/src/src/profile.cpp index 405ba4e..0060fa1 100644 --- a/src/src/profile.cpp +++ b/src/src/profile.cpp @@ -910,9 +910,9 @@ bool Profile::enableLocalSaves(bool enable) "games)"), QDialogButtonBox::No | QDialogButtonBox::Yes | QDialogButtonBox::Cancel, QDialogButtonBox::No); - if (res == QMessageBox::Yes) { + if (res == QDialogButtonBox::Yes) { shellDelete(QStringList(m_Directory.absoluteFilePath("saves"))); - } else if (res == QMessageBox::No) { + } else if (res == QDialogButtonBox::No) { // No action } else { return false; @@ -971,7 +971,7 @@ bool Profile::enableLocalSettings(bool enable) QDialogButtonBox::No | QDialogButtonBox::Yes | QDialogButtonBox::Cancel, QDialogButtonBox::No); - if (res == QMessageBox::Yes) { + if (res == QDialogButtonBox::Yes) { QStringList filesToDelete; for (QString file : m_GamePlugin->iniFiles()) { QString resolved = MOBase::resolveFileCaseInsensitive( @@ -979,7 +979,7 @@ bool Profile::enableLocalSettings(bool enable) filesToDelete << resolved; } shellDelete(filesToDelete, true); - } else if (res == QMessageBox::No) { + } else if (res == QDialogButtonBox::No) { // No action } else { return false; diff --git a/src/src/protonlauncher.cpp b/src/src/protonlauncher.cpp index 5c3ef54..233732f 100644 --- a/src/src/protonlauncher.cpp +++ b/src/src/protonlauncher.cpp @@ -1,4 +1,5 @@ #include "protonlauncher.h" +#include "fluorinepaths.h" #include #include @@ -224,6 +225,9 @@ ProtonLauncher& ProtonLauncher::setWrapper(const QString& wrapperCmd) const QStringList parts = QProcess::splitCommand(wrapperCmd.trimmed()); for (const QString& part : parts) { + if (part.compare("%command%", Qt::CaseInsensitive) == 0) { + continue; + } QString key; QString value; if (parseEnvAssignment(part, key, value)) { @@ -366,9 +370,7 @@ bool ProtonLauncher::launchWithUmu(qint64& pid) const // Use the full path to our copied umu-run since the host PATH won't include it. QString umuRun; if (isFlatpak()) { - const QString dataDir = - QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation); - const QString flatpakUmu = QDir(dataDir).filePath("fluorine/umu-run"); + const QString flatpakUmu = fluorineDataDir() + QStringLiteral("/umu-run"); if (QFileInfo::exists(flatpakUmu)) { umuRun = flatpakUmu; } else { diff --git a/src/src/qtgroupingproxy.cpp b/src/src/qtgroupingproxy.cpp index 1e866ca..0983e15 100644 --- a/src/src/qtgroupingproxy.cpp +++ b/src/src/qtgroupingproxy.cpp @@ -108,7 +108,7 @@ QList QtGroupingProxy::belongsTo(const QModelIndex& idx) int role = i.key(); QVariant variant = i.value(); - if (variant.type() == QVariant::List) { + if (variant.typeId() == QMetaType::QVariantList) { // a list of variants get's expanded to multiple rows QVariantList list = variant.toList(); for (int i = 0; i < list.length(); i++) { @@ -358,8 +358,8 @@ int QtGroupingProxy::columnCount(const QModelIndex& index) const static bool variantLess(const QVariant& LHS, const QVariant& RHS) { - if ((LHS.type() == RHS.type()) && - ((LHS.type() == QVariant::Int) || (LHS.type() == QVariant::UInt))) { + if ((LHS.typeId() == RHS.typeId()) && + ((LHS.typeId() == QMetaType::Int) || (LHS.typeId() == QMetaType::UInt))) { return LHS.toInt() < RHS.toInt(); } diff --git a/src/src/selfupdater.cpp b/src/src/selfupdater.cpp index 9d20421..4438f60 100644 --- a/src/src/selfupdater.cpp +++ b/src/src/selfupdater.cpp @@ -235,10 +235,13 @@ void SelfUpdater::openOutputFile(const QString& fileName) QString outputPath = QDir::fromNativeSeparators(qApp->property("dataPath").toString()) + "/" + fileName; - log::debug("downloading to {}", outputPath); - m_UpdateFile.setFileName(outputPath); - m_UpdateFile.open(QIODevice::WriteOnly); -} + log::debug("downloading to {}", outputPath); + m_UpdateFile.setFileName(outputPath); + if (!m_UpdateFile.open(QIODevice::WriteOnly)) { + log::error("failed to open update output file '{}': {}", outputPath, + m_UpdateFile.errorString()); + } +} void SelfUpdater::download(const QString& downloadLink) { diff --git a/src/src/settings.cpp b/src/src/settings.cpp index 5578daf..fc6316e 100644 --- a/src/src/settings.cpp +++ b/src/src/settings.cpp @@ -1409,7 +1409,7 @@ void PluginSettings::registerPlugin(IPlugin* plugin) if (!temp.isValid()) { temp = setting.defaultValue; - } else if (!temp.convert(setting.defaultValue.type())) { + } else if (!temp.convert(setting.defaultValue.metaType())) { log::warn("failed to interpret \"{}\" as correct type for \"{}\" in plugin " "\"{}\", using default", temp.toString(), setting.key, plugin->name()); diff --git a/src/src/settingsdialog.ui b/src/src/settingsdialog.ui index cd869a5..7755f6f 100644 --- a/src/src/settingsdialog.ui +++ b/src/src/settingsdialog.ui @@ -2171,7 +2171,7 @@ p, li { white-space: pre-wrap; } - + 0 @@ -2221,7 +2221,7 @@ p, li { white-space: pre-wrap; } - + Qt::Orientation::Horizontal diff --git a/src/src/settingsdialogmodlist.cpp b/src/src/settingsdialogmodlist.cpp index 8e93767..b46f1c4 100644 --- a/src/src/settingsdialogmodlist.cpp +++ b/src/src/settingsdialogmodlist.cpp @@ -18,10 +18,10 @@ ModListSettingsTab::ModListSettingsTab(Settings& s, SettingsDialog& d) {ModList::COL_VERSION, ui->collapsibleSeparatorsIconsVersionBox}} { // connect before setting to trigger - QObject::connect(ui->collapsibleSeparatorsAscBox, &QCheckBox::toggled, [=] { + QObject::connect(ui->collapsibleSeparatorsAscBox, &QCheckBox::toggled, [=, this] { updateCollapsibleSeparatorsGroup(); }); - QObject::connect(ui->collapsibleSeparatorsDscBox, &QCheckBox::toggled, [=] { + QObject::connect(ui->collapsibleSeparatorsDscBox, &QCheckBox::toggled, [=, this] { updateCollapsibleSeparatorsGroup(); }); diff --git a/src/src/settingsdialogproton.cpp b/src/src/settingsdialogproton.cpp index af1b344..f12b9c0 100644 --- a/src/src/settingsdialogproton.cpp +++ b/src/src/settingsdialogproton.cpp @@ -1,6 +1,7 @@ #include "settingsdialogproton.h" #include "fluorineconfig.h" +#include "fluorinepaths.h" #include "ui_settingsdialog.h" #include @@ -150,7 +151,7 @@ void ProtonSettingsTab::refreshState() ui->prefixLocationEdit->setReadOnly(false); if (ui->prefixLocationEdit->text().isEmpty()) { ui->prefixLocationEdit->setText( - QDir::homePath() + "/.var/app/com.fluorine.manager/Prefix"); + fluorineDataDir() + "/Prefix"); } } @@ -288,7 +289,7 @@ void ProtonSettingsTab::onBrowsePrefixLocation() QString ProtonSettingsTab::ensureWinetricks() { - const QString nakWinetricks = QDir::homePath() + "/.var/app/com.fluorine.manager/bin/winetricks"; + const QString nakWinetricks = fluorineDataDir() + "/bin/winetricks"; if (QFileInfo::exists(nakWinetricks)) { return nakWinetricks; } @@ -298,7 +299,7 @@ QString ProtonSettingsTab::ensureWinetricks() return systemWinetricks; } - const QString nakBinDir = QDir::homePath() + "/.var/app/com.fluorine.manager/bin"; + const QString nakBinDir = fluorineDataDir() + "/bin"; QDir().mkpath(nakBinDir); QString downloadTool; diff --git a/src/src/spawn.cpp b/src/src/spawn.cpp index b18282d..f68389e 100644 --- a/src/src/spawn.cpp +++ b/src/src/spawn.cpp @@ -914,7 +914,7 @@ bool checkSteam(QWidget* parent, const SpawnParameters& sp, const QDir& gameDire const auto c = dialogs::confirmStartSteam(parent, sp, details); - if (c == QDialogButtonBox::Yes) { + if (c == QMessageBox::Yes) { log::debug("user wants to start steam"); if (!startSteam(parent)) { @@ -928,7 +928,7 @@ bool checkSteam(QWidget* parent, const SpawnParameters& sp, const QDir& gameDire log::error("steam is still not running, hoping for the best"); return true; } - } else if (c == QDialogButtonBox::No) { + } else if (c == QMessageBox::No) { log::debug("user declined to start steam"); return true; } else { diff --git a/src/src/texteditor.cpp b/src/src/texteditor.cpp index 2ac9a7b..36d8103 100644 --- a/src/src/texteditor.cpp +++ b/src/src/texteditor.cpp @@ -99,13 +99,15 @@ bool TextEditor::load(const QString& filename) bool TextEditor::save() { - if (m_filename.isEmpty() || m_encoding.isEmpty()) { - return false; - } - - QFile file(m_filename); - file.open(QIODevice::WriteOnly); - file.resize(0); + if (m_filename.isEmpty() || m_encoding.isEmpty()) { + return false; + } + + QFile file(m_filename); + if (!file.open(QIODevice::WriteOnly)) { + return false; + } + file.resize(0); auto codec = QStringConverter::encodingForName(m_encoding.toUtf8()); if (!codec.has_value()) @@ -468,7 +470,7 @@ TextEditorToolbar::TextEditorToolbar(TextEditor& editor) m_save = new QAction(QIcon(":/MO/gui/save"), QObject::tr("&Save"), &editor); m_save->setShortcutContext(Qt::WidgetWithChildrenShortcut); - m_save->setShortcut(Qt::CTRL + Qt::Key_S); + m_save->setShortcut(Qt::CTRL | Qt::Key_S); m_editor.addAction(m_save); m_wordWrap = diff --git a/src/src/uilocker.cpp b/src/src/uilocker.cpp index e6189f6..9cdbf31 100644 --- a/src/src/uilocker.cpp +++ b/src/src/uilocker.cpp @@ -205,10 +205,10 @@ private: m_topLevel->setGeometry(mainUI->rect()); m_filter.reset(new Filter); - m_filter->resized = [=] { + m_filter->resized = [=, this] { m_topLevel->setGeometry(mainUI->rect()); }; - m_filter->closed = [=] { + m_filter->closed = [=, this] { checkTarget(); }; -- cgit v1.3.1