diff --git a/.github/workflows/build-project.yml b/.github/workflows/build-project.yml index 3dda9d0..dd88b75 100644 --- a/.github/workflows/build-project.yml +++ b/.github/workflows/build-project.yml @@ -22,8 +22,6 @@ jobs: with: runs_exclude: > [ - { "os": "windows-latest", "linkage": "shared" }, - { "os": "ubuntu-20.04"}, - { "os": "ubuntu-22.04"} + { "linkage": "shared" }, + { "compiler": "g++-10" } ] - \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 4b509b7..378f81f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ cmake_minimum_required(VERSION 3.24.0...3.30.0) # Project # NOTE: DON'T USE TRAILING ZEROS IN VERSIONS project(FIL - VERSION 0.7.5.3 + VERSION 0.7.5.4 LANGUAGES CXX DESCRIPTION "Flashpoint Importer for Launchers" ) @@ -34,7 +34,9 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) -enable_language("RC") +if(CMAKE_SYSTEM_NAME STREQUAL Windows) + enable_language("RC") +endif() #================= Build ========================= @@ -59,10 +61,17 @@ set(FIL_QX_COMPONENTS Gui Network Widgets - Windows-gui Xml ) +if(CMAKE_SYSTEM_NAME STREQUAL Windows) + list(APPEND FIL_QX_COMPONENTS Windows-gui) +endif() + +if(CMAKE_SYSTEM_NAME STREQUAL Linux) + list(APPEND FIL_QX_COMPONENTS Linux) +endif() + include(OB/FetchQx) ob_fetch_qx( REF "0572d288936afd63ff6f5b6ce4b1fbfc0f03b0eb" @@ -75,6 +84,8 @@ include(OB/Fetchlibfp) ob_fetch_libfp("183a479d00235d332aa1046a9b5ba98f62699752") # Fetch CLIFp (build and import from source) +include(OB/Utility) +ob_cache_project_version(CLIFp) include(OB/FetchCLIFp) ob_fetch_clifp("7139ae998b292eb595e751ba4cb8599230435358") diff --git a/README.md b/README.md index c16957b..637a15b 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ For Platforms, the importer is capable of importing each game/animation along wi Checkout **[Usage (Primary)](#usage-primary)** to get started. -[![Dev Builds](https://github.com/oblivioncth/FIL/actions/workflows/push-reaction.yml/badge.svg?branch=dev)](https://github.com/oblivioncth/FIL/actions/workflows/push-reaction.yml) +[![Dev Builds](https://github.com/oblivioncth/FIL/actions/workflows/build-project.yml/badge.svg?branch=dev)](https://github.com/oblivioncth/FIL/actions/workflows/build-project.yml) ## Function This utility makes use of its sister project [CLIFp (Command-line Interface for Flashpoint)](https://github.com/oblivioncth/CLIFp) to allow launchers to actually start and exit the games correctly. It is automatically deployed into your Flashpoint installation (updated if necessary) at the end of a successful import and the latest version of CLIFp will be included in each release of this utility so it is not generally something the end-user needs to concern themselves with. @@ -139,11 +139,11 @@ The symbolic link related options for handling images require the importer to be ## Usage (Tools) ### Tag Filter -The tag filter editor allows you to customize which titles will be imported based on their tags. +The tag filter editor allows you to customize which titles will be imported based on their tags. ![Tag Filter](https://i.imgur.com/EzEd0H1.png) -Tags are listed alphabetically, nested under their categories names so that you can select or unselect an entire category easily. Exclusions take precedence, so if a title features a single tag that you have unselected it will not be included in the import. +Tags are listed alphabetically, nested under their categories names so that you can select or unselect an entire category easily. Exclusions take precedence, so if a title features a single tag that you have unselected it will not be included in the import. All tags are included by default. @@ -160,7 +160,7 @@ This tool automatically handles installing/updating the command-line interface F ## Other Features - The playlist import feature is "smart" in the sense that it won't include games that you aren't importing. So if you only want to import the Flash platform for example and a couple playlists, you wont have to worry about useless entries in the playlist that point to games from other platforms you didn't import. This of course does not apply if you are using the "Force All" playlist game mode. - + ## Limitations - Although general compatibility is quite high, compatibility with every single title cannot be assured. Issues with a title or group of titles will be fixed as they are discovered. - The "smart" feature of the Playlist import portion of the tool has the drawback that only games that were included in the same import will be considered for that playlist. If you previously imported a Platform and now want to import a Playlist that contains games from that Platform you must make sure you select it again for it to be updated/re-imported in order for those games to be added to that Playlist. Alternatively, you can use the "Force All" playlist game mode, but this will also possibly add new platforms you did not previously import. @@ -171,7 +171,7 @@ This tool automatically handles installing/updating the command-line interface F ### Summary - C++20 - - CMake 3.24.0 + - CMake >= 3.24.0 - Targets Windows 10 and above ### Dependencies @@ -182,8 +182,30 @@ This tool automatically handles installing/updating the command-line interface F - [Neargye's Magic Enum](https://github.com/Neargye/magic_enum) - [OBCMake](https://github.com/oblivioncth/OBCmake) -### Builds -Tested with MSVC2022. - ### Details -The source for this project is managed by a sensible CMake configuration that allows for straightforward compilation and consumption of its target(s), either as a sub-project or as an imported package. All required dependencies except for Qt6 are automatically acquired via CMake's FetchContent mechanism. \ No newline at end of file +The source for this project is managed by a sensible CMake configuration that allows for straightforward compilation and consumption of its target(s), either as a sub-project or as an imported package. All required dependencies except for Qt6 are automatically acquired via CMake's FetchContent mechanism. + +### Building +Ensure Qt6 is installed and locatable by CMake (or alternatively use the `qt-cmake` script that comes with Qt in-place of the`cmake` command). + +Right now, a static build is required in order for CLIFp to work correctly. + +Should work with MSVC, MINGW64, clang, and gcc. + +``` +# Acquire source +git clone https://github.com/oblivioncth/FIL + +# Configure (ninja optional, but recommended) +cmake -S FIL -B build-FIL -G "Ninja Multi-config" + +# Build +cmake --build build-FIL + +# Install +cmake --install build-FIL + +# Run +cd "build-FIL/out/install/bin" +fil +``` diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index a2e6ca3..d5e7e9b 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -5,7 +5,7 @@ # This statement creates a command that is known to be able to create the appropraite # output file (CLIFP.exe) by copying it from the CLIFp build. The command depends on # CLIFp having been built in-order to be invoked -set(CLIFP_RES_PATH "${CMAKE_CURRENT_BINARY_DIR}/res/file/CLIFp.exe") +set(CLIFP_RES_PATH "${CMAKE_CURRENT_BINARY_DIR}/res/file/clifp") add_custom_command(OUTPUT "${CLIFP_RES_PATH}" COMMAND ${CMAKE_COMMAND} -E copy $ @@ -22,63 +22,84 @@ add_custom_target(fil_copy_clifp ) # ------------------ Setup FIL -------------------------- +set(FIL_SOURCE + frontend/fe-data.h + frontend/fe-data.cpp + frontend/fe-installfoundation.h + frontend/fe-installfoundation.cpp + frontend/fe-installfoundation_win.cpp + frontend/fe-installfoundation_linux.cpp + frontend/fe-install.h + frontend/fe-install.cpp + frontend/fe-items.h + frontend/fe-items.cpp + frontend/attractmode/am-data.h + frontend/attractmode/am-data.cpp + frontend/attractmode/am-install.h + frontend/attractmode/am-install.cpp + frontend/attractmode/am-install_win.cpp + frontend/attractmode/am-install_linux.cpp + frontend/attractmode/am-items.h + frontend/attractmode/am-items.cpp + frontend/attractmode/am-settings-data.h + frontend/attractmode/am-settings-data.cpp + frontend/attractmode/am-settings-items.h + frontend/attractmode/am-settings-items.cpp + ui/mainwindow.h + ui/mainwindow.cpp + ui/mainwindow.ui + ui/progresspresenter.h + ui/progresspresenter.cpp + clifp.h + clifp.cpp + import-worker.h + import-worker.cpp + main.cpp +) -# Add via ob standard executable -include(OB/Executable) -ob_add_standard_executable(${APP_TARGET_NAME} - NAMESPACE "${PROJECT_NAMESPACE}" - ALIAS "${APP_ALIAS_NAME}" - SOURCE - clifp.h - clifp.cpp - frontend/fe-data.h - frontend/fe-data.cpp - frontend/fe-installfoundation.h - frontend/fe-installfoundation.cpp - frontend/fe-install.h - frontend/fe-install.cpp - frontend/fe-items.h - frontend/fe-items.cpp - frontend/attractmode/am-data.h - frontend/attractmode/am-data.cpp - frontend/attractmode/am-install.h - frontend/attractmode/am-install.cpp - frontend/attractmode/am-items.h - frontend/attractmode/am-items.cpp - frontend/attractmode/am-settings-data.h - frontend/attractmode/am-settings-data.cpp - frontend/attractmode/am-settings-items.h - frontend/attractmode/am-settings-items.cpp +if(CMAKE_SYSTEM_NAME STREQUAL Windows) + list(APPEND FIL_SOURCE frontend/launchbox/lb-data.h frontend/launchbox/lb-data.cpp frontend/launchbox/lb-install.h frontend/launchbox/lb-install.cpp frontend/launchbox/lb-items.h frontend/launchbox/lb-items.cpp - import-worker.h - import-worker.cpp - mainwindow.h - mainwindow.cpp - mainwindow.ui - main.cpp + ) +endif() + +set(FIL_LINKS + PRIVATE + Qt6::Core + Qt6::Gui + Qt6::Widgets + Qt6::Xml + Qt6::Sql + Qx::Core + Qx::Io + Qx::Gui + Qx::Network + Qx::Widgets + Qx::Xml + Fp::Fp + magic_enum::magic_enum +) + +if(CMAKE_SYSTEM_NAME STREQUAL Windows) + list(APPEND FIL_LINKS + Qx::Windows + Qx::Windows-gui + ) +endif() + +# Add via ob standard executable +include(OB/Executable) +ob_add_standard_executable(${APP_TARGET_NAME} + NAMESPACE "${PROJECT_NAMESPACE}" + ALIAS "${APP_ALIAS_NAME}" + SOURCE ${FIL_SOURCE} RESOURCE "resources.qrc" - LINKS - PRIVATE - Qt6::Core - Qt6::Gui - Qt6::Widgets - Qt6::Xml - Qt6::Sql - Qx::Core - Qx::Io - Qx::Gui - Qx::Network - Qx::Widgets - Qx::Windows - Qx::Windows-gui - Qx::Xml - Fp::Fp - magic_enum::magic_enum + LINKS ${FIL_LINKS} CONFIG STANDARD WIN32 ) @@ -104,20 +125,23 @@ ob_add_cpp_vars(${APP_TARGET_NAME} SHORT_NAME "\"${PROJECT_NAME}\"" VERSION_STR "\"${PROJECT_VERSION}\"" TARGET_FP_VER_PFX_STR "\"${TARGET_FP_VERSION_PREFIX}\"" + BUNDLED_CLIFP_VERSION "\"${CLIFp_VERSION}\"" ) # Set target exe details -include(OB/WinExecutableDetails) -ob_set_win_executable_details(${APP_TARGET_NAME} - ICON "res/app/FIL.ico" - FILE_VER ${PROJECT_VERSION} - PRODUCT_VER ${TARGET_FP_VERSION_PREFIX} - COMPANY_NAME "oblivioncth" - FILE_DESC "Flashpoint Importer for Launchers" - INTERNAL_NAME "FIL" - COPYRIGHT "Open Source @ 2021 oblivioncth" - TRADEMARKS_ONE "All Rights Reserved" - TRADEMARKS_TWO "GNU AGPL V3" - ORIG_FILENAME "FIL.exe" - PRODUCT_NAME "${PROJECT_DESCRIPTION}" -) +if(CMAKE_SYSTEM_NAME STREQUAL Windows) + include(OB/WinExecutableDetails) + ob_set_win_executable_details(${APP_TARGET_NAME} + ICON "res/app/FIL.ico" + FILE_VER ${PROJECT_VERSION} + PRODUCT_VER ${TARGET_FP_VERSION_PREFIX} + COMPANY_NAME "oblivioncth" + FILE_DESC "Flashpoint Importer for Launchers" + INTERNAL_NAME "FIL" + COPYRIGHT "Open Source @ 2021 oblivioncth" + TRADEMARKS_ONE "All Rights Reserved" + TRADEMARKS_TWO "GNU AGPL V3" + ORIG_FILENAME "FIL.exe" + PRODUCT_NAME "${PROJECT_DESCRIPTION}" + ) +endif() diff --git a/app/src/clifp.cpp b/app/src/clifp.cpp index 94bd939..22eae86 100644 --- a/app/src/clifp.cpp +++ b/app/src/clifp.cpp @@ -2,34 +2,56 @@ #include "clifp.h" // Qx Includes +#ifdef _WIN32 #include +#endif // libfp Includes #include +// Project Includes +#include "project_vars.h" + //=============================================================================================================== // CLIFp //=============================================================================================================== //-Class Functions-------------------------------------------------------------------------------------------- //Public: -QString CLIFp::standardCLIFpPath(const Fp::Install& fpInstall) { return fpInstall.dir().absoluteFilePath(EXE_NAME); } - -bool CLIFp::hasCLIFp(const Fp::Install& fpInstall) +Qx::VersionNumber CLIFp::internalVersion() { - QFileInfo presentInfo(standardCLIFpPath(fpInstall)); - return presentInfo.exists() && presentInfo.isFile(); + static Qx::VersionNumber v = Qx::VersionNumber::fromString(PROJECT_BUNDLED_CLIFP_VERSION); + return v; } -Qx::VersionNumber CLIFp::currentCLIFpVersion(const Fp::Install& fpInstall) +Qx::VersionNumber CLIFp::installedVersion(const Fp::Install& fpInstall) { if(!hasCLIFp(fpInstall)) return Qx::VersionNumber(); else + { +#ifdef _WIN32 return Qx::FileDetails::readFileDetails(standardCLIFpPath(fpInstall)).fileVersion(); +#endif + /* TODO: For now on Linux we just return a null version so that deployment always + * occurs. Eventually, find a good way to grab version info from the installed ELF. + * + * Currently, we can't run it since it doesn't output to console, and there is no + * standardized way to embed the info as part of the ELF structure. + */ + return Qx::VersionNumber(); + } +} + +QString CLIFp::standardCLIFpPath(const Fp::Install& fpInstall) { return fpInstall.dir().absoluteFilePath(EXE_NAME); } + +bool CLIFp::hasCLIFp(const Fp::Install& fpInstall) +{ + QFileInfo presentInfo(standardCLIFpPath(fpInstall)); + return presentInfo.exists() && presentInfo.isFile(); } -bool CLIFp::deployCLIFp(QString& errorMsg, const Fp::Install& fpInstall, const QString& sourcePath) +bool CLIFp::deployCLIFp(QString& errorMsg, const Fp::Install& fpInstall) { // Delete existing if present QFile clifp(standardCLIFpPath(fpInstall)); @@ -44,7 +66,7 @@ bool CLIFp::deployCLIFp(QString& errorMsg, const Fp::Install& fpInstall, const Q } // Deploy new - QFile internalCLIFp(sourcePath); + QFile internalCLIFp(u":/file/clifp"_s); if(!internalCLIFp.copy(clifp.fileName())) { errorMsg = internalCLIFp.errorString(); diff --git a/app/src/clifp.h b/app/src/clifp.h index aab2d95..d209c25 100644 --- a/app/src/clifp.h +++ b/app/src/clifp.h @@ -15,7 +15,11 @@ class CLIFp // Class members public: static inline const QString NAME = u"CLIFp"_s; +#ifdef _WIN32 static inline const QString EXE_NAME = NAME + u".exe"_s; +#else + static inline const QString EXE_NAME = u"clifp"_s; +#endif static inline const QString PLAY_COMMAND = u"play"_s; static inline const QString RUN_COMMAND = u"run"_s; static inline const QString SHOW_COMMAND = u"show"_s; @@ -33,10 +37,11 @@ class CLIFp // Class functions public: + static Qx::VersionNumber internalVersion(); + static Qx::VersionNumber installedVersion(const Fp::Install& fpInstall); static QString standardCLIFpPath(const Fp::Install& fpInstall); static bool hasCLIFp(const Fp::Install& fpInstall); - static Qx::VersionNumber currentCLIFpVersion(const Fp::Install& fpInstall); - static bool deployCLIFp(QString& errorMsg, const Fp::Install& fpInstall, const QString& sourcePath); + static bool deployCLIFp(QString& errorMsg, const Fp::Install& fpInstall); static QString parametersFromStandard(QStringView originalAppPath, QStringView originalAppParams); static QString parametersFromStandard(QUuid titleId); diff --git a/app/src/frontend/attractmode/am-install.cpp b/app/src/frontend/attractmode/am-install.cpp index 3eeae3a..adc9a82 100644 --- a/app/src/frontend/attractmode/am-install.cpp +++ b/app/src/frontend/attractmode/am-install.cpp @@ -5,7 +5,6 @@ #include // Qx Includes -#include #include #include @@ -33,7 +32,7 @@ Install::Install(const QString& installPath) : mEmulatorConfigFile(installPath + '/' + EMULATORS_PATH + '/' + Fp::NAME + '.' + CFG_EXT) { /* - * Directories are required because they are generated byi default and help prevent + * Directories are required because they are generated by default and help prevent * false positives. The config file is required for obvious reasons. Executables * are optional since the config folder might be separated by them. */ @@ -304,20 +303,8 @@ QString Install::versionString() const // Limits to first 3 segments for consistency since that's what AttractMode seems to use // Try executables if they exist - QString exePath = mMainExe.exists() ? mMainExe.fileName() : - mConsoleExe.exists() ? mConsoleExe.fileName() : - QString(); - - if(!exePath.isEmpty()) - { - Qx::FileDetails exeDetails = Qx::FileDetails::readFileDetails(exePath); - if(!exeDetails.isNull()) - { - Qx::VersionNumber ver = exeDetails.productVersion(); - if(!ver.isNull()) - return ver.first(3).toString(); - } - } + if(QString exeVer = versionFromExecutable(); !exeVer.isEmpty()) + return exeVer; // Try main config file Qx::TextStreamReader configReader(mMainConfigFile.fileName()); @@ -337,7 +324,7 @@ QString Install::versionString() const } // Can't determine version - return u"UNKNOWN VERSION"_s; + return Fe::Install::versionString(); } QString Install::translateDocName(const QString& originalName, Fe::DataDoc::Type type) const diff --git a/app/src/frontend/attractmode/am-install.h b/app/src/frontend/attractmode/am-install.h index cf07ad5..81b7540 100644 --- a/app/src/frontend/attractmode/am-install.h +++ b/app/src/frontend/attractmode/am-install.h @@ -33,8 +33,12 @@ class Install : public Fe::Install static inline const QString ROMLISTS_PATH = u"romlists"_s; static inline const QString SCRAPER_PATH = u"scraper"_s; static inline const QString MAIN_CFG_PATH = u"attract.cfg"_s; +#ifdef _WIN32 static inline const QString MAIN_EXE_PATH = u"attract.exe"_s; - static inline const QString CONSOLE_EXE_PATH = u"attract-console.exe"_s; +#else + static inline const QString MAIN_EXE_PATH = u"attract"_s; +#endif + static inline const QString CONSOLE_EXE_PATH = u"attract-console.exe"_s; // Removed in newer versions // Sub paths static inline const QString LOGO_FOLDER_NAME = u"flyer"_s; @@ -71,7 +75,7 @@ class Install : public Fe::Install QDir mFpTagDirectory; QDir mFpScraperDirectory; QFile mMainExe; - QFile mConsoleExe; + QFile mConsoleExe; // Removed in newer versions QFile mFpRomlist; QFile mEmulatorConfigFile; @@ -90,6 +94,7 @@ class Install : public Fe::Install // Install management void nullify() override; Qx::Error populateExistingDocs() override; + QString versionFromExecutable() const; // Image Processing QString imageDestinationPath(Fp::ImageType imageType, const Fe::Game* game) const; diff --git a/app/src/frontend/attractmode/am-install_linux.cpp b/app/src/frontend/attractmode/am-install_linux.cpp new file mode 100644 index 0000000..13df019 --- /dev/null +++ b/app/src/frontend/attractmode/am-install_linux.cpp @@ -0,0 +1,36 @@ +// Unit Include +#include "am-install.h" + +// Qx Includes +#include + +namespace Am +{ +//=============================================================================================================== +// INSTALL +//=============================================================================================================== + +QString Install::versionFromExecutable() const +{ + QProcess attract; + attract.setProgram(MAIN_EXE_PATH); + attract.setArguments({"--version"}); + + attract.start(); + if(!attract.waitForStarted(1000)) + return QString(); + + if(!attract.waitForFinished(1000)) + { + attract.kill(); // Force close + attract.waitForFinished(); + + return QString(); + } + + QString versionInfo = QString::fromLatin1(attract.readAllStandardOutput()); + QRegularExpressionMatch sv = Qx::RegularExpression::SEMANTIC_VERSION.match(versionInfo); + return sv.hasMatch() ? sv.captured() : QString(); +} + +} diff --git a/app/src/frontend/attractmode/am-install_win.cpp b/app/src/frontend/attractmode/am-install_win.cpp new file mode 100644 index 0000000..02129bd --- /dev/null +++ b/app/src/frontend/attractmode/am-install_win.cpp @@ -0,0 +1,33 @@ +// Unit Include +#include "am-install.h" + +// Qx Includes +#include + +namespace Am +{ +//=============================================================================================================== +// INSTALL +//=============================================================================================================== + +QString Install::versionFromExecutable() const +{ + QString exePath = mMainExe.exists() ? mMainExe.fileName() : + mConsoleExe.exists() ? mConsoleExe.fileName() : + QString(); + + if(!exePath.isEmpty()) + { + Qx::FileDetails exeDetails = Qx::FileDetails::readFileDetails(exePath); + if(!exeDetails.isNull()) + { + Qx::VersionNumber ver = exeDetails.productVersion(); + if(!ver.isNull()) + return ver.first(3).toString(); + } + } + + return QString(); +} + +} diff --git a/app/src/frontend/fe-data.h b/app/src/frontend/fe-data.h index 6775872..c6486cf 100644 --- a/app/src/frontend/fe-data.h +++ b/app/src/frontend/fe-data.h @@ -326,8 +326,8 @@ T* itemPtr(std::shared_ptr item) { return item.get(); } requires updateable_item_container void addUpdateableItem(C& existingItems, C& finalItems, - C::key_type key, - C::mapped_type newItem) + typename C::key_type key, + typename C::mapped_type newItem) { // Check if item exists if(existingItems.contains(key)) @@ -354,7 +354,7 @@ T* itemPtr(std::shared_ptr item) { return item.get(); } requires updateable_basicitem_container void addUpdateableItem(C& existingItems, C& finalItems, - C::mapped_type newItem) + typename C::mapped_type newItem) { addUpdateableItem(existingItems, finalItems, diff --git a/app/src/frontend/fe-installfoundation.cpp b/app/src/frontend/fe-installfoundation.cpp index 7766ddd..1e6148e 100644 --- a/app/src/frontend/fe-installfoundation.cpp +++ b/app/src/frontend/fe-installfoundation.cpp @@ -1,10 +1,6 @@ // Unit Include #include "fe-installfoundation.h" -// Windows Includes (Specifically for changing file permissions) -#include "Aclapi.h" -#include "sddl.h" - namespace Fe { @@ -51,46 +47,6 @@ InstallFoundation::InstallFoundation(const QString& installPath) : //Public: InstallFoundation::~InstallFoundation() {} -//-Class Functions-------------------------------------------------------------------------------------------- -//Private: -void InstallFoundation::allowUserWriteOnFile(const QString& filePath) -{ - PACL pDacl,pNewDACL; - EXPLICIT_ACCESS ExplicitAccess; - PSECURITY_DESCRIPTOR ppSecurityDescriptor; - PSID psid; - - /* NOTE: We do two things here that are technically risky, but should be ok: - * - * 1) We get a pointer to the underlying data of the QString and cast it to const wchar_t*. - * on other platforms the size of wchar_t can vary, but on Windows it's clear that it's - * 2-bytes, as it even caused a defect in the C++ standard for being so. - * 2) For some reason SetNamedSecurityInfo() takes the path string as non-const, which I'm - * almost certain is an oversight, as the docs make it clear its just an input to be - * read; therefore, we cast away the constness. - */ - LPCWSTR cPath = reinterpret_cast(filePath.data()); - - GetNamedSecurityInfo(cPath, SE_FILE_OBJECT,DACL_SECURITY_INFORMATION, NULL, NULL, &pDacl, NULL, &ppSecurityDescriptor); - ConvertStringSidToSid(L"S-1-1-0", &psid); - - ExplicitAccess.grfAccessMode = SET_ACCESS; - ExplicitAccess.grfAccessPermissions = GENERIC_ALL; - ExplicitAccess.grfInheritance = CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE; - ExplicitAccess.Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE; - ExplicitAccess.Trustee.pMultipleTrustee = NULL; - ExplicitAccess.Trustee.ptstrName = (LPTSTR) psid; - ExplicitAccess.Trustee.TrusteeForm = TRUSTEE_IS_SID; - ExplicitAccess.Trustee.TrusteeType = TRUSTEE_IS_UNKNOWN; - - SetEntriesInAcl(1, &ExplicitAccess, pDacl, &pNewDACL); - // This function should not modify the string, - SetNamedSecurityInfo(const_cast(cPath), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, pNewDACL, NULL); - - LocalFree(pNewDACL); - LocalFree(psid); -} - //Public: QString InstallFoundation::filePathToBackupPath(const QString& filePath) { @@ -215,7 +171,7 @@ Fe::DocHandlingError InstallFoundation::commitDataDocument(DataDoc* docToSave, s mDeletedDocuments.remove(id); commitError = docWriter->writeOutOf(); - allowUserWriteOnFile(docToSave->path()); + ensureModifiable(docToSave->path()); } else // Handle deletion { diff --git a/app/src/frontend/fe-installfoundation.h b/app/src/frontend/fe-installfoundation.h index 8c9131f..cc9e307 100644 --- a/app/src/frontend/fe-installfoundation.h +++ b/app/src/frontend/fe-installfoundation.h @@ -128,7 +128,7 @@ class InstallFoundation //-Class Functions------------------------------------------------------------------------------------------------------ private: - static void allowUserWriteOnFile(const QString& filePath); + static void ensureModifiable(const QString& filePath); public: static QString filePathToBackupPath(const QString& filePath); diff --git a/app/src/frontend/fe-installfoundation_linux.cpp b/app/src/frontend/fe-installfoundation_linux.cpp new file mode 100644 index 0000000..b802867 --- /dev/null +++ b/app/src/frontend/fe-installfoundation_linux.cpp @@ -0,0 +1,21 @@ +// Unit Include +#include "fe-installfoundation.h" + +// Qt Includes +#include + +namespace Fe +{ +//=============================================================================================================== +// InstallFoundation +//=============================================================================================================== + +//-Class Functions-------------------------------------------------------------------------------------------- +//Private: +void InstallFoundation::ensureModifiable(const QString& filePath) +{ + QFile f(filePath);; + f.setPermissions(f.permissions() | QFile::WriteOwner | QFile::WriteGroup); +} + +} diff --git a/app/src/frontend/fe-installfoundation_win.cpp b/app/src/frontend/fe-installfoundation_win.cpp new file mode 100644 index 0000000..922c7fa --- /dev/null +++ b/app/src/frontend/fe-installfoundation_win.cpp @@ -0,0 +1,89 @@ +// Unit Include +#include "fe-installfoundation.h" + +// Windows Includes (Specifically for changing file permissions) +#include "Aclapi.h" + +namespace Fe +{ +//=============================================================================================================== +// InstallFoundation +//=============================================================================================================== + +//-Class Functions-------------------------------------------------------------------------------------------- +//Private: +void InstallFoundation::ensureModifiable(const QString& filePath) +{ + PACL pCurrentDacl = nullptr, pNewDACL = nullptr; + PSECURITY_DESCRIPTOR pSecurityDescriptor = nullptr; + PSID pOwnerId = nullptr; + + // Ensure cleanup + QScopeGuard cleanup([&]{ + LocalFree(pSecurityDescriptor); + LocalFree(pNewDACL); + LocalFree(pOwnerId); + }); + + /* NOTE: We do two things here that are technically risky, but should be ok: + * + * 1) We get a pointer to the underlying data of the QString and cast it to const wchar_t*. + * on other platforms the size of wchar_t can vary, but on Windows it's clear that it's + * 2-bytes, as it even caused a defect in the C++ standard for being so. + * 2) For some reason SetNamedSecurityInfo() takes the path string as non-const, which I'm + * almost certain is an oversight, as the docs make it clear its just an input to be + * read; therefore, we cast away the constness. + */ + LPCWSTR cPath = reinterpret_cast(filePath.data()); + DWORD status; + + if(status = GetNamedSecurityInfo( + cPath, + SE_FILE_OBJECT, + OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, + &pOwnerId, + NULL, + &pCurrentDacl, + NULL, + &pSecurityDescriptor + ); status != ERROR_SUCCESS) + { + qWarning("Failed to query install file security info: 0x%X", status); + return; + } + + EXPLICIT_ACCESS access{ + .grfAccessPermissions = GENERIC_ALL, + .grfAccessMode = SET_ACCESS, + .grfInheritance = CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE, + .Trustee = { + .pMultipleTrustee = NULL, + .MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE, + .TrusteeForm = TRUSTEE_IS_SID, + .TrusteeType = TRUSTEE_IS_UNKNOWN, + .ptstrName = (LPTSTR) pOwnerId + } + }; + + if(status = SetEntriesInAcl(1, &access, pCurrentDacl, &pNewDACL); status != ERROR_SUCCESS) + { + qWarning("Failed to update install file ACL: 0x%X", status); + return; + } + + if(status = SetNamedSecurityInfo( + const_cast(cPath), + SE_FILE_OBJECT, + DACL_SECURITY_INFORMATION, + NULL, + NULL, + pNewDACL, + NULL + ); status != ERROR_SUCCESS) + { + qWarning("Failed to save install file ACLs: 0x%X", status); + return; + } +} + +} diff --git a/app/src/frontend/launchbox/lb-install.cpp b/app/src/frontend/launchbox/lb-install.cpp index 9a02a9a..a4769d7 100644 --- a/app/src/frontend/launchbox/lb-install.cpp +++ b/app/src/frontend/launchbox/lb-install.cpp @@ -312,7 +312,7 @@ QString Install::versionString() const else if(!productVersionStr.isEmpty()) return productVersionStr; else - return Install::versionString(); + return Fe::Install::versionString(); } QString Install::translateDocName(const QString& originalName, Fe::DataDoc::Type type) const diff --git a/app/src/main.cpp b/app/src/main.cpp index 1d42cad..5bfd2d3 100644 --- a/app/src/main.cpp +++ b/app/src/main.cpp @@ -1,4 +1,4 @@ -#include "mainwindow.h" +#include "ui/mainwindow.h" #include int main(int argc, char *argv[]) diff --git a/app/src/mainwindow.cpp b/app/src/ui/mainwindow.cpp similarity index 88% rename from app/src/mainwindow.cpp rename to app/src/ui/mainwindow.cpp index 8f34d2b..723ffbb 100644 --- a/app/src/mainwindow.cpp +++ b/app/src/ui/mainwindow.cpp @@ -20,7 +20,6 @@ #include #include #include -#include #include // Project Includes @@ -34,7 +33,7 @@ * used for the import, so that they can be loaded again when that install is targeted by future versions * of the tool. Would have to account for an initial import vs update (likely just leaving the update settings * blank). Wouldn't be a huge difference but could be a nice little time saver. - */ + */ //=============================================================================================================== // MAIN WINDOW @@ -43,7 +42,8 @@ //-Constructor--------------------------------------------------------------------------------------------------- MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), - ui(new Ui::MainWindow) + ui(new Ui::MainWindow), + mProgressPresenter(this) { /*Register metatypes * NOTE: Qt docs note these should be needed, as always, but since Qt6 signals/slots with these types seem to @@ -54,18 +54,8 @@ MainWindow::MainWindow(QWidget *parent) : //qRegisterMetaType(); //qRegisterMetaType>(); - // Get built-in CLIFp version - QTemporaryDir tempDir; - if(tempDir.isValid()) - { - // Create local copy of internal CLIFp.exe since internal path cannot be used with WinAPI - QString localCopyPath = tempDir.path() + '/' + CLIFp::EXE_NAME; - if(QFile::copy(u":/file/"_s + CLIFp::EXE_NAME, localCopyPath)) - mInternalCLIFpVersion = Qx::FileDetails::readFileDetails(localCopyPath).fileVersion(); - } - - // Abort if no version could be determined - if(mInternalCLIFpVersion.isNull()) + // Ensure built-in CLIFp version is valid + if(CLIFp::internalVersion().isNull()) { QMessageBox::critical(this, CAPTION_GENERAL_FATAL_ERROR, MSG_FATAL_NO_INTERNAL_CLIFP_VER); mInitCompleted = false; @@ -121,7 +111,7 @@ void MainWindow::initializeForms() mExistingItemColor = ui->label_existingItemColor->palette().color(QPalette::Window); // Add CLIFp version to deploy option - ui->action_deployCLIFp->setText(ui->action_deployCLIFp->text() + ' ' + mInternalCLIFpVersion.normalized(2).toString()); + ui->action_deployCLIFp->setText(ui->action_deployCLIFp->text() + ' ' + CLIFp::internalVersion().normalized(2).toString()); // Prepare help messages mArgedPlaylistGameModeHelp = MSG_PLAYLIST_GAME_MODE_HELP.arg(ui->radioButton_selectedPlatformsOnly->text(), @@ -619,24 +609,13 @@ void MainWindow::prepareImport() if(!feRunning) { - // Create progress dialog, set initial state and show - mImportProgressDialog = std::make_unique(STEP_FP_DB_INITIAL_QUERY, u"Cancel"_s, 0, 0, this); - mImportProgressDialog->setWindowTitle(CAPTION_IMPORTING); - mImportProgressDialog->setWindowModality(Qt::WindowModal); - mImportProgressDialog->setWindowFlags(mImportProgressDialog->windowFlags() & ~Qt::WindowContextHelpButtonHint); - mImportProgressDialog->setAutoReset(false); - mImportProgressDialog->setAutoClose(false); - mImportProgressDialog->setMinimumDuration(0); // Always show pd - mImportProgressDialog->setValue(0); // Get pd to show - - // Setup taskbar button progress indicator - mWindowTaskbarButton->setProgressMinimum(0); - mWindowTaskbarButton->setProgressMaximum(0); - mWindowTaskbarButton->setProgressValue(0); - mWindowTaskbarButton->setProgressState(Qx::TaskbarButton::Busy); - - // Force show progress immediately - QApplication::processEvents(); + // Start progress presentation + mProgressPresenter.setMinimum(0); + mProgressPresenter.setMaximum(0); + mProgressPresenter.setValue(0); + mProgressPresenter.setBusyState(); + mProgressPresenter.setLabelText(STEP_FP_DB_INITIAL_QUERY); + QApplication::processEvents(); // Force show progress immediately // Setup import worker ImportWorker::ImportSelections impSel{.platforms = getSelectedPlatforms(), @@ -657,12 +636,10 @@ void MainWindow::prepareImport() connect(&importWorker, &ImportWorker::authenticationRequired, this, &MainWindow::handleAuthRequest); // Create process update connections - connect(&importWorker, &ImportWorker::progressStepChanged, mImportProgressDialog.get(), &QProgressDialog::setLabelText); - connect(&importWorker, &ImportWorker::progressMaximumChanged, mImportProgressDialog.get(), &QProgressDialog::setMaximum); - connect(&importWorker, &ImportWorker::progressMaximumChanged, mWindowTaskbarButton, &Qx::TaskbarButton::setProgressMaximum); - connect(&importWorker, &ImportWorker::progressValueChanged, mImportProgressDialog.get(), &QProgressDialog::setValue); - connect(&importWorker, &ImportWorker::progressValueChanged, mWindowTaskbarButton, &Qx::TaskbarButton::setProgressValue); - connect(mImportProgressDialog.get(), &QProgressDialog::canceled, &importWorker, &ImportWorker::notifyCanceled); + connect(&importWorker, &ImportWorker::progressStepChanged, &mProgressPresenter, &ProgressPresenter::setLabelText); + connect(&importWorker, &ImportWorker::progressValueChanged, &mProgressPresenter, &ProgressPresenter::setValue); + connect(&importWorker, &ImportWorker::progressMaximumChanged, &mProgressPresenter, &ProgressPresenter::setMaximum); + connect(&mProgressPresenter, &ProgressPresenter::canceled, &importWorker, &ImportWorker::notifyCanceled); // Import error tracker Qx::Error importError; @@ -712,6 +689,29 @@ void MainWindow::revertAllFrontendChanges() mFrontendInstall->softReset(); } +void MainWindow::deployCLIFp(const Fp::Install& fp, QMessageBox::Button abandonButton) +{ + bool willDeploy = true; + + // Check for existing CLIFp + if(CLIFp::hasCLIFp(fp)) + { + // Notify user if this will be a downgrade + if(CLIFp::internalVersion() < CLIFp::installedVersion(fp)) + willDeploy = (QMessageBox::warning(this, CAPTION_CLIFP_DOWNGRADE, MSG_FP_CLFIP_WILL_DOWNGRADE, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes); + } + + // Deploy CLIFp if applicable + if(willDeploy) + { + // Deploy exe + QString deployError; + while(!CLIFp::deployCLIFp(deployError, fp)) + if(QMessageBox::critical(this, CAPTION_CLIFP_ERR, MSG_FP_CANT_DEPLOY_CLIFP.arg(deployError), QMessageBox::Retry | abandonButton, QMessageBox::Retry) == abandonButton) + break; + } +} + void MainWindow::standaloneCLIFpDeploy() { // Browse for install @@ -725,25 +725,7 @@ void MainWindow::standaloneCLIFpDeploy() if(!installMatchesTargetSeries(tempFlashpointInstall)) QMessageBox::warning(this, QApplication::applicationName(), MSG_FP_VER_NOT_TARGET); - bool willDeploy = true; - - // Check for existing CLIFp - if(CLIFp::hasCLIFp(tempFlashpointInstall)) - { - // Notify user if this will be a downgrade - if(mInternalCLIFpVersion < CLIFp::currentCLIFpVersion(tempFlashpointInstall)) - willDeploy = (QMessageBox::warning(this, CAPTION_CLIFP_DOWNGRADE, MSG_FP_CLFIP_WILL_DOWNGRADE, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes); - } - - // Deploy CLIFp if applicable - if(willDeploy) - { - // Deploy exe - QString deployError; - while(!CLIFp::deployCLIFp(deployError, tempFlashpointInstall, u":/file/CLIFp.exe"_s)) - if(QMessageBox::critical(this, CAPTION_CLIFP_ERR, MSG_FP_CANT_DEPLOY_CLIFP.arg(deployError), QMessageBox::Retry | QMessageBox::Cancel, QMessageBox::Retry) == QMessageBox::Cancel) - break; - } + deployCLIFp(tempFlashpointInstall, QMessageBox::Cancel); } else Qx::postBlockingError(tempFlashpointInstall.error(), QMessageBox::Ok); @@ -789,12 +771,8 @@ QSet MainWindow::generateTagExlusionSet() const //Protected: void MainWindow::showEvent(QShowEvent* event) { - // Call standard function + mProgressPresenter.attachWindow(windowHandle()); QMainWindow::showEvent(event); - - // Configure taskbar button - mWindowTaskbarButton = new Qx::TaskbarButton(this); - mWindowTaskbarButton->setWindow(this->windowHandle()); } //Public: @@ -993,18 +971,16 @@ void MainWindow::all_on_menu_triggered(QAction *action) void MainWindow::handleBlockingError(std::shared_ptr response, const Qx::Error& blockingError, QMessageBox::StandardButtons choices) { - // Get taskbar progress and indicate error - mWindowTaskbarButton->setProgressState(Qx::TaskbarButton::Stopped); + mProgressPresenter.setErrorState(); // Post error and get response int userChoice = Qx::postBlockingError(blockingError, choices); - // Clear taskbar error - mWindowTaskbarButton->setProgressState(Qx::TaskbarButton::Normal); - // If applicable return selection if(response) *response = userChoice; + + mProgressPresenter.resetState(); } void MainWindow::handleAuthRequest(const QString& prompt, QAuthenticator* authenticator) @@ -1023,10 +999,8 @@ void MainWindow::handleAuthRequest(const QString& prompt, QAuthenticator* authen void MainWindow::handleImportResult(ImportWorker::ImportResult importResult, const Qx::Error& errorReport) { - // Close progress dialog and reset taskbar progress indicator - mImportProgressDialog->close(); - mWindowTaskbarButton->resetProgress(); - mWindowTaskbarButton->setProgressState(Qx::TaskbarButton::Hidden); + // Reset progress presenter + mProgressPresenter.reset(); // Post error report if present if(errorReport.isValid()) @@ -1034,24 +1008,7 @@ void MainWindow::handleImportResult(ImportWorker::ImportResult importResult, con if(importResult == ImportWorker::Successful) { - bool willDeploy = true; - - // Check for existing CLIFp - if(CLIFp::hasCLIFp(*mFlashpointInstall)) - { - // Notify user if this will be a downgrade - if(mInternalCLIFpVersion < CLIFp::currentCLIFpVersion(*mFlashpointInstall)) - willDeploy = (QMessageBox::warning(this, CAPTION_CLIFP_DOWNGRADE, MSG_FP_CLFIP_WILL_DOWNGRADE, QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes); - } - - // Deploy CLIFp if applicable - if(willDeploy) - { - QString deployError; - while(!CLIFp::deployCLIFp(deployError, *mFlashpointInstall, u":/file/CLIFp.exe"_s)) - if(QMessageBox::critical(this, CAPTION_CLIFP_ERR, MSG_FP_CANT_DEPLOY_CLIFP.arg(deployError), QMessageBox::Retry | QMessageBox::Ignore, QMessageBox::Retry) == QMessageBox::Ignore) - break; - } + deployCLIFp(*mFlashpointInstall, QMessageBox::Ignore); // Post-import message QMessageBox::information(this, QApplication::applicationName(), MSG_POST_IMPORT); diff --git a/app/src/mainwindow.h b/app/src/ui/mainwindow.h similarity index 97% rename from app/src/mainwindow.h rename to app/src/ui/mainwindow.h index ce32667..76ed60b 100644 --- a/app/src/mainwindow.h +++ b/app/src/ui/mainwindow.h @@ -4,20 +4,19 @@ // Qt Includes #include #include -#include #include // Qx Includes #include #include #include -#include // libfp Includes #include // Project Includes #include "project_vars.h" +#include "ui/progresspresenter.h" #include "frontend/fe-install.h" #include "import-worker.h" #include "clifp.h" @@ -110,7 +109,7 @@ class MainWindow : public QMainWindow // Messages - FP CLIFp static inline const QString MSG_FP_CLFIP_WILL_DOWNGRADE = u"The existing version of "_s + CLIFp::EXE_NAME + u" in your Flashpoint install is newer than the version package with this tool.\n" "\n" - "Replacing it with the packaged Version (downgrade) will likely cause compatibility issues unless you are specifically re-importing are downgrading your Flashpoint install to a previous version.\n" + "Replacing it with the packaged Version (downgrade) will likely cause compatibility issues unless you are specifically re-importing after downgrading your Flashpoint install to a previous version.\n" "\n" "Do you wish to downgrade "_s + CLIFp::EXE_NAME + u"?"_s; @@ -141,7 +140,6 @@ class MainWindow : public QMainWindow static inline const QString CAPTION_REVERT = u"Reverting changes..."_s; static inline const QString CAPTION_CLIFP_ERR = u"Error deploying CLIFp"_s; static inline const QString CAPTION_CLIFP_DOWNGRADE = u"Downgrade CLIFp?"_s; - static inline const QString CAPTION_IMPORTING = u"FP Import"_s; static inline const QString CAPTION_TAG_FILTER = u"Tag Filter"_s; // Menus @@ -176,7 +174,6 @@ class MainWindow : public QMainWindow std::shared_ptr mFrontendInstall; std::shared_ptr mFlashpointInstall; - Qx::VersionNumber mInternalCLIFpVersion; QHash mPlatformItemCheckStates; QHash mPlaylistItemCheckStates; @@ -189,8 +186,7 @@ class MainWindow : public QMainWindow QString mArgedImageModeHelp; // Process monitoring - std::unique_ptr mImportProgressDialog; - Qx::TaskbarButton* mWindowTaskbarButton; + ProgressPresenter mProgressPresenter; //-Constructor--------------------------------------------------------------------------------------------------- public: @@ -239,6 +235,7 @@ class MainWindow : public QMainWindow void prepareImport(); void revertAllFrontendChanges(); + void deployCLIFp(const Fp::Install& fp, QMessageBox::Button abandonButton); void standaloneCLIFpDeploy(); void showTagSelectionDialog(); QSet generateTagExlusionSet() const; diff --git a/app/src/mainwindow.ui b/app/src/ui/mainwindow.ui similarity index 100% rename from app/src/mainwindow.ui rename to app/src/ui/mainwindow.ui diff --git a/app/src/ui/progresspresenter.cpp b/app/src/ui/progresspresenter.cpp new file mode 100644 index 0000000..5ff3c9e --- /dev/null +++ b/app/src/ui/progresspresenter.cpp @@ -0,0 +1,105 @@ +// Unit Includes +#include "progresspresenter.h" + +//=============================================================================================================== +// ProgressPresenter +//=============================================================================================================== + +//-Constructor--------------------------------------------------------------------------------------------------- +ProgressPresenter::ProgressPresenter(QWidget* parent) : + QObject(parent), +#ifdef _WIN32 + mButton(parent), +#endif + mDialog(parent) +{ + setupProgressDialog(); + +} + +//-Instance Functions-------------------------------------------------------------------------------------------- +//Private: +void ProgressPresenter::setupProgressDialog() +{ + // Initialize dialog + mDialog.setCancelButtonText(u"Cancel"_s); + mDialog.setWindowModality(Qt::WindowModal); + mDialog.setWindowTitle(CAPTION_IMPORTING); + mDialog.setWindowFlags(mDialog.windowFlags() & ~Qt::WindowContextHelpButtonHint); + mDialog.setAutoReset(false); + mDialog.setAutoClose(false); + mDialog.setMinimumDuration(0); // Always show pd + mDialog.reset(); // Stops the auto-show timer that is started by QProgressDialog's ctor + connect(&mDialog, &QProgressDialog::canceled, this, &ProgressPresenter::canceled); +} + +//Public: +void ProgressPresenter::attachWindow(QWindow* window) +{ +#ifdef _WIN32 + mButton.setWindow(window); +#else + Q_UNUSED(window); +#endif +} + +void ProgressPresenter::setErrorState() +{ +#ifdef _WIN32 + // Get taskbar progress and indicate error + mButton.setProgressState(Qx::TaskbarButton::Stopped); +#endif +} + +void ProgressPresenter::setBusyState() +{ +#ifdef _WIN32 + // Get taskbar progress and indicate error + mButton.setProgressState(Qx::TaskbarButton::Busy); +#endif +} + +void ProgressPresenter::resetState() +{ +#ifdef _WIN32 + // Clear taskbar error + mButton.setProgressState(Qx::TaskbarButton::Normal); +#endif +} + +void ProgressPresenter::reset() +{ + mDialog.close(); +#ifdef _WIN32 + mButton.resetProgress(); + mButton.setProgressState(Qx::TaskbarButton::Hidden); +#endif +} + +//-Slots--------------------------------------------------------------------------------------------------------- +//Public: +void ProgressPresenter::setLabelText(const QString& text) { mDialog.setLabelText(text); } + +void ProgressPresenter::setValue(int value) +{ + mDialog.setValue(value); +#ifdef _WIN32 + mButton.setProgressValue(value); +#endif +} + +void ProgressPresenter::setMaximum(int max) +{ + mDialog.setMaximum(max); +#ifdef _WIN32 + mButton.setProgressMaximum(max); +#endif +} + +void ProgressPresenter::setMinimum(int min) +{ + mDialog.setMinimum(min); +#ifdef _WIN32 + mButton.setProgressMinimum(min); +#endif +} diff --git a/app/src/ui/progresspresenter.h b/app/src/ui/progresspresenter.h new file mode 100644 index 0000000..2ba9c46 --- /dev/null +++ b/app/src/ui/progresspresenter.h @@ -0,0 +1,55 @@ +#ifndef PROGRESSPRESENTER_H +#define PROGRESSPRESENTER_H + +// Qt Includes +#include + +// Qx Includes +#ifdef _WIN32 +#include +#endif + +using namespace Qt::StringLiterals; + +class ProgressPresenter : public QObject +{ + Q_OBJECT +//-Class Variables-------------------------------------------------------------------------------------------- +private: + static inline const QString CAPTION_IMPORTING = u"FP Import"_s; + +//-Instance Variables-------------------------------------------------------------------------------------------- +private: +#ifdef _WIN32 + Qx::TaskbarButton mButton; +#endif + QProgressDialog mDialog; + +//-Constructor--------------------------------------------------------------------------------------------------- +public: + ProgressPresenter(QWidget* parent); + +//-Instance Functions-------------------------------------------------------------------------------------------- +private: + void setupProgressDialog(); + +public: + void attachWindow(QWindow* window); + void setErrorState(); + void setBusyState(); + void resetState(); + void reset(); + +//-Slots--------------------------------------------------------------------------------------------------------- +public slots: + void setLabelText(const QString& text); + void setValue(int value); + void setMinimum(int min); + void setMaximum(int max); + +//-Signals--------------------------------------------------------------------------------------------------------- +signals: + void canceled(); +}; + +#endif // PROGRESSPRESENTER_H