diff options
| author | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-02-14 15:40:33 -0600 |
|---|---|---|
| committer | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-02-14 15:40:33 -0600 |
| commit | 6410929e17d642618f284d5c97d457f1ac653e6e (patch) | |
| tree | 5c0ee09e04f38a2dd70f1734d25c74e0b8ae5d43 | |
| parent | 817e8f5cd26739c69d930d21cd9dc4c0b6e4984e (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>
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) @@ -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();
};
|
