diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c6351f7e..0c5f58a7b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,6 @@ -CMAKE_MINIMUM_REQUIRED(VERSION 2.8) +CMAKE_MINIMUM_REQUIRED(VERSION 2.8.12) + +ADD_COMPILE_OPTIONS($<$:/MP>) PROJECT(organizer) @@ -8,7 +10,6 @@ set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" PROPERTY VS_STARTUP_PROJECT SET(DEPENDENCIES_DIR CACHE PATH "") # hint to find qt in dependencies path LIST(APPEND CMAKE_PREFIX_PATH ${QT_ROOT}/lib/cmake) -FILE(GLOB_RECURSE BOOST_ROOT ${DEPENDENCIES_DIR}/boost*/project-config.jam) -GET_FILENAME_COMPONENT(BOOST_ROOT ${BOOST_ROOT} DIRECTORY) +LIST(APPEND CMAKE_PREFIX_PATH ${LZ4_ROOT}/dll) ADD_SUBDIRECTORY(src) diff --git a/readme.md b/readme.md index fd7f3e53b..0f3d94a61 100644 --- a/readme.md +++ b/readme.md @@ -12,7 +12,7 @@ Mod Organizer 2 is an open project in the hands of the community, there are prob To have more information, please join the open MO2 Development discord server :* [ModOrganizerDevs](https://discord.gg/vD2ZbfX) If you want to help translate MO2 to your language you should join the discord server too and head to the #translation channel. -To setup a development environment on your machine, there is the [umbrella project](https://github.com/LePresidente/modorganizer-umbrella) that handles that. +To setup a development environment on your machine, there is the [umbrella project](https://github.com/Modorganizer2/modorganizer-umbrella) that handles that. If you want to submit your code changes, please use a good formating style like the default one in Visual Studio. Through the work of a few people of the community MO2 has come quite far, now it needs some more of those people to go further. @@ -24,7 +24,7 @@ Credits to Tannin, LePresidente, Silarn, erasmux, AL12 and many others for the d ## Download Location -* on [GitHub.com](https://github.com/LePresidente/modorganizer/releases) +* on [GitHub.com](https://github.com/Modorganizer2/modorganizer/releases) * on [NexusMods.com](https://www.nexusmods.com/skyrimspecialedition/mods/6194) ## Old Download Location @@ -33,7 +33,7 @@ Credits to Tannin, LePresidente, Silarn, erasmux, AL12 and many others for the d ## Building -Please refer to [LePresidente/modorganizer-umbrella](https://github.com/LePresidente/modorganizer-umbrella) for build instructions. +Please refer to [Modorganizer2/modorganizer-umbrella](https://github.com/Modorganizer2/modorganizer-umbrella) for build instructions. ## Other Repositories @@ -41,39 +41,43 @@ MO2 consists of multiple repositories on github. The Umbrella project will downl Here is a complete list: * https://github.com/LePresidente/cpython-1 -* https://github.com/LePresidente/githubpp -* https://github.com/LePresidente/modorganizer -* https://github.com/LePresidente/modorganizer-archive -* https://github.com/LePresidente/modorganizer-bsatk -* https://github.com/LePresidente/modorganizer-bsa_extractor -* https://github.com/LePresidente/modorganizer-check_fnis -* https://github.com/LePresidente/modorganizer-diagnose_basic -* https://github.com/LePresidente/modorganizer-esptk -* https://github.com/LePresidente/modorganizer-helper -* https://github.com/LePresidente/modorganizer-game_fallout3 -* https://github.com/LePresidente/modorganizer-game_fallout4 -* https://github.com/LePresidente/modorganizer-game_fallout4vr -* https://github.com/LePresidente/modorganizer-game_falloutnv -* https://github.com/LePresidente/modorganizer-game_features -* https://github.com/LePresidente/modorganizer-game_gamebryo -* https://github.com/LePresidente/modorganizer-game_oblivion -* https://github.com/LePresidente/modorganizer-game_skyrim -* https://github.com/LePresidente/modorganizer-game_skyrimSE -* https://github.com/LePresidente/modorganizer-hookdll -* https://github.com/LePresidente/modorganizer-installer_bain -* https://github.com/LePresidente/modorganizer-installer_bundle -* https://github.com/LePresidente/modorganizer-installer_fomod -* https://github.com/LePresidente/modorganizer-installer_manual -* https://github.com/LePresidente/modorganizer-installer_ncc -* https://github.com/LePresidente/modorganizer-installer_quick -* https://github.com/LePresidente/modorganizer-lootcli -* https://github.com/LePresidente/modorganizer-NCC -* https://github.com/LePresidente/modorganizer-nxmhandler -* https://github.com/LePresidente/modorganizer-plugin_python -* https://github.com/LePresidente/modorganizer-preview_base -* https://github.com/LePresidente/modorganizer-tool_configurator -* https://github.com/LePresidente/modorganizer-tool_inibakery -* https://github.com/LePresidente/modorganizer-uibase -* https://github.com/LePresidente/usvfs +* https://github.com/Modorganizer2/githubpp +* https://github.com/Modorganizer2/modorganizer +* https://github.com/Modorganizer2/modorganizer-archive +* https://github.com/Modorganizer2/modorganizer-bsatk +* https://github.com/Modorganizer2/modorganizer-bsa_extractor +* https://github.com/Modorganizer2/modorganizer-check_fnis +* https://github.com/Modorganizer2/modorganizer-diagnose_basic +* https://github.com/Modorganizer2/modorganizer-esptk +* https://github.com/Modorganizer2/modorganizer-helper +* https://github.com/Modorganizer2/modorganizer-game_fallout3 +* https://github.com/Modorganizer2/modorganizer-game_fallout4 +* https://github.com/Modorganizer2/modorganizer-game_fallout4vr +* https://github.com/Modorganizer2/modorganizer-game_falloutnv +* https://github.com/Modorganizer2/modorganizer-game_features +* https://github.com/Modorganizer2/modorganizer-game_gamebryo +* https://github.com/Modorganizer2/modorganizer-game_morrowind +* https://github.com/Modorganizer2/modorganizer-game_oblivion +* https://github.com/Modorganizer2/modorganizer-game_skyrim +* https://github.com/Modorganizer2/modorganizer-game_skyrimSE +* https://github.com/Modorganizer2/modorganizer-game_ttw +* https://github.com/Modorganizer2/modorganizer-installer_bain +* https://github.com/Modorganizer2/modorganizer-installer_bundle +* https://github.com/Modorganizer2/modorganizer-installer_fomod +* https://github.com/Modorganizer2/modorganizer-installer_manual +* https://github.com/Modorganizer2/modorganizer-installer_ncc +* https://github.com/Modorganizer2/modorganizer-installer_quick +* https://github.com/Modorganizer2/modorganizer-lootcli +* https://github.com/Modorganizer2/modorganizer-NCC +* https://github.com/Modorganizer2/modorganizer-nxmhandler +* https://github.com/Modorganizer2/modorganizer-plugin_python +* https://github.com/Modorganizer2/modorganizer-preview_base +* https://github.com/Modorganizer2/modorganizer-tool_configurator +* https://github.com/Modorganizer2/modorganizer-tool_inibakery +* https://github.com/Modorganizer2/modorganizer-uibase +* https://github.com/Modorganizer2/usvfs + +### Unused Repositories +* https://github.com/Modorganizer2/modorganizer-hookdll * https://github.com/TanninOne/modorganizer-tool_nmmimport diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 60b366833..fef640b31 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,4 +1,4 @@ -CMAKE_MINIMUM_REQUIRED (VERSION 2.8) +CMAKE_MINIMUM_REQUIRED (VERSION 2.8.11) CMAKE_POLICY(SET CMP0020 NEW) #CMAKE_POLICY(SET CMP0043 NEW) @@ -249,10 +249,13 @@ FIND_PACKAGE(Qt5Quick REQUIRED) FIND_PACKAGE(Qt5Network REQUIRED) FIND_PACKAGE(Qt5WinExtras REQUIRED) FIND_PACKAGE(Qt5WebEngineWidgets REQUIRED) +FIND_PACKAGE(Qt5Script REQUIRED) +FIND_PACKAGE(Qt5Qml REQUIRED) FIND_PACKAGE(Qt5LinguistTools) QT5_WRAP_UI(organizer_UIHDRS ${organizer_UIS}) QT5_ADD_RESOURCES(organizer_RCCPPS ${organizer_QRCS}) QT5_CREATE_TRANSLATION(organizer_translations_qm ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/src/organizer_en.ts) +ADD_CUSTOM_TARGET(translations DEPENDS ${organizer_translations_qm}) INCLUDE_DIRECTORIES(${Qt5Declarative_INCLUDES}) @@ -288,11 +291,13 @@ INCLUDE_DIRECTORIES(${project_path}/uibase/src ${project_path}/../usvfs/include ${project_path}/game_gamebryo/src ${project_path}/game_features/src - ${project_path}/githubpp/src) + ${project_path}/githubpp/src + ${LZ4_ROOT}/include) INCLUDE_DIRECTORIES(shared ${ZLIB_INCLUDE_DIRS}) LINK_DIRECTORIES(${lib_path} - ${ZLIB_ROOT}/lib) + ${ZLIB_ROOT}/lib + ${LZ4_ROOT}/dll) EXECUTE_PROCESS( COMMAND git log -1 --format=%h WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} @@ -311,11 +316,12 @@ ENDIF() ADD_EXECUTABLE(ModOrganizer WIN32 ${organizer_HDRS} ${organizer_SRCS} ${organizer_UIHDRS} ${organizer_RCS} ${organizer_RCCPPS} ${organizer_translations_qm}) TARGET_LINK_LIBRARIES(ModOrganizer Qt5::Widgets Qt5::WinExtras Qt5::WebEngineWidgets Qt5::Quick + Qt5::Script Qt5::Qml Qt5::QuickWidgets Qt5::Network ${Boost_LIBRARIES} zlibstatic uibase esptk bsatk githubpp ${usvfs_name} - Dbghelp advapi32 Version Shlwapi) + Dbghelp advapi32 Version Shlwapi liblz4) IF (NOT "${OPTIMIZE_COMPILE_FLAGS}" STREQUAL "") SET_TARGET_PROPERTIES(ModOrganizer PROPERTIES COMPILE_FLAGS_RELWITHDEBINFO ${OPTIMIZE_COMPILE_FLAGS}) @@ -324,9 +330,6 @@ SET_TARGET_PROPERTIES(ModOrganizer PROPERTIES LINK_FLAGS_RELWITHDEBINFO "/LARGEADDRESSAWARE ${OPTIMIZE_LINK_FLAGS}") -QT5_USE_MODULES(ModOrganizer Widgets Script Qml QuickWidgets Quick Network WebEngineWidgets) - - ############### ## Installation @@ -368,6 +371,11 @@ INSTALL( file(REMOVE_RECURSE ${CMAKE_INSTALL_PREFIX}/bin/qtplugins)" ) +# qdds.dll needs installing manually as Qt no longer ships with it by default. +INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/../qdds.dll DESTINATION bin/dlls/imageformats) + +install(FILES ${organizer_translations_qm} DESTINATION bin/translations) + INSTALL(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/stylesheets ${CMAKE_CURRENT_SOURCE_DIR}/tutorials DESTINATION bin) diff --git a/src/aboutdialog.cpp b/src/aboutdialog.cpp index ed57a2174..a350036fd 100644 --- a/src/aboutdialog.cpp +++ b/src/aboutdialog.cpp @@ -29,7 +29,7 @@ along with Mod Organizer. If not, see . #include #include #include - +#include AboutDialog::AboutDialog(const QString &version, QWidget *parent) : QDialog(parent) @@ -37,24 +37,44 @@ AboutDialog::AboutDialog(const QString &version, QWidget *parent) { ui->setupUi(this); - m_LicenseFiles[LICENSE_LGPL3] = "lgpl-3.0.txt"; - m_LicenseFiles[LICENSE_GPL3] = "gpl-3.0.txt"; - m_LicenseFiles[LICENSE_BSD3] = "bsd3.txt"; + m_LicenseFiles[LICENSE_LGPL3] = "LGPL-v3.0.txt"; + m_LicenseFiles[LICENSE_LGPL21] = "GNU-LGPL-v2.1.txt"; + m_LicenseFiles[LICENSE_GPL3] = "GPL-v3.0.txt"; + m_LicenseFiles[LICENSE_GPL2] = "GPL-v2.0.txt"; m_LicenseFiles[LICENSE_BOOST] = "boost.txt"; - m_LicenseFiles[LICENSE_CCBY3] = "by-sa3.txt"; + m_LicenseFiles[LICENSE_7ZIP] = "7zip.txt"; + m_LicenseFiles[LICENSE_CCBY3] = "BY-SA-v3.0.txt"; m_LicenseFiles[LICENSE_ZLIB] = "zlib.txt"; - m_LicenseFiles[LICENSE_APACHE2] = "apache-license-2.0.txt"; + m_LicenseFiles[LICENSE_PYTHON] = "python.txt"; + m_LicenseFiles[LICENSE_SSL] = "openssl.txt"; + m_LicenseFiles[LICENSE_CPPTOML] = "cpptoml.txt"; + m_LicenseFiles[LICENSE_UDIS] = "udis86.txt"; + m_LicenseFiles[LICENSE_SPDLOG] = "spdlog.txt"; + m_LicenseFiles[LICENSE_FMT] = "fmt.txt"; + m_LicenseFiles[LICENSE_SIP] = "sip.txt"; + m_LicenseFiles[LICENSE_CASTLE] = "Castle.txt"; + m_LicenseFiles[LICENSE_ANTLR] = "AntlrBuildTask.txt"; + m_LicenseFiles[LICENSE_DXTEX] = "DXTex.txt"; addLicense("Qt", LICENSE_LGPL3); addLicense("Qt Json", LICENSE_GPL3); addLicense("Boost Library", LICENSE_BOOST); - addLicense("7-zip", LICENSE_LGPL3); - addLicense("ZLib", LICENSE_ZLIB); + addLicense("7-zip", LICENSE_7ZIP); + addLicense("ZLib", LICENSE_NONE); addLicense("Tango Icon Theme", LICENSE_NONE); addLicense("RRZE Icon Set", LICENSE_CCBY3); addLicense("Icons by Lorc, Delapouite and sbed available on http://game-icons.net", LICENSE_CCBY3); - addLicense("Castle Core", LICENSE_APACHE2); + addLicense("Castle Core", LICENSE_CASTLE); + addLicense("ANTLR", LICENSE_ANTLR); addLicense("LOOT", LICENSE_GPL3); + addLicense("Python", LICENSE_PYTHON); + addLicense("OpenSSL", LICENSE_SSL); + addLicense("cpptoml", LICENSE_CPPTOML); + addLicense("Udis86", LICENSE_UDIS); + addLicense("spdlog", LICENSE_SPDLOG); + addLicense("{fmt}", LICENSE_FMT); + addLicense("SIP", LICENSE_SIP); + addLicense("DXTex Headers", LICENSE_DXTEX); ui->nameLabel->setText(QString("%1 %2").arg(ui->nameLabel->text()).arg(version)); #if defined(HGID) @@ -64,6 +84,8 @@ AboutDialog::AboutDialog(const QString &version, QWidget *parent) #else ui->revisionLabel->setText(ui->revisionLabel->text() + " unknown"); #endif + + ui->licenseText->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); } @@ -85,7 +107,7 @@ void AboutDialog::on_creditsList_currentItemChanged(QListWidgetItem *current, QL { auto iter = m_LicenseFiles.find(current->data(Qt::UserRole).toInt()); if (iter != m_LicenseFiles.end()) { - QString filePath = qApp->applicationDirPath() + "/license/" + iter->second; + QString filePath = qApp->applicationDirPath() + "/licenses/" + iter->second; QString text = MOBase::readFileText(filePath); ui->licenseText->setText(text); } else { diff --git a/src/aboutdialog.h b/src/aboutdialog.h index 103a0d2ff..3ae3237ba 100644 --- a/src/aboutdialog.h +++ b/src/aboutdialog.h @@ -45,12 +45,23 @@ class AboutDialog : public QDialog enum Licenses { LICENSE_NONE, LICENSE_LGPL3, + LICENSE_LGPL21, LICENSE_GPL3, - LICENSE_BSD3, + LICENSE_GPL2, LICENSE_BOOST, LICENSE_CCBY3, + LICENSE_PYTHON, + LICENSE_SSL, + LICENSE_CPPTOML, + LICENSE_7ZIP, LICENSE_ZLIB, - LICENSE_APACHE2 + LICENSE_UDIS, + LICENSE_SPDLOG, + LICENSE_FMT, + LICENSE_SIP, + LICENSE_CASTLE, + LICENSE_ANTLR, + LICENSE_DXTEX }; private: diff --git a/src/aboutdialog.ui b/src/aboutdialog.ui index 50a9de5ff..69e52708d 100644 --- a/src/aboutdialog.ui +++ b/src/aboutdialog.ui @@ -6,8 +6,8 @@ 0 0 - 604 - 369 + 1010 + 477 @@ -120,14 +120,14 @@ - Copyright 2011-2016 Sebastian Herbord + <html><head/><body><p>Copyright 2011-2016 Sebastian Herbord</p><p>Copyright 2016-2018 Mod Organizer 2 contributors</p></body></html> - <html><head/><body><p>This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.</p><p>See the GNU General Public License for more details.</p><p>Source code can be found at <a href="https://github.com/TanninOne/modorganizer"><span style=" text-decoration: underline; color:#007af4;">GitHub</span></a>.</p></body></html> + <html><head/><body><p>This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.</p><p>See the GNU General Public License for more details.</p><p>Source code can be found at <a href="https://github.com/Modorganizer2/modorganizer"><span style=" text-decoration: underline; color:#007af4;">GitHub</span></a>.</p></body></html> true @@ -154,7 +154,7 @@ Thanks - + Current Maintainers @@ -167,12 +167,17 @@ - LePresidente + LePresidente (Project Lead) - Silarn + AL12 + + + + + Diana @@ -182,7 +187,7 @@ - Diana + Silarn @@ -203,17 +208,22 @@ - pndrev (German) + Scythe1912 (Chinese) - DaWul (Spanish) + yc0620shen (Chinese) - Fiama (Spanish) + miraclefreak (Czech) + + + + + Cyb3r (Dutch) @@ -221,6 +231,11 @@ Alyndiar (French) + + + fruttyx (French) + + Jlkawaii (French) @@ -233,22 +248,22 @@ - Scythe1912 (Chinese) + Yoplala (French) - yc0620shen (Chinese) + Faron (German) - miraclefreak (Czech) + pndrev (German) - tokcdk (Russian) + Mordan (Greek) @@ -261,7 +276,37 @@ - ... more (Can't track) + Yoosk (Polish) + + + + + Brgodfx (Portuguese) + + + + + tokcdk (Russian) + + + + + DaWul (Spanish) + + + + + Fiama (Spanish) + + + + + Jax (Swedish) + + + + + ...and all other Transifex contributors! @@ -275,7 +320,7 @@ - Other Supporter + Other Supporters && Contributors @@ -283,9 +328,9 @@ QAbstractItemView::NoSelection - + - Al12 (Discord) + AnyOldName3 @@ -308,6 +353,11 @@ Bridger + + + Brixified + + GamerPoet @@ -325,27 +375,37 @@ - Uhuru + ogrotten - Wolverine2710 + Schilduin - z929669 + SuperSandro2000 - thosrtanner + Uhuru - ogrotten + Wolverine2710 + + + + + z929669 + + + + + thosrtanner diff --git a/src/categories.cpp b/src/categories.cpp index d8cd49fbb..4d89eff97 100644 --- a/src/categories.cpp +++ b/src/categories.cpp @@ -167,7 +167,7 @@ void CategoryFactory::saveCategories() } -unsigned int CategoryFactory::countCategories(std::tr1::function filter) +unsigned int CategoryFactory::countCategories(std::function filter) { unsigned int result = 0; for (const Category &cat : m_Categories) { diff --git a/src/categories.h b/src/categories.h index 66299c307..474a14400 100644 --- a/src/categories.h +++ b/src/categories.h @@ -99,7 +99,7 @@ class CategoryFactory { * @param filter the filter to test * @return number of matching categories */ - unsigned int countCategories(std::tr1::function filter); + unsigned int countCategories(std::function filter); /** * @brief get the id of the parent category diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index a4fde093a..569a7d319 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -460,7 +460,29 @@ void DownloadManager::addNXMDownload(const QString &url) return; } + for (auto pair : m_PendingDownloads) { + if (pair.first == nxmInfo.modId() && pair.second == nxmInfo.fileId()) { + qDebug("download requested is already started (mod id: %s, file id: %s)", qPrintable(QString(nxmInfo.modId())), qPrintable(QString(nxmInfo.fileId()))); + QMessageBox::information(nullptr, tr("Already Started"), tr("A download for this mod file has already been queued."), QMessageBox::Ok); + return; + } + } + + for (DownloadInfo *download : m_ActiveDownloads) { + if (download->m_FileInfo->modID == nxmInfo.modId() && download->m_FileInfo->fileID == nxmInfo.fileId()) { + if (download->m_State == STATE_DOWNLOADING || download->m_State == STATE_PAUSED || download->m_State == STATE_STARTED) { + qDebug("download requested is already started (mod: %s, file: %s)", qPrintable(download->m_FileInfo->modName), + qPrintable(download->m_FileInfo->fileName)); + + QMessageBox::information(nullptr, tr("Already Started"), tr("There is already a download started for this file (%2).") + .arg(download->m_FileInfo->modName).arg(download->m_FileInfo->name), QMessageBox::Ok); + return; + } + } + } + emit aboutToUpdate(); + m_PendingDownloads.append(std::make_pair(nxmInfo.modId(), nxmInfo.fileId())); emit update(-1); @@ -558,36 +580,20 @@ void DownloadManager::removeDownload(int index, bool deleteFile) emit aboutToUpdate(); if (index < 0) { - if (index == -1) { - DownloadState minState = STATE_READY; + DownloadState minState = index == -1 ? STATE_READY : STATE_INSTALLED; index = 0; for (QVector::iterator iter = m_ActiveDownloads.begin(); iter != m_ActiveDownloads.end();) { if ((*iter)->m_State >= minState) { removeFile(index, deleteFile); delete *iter; iter = m_ActiveDownloads.erase(iter); + QCoreApplication::processEvents(); } else { ++iter; ++index; } } - } - else { - DownloadState minState = STATE_INSTALLED; - index = 0; - for (QVector::iterator iter = m_ActiveDownloads.begin(); iter != m_ActiveDownloads.end();) { - if ((*iter)->m_State == minState) { - removeFile(index, deleteFile); - delete *iter; - iter = m_ActiveDownloads.erase(iter); - } - else { - ++iter; - ++index; - } - } - } } else { if (index >= m_ActiveDownloads.size()) { reportError(tr("remove: invalid download index %1").arg(index)); @@ -896,6 +902,26 @@ void DownloadManager::markInstalled(int index) setState(m_ActiveDownloads.at(index), STATE_INSTALLED); } +void DownloadManager::markInstalled(QString fileName) +{ + int index = indexByName(fileName); + if (index >= 0) { + markInstalled(index); + } else { + DownloadInfo *info = getDownloadInfo(fileName); + if (info != nullptr) { + QSettings metaFile(info->m_Output.fileName() + ".meta", QSettings::IniFormat); + metaFile.setValue("installed", true); + metaFile.setValue("uninstalled", false); + } + delete info; + } +} + +DownloadManager::DownloadInfo* DownloadManager::getDownloadInfo(QString fileName) +{ + return DownloadInfo::createFromMeta(fileName, true); +} void DownloadManager::markUninstalled(int index) { @@ -911,6 +937,23 @@ void DownloadManager::markUninstalled(int index) } +void DownloadManager::markUninstalled(QString fileName) +{ + int index = indexByName(fileName); + if (index >= 0) { + markUninstalled(index); + } else { + QString filePath = QDir::fromNativeSeparators(m_OutputDirectory) + "/" + fileName; + DownloadInfo *info = getDownloadInfo(filePath); + if (info != nullptr) { + QSettings metaFile(info->m_Output.fileName() + ".meta", QSettings::IniFormat); + metaFile.setValue("uninstalled", true); + } + delete info; + } +} + + QString DownloadManager::getDownloadFileName(const QString &baseName) const { QString fullPath = m_OutputDirectory + "/" + baseName; diff --git a/src/downloadmanager.h b/src/downloadmanager.h index e63a01138..a6d3b20ce 100644 --- a/src/downloadmanager.h +++ b/src/downloadmanager.h @@ -307,6 +307,8 @@ class DownloadManager : public MOBase::IDownloadManager */ void markInstalled(int index); + void markInstalled(QString download); + /** * @brief mark a download as uninstalled * @@ -314,6 +316,8 @@ class DownloadManager : public MOBase::IDownloadManager */ void markUninstalled(int index); + void markUninstalled(QString download); + /** * @brief refreshes the list of downloads */ @@ -431,6 +435,7 @@ private slots: private: void createMetaFile(DownloadInfo *info); + DownloadManager::DownloadInfo* getDownloadInfo(QString fileName); public: diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index d12ec119b..43ae152b3 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -105,7 +105,7 @@ QString InstanceManager::manageInstances(const QStringList &instanceList) const nullptr); for (const QString &instance : instanceList) { - selection.addChoice(instance, "", instance); + selection.addChoice(QIcon(":/MO/gui/multiply_red"), instance, "", instance); } if (selection.exec() == QDialog::Rejected) { @@ -120,7 +120,7 @@ QString InstanceManager::manageInstances(const QStringList &instanceList) const if (!deleteLocalInstance(choice)) { QMessageBox::warning(nullptr, QObject::tr("Failed to delete Instance"), - QObject::tr("Could not delete Instance \"%1\"").arg(choice), QMessageBox::Ok); + QObject::tr("Could not delete Instance \"%1\". \nIf the folder was still in use, restart MO and try again.").arg(choice), QMessageBox::Ok); } } } diff --git a/src/main.cpp b/src/main.cpp index 935b7404c..fc332fe71 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -402,6 +402,8 @@ void setupPath() qDebug("MO at: %s", qPrintable(QDir::toNativeSeparators( QCoreApplication::applicationDirPath()))); + QCoreApplication::setLibraryPaths(QStringList(QCoreApplication::applicationDirPath() + "/dlls") + QCoreApplication::libraryPaths()); + boost::scoped_array oldPath(new TCHAR[BUFSIZE]); DWORD offset = ::GetEnvironmentVariable(TEXT("PATH"), oldPath.get(), BUFSIZE); if (offset > BUFSIZE) { @@ -409,12 +411,10 @@ void setupPath() ::GetEnvironmentVariable(TEXT("PATH"), oldPath.get(), offset); } - std::wstring newPath(oldPath.get()); + std::wstring newPath(ToWString(QDir::toNativeSeparators( + QCoreApplication::applicationDirPath())) + L"\\dlls"); newPath += L";"; - newPath += ToWString(QDir::toNativeSeparators( - QCoreApplication::applicationDirPath())) - .c_str(); - newPath += L"\\dlls"; + newPath += oldPath.get(); ::SetEnvironmentVariableW(L"PATH", newPath.c_str()); } @@ -561,36 +561,42 @@ int runApplication(MOApplication &application, SingleInstance &instance, // if we have a command line parameter, it is either a nxm link or // a binary to start - if (arguments.size() > 1) { - if (MOShortcut shortcut{ arguments.at(1) }) { - try { - organizer.runShortcut(shortcut); - return 0; - } catch (const std::exception &e) { - reportError( - QObject::tr("failed to start shortcut: %1").arg(e.what())); - return 1; - } - } else if (OrganizerCore::isNxmLink(arguments.at(1))) { - qDebug("starting download from command line: %s", - qPrintable(arguments.at(1))); - organizer.externalMessage(arguments.at(1)); - } else { - QString exeName = arguments.at(1); - qDebug("starting %s from command line", qPrintable(exeName)); - arguments.removeFirst(); // remove application name (ModOrganizer.exe) - arguments.removeFirst(); // remove binary name - // pass the remaining parameters to the binary - try { - organizer.startApplication(exeName, arguments, QString(), QString()); - return 0; - } catch (const std::exception &e) { - reportError( - QObject::tr("failed to start application: %1").arg(e.what())); - return 1; - } - } - } + if (arguments.size() > 1) { + if (MOShortcut shortcut{ arguments.at(1) }) { + if (shortcut.hasExecutable()) { + try { + organizer.runShortcut(shortcut); + return 0; + } + catch (const std::exception &e) { + reportError( + QObject::tr("failed to start shortcut: %1").arg(e.what())); + return 1; + } + } + } + else if (OrganizerCore::isNxmLink(arguments.at(1))) { + qDebug("starting download from command line: %s", + qPrintable(arguments.at(1))); + organizer.externalMessage(arguments.at(1)); + } + else { + QString exeName = arguments.at(1); + qDebug("starting %s from command line", qPrintable(exeName)); + arguments.removeFirst(); // remove application name (ModOrganizer.exe) + arguments.removeFirst(); // remove binary name + // pass the remaining parameters to the binary + try { + organizer.startApplication(exeName, arguments, QString(), QString()); + return 0; + } + catch (const std::exception &e) { + reportError( + QObject::tr("failed to start application: %1").arg(e.what())); + return 1; + } + } + } QPixmap pixmap(splashPath); QSplashScreen splash(pixmap); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index fe93b5467..abcb53125 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -89,6 +89,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include #include #include #include @@ -153,8 +154,6 @@ along with Mod Organizer. If not, see . #include #include -#include - #ifndef Q_MOC_RUN #include #include @@ -901,6 +900,8 @@ void MainWindow::cleanup() QWebEngineProfile::defaultProfile()->clearAllVisitedLinks(); m_IntegratedBrowser.close(); + m_SaveMetaTimer.stop(); + m_MetaSave.waitForFinished(); } @@ -1568,9 +1569,13 @@ void MainWindow::checkBSAList() void MainWindow::saveModMetas() { - for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(i); - modInfo->saveMeta(); + if (m_MetaSave.isFinished()) { + m_MetaSave = QtConcurrent::run([this]() { + for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(i); + modInfo->saveMeta(); + } + }); } } @@ -1886,7 +1891,7 @@ void MainWindow::wikiTriggered() void MainWindow::issueTriggered() { - ::ShellExecuteW(nullptr, L"open", L"http://github.com/LePresidente/modorganizer/issues", nullptr, nullptr, SW_SHOWNORMAL); + ::ShellExecuteW(nullptr, L"open", L"http://github.com/Modorganizer2/modorganizer/issues", nullptr, nullptr, SW_SHOWNORMAL); } void MainWindow::tutorialTriggered() @@ -2307,10 +2312,7 @@ void MainWindow::removeMod_clicked() void MainWindow::modRemoved(const QString &fileName) { if (!fileName.isEmpty() && !QFileInfo(fileName).isAbsolute()) { - int index = m_OrganizerCore.downloadManager()->indexByName(fileName); - if (index >= 0) { - m_OrganizerCore.downloadManager()->markUninstalled(index); - } + m_OrganizerCore.downloadManager()->markUninstalled(fileName); } } @@ -2450,7 +2452,7 @@ void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, } } else { modInfo->saveMeta(); - ModInfoDialog dialog(modInfo, m_OrganizerCore.directoryStructure(), modInfo->hasFlag(ModInfo::FLAG_FOREIGN), this); + ModInfoDialog dialog(modInfo, m_OrganizerCore.directoryStructure(), modInfo->hasFlag(ModInfo::FLAG_FOREIGN), &m_OrganizerCore, &m_PluginContainer,this); connect(&dialog, SIGNAL(nexusLinkActivated(QString)), this, SLOT(nexusLinkActivated(QString))); connect(&dialog, SIGNAL(downloadRequest(QString)), &m_OrganizerCore, SLOT(downloadRequestedNXM(QString))); connect(&dialog, SIGNAL(modOpen(QString, int)), this, SLOT(displayModInformation(QString, int)), Qt::QueuedConnection); @@ -3035,6 +3037,12 @@ void MainWindow::openInstanceFolder() ::ShellExecuteW(nullptr, L"explore", ToWString(m_OrganizerCore.settings().getBaseDirectory()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); } +void MainWindow::openLogsFolder() +{ + QString logsPath = qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::logPath()); + ::ShellExecuteW(nullptr, L"explore", ToWString(logsPath).c_str(), nullptr, nullptr, SW_SHOWNORMAL); +} + void MainWindow::openInstallFolder() { ::ShellExecuteW(nullptr, L"explore", ToWString(qApp->applicationDirPath()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); @@ -3073,6 +3081,10 @@ void MainWindow::exportModListCSV() QGridLayout *grid = new QGridLayout; selection.setWindowTitle(tr("Export to csv")); + QLabel *csvDescription = new QLabel(); + csvDescription->setText(tr("CSV (Comma Separated Values) is a format that can be imported in programs like Excel to create a spreadsheet.\nYou can also use online editors and converters instead.")); + grid->addWidget(csvDescription); + QGroupBox *groupBoxRows = new QGroupBox(tr("Select what mods you want export:")); QRadioButton *all = new QRadioButton(tr("All installed mods")); QRadioButton *active = new QRadioButton(tr("Only active (checked) mods from your current profile")); @@ -3195,7 +3207,7 @@ void MainWindow::exportModListCSV() if (nexus_ID->isChecked()) builder.setRowField("#Nexus_ID", info->getNexusID()); if (mod_Nexus_URL->isChecked()) - builder.setRowField("#Mod_Nexus_URL", info->getURL()); + builder.setRowField("#Mod_Nexus_URL",(info->getNexusID()>0)? NexusInterface::instance()->getModURL(info->getNexusID()) : ""); if (mod_Version->isChecked()) builder.setRowField("#Mod_Version", info->getVersion().canonicalString()); if (install_Date->isChecked()) @@ -3227,10 +3239,15 @@ static void addMenuAsPushButton(QMenu *menu, QMenu *subMenu) } QMenu *MainWindow::openFolderMenu() -{ +{ QMenu *FolderMenu = new QMenu(this); + FolderMenu->addAction(tr("Open Game folder"), this, SLOT(openGameFolder())); + + FolderMenu->addAction(tr("Open MyGames folder"), this, SLOT(openMyGamesFolder())); + + FolderMenu->addSeparator(); FolderMenu->addAction(tr("Open Instance folder"), this, SLOT(openInstanceFolder())); @@ -3238,13 +3255,19 @@ QMenu *MainWindow::openFolderMenu() FolderMenu->addAction(tr("Open Downloads folder"), this, SLOT(openDownloadsFolder())); - FolderMenu->addAction(tr("Open MO Install folder"), this, SLOT(openInstallFolder())); - FolderMenu->addSeparator(); - FolderMenu->addAction(tr("Open Game folder"), this, SLOT(openGameFolder())); + FolderMenu->addAction(tr("Open MO2 Install folder"), this, SLOT(openInstallFolder())); + + FolderMenu->addAction(tr("Open MO2 Logs folder"), this, SLOT(openLogsFolder())); + + + + + + + - FolderMenu->addAction(tr("Open MyGames folder"), this, SLOT(openMyGamesFolder())); return FolderMenu; @@ -3304,15 +3327,18 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) } else if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_FOREIGN) != flags.end()) { // nop, nothing to do with this mod } else { - QMenu *addRemoveCategoriesMenu = new QMenu(tr("Add/Remove Categories")); + QMenu *addRemoveCategoriesMenu = new QMenu(tr("Change Categories")); populateMenuCategories(addRemoveCategoriesMenu, 0); connect(addRemoveCategoriesMenu, SIGNAL(aboutToHide()), this, SLOT(addRemoveCategories_MenuHandler())); addMenuAsPushButton(menu, addRemoveCategoriesMenu); + //Removed as it was redundant, just making the categories look more complicated. + /* QMenu *replaceCategoriesMenu = new QMenu(tr("Replace Categories")); populateMenuCategories(replaceCategoriesMenu, 0); connect(replaceCategoriesMenu, SIGNAL(aboutToHide()), this, SLOT(replaceCategories_MenuHandler())); addMenuAsPushButton(menu, replaceCategoriesMenu); + */ QMenu *primaryCategoryMenu = new QMenu(tr("Primary Category")); connect(primaryCategoryMenu, SIGNAL(aboutToShow()), this, SLOT(addPrimaryCategoryCandidates())); @@ -3332,8 +3358,8 @@ void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) menu->addSeparator(); menu->addAction(tr("Rename Mod..."), this, SLOT(renameMod_clicked())); - menu->addAction(tr("Remove Mod..."), this, SLOT(removeMod_clicked())); menu->addAction(tr("Reinstall Mod"), this, SLOT(reinstallMod_clicked())); + menu->addAction(tr("Remove Mod..."), this, SLOT(removeMod_clicked())); menu->addSeparator(); @@ -3909,10 +3935,24 @@ void MainWindow::previewDataFile() // what we have is an absolute path to the file in its actual location (for the primary origin) // what we want is the path relative to the virtual data directory - // crude: we search for the next slash after the base mod directory to skip everything up to the data-relative directory - int offset = m_OrganizerCore.settings().getModDirectory().size() + 1; - offset = fileName.indexOf("/", offset); - fileName = fileName.mid(offset + 1); + // we need to look in the virtual directory for the file to make sure the info is up to date. + + // check if the file comes from the actual data folder instead of a mod + QDir gameDirectory = m_OrganizerCore.managedGame()->dataDirectory().absolutePath(); + QString relativePath = gameDirectory.relativeFilePath(fileName); + QDir dirRelativePath = gameDirectory.relativeFilePath(fileName); + // if the file is on a different drive the dirRelativePath will actually be an absolute path so we make sure that is not the case + if (!dirRelativePath.isAbsolute() && !relativePath.startsWith("..")) { + fileName = relativePath; + } + else { + // crude: we search for the next slash after the base mod directory to skip everything up to the data-relative directory + int offset = m_OrganizerCore.settings().getModDirectory().size() + 1; + offset = fileName.indexOf("/", offset); + fileName = fileName.mid(offset + 1); + } + + const FileEntry::Ptr file = m_OrganizerCore.directoryStructure()->searchFile(ToWString(fileName), nullptr); @@ -4250,27 +4290,40 @@ bool MainWindow::extractProgress(QProgressDialog &progress, int percentage, std: void MainWindow::extractBSATriggered() { QTreeWidgetItem *item = m_ContextItem; + QString origin; QString targetFolder = FileDialogMemory::getExistingDirectory("extractBSA", this, tr("Extract BSA")); + QStringList archives = {}; if (!targetFolder.isEmpty()) { - BSA::Archive archive; - QString originPath = QDir::fromNativeSeparators(ToQString(m_OrganizerCore.directoryStructure()->getOriginByName(ToWString(item->text(1))).getPath())); - QString archivePath = QString("%1\\%2").arg(originPath).arg(item->text(0)); - - BSA::EErrorCode result = archive.read(archivePath.toLocal8Bit().constData(), true); - if ((result != BSA::ERROR_NONE) && (result != BSA::ERROR_INVALIDHASHES)) { - reportError(tr("failed to read %1: %2").arg(archivePath).arg(result)); - return; + if (!item->parent()) { + for (int i = 0; i < item->childCount(); ++i) { + archives.append(item->child(i)->text(0)); + } + origin = QDir::fromNativeSeparators(ToQString(m_OrganizerCore.directoryStructure()->getOriginByName(ToWString(item->text(0))).getPath())); + } else { + origin = QDir::fromNativeSeparators(ToQString(m_OrganizerCore.directoryStructure()->getOriginByName(ToWString(item->text(1))).getPath())); + archives = QStringList({ item->text(0) }); } - QProgressDialog progress(this); - progress.setMaximum(100); - progress.setValue(0); - progress.show(); - archive.extractAll(QDir::toNativeSeparators(targetFolder).toLocal8Bit().constData(), - boost::bind(&MainWindow::extractProgress, this, boost::ref(progress), _1, _2)); - if (result == BSA::ERROR_INVALIDHASHES) { - reportError(tr("This archive contains invalid hashes. Some files may be broken.")); + for (auto archiveName : archives) { + BSA::Archive archive; + QString archivePath = QString("%1\\%2").arg(origin).arg(archiveName); + BSA::EErrorCode result = archive.read(archivePath.toLocal8Bit().constData(), true); + if ((result != BSA::ERROR_NONE) && (result != BSA::ERROR_INVALIDHASHES)) { + reportError(tr("failed to read %1: %2").arg(archivePath).arg(result)); + return; + } + + QProgressDialog progress(this); + progress.setMaximum(100); + progress.setValue(0); + progress.show(); + archive.extractAll(QDir::toNativeSeparators(targetFolder).toLocal8Bit().constData(), + boost::bind(&MainWindow::extractProgress, this, boost::ref(progress), _1, _2)); + if (result == BSA::ERROR_INVALIDHASHES) { + reportError(tr("This archive contains invalid hashes. Some files may be broken.")); + } + archive.close(); } } } @@ -4307,6 +4360,18 @@ void MainWindow::displayColumnSelection(const QPoint &pos) } } +void MainWindow::on_bsaList_customContextMenuRequested(const QPoint &pos) +{ + m_ContextItem = ui->bsaList->itemAt(pos); + +// m_ContextRow = ui->bsaList->indexOfTopLevelItem(ui->bsaList->itemAt(pos)); + + QMenu menu; + menu.addAction(tr("Extract..."), this, SLOT(extractBSATriggered())); + + menu.exec(ui->bsaList->mapToGlobal(pos)); +} + void MainWindow::on_bsaList_itemChanged(QTreeWidgetItem*, int) { m_ArchiveListWriter.write(); @@ -4576,8 +4641,8 @@ void MainWindow::processLOOTOut(const std::string &lootOut, std::string &errorMe std::vector lines; boost::split(lines, lootOut, boost::is_any_of("\r\n")); - std::tr1::regex exRequires("\"([^\"]*)\" requires \"([^\"]*)\", but it is missing\\."); - std::tr1::regex exIncompatible("\"([^\"]*)\" is incompatible with \"([^\"]*)\", but both are present\\."); + std::regex exRequires("\"([^\"]*)\" requires \"([^\"]*)\", but it is missing\\."); + std::regex exIncompatible("\"([^\"]*)\" is incompatible with \"([^\"]*)\", but both are present\\."); for (const std::string &line : lines) { if (line.length() > 0) { @@ -4589,12 +4654,12 @@ void MainWindow::processLOOTOut(const std::string &lootOut, std::string &errorMe qWarning("%s", line.c_str()); errorMessages.append(boost::algorithm::trim_copy(line.substr(erroridx + 8)) + "\n"); } else { - std::tr1::smatch match; - if (std::tr1::regex_match(line, match, exRequires)) { + std::smatch match; + if (std::regex_match(line, match, exRequires)) { std::string modName(match[1].first, match[1].second); std::string dependency(match[2].first, match[2].second); m_OrganizerCore.pluginList()->addInformation(modName.c_str(), tr("depends on missing \"%1\"").arg(dependency.c_str())); - } else if (std::tr1::regex_match(line, match, exIncompatible)) { + } else if (std::regex_match(line, match, exIncompatible)) { std::string modName(match[1].first, match[1].second); std::string dependency(match[2].first, match[2].second); m_OrganizerCore.pluginList()->addInformation(modName.c_str(), tr("incompatible with \"%1\"").arg(dependency.c_str())); @@ -4628,12 +4693,12 @@ void MainWindow::on_bossButton_clicked() dialog.show(); QString outPath = QDir::temp().absoluteFilePath("lootreport.json"); - + QStringList parameters; parameters << "--game" << m_OrganizerCore.managedGame()->gameShortName() - << "--gamePath" << QString("\"%1\"").arg(m_OrganizerCore.managedGame()->gameDirectory().absolutePath()) - << "--pluginListPath" << QString("\"%1/loadorder.txt\"").arg(m_OrganizerCore.profilePath()) - << "--out" << QString("\"%1\"").arg(outPath); + << "--gamePath" << QString("\"%1\"").arg(m_OrganizerCore.managedGame()->gameDirectory().absolutePath()) + << "--pluginListPath" << QString("\"%1/loadorder.txt\"").arg(m_OrganizerCore.profilePath()) + << "--out" << QString("\"%1\"").arg(outPath); if (m_DidUpdateMasterList) { parameters << "--skipUpdateMasterlist"; @@ -4753,7 +4818,7 @@ void MainWindow::on_bossButton_clicked() if (success) { m_DidUpdateMasterList = true; - /*if (reportURL.length() > 0) { + if (reportURL.length() > 0) { m_IntegratedBrowser.setWindowTitle("LOOT Report"); QString report(reportURL.c_str()); QStringList temp = report.split("?"); @@ -4762,15 +4827,8 @@ void MainWindow::on_bossButton_clicked() url.setQuery(temp.at(1).toUtf8()); } m_IntegratedBrowser.openUrl(url); - }*/ - - // if the game specifies load order by file time, our own load order file needs to be removed because it's outdated. - // refreshESPList will then use the file time as the load order. - if (m_OrganizerCore.managedGame()->loadOrderMechanism() == IPluginGame::LoadOrderMechanism::FileTime) { - qDebug("removing loadorder.txt"); - QFile::remove(m_OrganizerCore.currentProfile()->getLoadOrderFileName()); } - m_OrganizerCore.refreshESPList(); + m_OrganizerCore.refreshESPList(false); m_OrganizerCore.savePluginList(); } } @@ -4847,7 +4905,7 @@ void MainWindow::on_restoreButton_clicked() QMessageBox::critical(this, tr("Restore failed"), tr("Failed to restore the backup. Errorcode: %1").arg(windowsErrorString(::GetLastError()))); } - m_OrganizerCore.refreshESPList(); + m_OrganizerCore.refreshESPList(true); } } @@ -5045,4 +5103,3 @@ void MainWindow::on_clearFiltersButton_clicked() { deselectFilters(); } - diff --git a/src/mainwindow.h b/src/mainwindow.h index 2c2e27237..ef6eb262b 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -63,6 +63,7 @@ namespace MOShared { class DirectoryEntry; } #include #include #include +#include class QAction; class QAbstractItemModel; @@ -336,6 +337,8 @@ private slots: QTimer m_SaveMetaTimer; QTimer m_UpdateProblemsTimer; + QFuture m_MetaSave; + QTime m_StartTime; //SaveGameInfoWidget *m_CurrentSaveView; MOBase::ISaveGameInfoWidget *m_CurrentSaveView; @@ -487,6 +490,7 @@ private slots: void disableVisibleMods(); void exportModListCSV(); void openInstanceFolder(); + void openLogsFolder(); void openInstallFolder(); void openDownloadsFolder(); void openProfileFolder(); @@ -552,6 +556,7 @@ private slots: // ui slots void on_actionUpdate_triggered(); void on_actionEndorseMO_triggered(); + void on_bsaList_customContextMenuRequested(const QPoint &pos); void on_clearFiltersButton_clicked(); void on_btnRefreshData_clicked(); void on_categoriesList_customContextMenuRequested(const QPoint &pos); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 31522c03d..e0aa6f36e 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -194,7 +194,7 @@ - + Pick a module collection @@ -253,7 +253,7 @@ p, li { white-space: pre-wrap; } - Quickly open in Explorer relevant Folders... + Show Open Folders menu... @@ -463,7 +463,7 @@ p, li { white-space: pre-wrap; } - + @@ -507,15 +507,25 @@ p, li { white-space: pre-wrap; } true - - - + + + + Qt::Horizontal + + + + 40 + 20 + + + + - + 0 0 @@ -523,9 +533,15 @@ p, li { white-space: pre-wrap; } 0 - 25 + 22 + + + 95 + 0 + + false @@ -551,7 +567,26 @@ p, li { white-space: pre-wrap; } - + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 220 + 0 + + Qt::ClickFocus @@ -573,7 +608,13 @@ p, li { white-space: pre-wrap; } - + + + + 220 + 0 + + Namefilter diff --git a/src/modinfo.cpp b/src/modinfo.cpp index c14eedb72..d80c636b4 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -55,7 +55,7 @@ QMutex ModInfo::s_Mutex(QMutex::Recursive); QString ModInfo::s_HiddenExt(".mohidden"); -static bool ByName(const ModInfo::Ptr &LHS, const ModInfo::Ptr &RHS) +bool ModInfo::ByName(const ModInfo::Ptr &LHS, const ModInfo::Ptr &RHS) { return QString::compare(LHS->name(), RHS->name(), Qt::CaseInsensitive) < 0; } @@ -98,7 +98,7 @@ QString ModInfo::getContentTypeName(int contentType) case CONTENT_INTERFACE: return tr("UI Changes"); case CONTENT_SOUND: return tr("Sound Effects"); case CONTENT_SCRIPT: return tr("Scripts"); - case CONTENT_SKSE: return tr("SKSE Plugins"); + case CONTENT_SKSE: return tr("Script Extender"); case CONTENT_SKYPROC: return tr("SkyProc Tools"); case CONTENT_MCM: return tr("MCM Data"); default: throw MyException(tr("invalid content type %1").arg(contentType)); @@ -239,7 +239,7 @@ void ModInfo::updateFromDisc(const QString &modDirectory, createFromOverwrite(); - std::sort(s_Collection.begin(), s_Collection.end(), ByName); + std::sort(s_Collection.begin(), s_Collection.end(), ModInfo::ByName); updateIndices(); } diff --git a/src/modinfo.h b/src/modinfo.h index d00c04e79..1ceb8e1f5 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -599,6 +599,7 @@ class ModInfo : public QObject, public MOBase::IModInterface ModInfo(); static void updateIndices(); + static bool ByName(const ModInfo::Ptr &LHS, const ModInfo::Ptr &RHS); private: diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index ccd2a122c..6a53d9158 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -20,6 +20,7 @@ along with Mod Organizer. If not, see . #include "modinfodialog.h" #include "ui_modinfodialog.h" #include "descriptionpage.h" +#include "mainwindow.h" #include "iplugingame.h" #include "nexusinterface.h" @@ -30,6 +31,10 @@ along with Mod Organizer. If not, see . #include "questionboxmemory.h" #include "settings.h" #include "categories.h" +#include "organizercore.h" +#include "pluginlistsortproxy.h" +#include "previewgenerator.h" +#include "previewdialog.h" #include #include @@ -40,6 +45,7 @@ along with Mod Organizer. If not, see . #include #include #include +#include #include @@ -66,11 +72,12 @@ static bool operator<(const ModFileListWidget &LHS, const ModFileListWidget &RHS } -ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directory, bool unmanaged, QWidget *parent) +ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directory, bool unmanaged, OrganizerCore *organizerCore, PluginContainer *pluginContainer, QWidget *parent) : TutorableDialog("ModInfoDialog", parent), ui(new Ui::ModInfoDialog), m_ModInfo(modInfo), m_ThumbnailMapper(this), m_RequestStarted(false), m_DeleteAction(nullptr), m_RenameAction(nullptr), m_OpenAction(nullptr), - m_Directory(directory), m_Origin(nullptr) + m_Directory(directory), m_Origin(nullptr), + m_OrganizerCore(organizerCore), m_PluginContainer(pluginContainer) { ui->setupUi(this); this->setWindowTitle(modInfo->name()); @@ -293,7 +300,9 @@ void ModInfoDialog::refreshLists() QStringList fields(relativeName); fields.append(ToQString(realOrigin.getName())); QTreeWidgetItem *item = new QTreeWidgetItem(fields); + item->setData(0, Qt::UserRole, fileName); item->setData(1, Qt::UserRole, ToQString(realOrigin.getName())); + item->setData(1, Qt::UserRole + 2, archive); ui->overwrittenTree->addTopLevelItem(item); ++numOverwritten; } @@ -1197,6 +1206,151 @@ void ModInfoDialog::unhideConflictFile() } } +int ModInfoDialog::getBinaryExecuteInfo(const QFileInfo &targetInfo, QFileInfo &binaryInfo, QString &arguments) +{ + QString extension = targetInfo.suffix(); + if ((extension.compare("cmd", Qt::CaseInsensitive) == 0) || + (extension.compare("com", Qt::CaseInsensitive) == 0) || + (extension.compare("bat", Qt::CaseInsensitive) == 0)) { + binaryInfo = QFileInfo("C:\\Windows\\System32\\cmd.exe"); + arguments = QString("/C \"%1\"").arg(QDir::toNativeSeparators(targetInfo.absoluteFilePath())); + return 1; + } + else if (extension.compare("exe", Qt::CaseInsensitive) == 0) { + binaryInfo = targetInfo; + return 1; + } + else if (extension.compare("jar", Qt::CaseInsensitive) == 0) { + // types that need to be injected into + std::wstring targetPathW = ToWString(targetInfo.absoluteFilePath()); + QString binaryPath; + + { // try to find java automatically + WCHAR buffer[MAX_PATH]; + if (::FindExecutableW(targetPathW.c_str(), nullptr, buffer) > (HINSTANCE)32) { + DWORD binaryType = 0UL; + if (!::GetBinaryTypeW(buffer, &binaryType)) { + qDebug("failed to determine binary type of \"%ls\": %lu", buffer, ::GetLastError()); + } + else if (binaryType == SCS_32BIT_BINARY) { + binaryPath = ToQString(buffer); + } + } + } + if (binaryPath.isEmpty() && (extension == "jar")) { + // second attempt: look to the registry + QSettings javaReg("HKEY_LOCAL_MACHINE\\Software\\JavaSoft\\Java Runtime Environment", QSettings::NativeFormat); + if (javaReg.contains("CurrentVersion")) { + QString currentVersion = javaReg.value("CurrentVersion").toString(); + binaryPath = javaReg.value(QString("%1/JavaHome").arg(currentVersion)).toString().append("\\bin\\javaw.exe"); + } + } + if (binaryPath.isEmpty()) { + binaryPath = QFileDialog::getOpenFileName(this, tr("Select binary"), QString(), tr("Binary") + " (*.exe)"); + } + if (binaryPath.isEmpty()) { + return 0; + } + binaryInfo = QFileInfo(binaryPath); + if (extension == "jar") { + arguments = QString("-jar \"%1\"").arg(QDir::toNativeSeparators(targetInfo.absoluteFilePath())); + } + else { + arguments = QString("\"%1\"").arg(QDir::toNativeSeparators(targetInfo.absoluteFilePath())); + } + return 1; + } + else { + return 2; + } +} + +void ModInfoDialog::openDataFile() +{ + if (m_ConflictsContextItem != nullptr) { + QFileInfo targetInfo(m_ConflictsContextItem->data(0, Qt::UserRole).toString()); + QFileInfo binaryInfo; + QString arguments; + switch (getBinaryExecuteInfo(targetInfo, binaryInfo, arguments)) { + case 1: { + m_OrganizerCore->spawnBinaryDirect( + binaryInfo, arguments, m_OrganizerCore->currentProfile()->name(), + targetInfo.absolutePath(), "", ""); + } break; + case 2: { + ::ShellExecuteW(nullptr, L"open", + ToWString(targetInfo.absoluteFilePath()).c_str(), + nullptr, nullptr, SW_SHOWNORMAL); + } break; + default: { + // nop + } break; + } + } +} + +void ModInfoDialog::previewDataFile() +{ + QString fileName = QDir::fromNativeSeparators(m_ConflictsContextItem->data(0, Qt::UserRole).toString()); + + // what we have is an absolute path to the file in its actual location (for the primary origin) + // what we want is the path relative to the virtual data directory + + // we need to look in the virtual directory for the file to make sure the info is up to date. + + // check if the file comes from the actual data folder instead of a mod + QDir gameDirectory = m_OrganizerCore->managedGame()->dataDirectory().absolutePath(); + QString relativePath = gameDirectory.relativeFilePath(fileName); + QDir direRelativePath = gameDirectory.relativeFilePath(fileName); + // if the file is on a different drive the dirRelativePath will actually be an absolute path so we make sure that is not the case + if (!direRelativePath.isAbsolute() && !relativePath.startsWith("..")) { + fileName = relativePath; + } + else { + // crude: we search for the next slash after the base mod directory to skip everything up to the data-relative directory + int offset = m_OrganizerCore->settings().getModDirectory().size() + 1; + offset = fileName.indexOf("/", offset); + fileName = fileName.mid(offset + 1); + } + + + + const FileEntry::Ptr file = m_OrganizerCore->directoryStructure()->searchFile(ToWString(fileName), nullptr); + + if (file.get() == nullptr) { + reportError(tr("file not found: %1").arg(fileName)); + return; + } + + // set up preview dialog + PreviewDialog preview(fileName); + auto addFunc = [&](int originId) { + FilesOrigin &origin = m_OrganizerCore->directoryStructure()->getOriginByID(originId); + QString filePath = QDir::fromNativeSeparators(ToQString(origin.getPath())) + "/" + fileName; + if (QFile::exists(filePath)) { + // it's very possible the file doesn't exist, because it's inside an archive. we don't support that + QWidget *wid = m_PluginContainer->previewGenerator().genPreview(filePath); + if (wid == nullptr) { + reportError(tr("failed to generate preview for %1").arg(filePath)); + } + else { + preview.addVariant(ToQString(origin.getName()), wid); + } + } + }; + + addFunc(file->getOrigin()); + for (auto alt : file->getAlternatives()) { + addFunc(alt.first); + } + if (preview.numVariants() > 0) { + preview.exec(); + } + else { + QMessageBox::information(this, tr("Sorry"), tr("Sorry, can't preview anything. This function currently does not support extracting from bsas.")); + } +} + void ModInfoDialog::on_overwriteTree_customContextMenuRequested(const QPoint &pos) { @@ -1211,11 +1365,39 @@ void ModInfoDialog::on_overwriteTree_customContextMenuRequested(const QPoint &po } else { menu.addAction(tr("Hide"), this, SLOT(hideConflictFile())); } + + menu.addAction(tr("Open/Execute"), this, SLOT(openDataFile())); + + QString fileName = m_ConflictsContextItem->data(0, Qt::UserRole).toString(); + if (m_PluginContainer->previewGenerator().previewSupported(QFileInfo(fileName).suffix())) { + menu.addAction(tr("Preview"), this, SLOT(previewDataFile())); + } + menu.exec(ui->overwriteTree->mapToGlobal(pos)); } } } +void ModInfoDialog::on_overwrittenTree_customContextMenuRequested(const QPoint &pos) +{ + m_ConflictsContextItem = ui->overwrittenTree->itemAt(pos.x(), pos.y()); + + if (m_ConflictsContextItem != nullptr) { + if (!m_ConflictsContextItem->data(1, Qt::UserRole + 2).toBool()) { + QMenu menu; + + menu.addAction(tr("Open/Execute"), this, SLOT(openDataFile())); + + QString fileName = m_ConflictsContextItem->data(0, Qt::UserRole).toString(); + if (m_PluginContainer->previewGenerator().previewSupported(QFileInfo(fileName).suffix())) { + menu.addAction(tr("Preview"), this, SLOT(previewDataFile())); + } + + menu.exec(ui->overwrittenTree->mapToGlobal(pos)); + } + } +} + void ModInfoDialog::on_overwrittenTree_itemDoubleClicked(QTreeWidgetItem *item, int) { diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 312ff99e8..9eb17be58 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -23,6 +23,8 @@ along with Mod Organizer. If not, see . #include "modinfo.h" #include "tutorabledialog.h" +#include "plugincontainer.h" +#include "organizercore.h" #include #include @@ -76,7 +78,7 @@ class ModInfoDialog : public MOBase::TutorableDialog * @param modInfo info structure about the mod to display * @param parent parend widget **/ - explicit ModInfoDialog(ModInfo::Ptr modInfo, const MOShared::DirectoryEntry *directory, bool unmanaged, QWidget *parent = 0); + explicit ModInfoDialog(ModInfo::Ptr modInfo, const MOShared::DirectoryEntry *directory, bool unmanaged, OrganizerCore *organizerCore, PluginContainer *pluginContainer,QWidget *parent = 0); ~ModInfoDialog(); /** @@ -155,6 +157,10 @@ private slots: void hideConflictFile(); void unhideConflictFile(); + int getBinaryExecuteInfo(const QFileInfo &targetInfo, QFileInfo &binaryInfo, QString &arguments); + void previewDataFile(); + void openDataFile(); + void thumbnailClicked(const QString &fileName); void linkClicked(const QUrl &url); @@ -185,6 +191,7 @@ private slots: void on_overwriteTree_itemDoubleClicked(QTreeWidgetItem *item, int column); void on_overwrittenTree_itemDoubleClicked(QTreeWidgetItem *item, int column); void on_overwriteTree_customContextMenuRequested(const QPoint &pos); + void on_overwrittenTree_customContextMenuRequested(const QPoint &pos); void on_fileTree_customContextMenuRequested(const QPoint &pos); void on_refreshButton_clicked(); @@ -208,6 +215,9 @@ private slots: QSignalMapper m_ThumbnailMapper; QString m_RootPath; + OrganizerCore *m_OrganizerCore; + PluginContainer *m_PluginContainer; + QFileSystemModel *m_FileSystemModel; QTreeView *m_FileTree; QModelIndexList m_FileSelection; diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index 25822fd06..054154450 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -110,13 +110,19 @@ + + false + - Ini Tweaks + Ini Tweaks *This feature is non-functional* + + false + 228 @@ -483,6 +489,9 @@ Most mods do not have optional esps, so chances are good you are looking at an e + + Qt::CustomContextMenu + Qt::ElideLeft @@ -492,6 +501,9 @@ Most mods do not have optional esps, so chances are good you are looking at an e true + + 2 + 365 diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp index 5486bb2cb..4572f5bf8 100644 --- a/src/modinforegular.cpp +++ b/src/modinforegular.cpp @@ -299,7 +299,7 @@ bool ModInfoRegular::setName(const QString &name) s_ModsByName[m_Name] = index; - std::sort(s_Collection.begin(), s_Collection.end(), ByName); + std::sort(s_Collection.begin(), s_Collection.end(), ModInfo::ByName); updateIndices(); } else { // otherwise mod isn't registered yet? m_Name = name; diff --git a/src/modlist.cpp b/src/modlist.cpp index 4002a424b..7b2ad1f94 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -424,6 +424,11 @@ bool ModList::renameMod(int index, const QString &newName) return false; } + if (ModList::allMods().contains(newName,Qt::CaseInsensitive)) { + MessageDialog::showMessage(tr("Name is already in use by another mod"), nullptr); + return false; + } + ModInfo::Ptr modInfo = ModInfo::getByIndex(index); QString oldName = modInfo->name(); if (newName != oldName) { diff --git a/src/moshortcut.cpp b/src/moshortcut.cpp index c8c2ef6f3..aad7380ec 100644 --- a/src/moshortcut.cpp +++ b/src/moshortcut.cpp @@ -24,6 +24,7 @@ along with Mod Organizer. If not, see . MOShortcut::MOShortcut(const QString& link) : m_valid(link.startsWith("moshortcut://")) , m_hasInstance(false) + , m_hasExecutable(false) { if (m_valid) { int start = (int)strlen("moshortcut://"); @@ -35,5 +36,7 @@ MOShortcut::MOShortcut(const QString& link) } else m_executable = link.mid(start); + if(!(m_executable=="")) + m_hasExecutable=true; } } diff --git a/src/moshortcut.h b/src/moshortcut.h index 7b7574b97..2ce54910b 100644 --- a/src/moshortcut.h +++ b/src/moshortcut.h @@ -33,6 +33,8 @@ class MOShortcut { operator bool() const { return m_valid; } bool hasInstance() const { return m_hasInstance; } + + bool hasExecutable() const { return m_hasExecutable; } const QString& instance() const { return m_instance; } @@ -43,4 +45,5 @@ class MOShortcut { QString m_executable; bool m_valid; bool m_hasInstance; + bool m_hasExecutable; }; diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index eba02a6f4..97186e8ba 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -500,7 +500,7 @@ void NexusInterface::requestFinished(std::list::iterator iter) iter->m_URL = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toString(); iter->m_Reroute = true; m_RequestQueue.enqueue(*iter); - nextRequest(); + //nextRequest(); return; } QByteArray data = reply->readAll(); diff --git a/src/organizercore.cpp b/src/organizercore.cpp index b84488daf..999c77328 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -602,8 +602,9 @@ void OrganizerCore::downloadRequestedNXM(const QString &url) void OrganizerCore::externalMessage(const QString &message) { - if (MOShortcut moshortcut{ message }) { - runShortcut(moshortcut); + if (MOShortcut moshortcut{ message } ) { + if(moshortcut.hasExecutable()) + runShortcut(moshortcut); } else if (isNxmLink(message)) { MessageDialog::showMessage(tr("Download started"), qApp->activeWindow()); @@ -894,6 +895,8 @@ MOBase::IModInterface *OrganizerCore::installMod(const QString &fileName, ModInfoDialog::TAB_INIFILES); } m_ModInstalled(modName); + m_DownloadManager.markInstalled(fileName); + emit modInstalled(modName); return modInfo.data(); } else { reportError(tr("mod \"%1\" not found").arg(modName)); @@ -1106,7 +1109,7 @@ void OrganizerCore::spawnBinary(const QFileInfo &binary, const QString &argument } refreshDirectoryStructure(); - refreshESPList(); + refreshESPList(true); savePluginList(); //These callbacks should not fiddle with directoy structure and ESPs. @@ -1558,13 +1561,13 @@ void OrganizerCore::refreshModList(bool saveChanges) refreshDirectoryStructure(); } -void OrganizerCore::refreshESPList() +void OrganizerCore::refreshESPList(bool force) { if (m_DirectoryUpdate) { // don't mess up the esp list if we're currently updating the directory // structure - m_PostRefreshTasks.append([this]() { - this->refreshESPList(); + m_PostRefreshTasks.append([=]() { + this->refreshESPList(force); }); return; } @@ -1573,7 +1576,7 @@ void OrganizerCore::refreshESPList() // clear list try { m_PluginList.refresh(m_CurrentProfile->name(), *m_DirectoryStructure, - m_CurrentProfile->getLockedOrderFileName()); + m_CurrentProfile->getLockedOrderFileName(), force); } catch (const std::exception &e) { reportError(tr("Failed to refresh list of esps: %1").arg(e.what())); } @@ -1614,7 +1617,7 @@ void OrganizerCore::refreshBSAList() void OrganizerCore::refreshLists() { if ((m_CurrentProfile != nullptr) && m_DirectoryStructure->isPopulated()) { - refreshESPList(); + refreshESPList(true); refreshBSAList(); } // no point in refreshing lists if no files have been added to the directory // tree @@ -1677,7 +1680,7 @@ void OrganizerCore::updateModInDirectoryStructure(unsigned int index, modInfo->stealFiles()); DirectoryRefresher::cleanStructure(m_DirectoryStructure); // need to refresh plugin list now so we can activate esps - refreshESPList(); + refreshESPList(true); // activate all esps of the specified mod so the bsas get activated along with // it updateModActiveState(index, true); @@ -1838,7 +1841,7 @@ void OrganizerCore::modStatusChanged(unsigned int index) updateModInDirectoryStructure(index, modInfo); } else { updateModActiveState(index, false); - refreshESPList(); + refreshESPList(true); if (m_DirectoryStructure->originExists(ToWString(modInfo->name()))) { FilesOrigin &origin = m_DirectoryStructure->getOriginByName(ToWString(modInfo->name())); diff --git a/src/organizercore.h b/src/organizercore.h index 9e81f0c36..54d9dd6d6 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -130,7 +130,7 @@ class OrganizerCore : public QObject, public MOBase::IPluginDiagnose void prepareStart(); - void refreshESPList(); + void refreshESPList(bool force = false); void refreshBSAList(); void refreshDirectoryStructure(); diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 8bf438f11..bd863f527 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -140,8 +140,15 @@ void PluginList::highlightPlugins(const QItemSelection &selected, const MOShared void PluginList::refresh(const QString &profileName , const DirectoryEntry &baseDirectory - , const QString &lockedOrderFile) + , const QString &lockedOrderFile + , bool force) { + if (force) { + m_ESPs.clear(); + m_ESPsByName.clear(); + m_ESPsByPriority.clear(); + } + ChangeBracket layoutChange(this); QStringList primaryPlugins = m_GamePlugin->primaryPlugins(); @@ -871,8 +878,12 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const text += "
" + tr("Enabled Masters") + ": " + SetJoin(enabledMasters, ", "); } if (m_ESPs[index].m_HasIni) { - text += "
There is an ini file connected to this esp. Its settings will be added to your game settings, overwriting " - "in case of conflicts."; + text += "
" + tr("There is an ini file connected to this esp. " + "Its settings will be added to your game settings, overwriting in case of conflicts."); + } + if (m_ESPs[index].m_IsLightFlagged && !m_ESPs[index].m_IsLight) { + text += "

