aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSulfurNitride <SulfurNitride@users.noreply.github.com>2026-02-14 15:40:33 -0600
committerSulfurNitride <SulfurNitride@users.noreply.github.com>2026-02-14 15:40:33 -0600
commit6410929e17d642618f284d5c97d457f1ac653e6e (patch)
tree5c0ee09e04f38a2dd70f1734d25c74e0b8ae5d43
parent817e8f5cd26739c69d930d21cd9dc4c0b6e4984e (diff)
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 <noreply@anthropic.com>
-rw-r--r--CMakeLists.txt7
-rw-r--r--README.md48
-rwxr-xr-xbuild-flatpak.sh9
-rwxr-xr-xbuild-native.sh78
-rwxr-xr-xbuild.sh27
-rw-r--r--docker/Dockerfile1
-rwxr-xr-xdocker/build-inner.sh25
-rw-r--r--flatpak/com.fluorine.manager.yml3
-rwxr-xr-xflatpak/fluorine-manager-wrapper.sh5
-rw-r--r--libs/7zip/CMakeLists.txt2
-rw-r--r--libs/basic_games/basic_game.py8
-rw-r--r--libs/bsa_ffi/src/archive/mod.rs2
-rw-r--r--libs/bsapacker/src/qlibbsarch/QLibbsarch.h12
-rw-r--r--libs/bsatk/src/CMakeLists.txt3
-rw-r--r--libs/bsatk/src/bsaarchive.cpp12
-rw-r--r--libs/bsatk/src/bsafile.cpp1
-rw-r--r--libs/game_bethesda/src/gamebryo/dummybsa.cpp6
-rw-r--r--libs/game_bethesda/src/gamebryo/gamebryosavegame.cpp3
-rw-r--r--libs/game_bethesda/src/gamebryo/gamegamebryo.cpp6
-rw-r--r--libs/game_bethesda/src/games/enderal/enderalsavegame.cpp3
-rw-r--r--libs/game_bethesda/src/games/enderalse/enderalsesavegame.cpp3
-rw-r--r--libs/game_bethesda/src/games/fallout4/fallout4savegame.cpp3
-rw-r--r--libs/game_bethesda/src/games/fallout4london/fo4londonsavegame.cpp3
-rw-r--r--libs/game_bethesda/src/games/fallout4vr/fallout4vrsavegame.cpp3
-rw-r--r--libs/game_bethesda/src/games/fallout76/fallout76savegame.cpp3
-rw-r--r--libs/game_bethesda/src/games/skyrim/skyrimsavegame.cpp3
-rw-r--r--libs/game_bethesda/src/games/skyrimse/skyrimsesavegame.cpp3
-rw-r--r--libs/game_bethesda/src/games/skyrimvr/skyrimvrsavegame.cpp3
-rw-r--r--libs/game_bethesda/src/games/starfield/starfieldsavegame.cpp3
-rw-r--r--libs/installer_fomod/src/fomodinstallerdialog.cpp27
-rw-r--r--libs/installer_fomod_plus/installer/CMakeLists.txt2
-rw-r--r--libs/installer_fomod_plus/patchwizard/CMakeLists.txt2
-rw-r--r--libs/installer_fomod_plus/scanner/CMakeLists.txt2
-rw-r--r--libs/nak/src/deps/tools.rs9
-rw-r--r--libs/nak/src/dxvk.rs6
-rw-r--r--libs/nak/src/lib.rs1
-rw-r--r--libs/nak/src/logging.rs5
-rw-r--r--libs/nak/src/paths.rs12
-rw-r--r--libs/nak/src/steam/proton.rs9
-rw-r--r--libs/plugin_python/src/mobase/CMakeLists.txt7
-rw-r--r--libs/plugin_python/src/mobase/wrappers/basic_classes.cpp5
-rw-r--r--libs/plugin_python/src/pybind11-qt/CMakeLists.txt3
-rw-r--r--libs/plugin_python/src/pybind11-utils/CMakeLists.txt5
-rw-r--r--libs/plugin_python/src/runner/CMakeLists.txt3
-rw-r--r--libs/uibase/include/uibase/filterwidget.h6
-rw-r--r--libs/uibase/src/utility.cpp18
-rw-r--r--src/src/CMakeLists.txt2
-rw-r--r--src/src/bbcode.cpp2
-rw-r--r--src/src/categoriesdialog.cpp56
-rw-r--r--src/src/commandline.cpp17
-rw-r--r--src/src/copyeventfilter.cpp2
-rw-r--r--src/src/createinstancedialog.cpp54
-rw-r--r--src/src/createinstancedialog.h39
-rw-r--r--src/src/datatab.cpp2
-rw-r--r--src/src/downloadlistview.cpp46
-rw-r--r--src/src/downloadmanager.cpp117
-rw-r--r--src/src/envmodule.cpp2
-rw-r--r--src/src/fluorineconfig.cpp5
-rw-r--r--src/src/fluorinepaths.cpp91
-rw-r--r--src/src/fluorinepaths.h15
-rw-r--r--src/src/icondelegate.cpp2
-rw-r--r--src/src/instancemanager.cpp7
-rw-r--r--src/src/instancemanagerdialog.cpp52
-rw-r--r--src/src/loglist.cpp16
-rw-r--r--src/src/main.cpp3
-rw-r--r--src/src/mainwindow.cpp23
-rw-r--r--src/src/modinfo.cpp3
-rw-r--r--src/src/modinforegular.cpp87
-rw-r--r--src/src/modlist.cpp120
-rw-r--r--src/src/modlistcontextmenu.cpp130
-rw-r--r--src/src/modlistsortproxy.cpp74
-rw-r--r--src/src/modlistsortproxy.h5
-rw-r--r--src/src/modlistview.cpp60
-rw-r--r--src/src/modlistviewactions.cpp16
-rw-r--r--src/src/nxmaccessmanager.cpp8
-rw-r--r--src/src/organizercore.cpp4
-rw-r--r--src/src/plugincontainer.cpp19
-rw-r--r--src/src/pluginlist.cpp7
-rw-r--r--src/src/pluginlistcontextmenu.cpp22
-rw-r--r--src/src/pluginlistsortproxy.cpp36
-rw-r--r--src/src/pluginlistsortproxy.h5
-rw-r--r--src/src/pluginlistview.cpp16
-rw-r--r--src/src/profile.cpp8
-rw-r--r--src/src/protonlauncher.cpp8
-rw-r--r--src/src/qtgroupingproxy.cpp6
-rw-r--r--src/src/selfupdater.cpp11
-rw-r--r--src/src/settings.cpp2
-rw-r--r--src/src/settingsdialog.ui4
-rw-r--r--src/src/settingsdialogmodlist.cpp4
-rw-r--r--src/src/settingsdialogproton.cpp7
-rw-r--r--src/src/spawn.cpp4
-rw-r--r--src/src/texteditor.cpp18
-rw-r--r--src/src/uilocker.cpp4
93 files changed, 1008 insertions, 643 deletions
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" <<EOF
+[Desktop Entry]
+Type=Application
+Name=Fluorine Manager
+Comment=Mod Organizer 2 for Linux
+Exec=${INSTALL_DIR}/fluorine-manager
+Icon=fluorine-manager
+Terminal=false
+Categories=Game;
+EOF
+
+# ── Symlink into ~/.local/bin for PATH access ──
+BIN_DIR="${HOME}/.local/bin"
+mkdir -p "${BIN_DIR}"
+ln -sf "${INSTALL_DIR}/fluorine-manager" "${BIN_DIR}/fluorine-manager"
+
+echo ""
+echo "=== Installed ==="
+du -sh "${INSTALL_DIR}"/*/ "${INSTALL_DIR}"/ModOrganizer-core 2>/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
+ $<$<COMPILE_LANGUAGE:CXX>:-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<GameVersion> {
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 <libbsarch/libbsarch.h>
#include <string>
#include <stdexcept>
+#include <cstring>
#include <QDebug>
#include <QDir>
#include <QStringList>
@@ -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<BSAULong>(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<Folder::Ptr>::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<unsigned char[]> Archive::decompress(unsigned char* inBuffer,
stream.next_out = reinterpret_cast<Bytef*>(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<FileInfo>& 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<FileInfo>& queue, boost::mutex& mutex,
reinterpret_cast<char*>(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<bool(int value, std::string fileName)>& progress,
bool overwrite)
{
-#pragma message("report errors")
createFolders(outputDirectory, m_RootFolder);
std::vector<File::Ptr> 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 <http://www.gnu.org/licenses/>.
#include "dummybsa.h"
#include <QFile>
+#include <stdexcept>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
@@ -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<unsigned long>(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 <QFileInfo>
#include <QScopedArrayPointer>
#include <QTime>
+#include <QTimeZone>
#ifdef _WIN32
#include <Windows.h>
@@ -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 <Windows.h>
#else
#include <QDateTime>
+#include <QTimeZone>
using SYSTEMTIME = _SYSTEMTIME;
@@ -14,7 +15,7 @@ static void FileTimeToSystemTime(const FILETIME* ft, SYSTEMTIME* st)
const int64_t unixSecs = static_cast<int64_t>(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 <Windows.h>
#else
#include <QDateTime>
+#include <QTimeZone>
union _ULARGE_INTEGER {
struct {
@@ -22,7 +23,7 @@ static void FileTimeToSystemTime(const FILETIME* ft, SYSTEMTIME* st)
const int64_t unixSecs = static_cast<int64_t>(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 <Windows.h>
#else
#include <QDateTime>
+#include <QTimeZone>
using SYSTEMTIME = _SYSTEMTIME;
@@ -14,7 +15,7 @@ static void FileTimeToSystemTime(const FILETIME* ft, SYSTEMTIME* st)
const int64_t unixSecs = static_cast<int64_t>(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 <Windows.h>
#else
#include <QDateTime>
+#include <QTimeZone>
using SYSTEMTIME = _SYSTEMTIME;
@@ -14,7 +15,7 @@ static void FileTimeToSystemTime(const FILETIME* ft, SYSTEMTIME* st)
const int64_t unixSecs = static_cast<int64_t>(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 <Windows.h>
#else
#include <QDateTime>
+#include <QTimeZone>
using SYSTEMTIME = _SYSTEMTIME;
@@ -14,7 +15,7 @@ static void FileTimeToSystemTime(const FILETIME* ft, SYSTEMTIME* st)
const int64_t unixSecs = static_cast<int64_t>(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 <Windows.h>
#else
#include <QDateTime>
+#include <QTimeZone>
using SYSTEMTIME = _SYSTEMTIME;
@@ -14,7 +15,7 @@ static void FileTimeToSystemTime(const FILETIME* ft, SYSTEMTIME* st)
const int64_t unixSecs = static_cast<int64_t>(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 <Windows.h>
#else
#include <QDateTime>
+#include <QTimeZone>
// 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<int64_t>(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 <Windows.h>
#else
#include <QDateTime>
+#include <QTimeZone>
// 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<int64_t>(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 <Windows.h>
#else
#include <QDateTime>
+#include <QTimeZone>
// 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<int64_t>(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 <Windows.h>
#else
#include <QDateTime>
+#include <QTimeZone>
using SYSTEMTIME = _SYSTEMTIME;
@@ -14,7 +15,7 @@ static void FileTimeToSystemTime(const FILETIME* ft, SYSTEMTIME* st)
const int64_t unixSecs = static_cast<int64_t>(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<const char*>(bytes), size));
+ };
- if (buffer.startsWith(reinterpret_cast<const char*>(BOM_UTF8)))
+ if (startsWithBytes(BOM_UTF8, sizeof(BOM_UTF8)))
return 3;
- if (buffer.startsWith(reinterpret_cast<const char*>(BOM_UTF16BE)) ||
- buffer.startsWith(reinterpret_cast<const char*>(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<const char*>(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<SteamProton> {
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<SteamProton> {
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 <http://www.gnu.org/licenses/>.
#include <QLineEdit>
#include <QMenu>
#include <QRegularExpressionValidator>
+#include <memory>
class NewIDValidator : public QIntValidator
{
@@ -223,25 +224,25 @@ void CategoriesDialog::fillTable()
++row;
table->insertRow(row);
- QScopedPointer<QTableWidgetItem> idItem(new QTableWidgetItem());
+ auto idItem = std::make_unique<QTableWidgetItem>();
idItem->setData(Qt::DisplayRole, category.ID());
- QScopedPointer<QTableWidgetItem> nameItem(new QTableWidgetItem(category.name()));
- QScopedPointer<QTableWidgetItem> parentIDItem(new QTableWidgetItem());
+ auto nameItem = std::make_unique<QTableWidgetItem>(category.name());
+ auto parentIDItem = std::make_unique<QTableWidgetItem>();
parentIDItem->setData(Qt::DisplayRole, category.parentID());
- QScopedPointer<QTableWidgetItem> nexusCatItem(new QTableWidgetItem());
+ auto nexusCatItem = std::make_unique<QTableWidgetItem>();
- 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<QListWidgetItem> nexusItem(new QListWidgetItem());
+ auto nexusItem = std::make_unique<QListWidgetItem>();
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<QTableWidgetItem> idItem(new QTableWidgetItem());
+ auto idItem = std::make_unique<QTableWidgetItem>();
idItem->setData(Qt::DisplayRole, ++m_HighestID);
- QScopedPointer<QTableWidgetItem> parentIDItem(new QTableWidgetItem());
+ auto parentIDItem = std::make_unique<QTableWidgetItem>();
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<QTableWidgetItem> nexusCatItem(
- new QTableWidgetItem(nexusLabel.join(", ")));
+ auto nexusCatItem = std::make_unique<QTableWidgetItem>(nexusLabel.join(", "));
nexusCatItem->setData(Qt::UserRole, nexusData);
if (!table->findItems(name, Qt::MatchExactly).size()) {
row = table->rowCount();
table->insertRow(table->rowCount());
- QScopedPointer<QTableWidgetItem> idItem(new QTableWidgetItem());
+ auto idItem = std::make_unique<QTableWidgetItem>();
idItem->setData(Qt::DisplayRole, ++m_HighestID);
- QScopedPointer<QTableWidgetItem> nameItem(new QTableWidgetItem(name));
- QScopedPointer<QTableWidgetItem> parentIDItem(new QTableWidgetItem());
+ auto nameItem = std::make_unique<QTableWidgetItem>(name);
+ auto parentIDItem = std::make_unique<QTableWidgetItem>();
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<QTableWidgetItem> blankItem(new QTableWidgetItem());
+ auto blankItem = std::make_unique<QTableWidgetItem>();
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<QListWidgetItem> nexusItem(new QListWidgetItem());
+ auto nexusItem = std::make_unique<QListWidgetItem>();
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<int> 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 <iplugingame.h>
#include <utility.h>
+#include <utility>
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 <class MF, class... Args>
+auto CreateInstanceDialog::getSelected(MF mf, Args&&... args) const
+{
+ // return type
+ using T = decltype((std::declval<cid::Page>().*mf)(std::forward<Args>(args)...));
+
+ for (auto&& p : m_pages) {
+ const auto t = (p.get()->*mf)(std::forward<Args>(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 <class Page>
+void CreateInstanceDialog::setSinglePage(const QString& instanceName)
+{
+ for (auto&& p : m_pages) {
+ if (auto* tp = dynamic_cast<Page*>(p.get())) {
+ tp->setSkip(false);
+ } else {
+ p->setSkip(true);
+ }
+ }
+
+ setSinglePageImpl(instanceName);
+}
+
+template <class Page>
+Page* CreateInstanceDialog::getPage()
+{
+ for (auto&& p : m_pages) {
+ if (auto* tp = dynamic_cast<Page*>(p.get())) {
+ return tp;
+ }
+ }
+
+ return nullptr;
+}
+
+// explicit instantiations for types used by instancemanager.cpp
+template void CreateInstanceDialog::setSinglePage<cid::GamePage>(const QString&);
+template void CreateInstanceDialog::setSinglePage<cid::VariantsPage>(const QString&);
+template cid::GamePage* CreateInstanceDialog::getPage<cid::GamePage>();
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 <class Page>
- void setSinglePage(const QString& instanceName)
- {
- for (auto&& p : m_pages) {
- if (auto* tp = dynamic_cast<Page*>(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 <class Page>
- Page* getPage()
- {
- for (auto&& p : m_pages) {
- if (auto* tp = dynamic_cast<Page*>(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 <class MF, class... Args>
- auto getSelected(MF mf, Args&&... args) const
- {
- // return type
- using T = decltype((std::declval<cid::Page>().*mf)(std::forward<Args>(args)...));
-
- for (auto&& p : m_pages) {
- const auto t = (p.get()->*mf)(std::forward<Args>(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<OrganizerCore*>(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<uintptr_t>(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 <QDir>
+#include <QFile>
+#include <QTextStream>
+
+#include <cstdio>
+
+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 <QString>
+
+/// 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 <http://www.gnu.org/licenses/>.
#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<std::size_t>(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<std::size_t>(row) >= m_instances.size()) {
+ return NoSelection;
+ }
+
+ return static_cast<std::size_t>(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 <http://www.gnu.org/licenses/>.
#include "loglist.h"
#include "copyeventfilter.h"
#include "env.h"
+#include "fluorinepaths.h"
#include "organizercore.h"
#include <cstdlib>
@@ -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<env::Console> 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 <http://www.gnu.org/licenses/>.
#include <QSize>
#include <QSizePolicy>
#include <QTime>
+#include <QTimeZone>
#include <QTimer>
#include <QToolButton>
#include <QToolTip>
@@ -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 <http://www.gnu.org/licenses/>.
#include <QApplication>
#include <QDirIterator>
#include <QMutexLocker>
+#include <QTimeZone>
using namespace MOBase;
using namespace MOShared;
@@ -412,7 +413,7 @@ std::set<QSharedPointer<ModInfo>> 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 <iplugingame.h>
-#include <utility.h>
+#include "report.h"
+#include "settings.h"
+#include <iplugingame.h>
+#include <utility.h>
#include <QApplication>
#include <QDirIterator>
#include <QSettings>
+#include <QTimeZone>
#include <sstream>
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<int> sourceIndices, int newPriority)
-{
- if (m_Profile == nullptr)
- return;
-
- sourceIndices.erase(
- std::remove_if(sourceIndices.begin(), sourceIndices.end(), [](int index) {
- return index < 0 || static_cast<unsigned int>(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<int> sourceIndices, int newPriority)
+{
+ if (m_Profile == nullptr)
+ return;
+
+ sourceIndices.erase(
+ std::remove_if(sourceIndices.begin(), sourceIndices.end(), [](int index) {
+ return index < 0 || static_cast<unsigned int>(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<int> 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<int> sourceRows = dropInfo.rows();
- sourceRows.erase(
- std::remove_if(sourceRows.begin(), sourceRows.end(), [](int index) {
- return index < 0 || static_cast<unsigned int>(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<int> sourceRows = dropInfo.rows();
+ sourceRows.erase(
+ std::remove_if(sourceRows.begin(), sourceRows.end(), [](int index) {
+ return index < 0 || static_cast<unsigned int>(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 <memory>
+
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<QCheckBox> checkBox(new QCheckBox(targetMenu));
+ auto checkBox = std::make_unique<QCheckBox>(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<QWidgetAction> checkableAction(new QWidgetAction(targetMenu));
- checkableAction->setDefaultWidget(checkBox.take());
+ auto checkableAction = std::make_unique<QWidgetAction>(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>& 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>& 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<ModInfo::EFlag>& 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<ModInfo::EConflictFlag>& 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<ModInfo::EFlag>& flags) const;
+private:
+ void refreshFilter();
+ unsigned long flagsId(const std::vector<ModInfo::EFlag>& flags) const;
unsigned long conflictFlagsId(const std::vector<ModInfo::EConflictFlag>& flags) const;
bool hasConflictFlag(const std::vector<ModInfo::EConflictFlag>& 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<int>::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<int> 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<int> 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<QAbstractSocket::SocketError>(&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<QString> 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<IPluginDiagnose>(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<ModInfo::EFlag> 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 <http://www.gnu.org/licenses/>.
#include <QTreeView>
#include <QWidgetAction>
-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<PluginList::COL_LASTCOLUMN + 1> 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<QString> 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 <nak_ffi.h>
#include <QCoreApplication>
@@ -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<RowData> 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; }
</item>
<item>
<widget class="QWidget" name="widget_12" native="true">
- <layout class="QHBoxLayout" name="horizontalLayout">
+ <layout class="QHBoxLayout" name="horizontalLayout_usvfs">
<property name="leftMargin">
<number>0</number>
</property>
@@ -2221,7 +2221,7 @@ p, li { white-space: pre-wrap; }
</widget>
</item>
<item>
- <spacer name="horizontalSpacer">
+ <spacer name="horizontalSpacer_usvfs">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
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 <QtConcurrent/QtConcurrentRun>
@@ -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();
};