" + tr("This ESP is flagged as an ESL. " + "It will adhere to the ESP load order but the records will be loaded in ESL space."); } toolTip += text; } @@ -895,6 +906,9 @@ QVariant PluginList::data(const QModelIndex &modelIndex, int role) const if (m_ESPs[index].m_HasIni) { result.append(":/MO/gui/attachment"); } + if (m_ESPs[index].m_IsLightFlagged && !m_ESPs[index].m_IsLight) { + result.append(":/MO/gui/awaiting"); + } return result; } return QVariant(); diff --git a/src/pluginlist.h b/src/pluginlist.h index 4762f79fd..f91738108 100644 --- a/src/pluginlist.h +++ b/src/pluginlist.h @@ -116,7 +116,8 @@ class PluginList : public QAbstractItemModel, public MOBase::IPluginList **/ void refresh(const QString &profileName , const MOShared::DirectoryEntry &baseDirectory - , const QString &lockedOrderFile); + , const QString &lockedOrderFile + , bool refresh); /** * @brief enable a plugin based on its name diff --git a/src/queryoverwritedialog.cpp b/src/queryoverwritedialog.cpp index 932b2637c..eb67719dc 100644 --- a/src/queryoverwritedialog.cpp +++ b/src/queryoverwritedialog.cpp @@ -20,6 +20,8 @@ along with Mod Organizer. If not, see . #include "queryoverwritedialog.h" #include "ui_queryoverwritedialog.h" +#include + QueryOverwriteDialog::QueryOverwriteDialog(QWidget *parent, Backup b) : QDialog(parent), ui(new Ui::QueryOverwriteDialog), m_Action(ACT_NONE) diff --git a/src/resources.qrc b/src/resources.qrc index 618d3cfc7..14c8e5334 100644 --- a/src/resources.qrc +++ b/src/resources.qrc @@ -72,6 +72,11 @@ resources/package.png resources/switch-instance-icon.png resources/open-Folder-Icon.png + resources/multiply-red.png + resources/archive-conflict-loser.png + resources/archive-conflict-mixed.png + resources/archive-conflict-neutral.png + resources/archive-conflict-winner.png resources/contents/jigsaw-piece.png diff --git a/src/resources/archive-conflict-loser.png b/src/resources/archive-conflict-loser.png new file mode 100644 index 000000000..561e8be86 Binary files /dev/null and b/src/resources/archive-conflict-loser.png differ diff --git a/src/resources/archive-conflict-mixed.png b/src/resources/archive-conflict-mixed.png new file mode 100644 index 000000000..df20532cb Binary files /dev/null and b/src/resources/archive-conflict-mixed.png differ diff --git a/src/resources/archive-conflict-neutral.png b/src/resources/archive-conflict-neutral.png new file mode 100644 index 000000000..5bedd9c41 Binary files /dev/null and b/src/resources/archive-conflict-neutral.png differ diff --git a/src/resources/archive-conflict-winner.png b/src/resources/archive-conflict-winner.png new file mode 100644 index 000000000..5cc568c87 Binary files /dev/null and b/src/resources/archive-conflict-winner.png differ diff --git a/src/resources/check.png b/src/resources/check.png index 0f294a02e..5df447552 100644 Binary files a/src/resources/check.png and b/src/resources/check.png differ diff --git a/src/resources/contents/breastplate.png b/src/resources/contents/breastplate.png index d3d39407d..f8c787e29 100644 Binary files a/src/resources/contents/breastplate.png and b/src/resources/contents/breastplate.png differ diff --git a/src/resources/contents/checkbox-tree.png b/src/resources/contents/checkbox-tree.png index a44dd5fd5..f4a632378 100644 Binary files a/src/resources/contents/checkbox-tree.png and b/src/resources/contents/checkbox-tree.png differ diff --git a/src/resources/contents/config.png b/src/resources/contents/config.png index 3896d8854..3aaa2e5ca 100644 Binary files a/src/resources/contents/config.png and b/src/resources/contents/config.png differ diff --git a/src/resources/contents/conversation.png b/src/resources/contents/conversation.png index 11030fffa..76286e33d 100644 Binary files a/src/resources/contents/conversation.png and b/src/resources/contents/conversation.png differ diff --git a/src/resources/contents/double-quaver.png b/src/resources/contents/double-quaver.png index 7102f472e..21dcf4c18 100644 Binary files a/src/resources/contents/double-quaver.png and b/src/resources/contents/double-quaver.png differ diff --git a/src/resources/contents/empty-chessboard.png b/src/resources/contents/empty-chessboard.png index b5983010f..fccb3aa5e 100644 Binary files a/src/resources/contents/empty-chessboard.png and b/src/resources/contents/empty-chessboard.png differ diff --git a/src/resources/contents/hand-of-god.png b/src/resources/contents/hand-of-god.png index e3a05335e..7cbd01029 100644 Binary files a/src/resources/contents/hand-of-god.png and b/src/resources/contents/hand-of-god.png differ diff --git a/src/resources/contents/jigsaw-piece.png b/src/resources/contents/jigsaw-piece.png index 0b26d56b6..4949be403 100644 Binary files a/src/resources/contents/jigsaw-piece.png and b/src/resources/contents/jigsaw-piece.png differ diff --git a/src/resources/contents/locked-chest.png b/src/resources/contents/locked-chest.png index d70d448e9..b49e11651 100644 Binary files a/src/resources/contents/locked-chest.png and b/src/resources/contents/locked-chest.png differ diff --git a/src/resources/contents/lyre.png b/src/resources/contents/lyre.png index 041dbb018..3adff24e5 100644 Binary files a/src/resources/contents/lyre.png and b/src/resources/contents/lyre.png differ diff --git a/src/resources/contents/tinker.png b/src/resources/contents/tinker.png index 9709cee86..dceb13676 100644 Binary files a/src/resources/contents/tinker.png and b/src/resources/contents/tinker.png differ diff --git a/src/resources/contents/usable.png b/src/resources/contents/usable.png index 2bc93a7da..c705997a8 100644 Binary files a/src/resources/contents/usable.png and b/src/resources/contents/usable.png differ diff --git a/src/resources/document-save.png b/src/resources/document-save.png index a94e0eab9..18e1f974e 100644 Binary files a/src/resources/document-save.png and b/src/resources/document-save.png differ diff --git a/src/resources/multiply-red.png b/src/resources/multiply-red.png new file mode 100644 index 000000000..f35adc741 Binary files /dev/null and b/src/resources/multiply-red.png differ diff --git a/src/resources/open-Folder-Icon.png b/src/resources/open-Folder-Icon.png index 345671f75..6218ed39b 100644 Binary files a/src/resources/open-Folder-Icon.png and b/src/resources/open-Folder-Icon.png differ diff --git a/src/resources/switch-instance-icon.png b/src/resources/switch-instance-icon.png index b992babe9..cd509d3da 100644 Binary files a/src/resources/switch-instance-icon.png and b/src/resources/switch-instance-icon.png differ diff --git a/src/selfupdater.cpp b/src/selfupdater.cpp index 273e9b455..8f962ba4f 100644 --- a/src/selfupdater.cpp +++ b/src/selfupdater.cpp @@ -125,7 +125,7 @@ void SelfUpdater::testForUpdate() // TODO: if prereleases are disabled we could just request the latest release // directly try { - m_GitHub.releases(GitHub::Repository("LePresidente", "modorganizer"), + m_GitHub.releases(GitHub::Repository("Modorganizer2", "modorganizer"), [this](const QJsonArray &releases) { QJsonObject newest; for (const QJsonValue &releaseVal : releases) { diff --git a/src/transfersavesdialog.cpp b/src/transfersavesdialog.cpp index b8dda056a..61dab6e5f 100644 --- a/src/transfersavesdialog.cpp +++ b/src/transfersavesdialog.cpp @@ -74,6 +74,11 @@ class DummySave : public ISaveGame return { m_File }; } + virtual bool hasScriptExtenderFile() const override + { + return false; + } + private: QString m_File; }; @@ -95,6 +100,11 @@ class DummyInfo : public SaveGameInfo { return nullptr; } + + virtual bool hasScriptExtenderSave(QString const &file) const override + { + return false; + } }; } //end anonymous namespace @@ -334,26 +344,6 @@ bool TransferSavesDialog::transferCharacters( sourceFile.absoluteFilePath().toUtf8().constData(), destinationFile.toUtf8().constData()); } - - QFileInfo sourceFileSE(sourceFile.absolutePath() + "/" + sourceFile.completeBaseName() + "." + m_GamePlugin->savegameSEExtension()); - if (sourceFileSE.exists()) { - QString destinationFileSE(destination.absoluteFilePath(sourceFileSE.fileName())); - - //If the file is already there, let them skip (or not). - if (QFile::exists(destinationFileSE)) { - if (!testOverwrite(overwriteMode, destinationFileSE)) { - continue; - } - //OK, they want to remove it. - QFile::remove(destinationFileSE); - } - - if (!method(sourceFileSE.absoluteFilePath(), destinationFileSE)) { - qCritical(errmsg, - sourceFileSE.absoluteFilePath().toUtf8().constData(), - destinationFileSE.toUtf8().constData()); - } - } } } return true; diff --git a/src/tutorials/tutorial_conflictresolution_main.js b/src/tutorials/tutorial_conflictresolution_main.js index a60a5c8db..3c782af29 100644 --- a/src/tutorials/tutorial_conflictresolution_main.js +++ b/src/tutorials/tutorial_conflictresolution_main.js @@ -65,7 +65,7 @@ function getTutorialSteps() { }, function() { tutorial.text = qsTr("Option A: Switch to the \"Data\"-tab if necessary") - if (!tutorialControl.waitForTabOpen("tabWidget", 1)) { + if (!tutorialControl.waitForTabOpen("tabWidget", 2)) { highlightItem("tabWidget", false) waitForClick() } else { diff --git a/src/version.rc b/src/version.rc index a71a462ab..142065bf2 100644 --- a/src/version.rc +++ b/src/version.rc @@ -1,7 +1,7 @@ #include "Winver.h" -#define VER_FILEVERSION 2,1,1 -#define VER_FILEVERSION_STR "2.1.1\0" +#define VER_FILEVERSION 2,1,2 +#define VER_FILEVERSION_STR "2.1.2\0" VS_VERSION_INFO VERSIONINFO FILEVERSION VER_FILEVERSION @@ -10,17 +10,19 @@ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK FILEFLAGS (0) FILEOS VOS__WINDOWS32 FILETYPE VFT_APP -FILESUBTYPE VFT2_UNKNOWN +FILESUBTYPE (0) BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904B0" BEGIN VALUE "FileVersion", VER_FILEVERSION_STR - VALUE "CompanyName", "Tannin\0" - VALUE "FileDescription", "Mod Organizer UI\0" + VALUE "CompanyName", "Mod Organizer 2 Team\0" + VALUE "FileDescription", "Main Mod Oranizer 2 UI\0" VALUE "OriginalFilename", "ModOrganizer.exe\0" - VALUE "ProductName", "Mod Organizer\0" + VALUE "InternalName", "ModOrganizer\0" + VALUE "LegalCopyright", "Copyright 2011-2016 Sebastian Herbord\r\nCopyright 2016-2018 Mod Organizer 2 contributors\0" + VALUE "ProductName", "Mod Organizer 2\0" VALUE "ProductVersion", VER_FILEVERSION_STR END END